libevent源码分析三--signal事件响应
libevent支持io事件,timeout事件,signal事件,这篇文件将分析libevent是如何组织signal事件,以及如何实现signal事件响应的。
1. sigmap
类似于io事件,event_base有另外一个hash表sigmap用于存储signal事件,hash表使用signal number做数组索引,同一个signal number的不同事件使用双向链表连接(使用struct event结构中的ev_. ev_signal. ev_signal_next成员来构造这个链表)。使用到的数据结构有struct event_signal_map与struct evmap_signal,定义如下:
/* Used to map signal numbers to a list of events. If EVMAP_USE_HT is not defined, this structure is also used as event_io_map, which maps fds to a list of events. */ struct event_signal_map { /* An array of evmap_io * or of evmap_signal *; empty entries are * set to NULL. */ void **entries; /* The number of entries available in entries */ int nentries; };
/* An entry for an evmap_signal list: notes all the events that want to know when a signal triggers. */ struct evmap_signal { struct event_dlist events; };
event_base中的成员sigmap即是struct event_signal_map结构的实例。entries指向一个动态分配的指针数组,数组长度为nentries;数组成员是一个指向动态分配的evmap_sinal结构的指针。sigmap的结构大致可以用图1-1表示,
图1-1 sigmap存储结构
2. signal后端
signal事件的后端与io事件的后端不同,它们的操作基本不相同,libevent在源文件signal.c中实现signal事件的后端操作,并在event_base中使用成员struct eventop *evsigsel专门指向signal事件的后端操作,未复用指向io后端的evsel指针,signal后端使用到的信息保存在struct evsig_info sig成员中。
struct evsig_info的定义如下,这里主要介绍ev_signal与ev_signal_pair[2]这两个成员。
/* Data structure for the default signal-handling implementation in signal.c */ struct evsig_info { /* Event watching ev_signal_pair[1] */ struct event ev_signal; /* Socketpair used to send notifications from the signal handler */ evutil_socket_t ev_signal_pair[2]; /* True iff we've added the ev_signal event yet. */ int ev_signal_added; /* Count of the number of signals we're currently watching. */ int ev_n_signals_added; /* Array of previous signal handler objects before Libevent started * messing with them. Used to restore old signal handlers. */ #ifdef EVENT__HAVE_SIGACTION struct sigaction **sh_old; #else ev_sighandler_t **sh_old; #endif /* Size of sh_old. */ int sh_old_max; };
- ev_signal_pair[2]是两个文件描述符,通常使用pipe函数创建。
- ev_signal是一个专职事件(内部事件),监听ev_signal_pair[0]上的读事件,它作为一个io事件被加入到event_base中,并将优先级设置为0,以便一旦该事件发生,它的回调函数能被优先执行。
在signal后端中,每一个被注册了事件的signal都会使用sigaction函数将signal handler修改为统一的函数static void __cdecl evsig_handler(int sig),这个函数的作用就是向ev_signal_pair[1]中写触发的signal_number,激活事件ev_signal。函数定义如下(删除了跨平台代码):
static void __cdecl evsig_handler(int sig) { int save_errno = errno; ev_uint8_t msg; if (evsig_base == NULL) { event_warnx( "%s: received signal %d, but have no base configured", __func__, sig); return; } /* Wake up our notification mechanism */ msg = sig; { int r = write(evsig_base_fd, (char*)&msg, 1); (void)r; /* Suppress 'unused return value' and 'unused var' */ } errno = save_errno; }
ev_signal的回调函数也是在signal.c源文件中定义,static void evsig_cb(evutil_socket_t fd, short what, void *arg),它的作用就是从ev_signal_pair[0]上读回被触发的signal number,并将sigmap上对应signal_number的所有event加入到event_base的待执行回调函数中。函数定义如下:
/* Callback for when the signal handler write a byte to our signaling socket */ static void evsig_cb(evutil_socket_t fd, short what, void *arg) { static char signals[1024]; ev_ssize_t n; int i; int ncaught[NSIG]; struct event_base *base; base = arg; memset(&ncaught, 0, sizeof(ncaught)); while (1) { n = read(fd, signals, sizeof(signals)); if (n == -1) { int err = evutil_socket_geterror(fd); if (! EVUTIL_ERR_RW_RETRIABLE(err)) event_sock_err(1, fd, "%s: recv", __func__); break; } else if (n == 0) { /* XXX warn? */ break; } for (i = 0; i < n; ++i) { ev_uint8_t sig = signals[i]; if (sig < NSIG) ncaught[sig]++; } } EVBASE_ACQUIRE_LOCK(base, th_base_lock); for (i = 0; i < NSIG; ++i) { if (ncaught[i]) evmap_signal_active_(base, i, ncaught[i]);//将signal number i 对应的事件加入到待执行回调函数链表中 } EVBASE_RELEASE_LOCK(base, th_base_lock); }
当signal发生时,evsig_handler会向ev_signal_pair[1]写数据(写入singal number),激活ev_signal事件,ev_signal事件的回调函数evsig_cb会从ev_signal_pair[0]上读到被触发的signal number,然后据此从event_base的sigmap中将对应的所有event的回调函数加入到待执行回调函数链表,这些回调函数将在event_base_loop中被执行,完成对signal事件的响应。