libevent实现了timer、IO、signal三者的统一,那么timer我们已经分析过了,比较简单的可以融入,那signal怎么统一进去呢?
有信号才能有处理,有动作才能有信号,所以我们首先需要知道有动作发生了,那么怎么办呢?之前我们讲过socket通信,可以用来传输信息。
socket pair
libevent采用了socket pair。就是用一个socket对来完成消息机制,接到信号后并不立即处理,而是通知IO事件。这里面就需要一个缓冲区来保存内容。所以,我们建一个socket pair,一个用来写,一个用来读。
创建过程和socket时类似。
开始->创建监听socket->绑定到本地“环回地址”,开始监听本地连接->创建一个socket(写socket)->调用connect()连接到监听socket监听的端口->调用accept()取得连接后返回一个socket(读socket)->将两个成对->over
libevent有辅助创建函数evutil_socketpair(),不做多说。
集成到事件主循环——通知到event_base
好,通道有了,可是怎么才能让主循环知道signal发生了,我们为socket pair的读socket在libevent的event_base实例上注册一个persist的读事件。这样,在写端写入信息时,读事件就能收到相应的通知,触发读事件,由于在event_base上注册,所以base也能获得此通知。
信号的处理在之前的dispatch中,在源码中有一段是检查signal是否有触发的,有则处理。以epoll为例:
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
if (errno != EINTR) {
event_warn("epoll_wait");
return (-1);
}
evsignal_process(base);// 处理signal事件
return (0);
} else if (base->sig.evsignal_caught) {
evsignal_process(base);// 处理signal事件
}
总结一下:
- 创建socket pair,并为读socket在libevent的event_base上注册persist事件
- 注册信号事件,将信号event添加到我们之前说过的signal event链表中
- 当信号触发时,信号置1,记录signo;并向写socket写数据触发读事件
- base收到通知,IO返回
- 检查signal事件发生,遍历链表,将signo的事件加入激活链表处理
evsignal_info
libevent中的signal事件的管理是通过结构体evsignal_info完成的。
struct evsignal_info {
struct event ev_signal;//为读socket注册时使用的event结构体
int ev_signal_pair[2];//socket对
int ev_signal_added;//记录是否已经注册ev_signal
volatile sig_atomic_t evsignal_caught;//volatile型检查是否signal发生的标记
struct event_list evsigevents[NSIG];//注册到信号signo的事件链表
sig_atomic_t evsigcaught[NSIG];//记录信号触发次数
#ifdef HAVE_SIGACTION
struct sigaction **sh_old;//记录signal处理函数指针
#else
ev_sighandler_t **sh_old;
#endif
int sh_old_max;
};
evsignal_info的初始化:创建socket pair,设置ev_signal事件(并不立即注册,信号注册时才检查并注册),标记为置0等。
注册和注销signal事件
reactor模型中,事件要有对应的handler函数。首先清楚注册流程
- 取得ev要注册信号signo
- signo未被注册,注册信号处理函数evsignal_handler
- ev_signal没有注册,注册event
- 将事件ev添加到链表
看一下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
// 向写socket写一个字节数据,触发event_base的I/O事件,从而通知其有信号触发,需要处理
send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno; // 错误代码
}
总结来说,信号的集成就是通过socket pair,将读socket注册到base上,信号触发时通过写触发读,使主循环得到信号通知,然后进行信号处理。
signal事件不同于其他IO事件,但是可以通过IO事件来承载,通过这样的方式就将事件集成到了一起,这就是libevent的高明之处,将信号和IO和timer统一管理,功能模块清晰,使用方便。
参考:libevent源码深度剖析——张亮