libevent-1.4/sample/signal-test.c
event_add(&signal_int, NULL);
将 struct event signal_int添加到struct event_base* base,即注册号监听事件以及回调后添加到Reactor上。
int event_add(struct event *ev, const struct timeval *tv)
{
struct event_base *base = ev->ev_base;
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase;
int res = 0;
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
if (min_heap_reserve(&base->timeheap,
1 + min_heap_size(&base->timeheap)) == -1)
return (-1); /* ENOMEM == errno */
}
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
res = evsel->add(evbase, ev);
if (res != -1)
event_queue_insert(base, ev, EVLIST_INSERTED);
}
/*
* we should change the timout state only if the previous event
* addition succeeded.
*/
if (res != -1 && tv != NULL) {
struct timeval now;
/*
* we already reserved memory above for the case where we
* are not replacing an exisiting timeout.
*/
if (ev->ev_flags & EVLIST_TIMEOUT)
event_queue_remove(base, ev, EVLIST_TIMEOUT);
/* Check if it is active due to a timeout. Rescheduling
* this timeout before the callback can be executed
* removes it from the active list. */
if ((ev->ev_flags & EVLIST_ACTIVE) &&
(ev->ev_res & EV_TIMEOUT)) {
/* See if we are just active executing this
* event in a loop
*/
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
event_queue_remove(base, ev, EVLIST_ACTIVE);
}
gettime(base, &now);
evutil_timeradd(&now, tv, &ev->ev_timeout);
event_queue_insert(base, ev, EVLIST_TIMEOUT);
}
return (res);
}
注意到event_add管理了定时事件、信号以及普通事件,而且还是用了队列,整个结构还是很清楚的,需要关注的是
evsel->add(evbase, ev)——epoll_add(evbase, ev),在event_base_new已经调用了epoll_init进行一系列初始化,event_add基本上就是调用evsignal_add以及epoll_ctl系统调用。
evsignal_add——evsignal_set_handler(base, SIGINT, evsignal_handler),最终会进行信号相关的系统调用:
/* save previous handler and setup new handler */
#ifdef HAVE_SIGACTION
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) {
event_warn("sigaction");
free(sig->sh_old[evsignal]);
sig->sh_old[evsignal] = NULL;
return (-1);
}
#else
if ((sh = signal(evsignal, handler)) == SIG_ERR) {
event_warn("signal");
free(sig->sh_old[evsignal]);
sig->sh_old[evsignal] = NULL;
return (-1);
}
*sig->sh_old[evsignal] = sh;
#endif
有没有觉得sigaction以及signal很熟悉,这个就是signal的系统调用,至此,信号SIGINT的回调函数evsignal_handler就被注册了。
很显然,这不是使用
event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, &signal_int);设置的回调函数signal_cb,那么signal_cb怎么样才会被调用了呢?
看看evsignal_handler函数:
static void evsignal_handler(int sig)
{
int save_errno = errno;
if (evsignal_base == NULL) {
event_warn(
"%s: received signal %d, but have no base configured",
__func__, sig);
return;
}
evsignal_base->sig.evsigcaught[sig]++;
evsignal_base->sig.evsignal_caught = 1;
#ifndef HAVE_SIGACTION
signal(sig, evsignal_handler);
#endif
/* Wake up our notification mechanism */
send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
}
其中关键部分在于
evsignal_base->sig.evsigcaught[sig]++;//sig=SIGINT
evsignal_base->sig.evsignal_caught = 1;
表明已经捕获到信号,使用以上信号计数以及标志,推迟处理回调函数signal_cb,在 event_base_dispatch(base)中处理,下节讲解,此时需要记住的是信号已经被捕获,并且有计数,关于计数的作用是不会遗漏相同的信号。
最后,可以看到使用了sockpair的写端ev_signal_pair[0],关于读端ev_signal_pair[1]的事件注册:
epoll_init——evsignal_init——
event_set(&base->sig.ev_signal,base->sig.ev_signal_pair [1],EV_READ|EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
ev_signal_pair[1]被设置为监听读就绪事件,在event_base_dispatch(base)中监听到ev_signal_pair[1]读事件,从而唤醒event_base_dispatch中的监听loop,evsignal_cb回调函数的作用就是读取sockpair中的数据,并不做任何处理,避免一直触发读就绪事件。
唤醒event_base_dispatch中的监听loop后就会处理信号捕获标记和计数,从而执行原本注册的回调函数signal_cb。
分离信号捕获,集中处理回调函数,整个逻辑清晰简洁,这样的做法很值得借鉴。