爱程序网

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后的显示,从中可见函数调用顺序。

关于爱程序网 - 联系我们 - 广告服务 - 友情链接 - 网站地图 - 版权声明 - 人才招聘 - 帮助