这一节首先分析Libev的定时器部分,然后分析signal部分。
对定时器的使用主要有两个函数:
ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.); ev_timer_start (loop, &timeout_watcher);
和ev_io类型的watcher类似,timeout_watcher是一个类型为ev_timer的watcher,上面的ev_timer_init函数将它设置为5.5秒之后调用回调函数timeout_cb,最后一个参数0表示定时器不重复超时,执行完一次回调函数后就停止计时,如果最后一个参数为非0,那么回调函数第一次执行完之后,每个指定秒后回调函数会被重复执行。将ev_timer结构体展开:
typedef struct ev_timer { 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_tstamp at; ev_tstamp repeat; /* rw */ } ev_timer;
其中at成员对应倒数第二个参数,表示多少秒后第一次触发超时;repeat成员对应最后一个参数,表示第一次超时触发之后每个多少秒重复触发。ev_timer_init函数实质上就是设置上面这些成员。ev_timer_start主要工作是将timer放入最小堆中,由最小堆统一管理所有的timer,代码如下:
/* 计算定时器的绝对触发事件并放入堆中 */ void noinline ev_timer_start (EV_P_ ev_timer *w) EV_THROW { if (expect_false (ev_is_active (w))) return; ev_at (w) += mn_now; /* 从现在开始经过at秒后触发 */++timercnt; ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1); /* 将定时器w放入timers堆中 */ array_needsize (ANHE, timers, timermax, ev_active (w) + 1, EMPTY2); ANHE_w (timers [ev_active (w)]) = (WT)w; ANHE_at_cache (timers [ev_active (w)]); /* 更新heap */ upheap (timers, ev_active (w)); }
代码首先根据当前时间计算timer超时时刻的绝对时间,然后增加loop管理的timer个数timercnt,修改一些标志变量,最后把timer放入最小堆timers中,timers使用数组实现的一个最小堆,堆顶为离当前最近的一个timer。
timer定制完毕后就可以调用ev_run函数开始event loop了。在ev_run函数中,关于timer的代码流程大致如下:
int ev_run (EV_P_ int flags) { .... do { ....
/* 计算epoll等事件驱动机制的阻塞时间 */ {/* 堆顶取出ANHE,减去当前时间,赋值给waittime */
/* 调整waittime */ backend_poll (EV_A_ waittime); /* epoll_poll() */ }
....
/* queue pending timers and reschedule them */ /* 调整堆,取出所有超时的timer */ timers_reify (EV_A); /* relative timers called last */
/* 调用pendings数组中watcher的回调函数 */ EV_INVOKE_PENDING; } }
根据最小堆timers计算事件驱动机制的阻塞等待时间,阻塞返回后timers_reify函数记录所有超时的timer,最后由EV_INVOKE_PENDING执行这些timer对应的超时回调函数。在timers_reify函数中,如果timer是单次触发类型,那么会把该timer从最小堆中删除,如果timer是重复触发类型,那么会把该timer重新放入最小堆中,等待下次触发。
接下来分析信号部分。Libev将对信号的处理融入到了I/O事件的处理当中。官方文档给出了一个关于使用signal的例子:
static void sigint_cb (struct ev_loop *loop, ev_signal *w, int revents) { ev_break (loop, EVBREAK_ALL); } ev_signal signal_watcher; ev_signal_init (&signal_watcher, sigint_cb, SIGINT); ev_signal_start (loop, &signal_watcher);
ev_signal是专门监控是否有指定信号发生的watcher。在上面的例子中,当有SIGINT信号发生时,执行回调函数sigint_cb。ev_signal_init就是初始化ev_signal结构体,包括保存回调函数,保存需要等待的信号等。之后调用ev_signal_start函数,该函数是理解Libev中信号处理机制的关键,它的关键代码如下:
void noinline ev_signal_start (EV_P_ ev_signal *w) EV_THROW { signals [w->signum - 1].loop = EV_A; ev_start (EV_A_ (W)w, 1);
wlist_add (&signals [w->signum - 1].head, (WL)w); if (!((WL)w)->next) { struct sigaction sa; evpipe_init (EV_A); sa.sa_handler = ev_sighandler; /* 设置信号处理程序 */ sigfillset (&sa.sa_mask); sa.sa_flags = SA_RESTART; /* if restarting works we save one iteration */ sigaction (w->signum, &sa, 0); } }
其中,signals是一个数组,数组元素类型为ANSIG,它代表信号,数组中的一个元素代表Libev需要监控的一个信号。在Linux中,信号值是一个整数,那么用信号值作为下标,就能很快找到对应的ANSIG结构体了。ANSIG结构体定义如下:
/* 一个信号对应一个ANSIG */ typedef struct { EV_ATOMIC_T pending; /* 1表示收到信号 */ #if EV_MULTIPLICITY EV_P; /* 信号所属的loop */ #endif WL head; /* 多个watcher可以同时检测一个信号,用该成员作链表头 */ } ANSIG;
所以,ev_signal_start函数主要做了如下几件事:
那么整理一下流程,当系统接收到指定信号后,可以得出如下一系列步骤:
以上就是Libev处理信号的大致流程。可以看出,对信号的监控最后都同一到了对文件描述符的监控,也就是最终使用到了ev_io类型的watcher。
参考:
http://my.oschina.net/u/917596/blog/177300
http://www.cnblogs.com/foxmailed/archive/2013/02/04/2891077.html