这一节根据官方文档给出的简单示例,深入代码内部,了解其实现机制。示例代码如下:

int
main (void)
{
struct ev_loop *loop = EV_DEFAULT; ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ , EV_READ);
ev_io_start (loop, &stdin_watcher); ev_timer_init (&timeout_watcher, timeout_cb, 5.5, .);
ev_timer_start (loop, &timeout_watcher); ev_run (loop, ); return ;
}

宏EV_DEFAULT对缺省的ev_loop进行初始化,然后将指针返回给main函数中的loop,它的定义为:

# define EV_DEFAULT  ev_default_loop ()

函数ev_default_loop定义如下:

#if EV_MULTIPLICITY
struct ev_loop * ecb_cold
#else
int
#endif
ev_default_loop (unsigned int flags) EV_THROW
{
if (!ev_default_loop_ptr)
{
/* EV_P = struct ev_loop *loop */
EV_P = ev_default_loop_ptr = &default_loop_struct; /* 初始化ev_loop各个字段, flags = 0 */
loop_init (EV_A_ flags); if (ev_backend (EV_A))
{
#if EV_CHILD_ENABLE
/* 接收SIGCHLD信号 */
ev_signal_init (&childev, childcb, SIGCHLD);
ev_set_priority (&childev, EV_MAXPRI);
ev_signal_start (EV_A_ &childev);
ev_unref (EV_A); /* child watcher should not keep loop alive */
#endif
}
else
ev_default_loop_ptr = ;
} return ev_default_loop_ptr;
}

ev_default_loop_ptr是一个全局指针,指向缺省的ev_loop,在这个函数中可以看出,这个缺省的ev_loop就是default_loop_struct。该函数最终就是把这个ev_default_loop_ptr赋值给main函数中的loop的。初始化完指针后,就要对这个缺省ev_loop进行初始化了,这个任务交给loop_init函数来完成。loop_init函数主要是对default_loop_struct中的各个字段初始化,包括对事件驱动机制(如poll、epoll、select等)的初始化。

初始化完毕后回到main函数,此时的loop已经指向了一个初始化后的ev_loop结构体。

接下来调用ev_io_init宏函数。该函数主要针对如下几个问题进行初始化:要监听几号描述符?监听这个描述符的什么事件?监听事件发生时做什么动作?这几个问题的答案就是由我们调用该函数时所传入的参数来回答的。该宏的定义如下:

#define ev_io_init(ev,cb,fd,events)          do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)

这个宏的初始化工作可以分为两个部分,一部分由ev_init宏来完成,另一部分由ev_io_set宏来完成。为什么要分成两部分,还需要从Libev对结构体的设计说起。

Libev中有不同的事件,每一种事件的定义不同,一个需要监控的事件称为一个watcher。在官方示例中,定义了两个watcher,一个是监控I/O的名为stdin_watcher的watcher,另一个监控定时器的名为timeout_watcher的watcher。这些watcher作为派生类,共同继承自基类EV_WATCHER,基类的定义如下:

#define EV_WATCHER(type)            \
int active; /* private */ \ /* 非0表示watcher为激活状态,是periodics或timers数组的下标 */
int pending; /* private */ \ /* 非0表示watcher中有事件被触发,是pendings数组的下标 */
EV_DECL_PRIORITY /* private */ \ /* watcher的优先级 */
EV_COMMON /* rw */ \ /* void *data; */
EV_CB_DECLARE (type) /* private */ /* void (*cb)(EV_P_ struct type *w, int revents); */

实际上这个宏就是定义了一些变量,每个变量的含义我已经在注释中表明,最后一个宏展开后就是定义了一个函数指针,倒数第二个宏展开后就是一个通用指针,供用户自由使用。再看看派生类ev_io的定义:

#define EV_WATCHER_LIST(type)            \
EV_WATCHER (type) \
struct ev_watcher_list *next; /* private */ typedef struct ev_io
{
EV_WATCHER_LIST (ev_io) int fd; /* ro */
int events; /* ro */
} ev_io;

ev_io是一个真正的结构体,它包含的成员包括:基类中定义的各个变量,next指针,自己结构体中独有的变量。next指针的目的是将不同的watcher连接起来,形成链表。

所以,对一个派生类的初始化需要分别对继承的基类部分和派生类部分进行初始化。ev_init专门负责初始化基类部分,ev_XXX_set专门负责初始化派生类部分,XXX需要根据不同类型的watcher而定。回到刚才的ev_io_init宏,该宏分别调用了两个宏:ev_init和ev_io_set分别初始化基类成员和派生类成员。如果需要初始化timer类型的watcher,那么就调用ev_init和ev_timer_set分别进行初始化。ev_init是每一种类型的watcher都需要调用的,因为它们都继承自同一个基类,而各自特有的成员变量则通过ev_XXX_set初始化。这样的模拟面向对象的设计和各种宏的定义使得代码阅读起来变得困难,但是代码的复用性会增强,执行效率也会变得更高。

对watcher初始化完毕后回到main函数,接下来调用ev_io_start,主要代码如下:

/* EV_P_ = EV_P = struct ev_loop *loop, 单一参数用EV_P, 第一个参数用EV_P_ */
void noinline
ev_io_start (EV_P_ ev_io *w) EV_THROW
{
int fd = w->fd;/* 设置为启动状态 */
ev_start (EV_A_ (W)w, ); /* 将watcher挂到loop->anfds的head链表中 */
array_needsize (ANFD, anfds, anfdmax, fd + , array_init_zero);
wlist_add (&anfds[fd].head, (WL)w); fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);
w->events &= ~EV__IOFDSET; EV_FREQUENT_CHECK;
}

该函数主要完成两件事:

  1. 开启对stdin_watcher这个watcher的监听,包括设置相关的标志位和加入事件驱动机制中(如epoll调用epoll_ctl)。
  2. 将watcher挂到初始化好的loop中统一管理。

下面进行详细的分析。

ev_start函数就是设置一些watcher中的标志,表示开始监听该watcher。(W)w可以看成是将派生类指针ev_io*强制转换成基类指针,也就是多态机制。

在Libev中,有一个非常重要的结构体叫ANFD,它是对文件描述符的封装,一个fd对应一个ANFD,内部包含了对该fd所做动作或已触发动作的描述:

/* file descriptor info structure */
/* 一个fd对应一个ANFD */
typedef struct
{
/* 一个fd可以有多个watcher同时监听,多个watcher形成一个链表,head表示链表头 */
WL head; /* 上面链表中事件按位或 */
unsigned char events; /* the events watched for */ /* 为1表示需要监听的事件发生了变化,需要重新epoll_ctl */
unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */ /* 实际触发的事件 */
unsigned char emask; /* the epoll backend stores the actual kernel mask in here */
unsigned char unused;
#if EV_USE_EPOLL
unsigned int egen; /* generation counter to counter epoll bugs */
#endif
} ANFD;

那么fd如何和对应的ANFD相关联呢?答案是将ANFD放入ev_loop的anfds数组中,fd作为下标,这样寻找对应ANFD的时间复杂度是O(1),速度非常的快。anfds数组的大小是可调的,它根据当前需要监听的fd的最大值调整自己的大小,array_needsize宏就是做调整工作的:

#define array_needsize(type,base,cur,cnt,init)            \
if (expect_false ((cnt) > (cur))) \
{ \
int ecb_unused ocur_ = (cur); \
(base) = (type *)array_realloc \
(sizeof (type), (base), &(cur), (cnt)); \
/* 只初始化新分配空间 */
init ((base) + (ocur_), (cur) - ocur_); \
}

如果新加入的fd大于anfds数组当前的大小cur,就增加容量。expect_false宏的作用是让编译期对代码在分支预测方面进行优化,提高代码性能,这涉及到流水线方面的一些知识。

回到ev_io_start函数,调整完loop的anfds数组后,调用wlist_add函数将初始化的watcher加入到anfds数组中,watcher监听的fd作为数组的下标。在ANFD的定义中有一个head成员,它就是连接所有相同fd的watcher的链表头。

回到ev_io_start函数,到目前为止,需要监听的fd还没有加入到事件驱动机制中。Libev采用的方法是将新加入的fd添加到一个数组中,之后再扫描这个数组,将数组中的所有fd加入到事件驱动机制中。ev_io_start函数中调用fd_change函数的目的就是将新fd加入到数组中,这个数组的名字叫做fdchanges,属于ev_loop的成员。不光是新的fd需要加入到fdchanges数组,被修改了监听内容的fd都需要加入到fdchanges数组。

ev_io_start函数大体上分析完毕,它的作用是开启对一个watcher的监听,而不是fd,因为一个fd可以对应多个watcher。例如监听的fd=100,需要同时监听输入和输出,那么会有两个watcher对应这个fd,一个是输入,一个是输出。ev_io_stop函数就是停止监听一个watcher,操作和ev_io_start相反:

void noinline
ev_io_stop (EV_P_ ev_io *w) EV_THROW
{
clear_pending (EV_A_ (W)w);
if (expect_false (!ev_is_active (w)))
return; assert (("libev: ev_io_stop called with illegal fd (must stay constant after start!)", w->fd >= && w->fd < anfdmax)); EV_FREQUENT_CHECK; wlist_del (&anfds[w->fd].head, (WL)w);
ev_stop (EV_A_ (W)w); fd_change (EV_A_ w->fd, EV_ANFD_REIFY); EV_FREQUENT_CHECK;
}

Libev学习笔记2的更多相关文章

  1. libev学习笔记

    转 libev的使用--结合Socket编程 作者:cxy450019566 之前自己学过一些libev编程的基础,这次写压测刚好用上了,才算真正动手写了些东西,在这里做一些总结.写这篇文章是为了用浅 ...

  2. Libev学习笔记4

    这一节首先分析Libev的定时器部分,然后分析signal部分. 对定时器的使用主要有两个函数: ev_timer_init (&timeout_watcher, timeout_cb, .) ...

  3. Libev学习笔记3

    设置完需要监听的事件之后,就开始event loop了.在Libev中,该工作由ev_run函数完成.它的大致流程如下: int ev_run (EV_P_ int flags) { do { /* ...

  4. Libev学习笔记1

    和Libevent相似,Libev是一个高性事件驱动框架,据说性能比Libevent要高,bug比Libevent要少.Libev只是一个事件驱动框架,不是网络库,因为它的内部并没有任何socket编 ...

  5. libev 学习使用

    libev 简单的I/O库.  a high performance full featured event loop written in c libev 的大小也比 libevent 小得多并且自 ...

  6. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  7. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  8. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  9. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

随机推荐

  1. webpack和webpack-dev-server的区别

    第一: webpack只是构建 webpack-dev-server除了构建,还提供web服务   第二:webpack.config.json的路径参数 显然,entry都一样,因为都要知道需要构建 ...

  2. codinglife主题小修改和有意思的博客挂件

    这个主题很漂亮,不过为了迎合自己的喜好ヽ(•̀ω•́ )ゝ,修改了字号.阴影之类的小细节.同时下面还有我博客里面的两个有意思的小挂件,请向右边看(๑و•̀ω•́)و 1.主题修改:复制下面的css代码 ...

  3. 1 2 5 10 20 --> 800

    用1元 2元 5元 10元 20元的钞票凑成800元的方法种数计算,使用了动态规划. 结果没打出来,只是保留在函数里各个vector中,调试可看所有结果. 优点:快 缺点:占空间占内存 耗时时间测试: ...

  4. python 推导式和迭代器、生成器

    1.常用推导式 推导式是从一个或者多个迭代器快速简洁创建数据结构的一种方法. 1.1 _ 列表推导式 最简单的形式:  [exprssion for item in iterable] 示例:  nu ...

  5. 字符串-06. IP地址转换

    /* * Main.c * D6-字符串-06. IP地址转换 * Created on: 2014年8月19日 *******测试通过******** *转载:http://blog.csdn.ne ...

  6. 服务启动项 Start类型详解

    注册表的服务启动项 Start类型详解 HKLM\SYSTEM\CurrentControlSet\services\ 下的服务项.不论有没有在services.msc服务管理控制台中显示,在注册表中 ...

  7. DNS解析

    大家好,今天51开源给大家介绍一个在配置文件,那就是/etc/resolv.conf.很多网友对此文件的用处不太了解.其实并不复杂,它是DNS客户机配置文件,用于设置DNS服务器的IP地址及DNS域名 ...

  8. CCNA实验(3) -- RIP

    RIP协议分为版本1和版本2,均具备以下特征:1.是距离向量路由协议2.使用跳数(Hop Count)作为度量值3.默认路由更新周期为30秒4.管理距离(AD)为1205.支持触发更新6.最大跳数为1 ...

  9. HDU 4633 Who's Aunt Zhang (Polya定理+快速幂)

    题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=4633 典型的Polya定理: 思路:根据Burnside引理,等价类个数等于所有的置换群中的不动点的个 ...

  10. Paths on a Grid(规律)

    Paths on a Grid Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 23270   Accepted: 5735 ...