一、前言
Libevent(1.4.13 版本)的源码中,event_base
结构体中的成员 sig
用于管理信号事件,sig
的变量类型为 struct evsignal_info
。
二、相关结构体
struct evsignal_info
定义在 evsignal.h 头文件中,如下:
typedef void (*ev_sighandler_t)(int);
struct evsignal_info {
struct event ev_signal; /* 为 socket pair 的读 socket 向 event_base 注册读事件时使用的 event 结构体 */
int ev_signal_pair[2]; /* socket pair,当有信号发生时,其中一个 socket 向另一个 socket 写数据,触发可读事件 */
int ev_signal_added; /* 记录 ev_signal 事件是否已经注册了 */
volatile sig_atomic_t evsignal_caught; /* 是否有信号发生的标记,是 volatile 类型,因为它会在另外的线程中被修改 */
struct event_list evsigevents[NSIG]; /* evsigevents[signo] 表示注册到信号 signo 的事件链表 */
sig_atomic_t evsigcaught[NSIG]; /* 具体记录每个信号触发的次数,evsigcaught[signo] 记录信号 signo 被触发的次数 */
#ifdef HAVE_SIGACTION
struct sigaction **sh_old; /* 记录了原来的 Signal 处理函数指针,当信号 signo 注册的 event 被清空时,需要恢复其旧的处理函数 */
#else
ev_sighandler_t **sh_old; /* 记录了原来的 Signal 处理函数指针,当信号 signo 注册的 event 被清空时,需要恢复其旧的处理函数 */
#endif
int sh_old_max; /* sh_old 数组的最大下标 */
};
下面分析 evsignal_info
结构体中的各个成员:
- ev_signal,一个 event 类型变量,关注 socket pair 中读 socket 的可读事件,socket pair 后面会分析
- ev_signal_pair,保存了 socket pair 的两个 socket 描述符,ev_signal_pair[0] 为写 socket,ev_signal_pair[1] 为读 socket,往写 socket 中写的数据会被发送到读 socket。当有信号发生时,在信号处理函数中往写 socket 写入一字节数据,触发**读 socket ** 的可读事件,引起 I/O 复用接口返回(I/O 复用接口关注了 **读 socket ** 的可读事件),达到及时处理信号事件的目的(如果 I/O 复用一直阻塞,会导致信号事件不能及时得到处理)
- ev_signal_added,记录 ev_signal 是否已经注册到 event_base
- evsignal_caught,是否有信号发生的标记,会在信号处理函数中被设置为1
- evsigevent,数组,数组的每个元素是一个链表(用尾队列实现),evsigevent[signo] 表示**信号 signo **的事件链表(即当信号 signo 发生,该事件链表上的所有事件都会被激活)
- evsigcaught,数组,evsigcaught[signo] 记录信号 signo发生的次数
- sh_old,数组,sh_old[signo] 记录了信号 signo旧的信号处理函数,HAVE_SIGACTION 这个宏用于判断系统是否支持sigaction系统调用,如果不支持则使用signal系统调用
- sh_old_max,记录 sh_old 数组的最大下标
从上面的分析可以看出,Libevent使用链表数组 evsigevent 来管理信号事件,signo信号相关的事件被插入链表 evsigevent[signo] 中。也就是说,每个信号都拥有一个链表用于管理与该信号相关的事件。
信号发生时,利用 socket pair 使 I/O 复用接口返回,及时处理信号事件。通过这种方式,信号的处理被合并到事件循环中,体现了 Libevent 统一事件源(I/O事件、定时事件和信号事件)的思想。
三、相关操作
与信号事件相关的函数声明在 evsignal.h 头文件中,如下:
int evsignal_init(struct event_base *); // 信号管理初始化
void evsignal_process(struct event_base *); // 处理信号事件,由 dispatch 函数调用
int evsignal_add(struct event *); // 添加信号事件
int evsignal_del(struct event *); // 删除信号事件
void evsignal_dealloc(struct event_base *); // 析构信号管理
上述在 evsignal.h 中声明的几个函数都是外部函数,此外还有几个内部函数。
3.1 信号管理初始化
evsignal_init 函数用于信号管理初始化,即初始化 event_base 中的 sig 成员,代码注释如下:
int
evsignal_init(struct event_base *base)
{
int i;
// 创建 socket pair,往 ev_signal_pair[0](写socket) 中写入的数据会被发送到 ev_signal_pair[1](读socket)
if (evutil_socketpair(
AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {
#ifdef WIN32
event_warn("%s: socketpair", __func__);
#else
event_err(1, "%s: socketpair", __func__);
#endif
return -1;
}
// closeonexec: 当 fork 一个子进程,在子进程执行 exec 时该描述符被自动关闭
FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]);
FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]);
base->sig.sh_old = NULL;
base->sig.sh_old_max = 0;
base->sig.evsignal_caught = 0;
memset(&base->sig.evsigcaught,