目录
Libevent中的event,主要分为三大类:io读写event、超时事件以及信号event。前面的文章对前两类的event都进行了分析,下面就来说一下Libevent是如何处理信号event的。
信号event处理流程
不管使用的是什么后端IO复用模型,这些复用模型本身都是只支持读写IO事件的,Libevent所实现的“信号event处理”,实际上也是把信号event最终转换到了IO读写event。它的工作原理为:用户层面指定一个需要监听的信号sig以及回调函数custom_cb,对于Libevent,它会为sig定义一个信号处理函数evsig_handler,然后再定义一个socket pair,这是一对全双工套接字,向其中一个写入数据那么就可以从另一个中读出数据,Libevent会将其中之一固定为读端,而另一个则为写端,并为读端套接字注册一个IOevent名为ev_signal,其回调函数为evsig_cb。
当需要监听的信号sig到达,就会自动去调用刚才设置的信号处理函数evsig_handler,在evsig_handler中会向写端套接字写入一个字节(这个字节实际上就是sig的值),此时读端套接字就会收到这个字节,触发可读事件,激活ev_signal然后调用evsig_cb,在evsig_cb函数中,会将所有监听sig信号的event全部激活,这些event中就包含根据用户要求所定义的event,处理这个event的时候就会回调custom_cb,这样,就完成了监听sig信号到回调custom_cb的信号event处理过程。如下图所示:
与信号event相关的结构体
struct event_base {
......
const struct eventop *evsigsel;//信号处理后端相关函数
/** Data to implement the common signal handelr code. */
struct evsig_info sig;//信号相关信息
......
/** Mapping from file descriptors to enabled (added) events */
struct event_io_map io;
/** Mapping from signal numbers to enabled (added) events. */
struct event_signal_map sigmap;
......
};
前面说过,event_base在初始化的时候就会绑定一个IO复用模型后端到evsel成员中,但是对于信号event,整个处理的过程中是不会也不应该直接使用这些IO复用后端,而是使用信号event专用的后端,并且将其绑定到evsigsel中,在该后端结构体中只含有添加和删除函数,如下所示:
static const struct eventop evsigops = {
"signal",
NULL,
evsig_add,
evsig_del,
NULL,
NULL,
0, 0, 0
};
然后再说event_base中的sig成员,这是一个evsig_info类型的结构体变量,该类型定义如下:
typedef void (*ev_sighandler_t)(int);
struct evsig_info {
struct event ev_signal; //需要添加到event_io_map中监听读端套接字
/* 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; //标识ev_signal是否被添加到io map中
/* Count of the number of signals we're currently watching. */
int ev_n_signals_added; //监听的信号数量
#ifdef _EVENT_HAVE_SIGACTION
struct sigaction **sh_old; //sigaction *数组,每一个元素都指向一个sigaction,其中含有信号的处理函数
#else
ev_sighandler_t **sh_old; //如果没有定义_EVENT_HAVE_SIGACTION,那么就是一个函数指针数组,存储每个信号的回调函数指针
#endif
/* Size of sh_old. */
int sh_old_max; //sh_old数组的大小
};
这里的ev_signal_pair[2]就是前面所说的socket pair全双工套接字。而evsig_info中的ev_signal,就是用于监听socket pair中读端套接字的event。sh_old是一个二级指针,也可以看做数组,它每个元素都对应了一个信号的处理函数。
初始化工作
对于普通的IO复用模型,是在event_base的初始化过程中将相应的后端结构体绑定到evsel中的,同样的,信号相关的后端结构体也是在event_base的初始化过程中绑定到evsigsel中的,并且还会做很多事情,如下所示:
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
......
for (i = 0; eventops[i] && !base->evbase; i++) {
......
base->evsel = eventops[i]; //将该backup作为base使用的backup
base->evbase = base->evsel->init(base); //用选定的backup的初始化函数来初始化base中的evbase
}
......
}
//以epoll后端为例
static void *
epoll_init(struct event_base *base)
{
......
evsig_init(base);
}
int
evsig_init(struct event_base *base