libevent信号机制

evsignal_info

struct evsignal_info {
    struct event ev_signal;   /* 整体作为一个事件,加入epoll中 */
    int ev_signal_pair[2];    /* 信号套接字 */
    int ev_signal_added;      /* 是否加入epoll */
    volatile sig_atomic_t evsignal_caught;  /* 是否有信号到来 */

    struct event_list evsigevents[NSIG];    /* 信号链表 */
    sig_atomic_t evsigcaught[NSIG];         /* 每个信号触发次数 */

    struct sigaction **sh_old;              /* 原信号 数组,对应sigaction第三个参数 */
    int sh_old_max;                         /* 原信号 数组大小 */
};

1. evsigevents 与 evsigcaught

    libevent信号机制_第1张图片

    evsigevents 数组存放信号注册的事件,当发生该信号的时候,激活该信号下注册的信号事件。数组下标代表信号。

上图中,信号1注册了event1、event2两个事件。

    evsigcaught 记录信号发生的次数,上图中,信号1发生了2次,而信号4发生了4次。信号事件在未被激活前,该信号发生了

4次,这样,在进行事件处理时,event3、event4的回调函数会分别被执行4次。这样,防止信号丢失。


2. sh_old、sh_old_max

    sh_old_max 表示数字sh_old的大小。而,sh_old 为sigaction类型数组。

    int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact); 注册新信号回调函数的时候,

记录oldact,即原信号的相关参数,以便于后续就行恢复工作。


难点

libevent需要将所有事件(IO、信号,定时器)统一处理,因此不能在信号发生的时候立即处理该信号事件,而是

应该让epoll_wait返回,将信号对应的事件加入激活事件列表中。其一,我们知道,信号能够打断系统调用,

因此,epoll_wait 返回的时候,进行检查,是否有信号发生。其二,如果epoll_wait返回后,信号到来,此时,

我们需要一种记录,记录有信号到来,否则,信号就会丢失;另外,再次调用epoll_wait的时候,由于无法通知

epoll_wait此时已经有信号,所以,epoll_wait无法立即返回,导致信号无法及时处理。

res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
    if (errno != EINTR) {
        return (-1);
    }

    evsignal_process(base);   /* epoll_wait被信号中断 */
    return (0);
} 
/* do something */
当处于/* do something */ 的时候,有信号到来,在注册的信号回调函数中,通过evsignal_caught置1,标示

有信号到来,而再次调用epoll_wait 的时候,可能会阻塞很长时间,这就导致信号得不到及时处理。

解决办法

创建一个socketpair,当有信号到来的时候,向socketpair中写数据,当调用epoll_wait的时候,由于有数据可读,

因此,epoll_wait可以立即返回,检测信号事件。 这样,需要向epoll中加入一个持久的读事件,上述结构体中,

ev_signal即为这个事件,而,该事件仅需要加入一次,且需要在有信号的时候加入,因此,通过ev_signal_added来

记录事件是否已经加入epoll。通过这种方式,在有信号的时候,让epoll_wait 尽快返回。

res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
    if (errno != EINTR) {
        return (-1);
    }

    evsignal_process(base);   /* 被信号中断 */
    return (0);
} else if (base->sig.evsignal_caught) {  /* epoll 返回,检测是否有信号到来 */
    evsignal_process(base);
}
通过这种设计思路,只有有信号发生,能够保证epoll_wait能够立即返回,处理信号事件。


流程

evsignal_init

libevent信号机制_第2张图片

1. 首先创建一个socketpair,用于向epoll_wait发送消息,及时返回,处理信号。

2. 将socket设置closeonexec,防止文件表无法被删除,参照《文件描述符》

3. 初始化evsigevents 队列,将收到的信号挂在对应的队列上,下标即为信号标示。

4. socket设置为非阻塞

5. ev_signal信号事件回调函数,仅仅用于读取发送端写入的一个字符。作用仅仅是让epoll_wait尽快返回。

6. 防止信号事件超时,将其设置为永久事件。


回调函数:

static void
evsignal_cb(int fd, short what, void *arg)
{
    static char signals[1];
    ssize_t n;

    n = recv(fd, signals, sizeof(signals), 0);
}


添加信号事件

libevent信号机制_第3张图片


1. 对于信号事件,event中的fd代表信号,取得信号

2. 如果该信号对应的数组为空,则说明,没有对该信号注册回调函数,通过sigaction注册

3. 信号事件整个处理过程中需要一个event加入epoll来处理整个信号,所以判断该event是否已经加入,确保仅加入一次。ev_sginal,有信号事件的时候,才需要加入。

4. 如果信号已经被注册了回调函数,则又加入新信号事件,允许加入,且查到信号队列队尾。也就是说,同一信号,可能会注册不同的信号事件,有多个信号事件回调函数。


信号回调函数

libevent信号机制_第4张图片

1. 分配sh_old, 用于sigaction最后一个参数,记录原有信号信息,用于后续信号恢复。

2. 设置信号回调函数,仅仅是想socketpair中写入一个字节的数据,让epoll返回。

3. sigaction 进行注册。

普通事件回调函数:

该回调函数为sigaction 注册的回调函数:

static void
evsignal_handler(int sig)
{
    int save_errno = errno;

    if (evsignal_base == NULL) {
        return;
    }

    evsignal_base->sig.evsigcaught[sig]++;
    evsignal_base->sig.evsignal_caught = 1;

    /* Wake up our notification mechanism */
    send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
    errno = save_errno;
}

这里,记录信号发生的次数,控制回调函数调用次数,如果信号发生n次,则调用回调函数n次。通知epoll_wait有信号啦。

 

 

信号事件处理函数:

libevent信号机制_第5张图片

1. 通过evsigcaught查找发生的信号。(元素非0,代表发生过)

2. 将evsigcaught 信号对应的元素清0

3. 若是非持久事件,则删除。即只响应一次后,还原。注意,若对应信号下没有事件了,恢复之前的信号处理函数。

4. 加入激活事件队列中

int
event_del(struct event *ev)
{
    if (ev->ev_flags & EVLIST_TIMEOUT)
        event_queue_remove(base, ev, EVLIST_TIMEOUT);

    if (ev->ev_flags & EVLIST_ACTIVE)
        event_queue_remove(base, ev, EVLIST_ACTIVE);

    if (ev->ev_flags & EVLIST_INSERTED) {
        event_queue_remove(base, ev, EVLIST_INSERTED);
        return (evsel->del(evbase, ev));
    }

    return (0);
}

信号事件属于EVLIST_INSERTED队列,从该队列删除后,需要在epoll中移除。

static int
epoll_del(void *arg, struct event *ev)
{
    if (ev->ev_events & EV_SIGNAL)
        return (evsignal_del(ev));

epoll_del调用了evsignal_del,参见如下事件删除。

 

事件删除:

找到信号对应的事件队列,删除该事件,如果删除后,队列为空,则恢复之前的信号处理函数。记录内容在sh_old中。



你可能感兴趣的:(libevent,libevent)