10.7 SIGCHLD定义

本文深入探讨了SIGCLD与SIGCHLD信号的区别,特别是在SystemV和BSD系统中的不同行为。详细解释了如何避免僵尸进程的产生,以及在编写信号处理函数时需要注意的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经常混淆的两个信号就是SIGCLD以及SIGCHLD,信号SIGCLD源于System V,该信号的含义与源自BSD的信号SIGCHLD不一致。同时POSIX.1信号也称为SIGCHLD.源自BSD的信号SIGCHLD的语义比较正常,当该信号出现的时候,表示子进程的状态发生了变化,然后我们需要调用一个wait函数来查看究竟发生了什么。

System V对于SIGCLD的处理历史以来都与其他信号不同,基于SVR4i的系统保持了这个有问题的传统,如果我们使用函数signal或者是sigset(SVR3中用于设置信号处理函数的函数)设置信号的处理函数,SIGCLD早期的处理包含如下行为:

  1. 如果将信号处理函数指定为SIG_IGN,调用进程的子进程将永远不会产生僵尸进程。注意这与该信号的默认处理时不同的,在图10.1中提到其默认处理时忽略。取而代之的是,当子进程终止的时候,子进程的状态将被抛弃,如果其父进程接着调用其中一个wait函数,调用进程将被阻塞直到所有子进程终止,然后wait函数返回-1,并且errno被设置为ECHILD.(注意:信号的默认处理时忽略信号,但是该默认处理方式并不会产生上述行为,而是我们必须将该信号的处理函数设置为SIG_IGN.)

    POSIX.1并没有指定当SIGCHLD被忽略的时候会发生什么,所以上述处理也是被允许的,而XSI选项要求这一处理被信号SIGCHLD支持。
    4.4BSD在SIGCHLD被忽略的时候总是生成僵尸进程,如果我们想要避免僵尸进程的产生,我们必须wait所有的子进程,在SVR4中,只要调用signal或者是sigset中的一个将SIGCHLD的处理函数设置为SIG_IGN,僵尸进程就永远不会产生,本书中描述的4个平台都与SVR4的相同。
    使用函数sigaction而言,我们可以设置SA_NOCLDWAIT标志来避免僵尸进程,该行为对于本书中介绍的4个平台都是支持的。

  2. 如果我们设置SIGCLD的处理函数,内核就会立即检查是否有子进程处于等待处理的情况,若有,则立即调用SIGCLD函数。

上述第二项改变了我们编写信号处理函数的方式,正如下面的例子中将会讲到的一样.

Example

在10.4节中提到,在信号处理函数中的第一件事就是再次调用signal函数,这样处理时为了最小化信号处理函数被还原为默认处理的时间窗口,图10.6就是按照这一规则进行编写的程序,但是该程序在传统的System V平台上并不能很好地工作,其输出将是连续不断的字符串”SIGCLD received”,最会进程由于堆栈空间溢出而异常终止。

 
 
  1. #include "apue.h"
  2. #include <sys/wait.h>
  3. static void sig_cld(int signo);
  4. int main()
  5. {
  6. pid_t pid;
  7. if(signal(SIGCLD, sig_cld) == SIG_ERR)
  8. perror("signal error");
  9. if((pid = fork()) < 0)
  10. {
  11. perror("fork error");
  12. }
  13. else if(pid == 0)
  14. {
  15. /* child process*/
  16. sleep(2);
  17. _exit(0);
  18. }
  19. pause(); /*parent process*/
  20. exit(0);
  21. }
  22. static void sig_cld(int signo)
  23. {
  24. pid_t pid;
  25. int status;
  26. printf("SIGCLD received\n");
  27. if(signal(SIGCLD, sig_cld) == SIG_ERR) /*reestablish handler*/
  28. perror("signal error");
  29. if((pid = wait(&status)) < 0) /*fetch child status*/
  30. perror("wait error");
  31. printf("pid = %d\n", pid);
  32. }

上述程序的问题是:在信号处理函数的开始位置调用了函数signal,这对应于上面讨论的第二项–内核检查是否有子进程需要wait for(在上述程序中符合这一情况,因为我们正在处理SIGCLD信号),所以对于signal函数的调用会产生另一次对于信号处理函数的调用,信号处理函数反过来又会调用signal函数,于是整个进程就会不断地重复运行。
为了解决上述程序的问题,我们必须将对函数signal的调用移动到wait函数之后,通过这样做我们在捕获到子进程的终止状态以后再调用signal函数,该信号仅仅在其他子进程终止的时候才会再次产生。

POSIX.1声明:在我们建立SIGCHLD的信号处理函数的时候,如果已经存在一个还没有被wait的终止的子进程,信号是否需要生成并没有指定,因此上述情况是允许的,但是对于POSIX.1而言,在信号产生以后并不会复位信号处理函数为默认函数(假设我们正在使用的是POSIX.1的sigaction函数来设置信号处理函数),便没有需要在信号处理函数中再次调用函数signal了。于是上述问题就消失了。





转载于:https://www.cnblogs.com/U201013687/p/5518345.html

<think>好的,用户现在想了解如何注册SIGCHLD信号来处理僵尸进程,包括具体的实现和代码示例。首先,我需要回忆之前关于僵尸进程和信号处理的回答,确保这次的信息准确且一致。用户之前的问题已经涉及了僵尸进程的定义、成因以及解决方法,现在需要更具体的编程步骤。 根据用户提供的引用内容,特别是引用[3]和[5],注册SIGCHLD信号有两种方法:使用signal()函数或者sigaction()函数。引用[3]提到了使用sigaction并设置阻塞信号集的重要性,而引用[5]展示了使用sigaction忽略SIGCHLD的示例代码。用户需要知道这两种方法的区别和适用场景。 接下来,需要考虑用户可能的背景。他们可能是有一定编程经验,但对Linux系统编程不太熟悉的开发者。因此,需要分步骤解释,并提供清晰的代码示例,避免术语过多,同时确保代码正确性。 需要覆盖以下几个关键点: 1. 使用signal()函数注册信号处理函数,并说明其优缺点。 2. 使用sigaction()函数,并解释为什么更推荐这种方法,比如可以设置SA_NOCLDWAIT或SA_RESTART等标志。 3. 处理函数中需要使用非阻塞的waitpid循环回收多个子进程,避免信号丢失。 4. 需要设置阻塞信号集,防止在注册处理函数前子进程结束导致僵尸进程。 5. 提供代码示例,并注释关键部分,帮助用户理解。 可能遇到的问题:用户可能不清楚阻塞信号集的作用,或者为什么需要循环调用waitpid。需要解释清楚,在信号处理函数执行期间,同种信号会被屏蔽,因此必须一次性回收所有已终止的子进程,避免遗漏。 还需要注意代码的可移植性和错误处理,比如检查函数返回值,处理可能的错误情况。例如,在sigaction调用失败时应该如何处理。 最后,确保回答结构清晰,分步骤说明,并总结常见问题,比如为什么有时僵尸进程仍然存在,或者如何处理多个子进程同时退出的情况。参考用户之前的问题,用户可能还会关心性能优化和实际应用中的注意事项,比如在高并发服务器中的处理方式。 现在需要将这些思考整合成一个自然的中文回答,确保符合之前的格式要求,使用适当的代码块,并正确引用提供的参考文献。</think>### 如何注册SIGCHLD信号处理僵尸进程 #### **1. 核心原理** 注册`SIGCHLD`信号后,父进程可通过信号处理函数**异步回收子进程资源**,避免僵尸进程产生[^3][^5]。 - **信号触发条件**:子进程终止时,内核自动向父进程发送`SIGCHLD`信号。 - **处理方式**:父进程需通过`signal()`或`sigaction()`注册信号处理函数,并在其中调用`waitpid()`回收子进程[^2][^3]。 --- #### **2. 实现步骤** ##### **方法一:使用`signal()`函数(简单接口)** ```c #include <signal.h> #include <sys/wait.h> // 信号处理函数 void sigchld_handler(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0); // 非阻塞循环回收所有终止的子进程[^3] } int main() { // 注册SIGCHLD信号处理函数 signal(SIGCHLD, sigchld_handler); // 创建子进程 pid_t pid = fork(); if (pid == 0) { // 子进程逻辑 exit(0); } else { // 父进程继续执行其他任务 while (1) pause(); } return 0; } ``` **关键点**: - `waitpid(-1, NULL, WNOHANG)`: - `-1`表示回收任意子进程 - `WNOHANG`表示非阻塞,避免处理函数卡死[^3] - **循环调用**:确保一次处理多个子进程退出(避免信号丢失)[^2] ##### **方法二:使用`sigaction()`函数(推荐,更灵活)** ```c #include <signal.h> #include <sys/wait.h> void sigchld_handler(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0); } int main() { struct sigaction act; act.sa_handler = sigchld_handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART | SA_NOCLDSTOP; // 注册信号前先阻塞SIGCHLD,防止竞态条件[^3] sigset_t set; sigemptyset(&set); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL); // 注册信号处理函数 if (sigaction(SIGCHLD, &act, NULL) == -1) { perror("sigaction error"); exit(1); } // 解除阻塞 sigprocmask(SIG_UNBLOCK, &set, NULL); // 创建子进程并执行任务 pid_t pid = fork(); if (pid == 0) { exit(0); // 子进程退出 } else { sleep(1); // 父进程等待信号处理 } return 0; } ``` **优势**: - `SA_RESTART`:自动重启被信号中断的系统调用[^3] - `SA_NOCLDSTOP`:仅接收子进程终止信号,忽略状态变更信号 - **阻塞信号集**:防止在注册完成前子进程退出导致僵尸进程[^3] ##### **方法三:直接忽略SIGCHLD(适用于无需状态反馈的场景)** ```c signal(SIGCHLD, SIG_IGN); // 内核自动回收子进程[^5] // 或使用sigaction设置忽略 struct sigaction act; act.sa_handler = SIG_IGN; sigaction(SIGCHLD, &act, NULL); ``` --- #### **3. 常见问题与解决** | **问题** | **原因** | **解决方案** | |-------------------------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------| | **僵尸进程仍存在** | 未在信号处理函数中循环调用`waitpid()` | 使用`while (waitpid(...) > 0)`确保回收所有子进程[^3] | | **信号处理函数未触发** | 注册信号前子进程已退出 | 提前阻塞SIGCHLD信号,注册后再解除阻塞[^3] | | **父进程阻塞在系统调用中** | 未设置`SA_RESTART`标志 | 在`sigaction`中指定`SA_RESTART`自动重启中断的系统调用[^3] | --- #### **4. 完整示例(结合信号阻塞与非阻塞回收)** ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> void cleanup_child(int sig) { int status; pid_t pid; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { printf("回收子进程 %d,退出状态: %d\n", pid, WEXITSTATUS(status)); } } int main() { struct sigaction sa; sa.sa_handler = cleanup_child; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // 阻塞SIGCHLD直到信号处理注册完成 sigset_t block_set; sigemptyset(&block_set); sigaddset(&block_set, SIGCHLD); sigprocmask(SIG_BLOCK, &block_set, NULL); if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction失败"); exit(EXIT_FAILURE); } // 创建多个子进程 for (int i = 0; i < 5; i++) { if (fork() == 0) { printf("子进程 %d 启动\n", getpid()); sleep(i + 1); // 模拟不同执行时间 exit(i); } } // 解除SIGCHLD阻塞 sigprocmask(SIG_UNBLOCK, &block_set, NULL); // 父进程持续运行 while (1) { printf("父进程工作中...\n"); sleep(2); } return 0; } ``` **运行结果**: ``` 子进程 1234 启动 子进程 1235 启动 父进程工作中... 回收子进程 1234,退出状态: 0 回收子进程 1235,退出状态: 1 父进程工作中... ... ``` --- ### 相关问题 1. **为什么需要循环调用`waitpid()`?** (多个子进程可能同时退出,需一次性回收所有终止进程[^3]) 2. **设置`SA_NOCLDWAIT`标志有何作用?** (自动回收子进程,无需显式调用`wait()`[^5]) 3. **如何防止信号处理函数与主程序竞态?** (使用信号阻塞集控制信号接收时机[^3])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值