这几乎是我第一次记录阅读开源库源码。参考了sparkliang博客的libevent源码深度剖析系列
先说说event和event_base结构体
struct event { TAILQ_ENTRY (event) ev_next; /*已注册I/O事件链表
TAILQ_ENTRY宏定义
*/ TAILQ_ENTRY (event) ev_active_next;/*已激活事件链表*/ TAILQ_ENTRY (event) ev_signal_next;/*已注册signal链表*/ unsigned int min_heap_idx; /* for managing timeouts *//*timeout事件小根堆索引*/ struct event_base *ev_base; /*这个事件所属的event_base实例*/ int ev_fd; /*对于I/O事件,是绑定的文件描述符;对于signal事件,是绑定的信号*/ short ev_events; /* event关注的事件类型,它可以是以下3种类型: I/O事件: EV_WRITE和EV_READ 定时事件:EV_TIMEOUT 信号: EV_SIGNAL 辅助选项:EV_PERSIST,表明是一个永久事件 */ short ev_ncalls; /*事件就绪执行时,需要调用ev_callback的次数,通常为1*/ short *ev_pncalls; /* Allows deletes in callback *//*通常指向ev_ncalls或者为NULL*/ struct timeval ev_timeout; /*timeout的event超时值*/ int ev_pri; /* smaller numbers are higher priority *//*事件优先级*/ void (*ev_callback)(int, short, void *arg); /*event的回调函数,被ev_base调用,执行事件处理程序,这是一个函数指针,原型为:
void (*ev_callback)(int fd, short events, void *arg)
其中参数fd对应于ev_fd;events对应于ev_events;arg对应于ev_arg
*/ void *ev_arg; /*void*,表明可以是任意类型的数据,在设置event时指定*/ int ev_res; /* result passed to event callback *//*记录了当前激活事件的类型*/ int ev_flags; /*libevent用于标记event信息的字段,表明其当前的状态,有以下几种类型
#define EVLIST_TIMEOUT 0x01 // event在time堆中
#define EVLIST_INSERTED 0x02 // event在已注册事件链表中
#define EVLIST_SIGNAL 0x04 // 未见使用
#define EVLIST_ACTIVE 0x08 // event在激活链表中
#define EVLIST_INTERNAL 0x10 // 内部使用标记
#define EVLIST_INIT 0x80 // event已被初始化*/ };
struct event_base { const struct eventop *evsel; /*evsel指向了全局变量static const struct eventop *eventops[]中的第一个可用I/O。
libevent将系统提供的I/O demultiplex机制统一封装成了eventop结构;因此eventops[]包含了select、poll、kequeue和epoll等等其中的若干个全局实例对象。
*/ void *evbase; /*evsel指向的event_top的实例对象
*/ int event_count; /* counts number of total events */ int event_count_active; /* counts number of active events */ int event_gotterm; /* Set to terminate loop */ int event_break; /* Set to terminate loop immediately */ /* active event management */ struct event_list **activequeues; /*一个二级指针,前面讲过libevent支持事件优先级,因此你可以把它看作是数组,其中的元素activequeues[priority]是一个链表,链表的每个节点指向一个优先级为priority的就绪事件event
*/ int nactivequeues; /*指明已激活事件链表有多少个优先级*/ /* signal handling info */ struct evsignal_info sig; struct event_list eventqueue; /*链表,保存了所有的注册事件event的指针。*/ struct timeval event_tv; struct min_heap timeheap; /*管理定时事件的小根堆,最先达到超时的事件总是在第一个位置上
*/ struct timeval tv_cache; };
看代码,我更喜欢找到一个demo,从demo的调用过程来阅读代码,个人感觉吧逻辑可能会更清晰。
在sample文件夹下面有3个demo,time-test.c、signal-test.c、event-test.c。就挑最简单的time-test.c好了。
int lasttime; static void timeout_cb(int fd, short event, void *arg) { struct timeval tv; struct event *timeout = arg; int newtime = time(NULL); printf("%s: called at %d: %d\n", __func__, newtime, newtime - lasttime); lasttime = newtime; evutil_timerclear(&tv); tv.tv_sec = 2; event_add(timeout, &tv); } int main (int argc, char **argv) { struct event timeout; struct timeval tv; /* Initalize the event library */ event_init(); /* Initalize one event */ evtimer_set(&timeout, timeout_cb, &timeout); evutil_timerclear(&tv); tv.tv_sec = 2; event_add(&timeout, &tv); lasttime = time(NULL); event_dispatch(); return (0); }
1:event_init()初始化一个event_base实例。
1 struct event_base * 2 event_init(void) 3 { 4 struct event_base *base = event_base_new(); 5 //如果初始化成功,全局struct event_base *current_base指向这个对象
6 if (base != NULL) 7 current_base = base; 8 9 return (base); 10 } 11 12 struct event_base * 13 event_base_new(void) 14 { 15 int i; 16 struct event_base *base; 17 18 if ((base = calloc(1, sizeof(struct event_base))) == NULL) 19 event_err(1, "%s: calloc", __func__);//分配一个event_base的内存,并全部置为0 20 21 event_sigcb = NULL; //信号暂时用不到,先不去管它,关于信号的都先略过 22 event_gotsig = 0; 23 24 detect_monotonic();//检测clock_gettime(CLOCK_MONOTONIC,&ts)是否可用,如果可用就将全局的static int use_monotonic设为1 25 gettime(base, &base->event_tv);//检测第一个参数(event_base* base)base的缓存时间(base->tv_cache.tv_sec)是否为0.
1.不为0,第二个参数(timeval* tp)base->event_tv设置为base->tv_cache.
2.为0,第二个参数(timeval* tp)base->event_tv设置为当前时间。(我们刚初始化event_base所以这里是获取当前时间)
26 27 min_heap_ctor(&base->timeheap);//初始化timeout小根堆,因为还没有事件,所以全部置为0 28 TAILQ_INIT(&base->eventqueue);//初始化注册事件链表,TAILQ_INIT宏定义:
29 base->sig.ev_signal_pair[0] = -1; 30 base->sig.ev_signal_pair[1] = -1; 31 //在前面event_base中已经定义好的eventops[]中第一个可用的libevent封装好的I/O模式 32 base->evbase = NULL; 33 for (i = 0; eventops[i] && !base->evbase; i++) { 34 base->evsel = eventops[i]; 35 36 base->evbase = base->evsel->init(base); //假如在我的机器上第一个可用的是epollops,在epoll.c中的定义是这样的:
然后调用了epoll_init(event_base* base),如果失败,返回NULL,如果成功,返回这个初始化了的epollop对象指针,epollop结构体如下
37 } 38 39 if (base->evbase == NULL) //初始化失败,打印信息,调用exit退出 40 event_errx(1, "%s: no event mechanism available", __func__); 41 42 if (evutil_getenv("EVENT_SHOW_METHOD")) 43 event_msgx("libevent using: %s\n", 44 base->evsel->name); 45 46 /* allocate a single active event queue */ 47 event_base_priority_init(base, 1);//为event_base实例base初始化激活链表优先级个数,1说明当前情况所有事件都是同一个优先级。这个函数可以多次调用
以修改优先级个数,需要先确保链表中没有已激活事件,否则会失败。 48 49 return (base); 50 }
2:evtimer_set()初始化一个event。
//对evtimer_set的说明是:定义一个计时器事件
1 #define evtimer_set(ev, cb, arg) event_set(ev, -1, 0, cb, arg) 2 void 3 event_set(struct event *ev, int fd, short events, 4 void (*callback)(int, short, void *), void *arg) 5 { 6 /* Take the current base - caller needs to set the real base later */ 7 ev->ev_base = current_base; //将这个事件绑定到event_base实例上,如果有多个event_base实例,之后可以调用event_base_set绑定到其他event_base实例上 8 9 ev->ev_callback = callback; //回调函数指针 10 ev->ev_arg = arg; //传递给callback的参数 11 ev->ev_fd = fd; //文件描述符 12 ev->ev_events = events; //需要被监视的事件类型,读,写,超时,信号的组合,这里的定时器显然不是其中任何一个事件类型 13 ev->ev_res = 0; //记录当前事件那个数据类型被激活 14 ev->ev_flags = EVLIST_INIT; //标记这个事件已被初始化 15 ev->ev_ncalls = 0; //callback调用次数0,一般这个值在将event插入激活链表时设置为1 16 ev->ev_pncalls = NULL; //一般为指向ev_ncalls的指针或为NULL 17 18 min_heap_elem_init(ev);//void min_heap_elem_init(struct event* e) { e->min_heap_idx = -1; },只是将这个事件的小根堆索引设置为-1
19 20 /* by default, we put new events into the middle priority */ 21 if(current_base) 22 ev->ev_pri = current_base->nactivequeues/2; //设置这个事件的优先级为中等 23 }
3:event_add()将一个event添加到一个event_base实例。
1 int 2 event_add(struct event *ev, const struct timeval *tv) 3 { 4 struct event_base *base = ev->ev_base; 5 const struct eventop *evsel = base->evsel; //在前面event_init()及event_base结构中已经提到过,它指向某个封装好的I/O模式的结构体,其中存了该模式的5种接口的函数指针,和一个名称和一个标记 6 void *evbase = base->evbase; //我们以epoll为例,这个指针指向epoll的实例 7 int res = 0; 8 9 event_debug(( 10 "event_add: event: %p, %s%s%scall %p", 11 ev, 12 ev->ev_events & EV_READ ? "EV_READ " : " ", 13 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ", 14 tv ? "EV_TIMEOUT " : " ", 15 ev->ev_callback)); 16 17 assert(!(ev->ev_flags & ~EVLIST_ALL)); 18 19 /* 20 * prepare for timeout insertion further below, if we get a 21 * failure on any step, we should not change any state. 22 */ 23 if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {//如果要为这个event设置超时时间,且这个event没有在timeout链表中,就用realloc为这个event预留位置,同时保证原来的数据不会丢失 24 if (min_heap_reserve(&base->timeheap, 25 1 + min_heap_size(&base->timeheap)) == -1) 26 return (-1); /* ENOMEM == errno */ 27 } 28 29 if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) && 30 !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {//如果这个事件是读|写|信号,并且这个事件没有在注册链表也没有在已激活链表中。对于time-test.c不会用到I/O模型 31 res = evsel->add(evbase, ev); //将事件ev添加到I/O模型实例中, 32 if (res != -1) //如果注册成功,将这个event插入到已注册列表 33 event_queue_insert(base, ev, EVLIST_INSERTED); 34 } 35 36 /* 37 * we should change the timout state only if the previous event 38 * addition succeeded. 39 */ 40 if (res != -1 && tv != NULL) {//如果注册成功,且设置了超时时间 41 struct timeval now; 42 43 /* 44 * we already reserved memory above for the case where we 45 * are not replacing an exisiting timeout. 46 */ 47 if (ev->ev_flags & EVLIST_TIMEOUT) //如果这个event已经在timeout链表中,从timeout链表中移除这个event 48 event_queue_remove(base, ev, EVLIST_TIMEOUT); 49 50 /* Check if it is active due to a timeout. Rescheduling 51 * this timeout before the callback can be executed 52 * removes it from the active list. */ 53 if ((ev->ev_flags & EVLIST_ACTIVE) && 54 (ev->ev_res & EV_TIMEOUT)) { //如果这个event已经激活,且是以超时被激活,将callback的调用次数置为0,然后从已激活链表中移除这个event 55 /* See if we are just active executing this 56 * event in a loop 57 */ 58 if (ev->ev_ncalls && ev->ev_pncalls) { 59 /* Abort loop */ 60 *ev->ev_pncalls = 0; 61 } 62 63 event_queue_remove(base, ev, EVLIST_ACTIVE); 64 } 65 66 gettime(base, &now); 67 evutil_timeradd(&now, tv, &ev->ev_timeout); //记录这个event下次超时的时刻到ev_timeout中 68 69 event_debug(( 70 "event_add: timeout in %ld seconds, call %p", 71 tv->tv_sec, ev->ev_callback)); 72 73 event_queue_insert(base, ev, EVLIST_TIMEOUT); /*将这个设定好的event注册到timeout链表中。
如果I/O或signal事件在event_add时带了超时属性,这里会分离出一个独立的超时事件?
这个超时事件和I/O或signal没有关系,纯粹的一个只执行一次的计时事件,而且执行的是I/O或signal的回调,我要这独立的超时事件有何用?
所以I/O或signal事件千万不能设置超时属性,不然明明I/O或signal事件没有发生却调用了他们的回调函数就尴尬了*/
74 } 75 76 return (res); 77 }
4:event_dispatch()持续等待并处理已激活事件,除非没有注册事件。
1 /* 2 * Wait continously for events. We exit only if no events are left. 3 */ 4 5 int 6 event_dispatch(void) 7 { 8 return (event_loop(0)); 9 } 10 11 /* not thread safe */ 12 13 int 14 event_loop(int flags) 15 { 16 return event_base_loop(current_base, flags); 17 } 18 19 int 20 event_base_loop(struct event_base *base, int flags) 21 { 22 const struct eventop *evsel = base->evsel; 23 void *evbase = base->evbase; 24 struct timeval tv; 25 struct timeval *tv_p; 26 int res, done; 27 28 /* clear time cache */ 29 base->tv_cache.tv_sec = 0; //清空缓存时间 30 31 if (base->sig.ev_signal_added) 32 evsignal_base = base; 33 done = 0; 34 while (!done) { //事件主循环 35 /* Terminate the loop if we have been asked to */ 36 if (base->event_gotterm) { // 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记 37 base->event_gotterm = 0; 38 break; 39 } 40 41 if (base->event_break) { // 查看是否需要跳出循环,程序可以调用event_base_loopbreak()设置event_break标记。区别于event_gotterm,它在调用激活事件回调函数后也会检测 42 base->event_break = 0; 43 break; 44 } 45 46 /* You cannot use this interface for multi-threaded apps */ 47 while (event_gotsig) { 48 event_gotsig = 0; 49 if (event_sigcb) { 50 res = (*event_sigcb)(); 51 if (res == -1) { 52 errno = EINTR; 53 return (-1); 54 } 55 } 56 } 57 58 timeout_correct(base, &tv); //校准系统时间,
1.如果是使用的MONOTONIC(use_monotonic==1),不需要校准,
2.如果不是用的MONOTONIC,且如果当前时间 < event_base->event_tv则说明系统时间被修改为实际已经过了的时刻,
那么遍历小根堆中的每一项,并根据event_base->event_tv - 当前时间的差值,调整每
一项的超时时间。
不管什么情况,都将event_base->event_tv更新为当前时间就行了,tv中存储的为当前时间
59 60 tv_p = &tv; 61 if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) { //没有已激活事件,且主循环不是非阻塞的, 62 timeout_next(base, &tv_p); //计算timeout小根堆中距离下一个超时事件发生的时间,用于设置主循环休眠时间 63 } else { 64 /* 65 * if we have active events, we just poll new events 66 * without waiting. 67 */ 68 evutil_timerclear(&tv); //存在已激活事件或者非阻塞,tv设置为0(也就是立即处理已激活事件或立即进入下一次主循环的检测) 69 } 70 71 /* If we have no events, we just exit */ 72 if (!event_haveevents(base)) { //如果没有注册的事件,结束主循环 73 event_debug(("%s: no events registered.", __func__)); 74 return (1); 75 } 76 77 /* update last old time */ 78 gettime(base, &base->event_tv); //timeout_correct()中已经更新过了,这里似乎重复更新了一次? 79 80 /* clear time cache */ 81 base->tv_cache.tv_sec = 0; //清空time cache 82 83 res = evsel->dispatch(base, evbase, tv_p); //这里调用的相应I/O模型(我们假设epoll)的dispatch,超时时间为刚刚计算出的最小等待时间,
其中会将就绪的相应event(timeout不在次列,将在后面处理)插入到已激活链表
系统I/O允许设定一个timeout时间,所以这里可以很好的和计时器最小超时时间结合起来,即使没有I/O发生也能及时返回,让后续的计时器事件能够得到即时处理。 84 85 if (res == -1) 86 return (-1); 87 gettime(base, &base->tv_cache); //将time cache设置为当前时间 88 89 timeout_process(base); // 检查timeout小根堆中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中
超时事件至少在这个版本不吃EV_PERSIST的持久化加成。如果给I/O或signal事件加了超时属性,那么万幸的是从带超时的I/O或signal事件中独立出来的那个超时事件只执行一次就被删除了,不幸的是连带那个I/O或signal事件也没了^_^
所以强调1.4版本,千万不要给I/O或signal加超时属性!
90 91 if (base->event_count_active) { 92 event_process_active(base); // 处理激活链表中的就绪event,调用其回调函数执行事件处理
该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表,然后处理链表中的所有就绪事件;
这里只处理一个优先级的链表,如果有多个优先级事件已激活,低优先级事件都只能等下一次主循环处理,因此低优先级的就绪事件可能得不到及时处理; 93 if (!base->event_count_active && (flags & EVLOOP_ONCE)) 94 done = 1; 95 } else if (flags & EVLOOP_NONBLOCK) 96 done = 1; 97 } 98 99 /* clear time cache */ 100 base->tv_cache.tv_sec = 0; 101 102 event_debug(("%s: asked to terminate loop.", __func__)); 103 return (0); 104 }
time-test.c到此基本已经结束,libevent框架的主要工作流程。最后在激活事件的回调中又注册了这个计时器事件,所以会持续运行下去。
那么,在这个demo中完成了
1.event_base的初始化;
2.event的初始化;
3.event注册到event_base实例;
4.循环检测事件并处理;
还有一些步骤是没有提及的,比如:注销event_base。注销event我们前面已经涉及到,只是我们没有深入进去说明,在第4部89行timeout_process(),92行event_process_active(),以及注销event_base时都有从对应的链表中(如果注册链表中也有,还需要注销I/O模型中的对应事件)。
注销event的操作,不涉及释放event内存。注销event_base先注销它所有链表中关联的event,然后释放event_base的内存。