这一节根据官方文档给出的简单示例,深入代码内部,了解其实现机制。示例代码如下:
int main (void) { struct ev_loop *loop = EV_DEFAULT; ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ); ev_io_start (loop, &stdin_watcher); ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.); ev_timer_start (loop, &timeout_watcher); ev_run (loop, 0); return 0; }
宏EV_DEFAULT对缺省的ev_loop进行初始化,然后将指针返回给main函数中的loop,它的定义为:
# define EV_DEFAULT ev_default_loop (0)
函数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 = 0; } 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, 1); /* 将watcher挂到loop->anfds的head链表中 */ array_needsize (ANFD, anfds, anfdmax, fd + 1, 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; }
该函数主要完成两件事:
下面进行详细的分析。
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 >= 0 && 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; }