收发信号思想是 Linux 程序设计特性之一,一个信号可以认为是一种软中断,通过用来向进程通知异步事件。
本文讲述的 信号处理内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解信号编程。
遵循 C11,POSIX.1 - 2008
标准 c 库,libc, -lc
- #include
-
- typedef void (*sighandler_t)(int);
-
- sighandler_t signal(int signum, sighandler_t handler);
注意:signal() 的行为会根据不同的 UNIX 版本而变化,同样它也会根据 Linux 版本的不同而不同。考虑到程序的可移植性,尽量避免 signal() 的使用,而是使用 sigacton(2) 代替,可以参考下面移植性部分。
signal() 设置 signum 信号的处理函数为 handler,处理函数可以是SIG_IGN、SIG_DFL,也可以是程序设计人员定义的函数地址。
如果 signum 被分发给到一个进程,那么会发生以下行为:
SIGKILL 和 SIGSTOP 两个信号不能被捕捉或者忽略。
signal() 返回信号之前 的处理函数值。发生错误时,signal() 会返回 SIG_ERR,并设置 errno 来提示具体错误。
错误值定义如下:
EINVAL | signum 参数不合法 |
sighandler_t 是一个 GNU 扩展,它会在 _GNU_SOURCE 定义时暴漏出来。如果定义了_BSD_SOURCE(glibc 2.19 或低版本) 或者 _DEFAULT_SOURCE(glibc 2.19 或高版本),glibc 也定义了 sig_t。不使用这些定义的情况下,signal() 的声明就会有些晦涩:
void ( *signal(int signum, void (*handler)(int)) ) (int);
可移植性
signal() 只有在将 handler 设置为 SIG_DFL/SIG_IGN 时,才具有移植性。使用 signal() 建立信号处理函数的语义随着系统的不同而不同,POSIX.1 明确允许这些不同的行为。所以不要使用它于此目的。
POSIX.1 通过 sigaction(2) 接口解决了这种移植上的混乱,sigaction(2) 提供了信号处理调用的明确语义定义。所以,使用 sigaction(2) 来代替 signal()。
C89,POSIX.1-2001
在原来的 UNIX 系统中,当使用 signal() 建立的信号处理函数被调用时,信号的处理会被设置成 SIG_DFT,并且系统不会阻塞该信号通往其他进程的发布。这相当于调用了 sigaction(2),附带以下标记:
sa.sa_flags = SA_RESETHAND | SA_NODEFER;
System V 已提供了 signal() 的语义,不过这个定义有点差劲,因为在处理函数重新建立连接前,可能会连续收到两个信号。更严重的,同一个信号的频繁分发会导致处理函数的递归调用。
BSD 对此进行了改善,但不幸的是这种改善却改变了现存 signal() 接口的语义。在 BSD 系统上,当一个处理函数调用时,信号处置并没有被重新设置,后面发生的该信号的实例因该处理函数正在执行无法进行分发。更严重的,一些阻塞系统调用会在信号处理函数打断后自动重启。BSD 语义相当于使用下面标记调用 sigaction(2):
sa.sa_flags = SA_RESTART;
Linux 上的情景如下:
signal() 在进程的多线程场景下的副作用是未定义的。
根据 POSIX 定义,进程忽略非 kill(2)/raise(3) 产生的 SIGFPE/SIGILL/SIGSEGV 信号后的行为是未定义的。整数除以 0 是未定义的结果,在一些架构上它会产生 SIGFPE 信号(同样使用 -1 除最大负整数也可能产生 SIGFPE。)忽略这些信号可能会导致无限循环。
参考 sigaction(2) 获取更多关于将 SIGCHLD 信号的处置设置为 SIGIGN 的信息。
参考 signal-safety(7) 来查看一些可以在信号处理函数内部调用的异步信号安全的函数。
下面是一个捕捉 CTRL + C 信号的程序。
- #include
- #include
- #include
- #include
-
- void sighandler(int);
-
- int main()
- {
- signal(SIGINT, sighandler);
-
- while(1)
- {
- printf("开始休眠一秒钟...\n");
- sleep(1);
- }
-
- return(0);
- }
-
- void sighandler(int signum)
- {
- printf("捕获信号 %d,跳出...\n", signum);
- exit(1);
- }