libevent通过socketpair实现对信号事件的监听。
还记得event_base吗?
struct event_base { struct evsig_info sig; // 略 };
evsig_info结构如下:
struct evsig_info { evutil_socket_t ev_signal_pair[2]; /* Socketpair used to send notifications from the signal handler */ struct event ev_signal; /* Event watching ev_signal_pair[1] */ int ev_signal_added; /* True if we've added the ev_signal event yet. */ int ev_n_signals_added; /* Count of the number of signals we're currently watching. */ struct sigaction **sh_old; /* sigaction* 指针数组,用于存放信号处理函数 */ int sh_old_max; /* Size of sh_old. */ };
evsig_init
在event_base初始化阶段会完成socketpair的创建。
int evsig_init(struct event_base *base) { /* * Our signal handler is going to write to one end of the socket * pair to wake up our event loop. The event loop then scans for * signals that got delivered. */ if (evutil_socketpair( AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) { #ifdef WIN32 /* Make this nonfatal on win32, where sometimes people have localhost firewalled. */ event_sock_warn(-1, "%s: socketpair", __func__); #else event_sock_err(1, -1, "%s: socketpair", __func__); #endif return -1; } evutil_make_socket_closeonexec(base->sig.ev_signal_pair[0]); evutil_make_socket_closeonexec(base->sig.ev_signal_pair[1]); base->sig.sh_old = NULL; base->sig.sh_old_max = 0; evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]); event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1], EV_READ | EV_PERSIST, evsig_cb, base); base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL; event_priority_set(&base->sig.ev_signal, 0); base->evsigsel = &evsigops; return 0; }
可以看到,sig.ev_signal关联了sig.ev_signal_pair[1]的读事件,并且回调函数为evsig_cb。
evsig_add
假设我们现在向event_base添加一个信号事件signal_int。
struct event signal_int; event_assign(&signal_int, base, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, &signal_int); event_add(&signal_int, NULL);
event_add最终会调用evsig_add
static int evsig_add(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p) { struct evsig_info *sig = &base->sig; (void)p; EVUTIL_ASSERT(evsignal >= 0 && evsignal < NSIG); /* catch signals if they happen quickly */ EVSIGBASE_LOCK(); if (evsig_base != base && evsig_base_n_signals_added) { event_warnx("Added a signal to event base %p with signals " "already added to event_base %p. Only one can have " "signals at a time with the %s backend. The base with " "the most recently added signal or the most recent " "event_base_loop() call gets preference; do " "not rely on this behavior in future Libevent versions.", base, evsig_base, base->evsel->name); } evsig_base = base; evsig_base_n_signals_added = ++sig->ev_n_signals_added; evsig_base_fd = base->sig.ev_signal_pair[0]; EVSIGBASE_UNLOCK(); event_debug(("%s: %d: changing signal handler", __func__, (int)evsignal)); if (_evsig_set_handler(base, (int)evsignal, evsig_handler) == -1) { goto err; } if (!sig->ev_signal_added) { if (event_add(&sig->ev_signal, NULL)) goto err; sig->ev_signal_added = 1; } return (0); err: EVSIGBASE_LOCK(); --evsig_base_n_signals_added; --sig->ev_n_signals_added; EVSIGBASE_UNLOCK(); return (-1); }
evsig_add做了两件事:
1. 设置信号的回调函数为evsig_handler
回调函数存放于sig.sh_old中。
2. 将I/O事件sig.ev_signal添加到epoll中。
整篇文章的两个主角出现了: evsig_handler、evsig_cb。
evsig_handler
static void __cdecl evsig_handler(int sig) { int save_errno = errno; #ifdef WIN32 int socket_errno = EVUTIL_SOCKET_ERROR(); #endif ev_uint8_t msg; if (evsig_base == NULL) { event_warnx( "%s: received signal %d, but have no base configured", __func__, sig); return; } #ifndef _EVENT_HAVE_SIGACTION signal(sig, evsig_handler); #endif /* Wake up our notification mechanism */ msg = sig; send(evsig_base_fd, (char*)&msg, 1, 0); errno = save_errno; #ifdef WIN32 EVUTIL_SET_SOCKET_ERROR(socket_errno); #endif }
这里evsig_base_fd = base->sig.ev_signal_pair[0];
evsig_cb
/* 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 = recv(fd, signals, sizeof(signals), 0); 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]); } EVBASE_RELEASE_LOCK(base, th_base_lock); }
这里的recv置于while中,说明每次evsig_cb运行时,会将sig.ev_signal_pair[0]上的数据全部读完。
总结
信号事件流程
signal_int、sig.ev_signal
每个事件都有个事件类型。
signal_int的事件类型为EV_SIGNAL,说明它是一个信号事件。
经过上述的流程之后,signal_int会被加入到信号事件队列sigmap,以及总事件队列eventqueue。
sig.ev_signal的事件类型为EV_READ,说明它是一个I/O事件。
经过上述的流程之后,sig.ev_signal会被加入到I/O事件队列io,以及总事件队列eventqueue。
同时,与sig.ev_signal关联的fd会被加入epoll中。