收发信号思想是 Linux 程序设计特性之一,一个信号可以认为是一种软中断,通过用来向进程通知异步事件。
本文讲述的 信号处理内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解信号编程。
遵循 C11, POSIX.1 - 2008
标准 c 库,libc, -lc
- #include
-
- [[noreturn]] void exit(int status);
exit() 函数会导致普通程序终止并将 status 的低字节返回给父进程。
通过 atexit(3) 和 on_exit(3) 注册的所有函数都会被调到,顺序和注册顺序相反。如果这些函数中有函数没有返回(比如调用了 _exit(2) 或者使用信号杀死了自己),那么剩下的函数都不会调用,剩下的退出过程也会跳过(比如刷新 stdio(3) 流等)。如果一个函数通过上面方式注册了多次,那么每次都会被调用。
所有打开的 stdio(3) 流都会被刷新并关闭。通过 tmpfile(3) 创建的文件会被删除。
C 标准规定了两个常量,EXIT_SUCCESS 和 EXIT_FAILURE,可以用来传递给 exit() 指示成功还是失败。
exit() 函数不会返回。
接口 | 属性 | 值 |
exit() | 线程安全 | MT-Unsafe race:exit |
exit() 不是线程安全的,会引起数据竞争问题,关于数据可以参考 attributes(7),在前几篇文章中也有介绍。
如果通过 atexit(3) 或者 on_exit(3) 注册的函数调用 exit() 或者 longjmp(3),那么产生的行为是未定义的。值得注意的是,调用 execve(2) 会移除通过 atexit(2) 和 on_exit(3) 注册的函数。
使用 EXIT_SUCCESS 和 EXIT_FAILURE 比用 0 或者非零值(1 或 -1)更具有移植性,尤其是 VMS 会使用不一样的使用习惯。
BSD 尝试标准化了退出码(GNU C 库已经采用了),可以参考
exit() 调用后,退出状态必须传递给父进程。主要有三种情况:
如果系统实现支持 SIGCHLD 信号,这时会发送这个信号给父进程。如果父进程设置了 SA_NOCLDWAIT,那么是否发送 SIGCHLD 并未定义。
发送给其他进程信号
如果退出进程是会话 leader 并且其终端正控制着会话,那么每个前台进程组中的进程都会收到 SIGHUP 信号,该终端会从会话上拿掉,允许其他新的控制进程获取该控制权。
如果退出进程导致一个进程组成为孤儿,并且新产生的孤儿进程组每个进程都停止了,那么会在 SIGCONT 信号后再跟上一个 SIGHUP 信号给进程组中的每个进程。参考 setpgid(2) 来查看关于孤儿进程组的解释。
除了以上情况,如果要发送的信号是发给要终止进程的子进程的,那么通常情况下是不会向子进程发送信号的。然而进程却可以使用 prctl(2) PR_SET_PDEATHSIG 操作来安全如何处理父进程终止时受到的信号。
下面程序演示了 fork(2) 和 waitpid() 的用法。程序创建了一个子进程,如果没有提供命令行参数,那么子进程会使用 pause(2) 来停止,允许用户向其发送信号。否则如果指定了命令行参数,那么子进程立即返回,使用命令行提供的整数作为返回状态值。父进程循环执行,通过 waitpid() 来监视子进程,使用 W*() 宏来分析返回的状态值。
下面 shell 会话演示了程序的使用:
- $ ./a.out &
- Child PID is 32360
- [1] 32359
- $ kill -STOP 32360
- stopped by signal 19
- $ kill -CONT 32360
- continued
- $ kill -TERM 32360
- killed by signal 15
- [1]+ Done ./a.out
- $
下面是程序源码:
- #include
- #include
- #include
- #include
- #include
-
- int
- main(int argc, char *argv[])
- {
- int wstatus;
- pid_t cpid, w;
-
- cpid = fork();
- if (cpid == -1) {
- perror("fork");
- exit(EXIT_FAILURE);
- }
-
- if (cpid == 0) { /* Code executed by child */
- printf("Child PID is %jd\n", (intmax_t) getpid());
- if (argc == 1)
- pause(); /* Wait for signals */
- _exit(atoi(argv[1]));
-
- } else { /* Code executed by parent */
- do {
- w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
- if (w == -1) {
- perror("waitpid");
- exit(EXIT_FAILURE);
- }
-
- if (WIFEXITED(wstatus)) {
- printf("exited, status=%d\n", WEXITSTATUS(wstatus));
- } else if (WIFSIGNALED(wstatus)) {
- printf("killed by signal %d\n", WTERMSIG(wstatus));
- } else if (WIFSTOPPED(wstatus)) {
- printf("stopped by signal %d\n", WSTOPSIG(wstatus));
- } else if (WIFCONTINUED(wstatus)) {
- printf("continued\n");
- }
- } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
- exit(EXIT_SUCCESS);
- }
- }