在《libev源码解析——监视器(watcher)结构和组织形式》中介绍过,监视器分为[2,-2]区间5个等级的优先级。等级为2的监视器最高优,然后依次递减。不区分监视器类型和关联的文件描述符的值,权限高的要优先于权限低的执行。但是ANFD结构中的监视器链表无法满足高等级优先执行的特性。那么libev是如何解决这个问题的呢?(转载请指明出于breaksoftware的csdn博客)
anfds结构是以文件描述符作为索引的,其关心的是该描述符对应的事件是否发生。那我们关心不同等级执行顺序时,要以什么作为索引呢?那当然是等级值。libev也的确是这么做的
VAR (pendings, ANPENDING *pendings [NUMPRI]) VAR (pendingmax, int pendingmax [NUMPRI]) VAR (pendingcnt, int pendingcnt [NUMPRI])NUMPRI是等级的个数,其定义是
#define NUMPRI (EV_MAXPRI - EV_MINPRI + 1)
pendings是一个具有5(2-(-2)+1))个元素的数组,不同等级和数组下标的对应关系通过下面这个宏来换算
# define ABSPRI(w) (((W)w)->priority - EV_MINPRI)可见高等级的位于数组末尾,低等级的位于数组头部。即等级为2的pendings数组下标是4,而等级为-2的下标是0。
pendingmax记录的是每个等级已经记录的监视器个数。
pendingcnt记录的是每个等级中当前有效的监视器个数。这个值和ev_watcher中pending值有很大的相关性,之后我们会去将讨论。
pendings的每个元素是一个ANPENDING指针,其定义如下
typedef ev_watcher *W; /* stores the pending event set for a given watcher */ typedef struct { W w; int events; /* the pending event set for the given watcher */ } ANPENDING;成员变量w是一个ev_watcher指针,它指向anfds中一个监视器。我们看到这个结构中没有指向自身的指针,如next、pre之类,那就说明ANPENDING是用数组结构保存的,而非动态链表。
那么anfds中的数据是如何转移到pendings上的呢?这个工作是由ev_feed_event函数完成
void noinline ev_feed_event (EV_P_ void *w, int revents) EV_THROW { W w_ = (W)w; int pri = ABSPRI (w_);w是监视器变量指针,revents是发生了的事件。ABSPRI宏将监视器中的等级转换成pendings数组下标,从而确定该监视器属于哪个数组。
在一次循环前,每个监视器的pending位都将是0。因为对于没有触发的事件,其默认是0;而对于本次触发的事件,则在事件对应的回调函数被执行前,pending值被设置为0。该pending位的作用是用于记录该监视器信息在相应等级pendings数组的子数组中的位置。
假如这个事件在一次循环中被触发两次。则第一次它会走入else的逻辑,根据pendingcnt中相应等级找到其应该属于的pending位数。如果此时pandings空间不足,则需要使用array_needsize重新分配并填充该空间;第二次时,pending位已经确定,此时只要更新events字段即可。
if (expect_false (w_->pending)) pendings [pri][w_->pending - 1].events |= revents; else { w_->pending = ++pendingcnt [pri]; array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2); pendings [pri][w_->pending - 1].w = w_; pendings [pri][w_->pending - 1].events = revents; } pendingpri = NUMPRI - 1; }
pendings里保存的是事件已经被触发的监视器信息,这就包括回调已经被调用的和即将被调用的。对于回调已经被调用过的监视器,libev不会将其从数组中去掉,而只是简单的将其pending值设置为0。那么本次循环要遍历的ANPENDING元素个数可能比数组个数要少,其个数是pendingcnt数组中相应等级作为下标对应的值。
pendings中将数据准备好后,libev使用EV_INVOKE_PENDING宏遍历本次循环中更新的ANPENDING对象,调用其回调函数。
# define EV_INVOKE_PENDING ev_invoke_pending (EV_A) void noinline ev_invoke_pending (EV_P) { pendingpri = NUMPRI; while (pendingpri) /* pendingpri possibly gets modified in the inner loop */ { --pendingpri; while (pendingcnt [pendingpri]) { ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri]; p->w->pending = 0; EV_CB_INVOKE (p->w, p->events); EV_FREQUENT_CHECK; } } } #ifndef EV_CB_INVOKE # define EV_CB_INVOKE(watcher,revents) (watcher)->cb (EV_A_ (watcher), (revents)) #endif最后我们看下包括函数调用的结构图