SIGCHLD 信号
SIGCHLD 信号的产生条件:
- 子进程终止时
- 子进程接收到 SIGSTOP 信号停止时
- 子进程处于停止态,接受到 SIGCONT 后唤醒时
上面三种情况都会给父进程发送 SIGCHLD 信号,父进程默认会忽略该信号。但通过捕捉该信号,可以解决僵尸进程。
下面的是代码举例,
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>
void myFun(int num) {
printf("捕捉到的信号 :%d\n", num);
// 回收子进程PCB的资源
// while(1) {
// wait(NULL);
// }
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if(ret > 0) {
printf("child die , pid = %d\n", ret);
} else if(ret == 0) {
// 说明还有子进程活着
break;
} else if(ret == -1) {
// 没有子进程
break;
}
}
}
int main() {
// 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
// 创建一些子进程,使一个父进程创建20个子进程
pid_t pid;
for(int i = 0; i < 20; i++) {
pid = fork();
//防止子进程再去创建子进程,不断嵌套
if(pid == 0) {
break;
}
}
if(pid > 0) {
// 父进程
// 捕捉子进程死亡时发送的SIGCHLD信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);
// 注册完信号捕捉以后,解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);
//死循环使父进程永远不会结束,而子进程结束,就会形成僵尸进程,这时可以通过捕捉SIGCHLD
while(1) {
printf("parent process pid : %d\n", getpid());
sleep(2);
}
} else if( pid == 0) {
// 子进程
printf("child process pid : %d\n", getpid());
}
return 0;
}
僵尸进程的出现往往是因为父进程还未结束,子进程却已经结束了。在该代码中,创建了20个子进程,同时因为父进程中有死循环所以永远无法结束。这就会导致出现20个僵尸进程。该如何处理掉这20个僵尸进程呢?
所以先要设置阻塞信号集,把SIGCHLD信号放入其中,然后再通过sigprocmask函数把自己自定义的阻塞信号集中的数据设置到内核中。父进程中通过sigaction函数来捕捉子进程结束时向父进程发送的SIGCHLD信号,而在父进程的内核收到SIGCHLD信号,就会将未决信号集中的17号位置置为1(也只有1次),但同时该信号被阻塞(因为之前我们已经将自己自定义的阻塞信号集中的数据设置到内核中,而在自定义数据集中存在SIGCHLD信号,所以系统的阻塞信号集中17号位置置为1,也就意味着未决信号集接收到17号位置的信号会暂停处理,直到解除阻塞)。
所以该17号位置保持1,等待处理。当注册完信号捕捉以后,再解除阻塞。因为内此时未决信号集第17号位是1,而阻塞已经被解除。所以SIGCHLD信号被发送,也就被父进程中的信号捕捉函数sigaction捕捉到。于是开始去执行对应的处理函数myFun。
在处理函数中,waitpid函数会处理掉所有结束的子进程(也就是僵尸进程)。waitpid函数一次只能处理一个,但因为在死循环中,所以直到处理完所有已经结束的子进程才会跳出死循环。
在main函数的第一行注释,说明要提早设置好阻塞信号集,阻塞SIGCHLD。因为要考虑极端情况,假设20个子进程老早就终止了,在信号捕捉还没注册好之前20个子进程就全部终止了。那信号注册号之后,就捕捉不到SIGCHLD信号,也就没办法启动我们自定义的处理函数myFun。所以要提前设置好阻塞信号集,不能在父进程中再去设置,这几行代码也是要消耗一定时间的,可能就因为消耗的这些时间导致捕捉函数还没有注册好,子进程就全部结束了。
因为20个子进程的结果不太好截图,所以我改了一下,下图的结果是产生5个进程的结果。可以看到,5个子进程陆续的创建出来,又陆续地全部被回收。