libevent信号集成


libevent作为unix/linux下的网络IO库高效的将IO事件、Timeout事件、信号事件集成在一起。本文主要讲解集成信号事件的来龙去脉。 首先贴一段客户端应用信号事件的代码: static void signal_cb(int fd, short event, void *arg) { struct event *signal = arg;   printf("%s: got signal %d\n", __func__, EVENT_SIGNAL(signal));   if (called >= 2) event_del(signal);   called++; }   int main (int argc, char **argv) { struct event signal_int;   /* Initalize the event library */ struct event_base* base = event_base_new();   /* Initalize one event */ event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,     &signal_int); event_base_set(base, &signal_int);   event_add(&signal_int, NULL);   event_base_dispatch(base); event_base_free(base);   return (0); }     event_init()初始化一个event_base(反应堆实例),然后由evtimer_set()设置定时器事件的回调函数,接着event_add()把定时器事件加入反应堆实例中.最后进入event_dispatch()主循环. 将事件用一元祖表示:(event,fd, flag, handler)即(事件,文件描述符,标志位,回调函数),上述代码中信号事件可表示为(signal_int ,SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb),其中SIG_INT信号通过ctrl+c产生。 首先初始化反应堆实例,核心代码为反应堆实例struct event_base分配存储空间,初始化用到的事件IO复用机制(select、epoll等) for (i = 0; eventops[i] && !base->evbase; i++) { base->evsel = eventops[i];   base->evbase = base->evsel->init(base);//struct selectop* }       本文假设IO复用机制为select,其中base->evsel->init(base)会调用select_init,该函数回调用evsignal_init(base),初始化base->sig.ev_signal_pair[ ]数组,libevent会为base->sig.ev_signal事件注册evsignal_cb回调函数,base->sig.ev_signal事件用三元组表示为(base->sig.ev_signal,base->sig.ev_signal_pair[1], evsignal_cb) ,evsignal_cb为信号发生时从base->sig.ev_signal_pair[1]读取1字节的数据,改事件为一普通IO事件,之后会加入到Inserted链表中。       之后将signal_int 加入到Inserted链表中,会调用到event_add,实际调用的是select_add,由于该事件是EV_SIGNAL事件进而调用evsignal_add,在该函数中会重新注册信号事件的回调函数evsignal_handler,此事件四元组变为(signal_int ,SIGINT, EV_SIGNAL|EV_PERSIST,  evsignal_handler )会设置 evsignal_base->sig.evsigcaught[sig]++; evsignal_base->sig.evsignal_caught = 1; 并向evsignal_base->sig.ev_signal_pair[0]写入一字节数据,此处是用sigaction注册的信号处理函数是针对SIGINT信号的,即当按下ctrl+c组合键时相应此函数,     在此处会将(base->sig.ev_signal,base->sig.ev_signal_pair[1], evsignal_cb)加入到Inserted链表中, if (!sig->ev_signal_added) { if (event_add(&sig->ev_signal, NULL)) return (-1); sig->ev_signal_added = 1; } 之后,会调用TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next);将signal_int  加入到sig队列中。 以上即为初始化和注册过程,之后为相应事件调用事件的回调函数的过程(执行event_base_loop的过程)。 主要分为两个过程evsel->dispatch(base, evbase, tv_p)即执行select等待描述符就绪将对应事件加入到active队列的过程和扫描active队列依次执行事件对应回调函数的过程。     在  select_dispatch循环中在select轮询文件描述符时,按下ctrl+c键会中断select的执行使其返回-1,即执行以下代码: if (res == -1) { if (errno != EINTR) { event_warn("select"); return (-1); }   evsignal_process(base); return (0); }  在evsignal_process中将使 base->sig.evsignal_caught = 0; 并将sig队列中的事件加入到active队列中, for (i = 1; i < NSIG; ++i) { ncalls = sig->evsigcaught[i]; if (ncalls == 0) continue; sig->evsigcaught[i] -= ncalls;   for (ev = TAILQ_FIRST(&sig->evsigevents[i]);     ev != NULL; ev = next_ev) { next_ev = TAILQ_NEXT(ev, ev_signal_next); if (!(ev->ev_events & EV_PERSIST)) event_del(ev); event_active(ev, EV_SIGNAL, ncalls); }   } select返回,之后继续进入事件循环,发现evsignal_base->sig.ev_signal_pair[1]可读,将其加入到active队列中,进入active队列的调度。 下述显示是打开debug后回调函数和主要函数的调用顺序: [debug] event_add: event: 0xbff320b8,    call 0x8048780
[debug] evsignal_add: 0xbff320b8: changing signal handler
[debug] _evsignal_set_handler: evsignal (2) >= sh_old_max (0), resizing
[debug] event_add: event: 0x99d8028, EV_READ   call 0xb7753d50
^C[debug] evsignal_handler: received signal 2,called
[debug] epoll_dispatch: epoll_wait reports--- res=-1 -1
signal_cb: got signal 2
[debug] epoll_dispatch: epoll_wait reports 1
[debug] evsignal_cb: evsignal_cb callded fd=5 其中^C之后的为按下ctrl+c后的显示,从中可见函数调用顺序。

相关内容