##另外两个重要的监控器 前面经过IO监控器将Libev的整个工做流程过了一遍。中间滤过了不少与其余事件监控器相关的部分,可是总体思路以及很明晰了,只要针对其余类型的watcher看下其初始化和注册过程以及在ev_run中的安排便可。这里咱们再分析另两个经常使用的watcher ###1.分析定时器监控器 定时器在程序中能够作固定周期tick操做,也能够作一次性的定时操做。Libev中与定时器相似的还有个周期事件watcher。其本质都是同样的,只是在时间的计算方法上略有不一样,并有他本身的一个事件管理的堆。对于定时器事件,咱们按照以前说的顺序从ev_init开始看起。html
####1.1定时器监控器的初始化 定时器初始化使用 ev_init(&timer_w,timer_action);
,这个过程和以前的IO相似,主要就是设置基类的active、pending、priority以及触发动做回调函数cb。linux
####1.2设置定时器监控器的触发条件 经过 ev_timer_set(&timer_w,2,0);
能够设置定时器在2秒钟后被触发。若是第三个参数不是0而是一个大于0的正整数n时,那么在第一次触发(2秒后),每隔n秒会再次触发定时器事件。数组
其为一个宏定义 do { ((ev_watcher_time *)(ev))->at = (after_); (ev)->repeat = (repeat_); } while (0)
也就是设置派生类定时器watcher的“at”为触发事件,以及重复条件“repeat”。数据结构
####1.3将定时器注册到事件驱动器上框架
ev_timer_start(main_loop,&timer_w);
会将定时器监控器注册到事件驱动器上。其首先 ev_at (w) += mn_now;
获得将来的时间,这样放到时间管理的堆“timers”中做为权重。而后经过以前说过的“ev_start”修改驱动器loop的状态。这里咱们又看到了动态大小的数组了。Libev的堆的内存管理也是经过这样的关系的。具体这里堆的实现,感兴趣的能够仔细看下实现。这里的操做就是将这个时间权重放到堆中合适的位置。这里堆单元的结构为:函数
typedef struct { ev_tstamp at; WT w; } ANHE;
其实质就是一个时刻at上挂一个放定时器watcher的list。当超时时会依次执行这些定时器watcher上的触发回调函数。oop
####1.4定时器监控器的触发 最后看下在一个事件驱动器循环中是如何处理定时器监控器的。这里咱们依然抛开其余的部分,只找定时器相关的看。在“/* calculate blocking time */”块里面,咱们看到计算blocking time的时候会先:学习
if (timercnt) { ev_tstamp to = ANHE_at (timers [HEAP0]) - mn_now; if (waittime > to) waittime = to; }
若是有定时器,那么就从定时器堆(一个最小堆)timers中取得堆顶上最小的一个时间。这样就保证了在这个时间前能够从backend_poll中出来。出来后执行timers_reify
处理将pengding的定时器。ui
在timers_reify
中依次取最小堆的堆顶,若是其上的ANHE.at小于当前时间,表示该定时器watcher超时了,那么将其压入一个数组中,因为在实际执行pendings二维数组上对应优先级上的watcher是从尾往头方向的,所以这里先用一个数组依时间前后次存下到一个中间数组loop->rfeeds中。而后将其逆序调用ev_invoke_pending
插入到pendings二维数组中。这样在执行pending事件的触发动做的时候就能够保证,时间靠前的定时器优先执行。函数 feed_reverse
和 feed_reverse_done
就是将超时的定时器加入到loop->rfeeds暂存数组以及将暂存数组中的pending的watcher插入到pengdings数组的操做。把pending的watcher加入到pendings数组,后续的操做就和以前的同样了。回依次执行相应的回调函数。线程
这个过程当中还判判定时器的 w->repeat 的值,若是不为0,那么会重置该定时器的时间,并将其压入堆中正确的位置,这样在指定的时间事后又会被执行。若是其为0,那么调用ev_timer_stop
关闭该定时器。 其首先经过clear_pending
置pendings数组中记录的该watcher上的回调函数为一个不执行任何动做的哑动做。
总结一下定时器就是在backend_poll以前经过定时器堆顶的超时时间,保证blocking的时间不超过最近的定时器时间,在backend_poll返回后,从定时器堆中取得超时的watcher放入到pendings二维数组中,从而在后续处理中能够执行其上注册的触发动做。而后从定时器管理堆上删除该定时器。最后调用和ev_start
呼应的ev_stop
修改驱动器loop的状态,即loop->activecnt减小一。并将该watcher的active置零。
对于周期性的事件监控器是一样的处理过程。只是将timers_reify
换成了periodics_reify
。其内部会对周期性事件监控器派生类的作相似定时器里面是否repeat的判断操做。判断是否从新调整时间,或者是否重复等逻辑,这些看下代码比较容易理解,这里再也不赘述。·
###2.分析信号监控器 分析完了定时器的部分,再看下另外一个比较经常使用的信号事件的处理。Libev里面的信号事件和Tornado.IOLoop是同样的,经过一个pipe的IO事件来处理。直白的说就是注册一个双向的pipe文件对象,而后监控上面的读事件,待相应的信号到来时,就往这个pipe中写入一个值然他的读端的读事件触发,这样就能够执行相应注册的触发动做回调函数了。
咱们仍是从初始化-》设置触发条件-》注册到驱动器-》触发过程这样的顺序介绍。
####2.1信号监控器的初始化 ev_init(&signal_w,signal_action);
这个函数和上面的同样不用说了 ####2.2设置信号监控器的触发条件 ev_signal_set(&signal_w,SIGINT);
该函数设置了Libev收到SIGINT信号是触发注册的触发动做回调函数。其操做和上面的同样,就是设置了信号监控器私有的(ev)->signum为标记。 ####2.3将信号监控器注册到驱动器上 这里首先介绍一个数据结构:
typedef struct { EV_ATOMIC_T pending; EV_P; WL head; } ANSIG; static ANSIG signals [EV_NSIG - 1];
EV_ATOMIC_T pending;
能够认为是一个原子对象,对他的读写是原子的。一个表示事件驱动器的loop,以及一个watcher的链表。
在ev_signal_start
中,经过signals数组存储信号监控单元。该数组和anfds数组相似,只是他以信号值为索引。这样能够立马找到信号所在的位置。从 Linux 2.6.27之后,Kernel提供了signalfd来为信号产生一个文件描述符从而能够用文件复用机制epoll、select等来管理信号。Libev就是用这样的方式来管理信号的。 这里的代码用宏控制了。其逻辑大致是这样的
#if EV_USE_SIGNALFD res = invoke_signalfd # if EV_USE_SIGNALFD if (res is not valied) # endif { use evpipe to instead }
这个是框架。其具体的实现能够参考使用signalfd和evpipe_init
实现。其实质就是经过一个相似于管道的文件描述符fd,设置对该fd的读事件监听,当收到信号时经过signal注册的回调函数往该fd里面写入,使其读事件触发,这样经过backend_poll返回后就能够处理ev_init
为该信号上注册的触发回调函数了。
在函数evpipe_init
里面也用了一个能够学习的技巧,和上面的#if XXX if() #endif {}
同样,处理了不支持eventfd
的状况。eventfd是Kernel 2.6.22之后才支持的系统调用,用来建立一个事件对象实现,进程(线程)间的等待/通知机制。他维护了一个能够读写的文件描述符,可是只能写入8byte的内容。可是对于咱们的使用以及够了,由于这里主要是得到其可读的状态。对于不支持eventfd
的状况,则使用上面说过的,用系统的pipe
调用产生的两个文件描述符分别作读写对象,来完成。
####2.4信号事件监控器的触发 在上面设置信号的pipe的IO事件是,根据使用的机制不一样,其实现和触发有点不一样。对于signalfd。
ev_io_init (&sigfd_w, sigfdcb, sigfd, EV_READ); /* for signalfd */ ev_set_priority (&sigfd_w, EV_MAXPRI); ev_io_start (EV_A_ &sigfd_w);
也就是注册了sigfdcb函数。该函数:
ssize_t res = read (sigfd, si, sizeof (si)); for (sip = si; (char *)sip < (char *)si + res; ++sip) ev_feed_signal_event (EV_A_ sip->ssi_signo);
首先将pipe内容读光,让后续的能够pengding在该fd上。而后对该signalfd上的全部信号弟阿勇ev_feed_signal_event
吧每一个信号上的ANSIG->head上挂的watcher都用ev_feed_event
加入到pendings二维数组中。这个过程和IO的彻底同样。
而对于eventfd和pipe则是:
ev_init (&pipe_w, pipecb); ev_set_priority (&pipe_w, EV_MAXPRI); ev_io_set (&pipe_w, evpipe [0] < 0 ? evpipe [1] : evpipe [0], EV_READ); ev_io_start (EV_A_ &pipe_w);
pipe_w是驱动器自身的loop->pipe_w。并为其设置了回调函数pipecb:
#if EV_USE_EVENTFD if (evpipe [0] < 0) { uint64_t counter; read (evpipe [1], &counter, sizeof (uint64_t)); } else #endif { char dummy[4]; read (evpipe [0], &dummy, sizeof (dummy)); } ... xxx ... for (i = EV_NSIG - 1; i--; ) if (expect_false (signals [i].pending)) ev_feed_signal_event (EV_A_ i + 1);
这里将上面的技巧#if XXX if() #endif {}
拓展为了#if XXX if() {} else #endif {}
。这里和上面的操做实际上是同样的。后续操做和signalfd里面同样,就是读光pipe里面的内容,而后依次将watcher加入到pendings数组中。