首页 > 代码库 > 使用sigaction来取代signal作为信号处理器函数

使用sigaction来取代signal作为信号处理器函数

早期ISO C提供了像这样的函数来支持自定义信号处理

typedef void (*sighandler)(int);
sighandler signal(sighandler func);

但是由于标准库并不涉及系统层次,所以很多细节方面都是未定义的,比如在执行某信号(下文均以SIGINT为例)的处理器函数时,是否阻塞该信号?

给出一段代码(均忽略了对系统调用的错误处理)

#include <stdio.h>
#include <signal.h>

void handler(int sig)
{
    if (sig == SIGINT)
    {
        sigset_t mask;
        sigprocmask(SIGINT, NULL, &mask);
        if (sigismember(&mask, SIGINT))
            printf("执行SIGINT处理器函数时阻塞了该信号\n");
        else
            printf("执行SIGINT处理器函数时没有阻塞该信号\n");
    }
}

int main()
{
    signal(SIGINT, handler);
    raise(SIGINT);
    return 0;
}

这段代码的执行结果是未定义的,参考APUE,早期版本signal函数的问题是在进程每次接到信号对其进行处理时会将该信号动作重置为默认值(即下面的处置方式1)。

回忆下,对信号的处置方式有3种:1、采取默认行为;2、忽略信号;3、调用处理器函数(记为handler)。

而这种行为是不可靠的,因为可能会导致信号的丢失。以考虑这样的时间顺序:
进入handler -> 又收到这个信号 -> 退出handler

虽然如果handler非常简单,其执行时间微乎其微的话,出现这种情况的可能性很小,但还是有出现的可能,这点不能置之不理。

结果就是,我们其实是想要用handler来取代默认行为,然后却执行了默认行为,假如默认行为是结束程序,那就是个问题了。

比如信号是SIGFPE(算术异常,比如0作为除数),我们要执行N次运算,每次都要检查,如果算术异常则将错误信息写到日志里(即handler的代码块),然后继续下次运算,我们不想因为一次运算错误而导致后面的运算都没进行就结束程序。

绝大多数情况下,可能handler内的代码执行时间很短,而SIGFPE短时间大量重复发送的可能性很小,导致一段有隐患的代码通过了测试。但是假如这段代码在长时间运行的服务器中,出现这种隐患的可能性越来越大,不可靠的signal函数很有可能会在某次出现问题。

 

虽然我们可以改变signal的内部实现,但由于不能跨平台,而且另一方面signal很多东西不能自定义,在最新的应用程序中应该使用sigaction来取代signal。

用法就是把上述代码的signal(SIGINT, handler);改成下面这样

    struct sigaction sa;
    sa.sa_handler = handler;
    sa.sa_flags = 0;  // 默认是执行处理器函数时阻塞该信号
//    sa.sa_flags |= SA_NODEFER;  // 若设置了SA_NODEFER, 则行为和早期的signal一样
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

通过像上述代码一样设置sa_flags来自定义处理器函数的行为,更多行为可以man 2 sigaction来查询手册

使用sigaction来取代signal作为信号处理器函数