事件库之Libev(四)

##另外两个重要的监控器 前面经过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_reversefeed_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数组中。

相关文章
相关标签/搜索