event_add是第三个函数,函数参数ev是指向要注册的事件,tv是超时时间,
函数将ev注册到ev->ev_base上,事件类型由ev->ev_events指明。如果注册成功,ev讲被插入到已经注册链表中,如果tv不是null,则会同时注册定时事件,将ev添加到timer堆上,其中的巧妙之处请看以下分析
int 720 event_add(struct event *ev, const struct timeval *tv) 721 { 722 struct event_base *base = ev->ev_base; // 要注册到的event_base 723 const struct eventop *evsel = base->evsel; //base使用的系统I/O策略 /** 新的timer事件,调用tiamer heap接口在堆上预留以一个位置 这样能保证该操作的原子性 向系统I./O机制注册可能会失败,而当在堆上预留成功后, 定时事件的添加讲肯定不会失败, 而预留位置的可能结果是堆扩充,但是内部元素并不会改变 */ 724 void *evbase = base->evbase; 725 int res = 0; 726 727 event_debug(( 728 "event_add: event: %p, %s%s%scall %p", 729 ev, 730 ev->ev_events & EV_READ ? "EV_READ " : " ", 731 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ", 732 tv ? "EV_TIMEOUT " : " ", 733 ev->ev_callback)); 734 735 assert(!(ev->ev_flags & ~EVLIST_ALL)); 736 737 /* 738 * prepare for timeout insertion further below, if we get a 739 * failure on any step, we should not change any state. 740 */ 741 if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) { 742 if (min_heap_reserve(&base->timeheap, 743 1 + min_heap_size(&base->timeheap)) == -1) 744 return (-1); /* ENOMEM == errno */ 745 } 746 747 if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) && 748 !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) { 749 res = evsel->add(evbase, ev); 750 if (res != -1) 751 event_queue_insert(base, ev, EVLIST_INSERTED); 752 } 753 754 /* 755 * we should change the timout state only if the previous event 756 * addition succeeded. 757 */ 758 if (res != -1 && tv != NULL) { 759 struct timeval now; 760 761 /* 762 * we already reserved memory above for the case where we 763 * are not replacing an exisiting timeout. 764 */ 765 if (ev->ev_flags & EVLIST_TIMEOUT) 766 event_queue_remove(base, ev, EVLIST_TIMEOUT); /* 755 * we should change the timout state only if the previous event 756 * addition succeeded. 757 */ 758 if (res != -1 && tv != NULL) { 759 struct timeval now; 760 761 /* 762 * we already reserved memory above for the case where we 763 * are not replacing an exisiting timeout. 764 */ 765 if (ev->ev_flags & EVLIST_TIMEOUT) 766 event_queue_remove(base, ev, EVLIST_TIMEOUT); 767 768 /* Check if it is active due to a timeout. Rescheduling 769 * this timeout before the callback can be executed 770 * removes it from the active list. */ 771 if ((ev->ev_flags & EVLIST_ACTIVE) && 772 (ev->ev_res & EV_TIMEOUT)) { 773 /* See if we are just active executing this 774 * event in a loop 775 */ 776 if (ev->ev_ncalls && ev->ev_pncalls) { 777 /* Abort loop */ 778 *ev->ev_pncalls = 0; 779 } 780 781 event_queue_remove(base, ev, EVLIST_ACTIVE); 782 } 783 784 gettime(base, &now); 785 evutil_timeradd(&now, tv, &ev->ev_timeout); 786 787 event_debug(( 788 "event_add: timeout in %ld seconds, call %p", 789 tv->tv_sec, ev->ev_callback)); 790 791 event_queue_insert(base, ev, EVLIST_TIMEOUT); 792 } 793 794 return (res); 795 } 函数的主要目的是讲event添加到相应的event_base中,首先将这个event的各个主要字段取出来,包括ev_base ,然后这个event_base的evsel和evbase都取出来, 741~745的解释就注释一样,为了保证原子操作,也是为了安全,首先将空余的位置准备好,然后再插入,而不是直接插入,前者肯定能够保证正确的完成,但是后者 不一定能报保证完成,如果这个事件是超时事件,那么就再扩充小根堆的大小,为这个事件预留一个位置。注意这里只是将小根堆扩充了一个位置,并没有实际的操作。 在747~752行中,如果这个事件是I/O事件,那么就判断,注意判断的地方多了一个ev_flags,就是判断这个事件是否已经在队列中了。如果用户重复event_add相同的event 这里就可以避免这类事情的发生。首先evsel->add(evbase,ev),将这个event添加到相应的event_base中,这里添加是指由什么操作,下面再讲解(这里已经欠了两个详细 的讲解了,一个是上篇文章中的关于evsel->init的讲解,一个是这里的evsel->add讲解),如果添加成功,那么就event_queue_insert添加到队列中,下面看看这个函数究竟 做了啥!!void 976 event_queue_insert(struct event_base *base, struct event *ev, int queue) 977 { 978 if (ev->ev_flags & queue) { 979 /* Double insertion is possible for active events */ 980 if (queue & EVLIST_ACTIVE) 981 return; 982 983 event_errx(1, "%s: %p(fd %d) already on queue %x", __func__, 984 ev, ev->ev_fd, queue); 985 } 986 987 if (~ev->ev_flags & EVLIST_INTERNAL) 988 base->event_count++; 989 990 ev->ev_flags |= queue; 991 switch (queue) { 992 case EVLIST_INSERTED: 993 TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next); 994 break; 995 case EVLIST_ACTIVE: 996 base->event_count_active++; 997 TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], 998 ev,ev_active_next); 999 break; 1000 case EVLIST_TIMEOUT: { 1001 min_heap_push(&base->timeheap, ev); 1002 break; 1003 } 1004 default: 1005 event_errx(1, "%s: unknown queue %x", __func__, queue); 1006 } 1007 }函数的参数说明,插入队列是有标志位的,ev_flags是libevent用于标记event信息的字段,表面其当前的状态,也就是这个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 已被初始化在event调用event_set初始化结束以后,event的ev_flags是EVLIST_INIT,在这里的978行,首先判断event的当前状态和将要变成的状态是否一样,一样的话就没必要
有多余的操作,尤其是往活跃链表中添加,如果正常的话,就进入991行的switch判断,不同的标志位就是不同的操作,总而言之都是有插入的操作的,不管是往事件的
双向链表中还是中还是往激活链表中或者是小根堆中,这里的三种情况我们都会慢慢遇到,这里遇到的第一个简单情况就是EVLIST_INSERTED.仍旧是一句话,
TAILQ_INSERT_TAIL。如果这是一个定时事件,就是运行从758开始的代码,判断的顺序还是差不多的,首先判断这个事件是否已经在链表中存在,不存在的话才会进行一下的
工作,如果这个event的ev_flags已经是EVLIST_TIMEOUT,那么就将这个event从链表中删除,如果这个event的ev_flags是EVLIST_ACTIVE,并且这个event是由EV)TIMEOUT
激活的,也就是说,这个event由于EV_TIMEOUT激活并且当前的ev_flags是EVLIST_TIMEOUT,那么仍旧将这个event从链表中删除。其实在758~783这些都是判断如果是
timeout事件的话,在添加这类事件之前的一些列判断。785~792这几行是进行操作timeout事件的东西。785行是处理的东西,在791行最后调用的是min_heap_push操作,
也就是往小根堆中添加一个元素。