libev 信号处理

2017-04-15 linux network

信号的处理非常敏感,如果处理不当很容易出现问题,如果在信号处理函数中使用了非信号安全函数,那么很容易发生死锁。

这里简单介绍 libev 中是如何处理信号的。

简介

使用时需要确保 信号安全,如下是使用示例。

#include <stdio.h>
#include <libev/ev.h>

static void sigint_cb (EV_P_ ev_signal *w, int revents)
{
        puts("catch SIGINT");
        ev_break (EV_A_ EVBREAK_ALL);
}

int main (void)
{
        EV_P EV_DEFAULT;
        static ev_signal signal_watcher;

        ev_signal_init (&signal_watcher, sigint_cb, SIGINT);
        ev_signal_start(EV_A_ &signal_watcher);

        ev_loop(EV_A_ 0);

        return 0;
}

同步处理

Linux 中的信号时异步发生的,一般是从内核态切换到用户态时进行检查,从而从用户代码角度看,就是异步处理。

而 libev 会将异步信号转换为同步。

常用的同步化方案有 signalfdpipeeventfdsigwaitinfo 等,在 libev 中采用的是前两种,将对异步信号的处理,转化成对文件描述符的处理,也就是将 ev_signal 转化为处理 ev_io ;而最后一种,需要单独起一个信号处理线程。

libev 中使用的是 signalfd 或者 pipe 的方式。

signalfd

signalfd 是最简单方便的信号同步机制,很容易将异步信号转化成对文件描述符的监听。

下面首先看一下使用 signalfd 时的信号处理流程,其函数声明为。

#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t*mask, intflags);

参数:
    fd: -1 生成新文件描述符;或者指定存在有效的 fd ,而 mask 会替换掉之前相关联的信号集。
    mask: 这个文件描述符接受的信号集,可以通过sigsetops()宏初始化。

函数使用示例如下:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/signalfd.h>

#define handle_error(msg) do {           \
        perror(msg); exit(EXIT_FAILURE); \
} while (0)

int main(void)
{
        int sfd;
        ssize_t rc;
        struct signalfd_siginfo fdsi;

        sigset_t mask;
        sigemptyset(&mask);
        sigaddset(&mask, SIGINT);
        sigaddset(&mask, SIGQUIT);

        if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
                handle_error("sigprocmask");

        if ((sfd = signalfd(-1, &mask, 0)) == -1)
                handle_error("signalfd");

        while(1) {
                rc = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
                if (rc != sizeof(struct signalfd_siginfo))
                        handle_error("read");

                if (fdsi.ssi_signo == SIGINT) {
                        printf("Got SIGINT\n");
                } else if (fdsi.ssi_signo == SIGQUIT) {
                        printf("Got SIGQUIT\n");
                        exit(EXIT_SUCCESS);
                } else {
                        printf("Read unexpected signal\n");
                }
        }

        return 0;
}

源码解析

对应的结构体展开后的成员对象如下:

typedef struct ev_signal {
	int active;
	int pending;
	int priority;
	void *data;
	void (*cb)(EV_P_ struct ev_signal *w, int revents);
	struct ev_watcher_list *next;
	int signum;
} ev_signal;

包括 cb 在内之前的都是比较标准的成员,其中 signum 记录了信号量,成员结构体通过 list 链接。另外,在 ev.c 内部,通过 ANSIG 结构体维护了一个数组结构,用来组织 ev_signal 结构体。

typedef struct {
    sig_atomic_t volatile pending;   // 信号处于未决状态,也就是触发但尚未处理
#if EV_MULTIPLICITY
    struct ev_loop *loop;
#endif
    ev_watcher_list *head;           // 该信号所注册的信号处理回调函数
} ANSIG;
static ANSIG signals [EV_NSIG - 1];

signals 是 ANSIG 类型数组,下标是 信号值 - 1,也就是说,每个信号都有对应的 ANSIG 结构。