libevent将IO事件、信号事件和定时器事件很好的结合在一起,采用了统一的事件源方式,即把信号事件也转换成IO事件,然后采用同一套IO复用机制去监听。
libevent的事件循环通过event_base_loop完成,另外一个事件循环函数是event_base_dispatch,其功能上即为没有设置标志的 event_base_loop(base, 0)。即event_base_dispatch()将一直运行,直到没有已经注册的事件了,或者调用了event_base_loopbreak()或者event_base_loopexit()为止。整个事件的循环流程如下图所示:
说明:
1 . 时间校正
2 . 事件等待
在循环前,libevent会检查signal标志位是否被设置。若设置要监听SIGINT这个信号,那么在实现的内部就对SIGINT这个信号设置捕抓函数。此外,在实现的内部还要建立一条管道(pipe,写管道ev_signal_pair[0]),并把这个管道加入到多路IO复用函数中。当SIGINT这个信号发生后,捕抓函数将会被调用。而这个捕抓函数的工作就是往管道写入一个字符(这个字符往往等于所捕抓到信号的信号值)。此时,这个管道ev_signal_pair[1]就变成是可读的了,多路IO复用函数能检测到这个管道变成可读的了。换句话说就是多路IO复用函数检测到这个SIGINT信号发生了,这也就完成了对信号的监听工作。
3 . 事件执行
对于激活的事件执行方式:按优先级从高到低。因而,低优先级的事件可能不能立即得到执行。
int
event_base_loop(struct event_base *base, int flags)
{
....
/* Grab the lock. We will release it inside evsel.dispatch, and again as we invoke user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->running_loop) {
event_warnx("%s: reentrant invocation. Only one event_base_loop"
" can run on each event_base at once.", __func__);
EVBASE_RELEASE_LOCK(base, th_base_lock);
return -1;
}
base->running_loop = 1;
clear_time_cache(base);
// 检查signal信号标记是否被设置
if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
// 设置evsignal_base=base,其为全局变量。
// 处理signal时,用于指明signal所属的event_base实例
evsig_set_base(base);
done = 0;
....
while (!done) {
base->event_continue = 0;
// 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记
if (base->event_gotterm) {
break;
}
// 调用event_base_loopbreak()设置event_break标记
if (base->event_break) {
break;
}
// 时间校正,若时间有问题,需要更新min-heap和common-timeout_list的超时时间
timeout_correct(base, &tv);
// 根据timer heap中事件的最小超时时间,计算系统I/O多路监听的最大等待时间
tv_p = &tv;
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);
}
// 如果当前没有注册事件,就退出
if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
event_debug(("%s: no events registered.", __func__));
retval = 1;
goto done;
}
// 更新last wait time,并清空time cache
gettime(base, &base->event_tv);
clear_time_cache(base);
// 调用系统IO多路复用机制监听IO事件,若设置了signal标志位,则signal事件也会被监听为IO事件。
// 将signal event、IO event插入到激活链表event_io_map中
res = evsel->dispatch(base, tv_p);
if (res == -1) {
event_debug(("%s: dispatch returned unsuccessfully.",
__func__));
retval = -1;
goto done;
}
// 将event_base的time cache赋值为当前系统时间
update_time_cache(base);
// 检查heap中的timer events,将就绪的timer event从min-heap上删除,
并插入到激活链表中
timeout_process(base);
if (N_ACTIVE_CALLBACKS(base)) {
// 按优先级处理链表中的所有激活事件
int n = event_process_active(base);
if ((flags & EVLOOP_ONCE)
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
event_debug(("%s: asked to terminate loop.", __func__));
done:
// 循环结束,清空时间缓存
clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);
}
其中,signal的结构体为:
//evsignal-internal.h文件
struct evsig_info {
//用于监听socketpair读端的event. ev_signal_pair[1]为读端
struct event ev_signal;
//socketpair
evutil_socket_t ev_signal_pair[2];
//用来标志是否已经将ev_signal这个event加入到event_base中了
int ev_signal_added;
//用户一共要监听多少个信号
int ev_n_signals_added;
//数组。用户可能已经设置过某个信号的信号捕抓函数。但
//Libevent还是要为这个信号设置另外一个信号捕抓函数,
//此时,就要保存用户之前设置的信号捕抓函数。当用户不要
//监听这个信号时,就能够恢复用户之前的捕抓函数。
//因为是有多个信号,所以得用一个数组保存。
#ifdef _EVENT_HAVE_SIGACTION
struct sigaction **sh_old;
#else//保存的是捕抓函数的函数指针,又因为是数组。所以是二级指针
ev_sighandler_t **sh_old;
#endif
/* Size of sh_old. */
int sh_old_max; //数组的长度
};
在libevent中,使用event_base_loopexit函数结束一个事件循环,这个函数其实只是注册了一个只运行一次的定时器(event_loopexit_cb),而event_loopexit_cb只是简单地将标志位(event_gotterm)设置为1,如下:
int
event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
{
return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb,
event_base, tv));
}
/** Callback: used to implement event_base_loopexit by telling the event_base * that it's time to exit its loop. */
static void
event_loopexit_cb(evutil_socket_t fd, short what, void *arg)
{
struct event_base *base = arg;
base->event_gotterm = 1;
}
本文参考:
1 . Libevent源码分析—–信号event的处理
2 . libevent源码深度剖析