libev I/O事件
libev是来实现reactor模式,主要包含三大部分:
1. watcher:watcher是Reactor中的Event Handler。
作用:1)向事件循环提供了统一的调用接口(按类型区分)
2)它是外部代码的注入口,维护着具体的watcher信息,如:绑定的回调函数,watcher的优先级,是否激活等。
定义位置:在ev.h中可以看到各种watcher的定义,如:ev_io, ev_timer等
2.ev_loop:充当一个Reactor的角色,是事件循环的上下文环境,像树干一样串起了各个叶子watcher。
定义位置:1)ev_loop定义在ev.c文件中
2)ev_vars.h定义了ev_loop的各种属性
3)ev_wrap.h定义了与loop相关的各种宏
watcher的管理:在ev_loop中watcher按各自类型进行分类存储
3.ev_run:执行事件的循环引擎,即reactor的select方法,是树的主体部分。
作用:1)通过像ev_run传入一个ev_loop实例,便可以开启一个事件循环
2)ev_run实际为do-while循环,在循环期间检查loop中注册的各种watcher事件。若事件就绪,就会触发相应的watcher,循环持续直至遇到ev_break或没有就绪的watcher
对于libev中的I/O事件的触发来说,也遵循上述所说。
1.I/O事件的watcher定义
typedef struct ev_io { EV_WATCHER_LIST (ev_io) int fd; //文件描述符 int events; //事件类型 } ev_io;
ev_io是一种具体的watcher,它有两个自己专有的成员变量fd和events。在ev_io中有部分参数是从EV_WATCHER_LIST (ev_io)继承而来
#define EV_WATCHER_LIST(type) EV_WATCHER (type) struct ev_watcher_list *next;
#define EV_WATCHER(type) \ int active; //active 表示当前watcher是否被激活。ev_TYPE_start调用后置位,ev_TYPE_stop调用后复位 int pending;//表示当前watcher有事件就绪,等待处理。pending的值其实就是当前watcher在pendings队列中的下标; EV_DECL_PRIORITY //是当前watcher的优先级; EV_COMMON // 在前面有定义为 *data;data: 附加数据指针,用来在watcher中携带额外所需的数据; EV_CB_DECLARE (type)//cb:是事件触发后的回调函数定义。
代码定义中嵌套了一些基类和其他一些宏定义,这里直接写出来,方便理解。ev_io的watcher的完整参数如下:
typedef struct ev_io { int active; int pending; int priority; void *data; void (*cb)(struct ev_loop *loop, struct ev_io *w, int revents); struct ev_watcher_list *next; int fd; /* 这里的fd,events就是派生类的私有成员,分别表示监听的文件fd和触发的事件(可读还是可写) */ int events; } ev_io;
2、ev_loop
struct ev_loop
{
ev_tstamp ev_rt_now;//其中ev_tstamp 就是时间单位,实际上就是double类型
#define ev_rt_now ((loop)->ev_rt_now)
// 这里decl是declare的意思,ev_vars.h 里面会定义一堆的变量,这些变量
// 都是本结构的成员,ev_vars.h展开的时候会用到下面这一行VAR的宏
#define VAR(name,decl) decl; #include "ev_vars.h" #undef VAR };
ev_vars.h中包含很多关键的成员,如:
epoll相关的成员变量
#if EV_USE_EPOLL || EV_GENWRAP VARx(struct epoll_event *, epoll_events) // 相当于struct epoll_event *epoll_events VARx(int, epoll_eventmax) ////目前epoll_events数组的大小,可以扩充,每次以2倍的大小扩充 VARx(int *, epoll_eperms) VARx(int, epoll_epermcnt) VARx(int, epoll_epermmax) #endif
VARx(int, backend_fd)// 对于epoll来说,就是epoll使用的fd VARx(ev_tstamp, backend_mintime) /* assumed typical timer resolution */ //对于epoll来说,实际的函数是ev_epoll.c中的epoll_modify函数,这个函数会执行epoll_ctl VAR (backend_modify, void (*backend_modify)(EV_P_ int fd, int oev, int nev)) //对于epoll来说,实际的函数是ev_poll.c中的epoll_poll函数,这个函数会执行epoll_wait VAR (backend_poll , void (*backend_poll)(EV_P_ ev_tstamp timeout))
fd相关的成员变量
VARx(ANFD *, anfds)//这个数组是以fd为索引 VARx(int, anfdmax)//anfd 数组的大小 VARx(int *, fdchanges)// fdchangemax大小的数组,每个元素是一个fd,这个数组中存了所有epoll需要poll的fd VARx(int, fdchangemax)//数组的容量 VARx(int, fdchangecnt)// 数组中实际的元素的大小
ANFD和fd一一对应,结构体ANFD如下(在ev.c中进行定义)
typedef struct { WL head;//关注同一个fd的事件的watcher的链表,一个fd可以有多个watcher监听它的事件 unsigned char events; /* 所监视的事件*/ unsigned char reify; /* 标志位,用来标记ANFD需要被重新实例化(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 #if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP SOCKET handle; #endif #if EV_USE_IOCP OVERLAPPED or, ow; #endif } ANFD;
ANFD表示事件循环中对一个文件描述符fd的监视的基本信息结构体
3 ev_run
在ev_run之前还有有如下几个需要关注的步骤,进行IO事件注册
1)IO事件的初始化和设置(在ev.h中进行定义)
#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)//初始化 #define ev_io_set(ev,fd_,events_) do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)//设置
2) ev_io_start:每当有新的IO监视器fd加入,调用wlist_add()添加到anfds[fd]的链表head中。
void noinline ev_io_start (EV_P_ ev_io *w) EV_THROW//# define EV_P struct ev_loop *loop # define EV_P_ EV_P, { int fd = w->fd; if (expect_false (ev_is_active (w))) return; assert (("libev: ev_io_start called with negative fd", fd >= 0)); assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE)))); EV_FREQUENT_CHECK; ev_start (EV_A_ (W)w, 1);//将参数w表示的watcher激活(w->active=1) array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero); wlist_add (&anfds[fd].head, (WL)w);//将watcher w 加入到 w所关注的fd在anfds[](loop中)中相应的位置处的结构体ANFD中的watcher list链表中。 /* common bug, apparently */ assert (("libev: ev_io_start called with corrupted watcher", ((WL)w)->next != (WL)w)); fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);//将w所关注的fd加入到int *fdchanges数组中。 w->events &= ~EV__IOFDSET; EV_FREQUENT_CHECK; }
3)fd_change:如果一个anfds的元素监控条件发生改变,如何修改这个元素的监控条件呢。anfds的下标可以用fd来表示,这里有一个新的数组,数组元素内容是新添加的要监视的IO事件的fd或者修改监视内容的fd,数组名是fdchanges,也是动态数组。这个数组记录了新加入fd或者修改的fd的值,具体实现函数为“fd_change”inline_size void
fd_change (EV_P_ int fd, int flags) { unsigned char reify = anfds [fd].reify; anfds [fd].reify |= flags;//标志,表示fd监视条件被修改了 if (expect_true (!reify))//如果fd最初的监视条件为空,表示新加入的fd { ++fdchangecnt;//fd计数器加一 array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2);//调整fdchanges数组大小
//添加到fdchanges数组中 fdchanges [fdchangecnt - 1] = fd; } //如果不是新加入的fd,则fdchanges数组中已经有fd了。表示以前添加过对fd的IO监视 }
一个新的IO事件注册时,首先是进行ev_io_start函数,通过事件的文件描述符fd,调用wlist_add()添加到anfds[fd]的链表head中。若fd的监控条件有改动了(监控条件初始为空则为新加入的时间),则在fdchanges数组中记录下该fd,fdchanges数组中包含所有事情的fd,在后续的步骤中调用系统的接口修改对该fd监控的条件。
3) ev_run
a.调用“fd_reify”:遍历fdchanges数组,如果发现fd的监视条件发生变化了,就会调用epoll_ctl()函数来改变fd的监视状态。
fdchanges数组记录了anfds数组中的watcher监控条件可能被修改的文件描述符,并在适当的时候将调用系统的epoll_ctl或则其他文件复用机制修改系统监控的条件。
inline_size void fd_reify (EV_P) { int i;
for (i = 0; i < fdchangecnt; ++i) { int fd = fdchanges [i]; //遍历取出可能改变监控条件的fd,得到anfds中的下标 ANFD *anfd = anfds + fd;// ev_io *w;//定义一个ev_io指针 unsigned char o_events = anfd->events; unsigned char o_reify = anfd->reify; anfd->reify = 0; /*if (expect_true (o_reify & EV_ANFD_REIFY)) probably a deoptimisation */ { anfd->events = 0; for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next) //获得fd全部的新的监控事件集合,存放在events成员变量中 anfd->events |= (unsigned char)w->events; if (o_events != anfd->events)//如果新监控事件和旧监控事件不同, o_reify = EV__IOFDSET; /* actually |= *///修改标志位,表示fd监控条件改变 } if (o_reify & EV__IOFDSET)//fd监控条件改变,调用backend_modify也就是epoll_ctl()修改fd的监控条件 backend_modify (EV_A_ fd, o_events, anfd->events); } fdchangecnt = 0;//一次遍历完成,fdchanges数组个数清零 }
b)time_update 更新时间,校准时间(后续再补充)
c) 调用backend_poll(loop, waittime):对于使用epoll的系统来说,它实际上是调用ev_epoll.c 中的epoll_poll()函数,执行wait 操作:
eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, timeout * 1e3);
成功的话,返回了响应事件的个数,然后执行了fd_event()(ev_epoll.c文件调用,在ev.c中定义函数)
inline_speed void fd_event (EV_P_ int fd, int revents) { ANFD *anfd = anfds + fd; // ! 逻辑反操作 if (expect_true (!anfd->reify))////reify是0,取反即为1 走进if条件里面 /*如果reify是0,则表示我们添加了新的事件在fd上*/ fd_event_nocheck (EV_A_ fd, revents); }
fd_event_nocheck:
inline_speed void fd_event_nocheck (EV_P_ int fd, int revents) { ANFD *anfd = anfds + fd; ev_io *w; for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next)//对fd上的监视器依次做检测, { int ev = w->events & revents;//相应的事件的条件 if (ev)//pending条件满足,监控器加入到pendings数组中pendings[pri]上的pendings[pri][old_lenght+1]的位置上 ev_feed_event (EV_A_ (W)w, ev); } }
ev_feed_event
void noinline ev_feed_event (EV_P_ void *w, int revents) EV_THROW { W w_ = (W)w; int pri = ABSPRI (w_); 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; }
fd_event
做一个监控条件是否改变的判断后到fd_event_nocheck
里面对anfds[fd]数组中的fd上的挂的监控器依次做检测,如果pending条件符合,便通过ev_feed_event
将该监控器加入到pendings数组中pendings[pri]上的pendings[pri][old_lenght+1]的位置上。这里要介绍一个新的数据结构,他表示pending中的wather也就是监控条件满足了,但是还没有触发动作的状态。
typedef struct { W w; int events; /* the pending event set for the given watcher */ } ANPENDING;
pendings就是这个类型的一个二维数组数组。其以watcher的优先级为一级下标,再以该优先级上pengding的监控器数目为二级下标,对应的监控器中的pending值就是该下标加一的结果。其定义为 ANPENDING *pendings [NUMPRI]
。同anfds一样,二维数组的第二维 ANPENDING *
是一个动态调整大小的数组。
以上这个一系列的操作可以认为是fd_reify的后续操作,xxx_reify目的最后都是将pending的watcher加入到这个pengdings二维数组中。 这里用个图梳理下结构
d)time_update 再次更新校准时间
timers_reify(loop) 相对时间定时器,如果最近的定时器已经触发过了,则重新选出下一个最近的定时器,将其置于堆顶。(具体分析见Libev——ev_timer 相对时间定时器)
/* 遍历pendings二维数组,执行事件触发回调函数 */ void noinline ev_invoke_pending (EV_P) { pendingpri = NUMPRI;//#define NUMPRI (EV_MAXPRI - EV_MINPRI + 1) while (pendingpri) /* pendingpri possibly gets modified in the inner loop 外层while循环是按优先级递减*/ { --pendingpri; while (pendingcnt [pendingpri]) //内层while循环同一优先级的ANPENDING按数组顺序进行遍历,遍历到的每一个ANPENDING都对应一个watcher,得到watcher之后就执行该watcher对应的回调函数。 { ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri];//这个是在干吗,触发回调函数了吗 p->w->pending = 0;//pending的值其实就是当前watcher在pendings队列中的下标,这里将pending值置0即为优先级放到最低 EV_CB_INVOKE (p->w, p->events); EV_FREQUENT_CHECK; } }