libevent源码分析(三)——信号处理

libevent实现了timer、IO、signal三者的统一,那么timer我们已经分析过了,比较简单的可以融入,那signal怎么统一进去呢?
有信号才能有处理,有动作才能有信号,所以我们首先需要知道有动作发生了,那么怎么办呢?之前我们讲过socket通信,可以用来传输信息。

socket pair

libevent采用了socket pair。就是用一个socket对来完成消息机制,接到信号后并不立即处理,而是通知IO事件。这里面就需要一个缓冲区来保存内容。所以,我们建一个socket pair,一个用来写,一个用来读。

socket
线程1
write socket
read socket
libevent线程

创建过程和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事件
    }

总结一下:

  1. 创建socket pair,并为读socket在libevent的event_base上注册persist事件
  2. 注册信号事件,将信号event添加到我们之前说过的signal event链表中
  3. 当信号触发时,信号置1,记录signo;并向写socket写数据触发读事件
  4. base收到通知,IO返回
  5. 检查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函数。首先清楚注册流程

  1. 取得ev要注册信号signo
  2. signo未被注册,注册信号处理函数evsignal_handler
  3. ev_signal没有注册,注册event
  4. 将事件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源码深度剖析——张亮

你可能感兴趣的:(信号处理,c语言)