libevent源码分析--event_add()函数

来源:互联网 发布:手机软件 知乎 编辑:程序博客网 时间:2024/06/05 10:41

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操作,
也就是往小根堆中添加一个元素。


                                             
0 0
原创粉丝点击