libevent1.4 阅读记录一

这几乎是我第一次记录阅读开源库源码。参考了sparkliang博客的libevent源码深度剖析系列

先说说event和event_base结构体

struct event {
    TAILQ_ENTRY (event) ev_next;  /*已注册I/O事件链表
                      TAILQ_ENTRY宏定义
                    #define TAILQ_ENTRY(type)           \
                    struct {                \
                      struct type *tqe_next;  /* next element */      \
                      struct type **tqe_prev; /* address of previous next element */  \
                    }
                    */
    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等等其中的若干个全局实例对象。
                  static const struct eventop *eventops[] = {
                  #ifdef HAVE_EVENT_PORTS
                      &evportops,
                  #endif
                  #ifdef HAVE_WORKING_KQUEUE
                      &kqops,
                  #endif
                  #ifdef HAVE_EPOLL
                      &epollops,
                  #endif
                  #ifdef HAVE_DEVPOLL
                      &devpollops,
                  #endif
                  #ifdef HAVE_POLL
                      &pollops,
                  #endif
                  #ifdef HAVE_SELECT
                      &selectops,
                  #endif
                  #ifdef WIN32
                      &win32ops,
                  #endif
                      NULL   //c中常用于结束的标记
                  };
                  */
    void *evbase;    /*evsel指向的event_top的实例对象
              struct eventop {
                  const char *name;
                  void *(*init)(struct event_base *);
                  int (*add)(void *, struct event *);
                  int (*del)(void *, struct event *);
                  int (*dispatch)(struct event_base *, void *, struct timeval *);
                  void (*dealloc)(struct event_base *, void *);
                  /* set if we need to reinitialize the event base */
                  int need_reinit;
              };
              也就是说,在libevent中,每种I/O demultiplex机制的实现都必须提供这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发
              */
    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
                      event_list定义如下:
                      /*
                      * Tail queue definitions.
                      */
                      #define _TAILQ_HEAD(name, type, qual)                   \
                      struct name {                               \
                          qual type *tqh_first;       /* first element */     \
                          qual type *qual *tqh_last;  /* addr of last next element */ \
                      }
                      #define TAILQ_HEAD(name, type)  _TAILQ_HEAD(name, struct type,)
                      TAILQ_HEAD (event_list, event);
                       */
    int nactivequeues;  /*指明已激活事件链表有多少个优先级*/

    /* signal handling info */
    struct evsignal_info sig;

    struct event_list eventqueue;    /*链表,保存了所有的注册事件event的指针。*/
    struct timeval event_tv;

    struct min_heap timeheap;      /*管理定时事件的小根堆,最先达到超时的事件总是在第一个位置上
                      typedef struct min_heap
                      {
                       struct event** p;
                      unsigned n, a; //a:小根堆分配的event指针个数,n:小根堆中实际存在的event指针数量
                      }min_heap_t;
                      */

    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宏定义:
                      #define TAILQ_INIT(head) do {                       \
                          (head)->tqh_first = NULL;                   \
                          (head)->tqh_last = &(head)->tqh_first;              \
                      } while (/*CONSTCOND*/0)
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中的定义是这样的:
                            const struct eventop epollops = {
                                "epoll",
                                epoll_init,
                                epoll_add,
                                epoll_del,
                                epoll_dispatch,
                                epoll_dealloc,
                                1 /* need reinit */
                            };
                            之后都用libevent封装的epoll模型来说明,
                                 然后调用了epoll_init(event_base* base),如果失败,返回NULL,如果成功,返回这个初始化了的epollop对象指针,epollop结构体如下
                            struct epollop {
                                struct evepoll *fds;
                                int nfds;
                                struct epoll_event *events;
                                int nevents;
                                int epfd;
                            };
                            这里虽然初始化了epoll模型,但是对于time-test.c来说event->ev_events值为0,它不涉及到I/O模型,只是一个计时器事件。
                            后面看情况再来说epoll_init,放在这里会让这段太长
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的内存。

 

转载于:https://www.cnblogs.com/dyan1024/p/9957057.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值