Linux中的信号(signal)是一个非常实用的功能,进程间能发送信号,内核也能向进程发送信号。本文总结了在进行信号编程时容易忽略的几个问题,方便后续开发过程中查阅。

在信号回调函数中操作互斥锁

信号回调函数是用户自定义的一个函数,它在程序中有需要处理的信号时被调用,但它在运行时既不是内核态也不是用户态。因为当程序运行在内核态时信号不会响应,运行在用户态时所有的信号都已经处理完成。

这个特性使得信号回调函数在编写时需要额外小心。例如在信号回调函数中是不能操作互斥锁的,若在信号回调函数中操作了互斥锁,可能会导致程序卡死。

这涉及到信号处理函数安全(signal safe)的概念,它与线程安全(thread safe)概念相似,这里 列举了所有能够在信号处理函数中执行的系统函数。

如果真的需要在信号回调函数中操作互斥锁,妥协的方法是在回调函数中创建一个线程,在这个新创建出的线程中操作互斥锁。这个方案的缺陷是线程可能会创建失败,如需高频地接收和处理信号就需要频繁地创建和销毁线程,可能会由于超过电脑的处理性能,最终导致线程创建失败。

相关博文:在Linux的信号处理函数中不要进行锁相关操作

信号屏蔽字 signal mask

如果不希望处理某个信号,可以通过设置信号屏蔽字(signal mask)暂停信号。

可以使用 sigprocmask 函数查看和修改当前程序的信号屏蔽字,信号屏蔽字可以根据需要增加和取消。

#include <signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* oset);

如果在取消信号屏蔽字时,程序有待处理的(pending)信号,这个信号会在 sigprocmask 函数返回之前就被发送到程序了。所以,希望通过组合 sigprocmasksigtimedwait 两个函数实现信号延迟处理的方案是行不通的,原因是在 sigprocmask 取消信号屏蔽字后,信号会在 sigtimedwait 开始等待之前就已经发送过来了。

Signal handlers are per process, signal masks are per thread.

上面这句话摘自 stackoverflow 上的某个回答,我认为说的很好。在多线程程序中每个线程有各自的信号屏蔽字,每个线程的信号屏蔽字只对本线程生效。多线程程序没有一个统一的程序级(process level)的信号屏蔽字,并且在多线程程序中使用 sigprocmask 函数是未定义行为(undefined behaviour),而应该使用对应多线程版本的 pthread_sigmask 函数。

查看待处理的信号 pending signal

可以使用 sigpending 函数读取当前线程的未决(待处理)信号集。未决信号就是被发送到程序中并处于屏蔽(blocked)状态的信号。

#include <signal.h>
int sigpending(sigset_t* set);

注意 :对于有些信号,在多次触发后有可能只记录一个 pending 状态。例如:当已经收到了 SIGINT 信号并且该信号处于待处理状态,后续又收到了新的 SIGINT 信号。在取消屏蔽这个信号时,有可能只会感知到一次 SIGINT 信号。