1. SIGCHLD信号产生条件:
(1)子进程终止;
(2)子进程收到SIGSTOP暂停;
(3)子进程处于暂停状态,收到SIGCONT被唤醒。
2. 捕捉SIGCHLD,避免僵尸进程:
方式1:
注册信号处理函数,捕捉SIGCHLD信号后回收。
示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<sys/wait.h>
void waitforchild(int signum) { // 收到子进程退出信号,才会执行信号处理函数
printf("捕捉到信号%d, 回收子进程..\n", signum);
while (waitpid(-1, NULL, WNOHANG) > 0) { // 非阻塞方式回收子进程
}
printf("回收到一个子进程,退出回收处理函数...\n");
}
int main(int argc, const char* argv[]) {
pid_t pid = -1;
int ret = -1;
struct sigaction act;
act.sa_handler = waitforchild; // 信号处理函数
act.sa_flags = 0; // 使用旧的信号处理函数
// 收到子进程退出信号,才会执行信号处理函数
ret = sigaction(SIGCHLD, &act, NULL);
if (-1 == ret) {
perror("sigaction");
return 1;
}
pid = fork();
if (-1 == pid) {
perror("fork");
return 1;
}
if (0 == pid) { // 子进程
sleep(1);
printf("子进程即将退出!\n");
exit(0);
} else { // 父进程
sleep(2);
system("ps -aux | grep Z"); // 查看是否有僵尸进程
}
printf("父进程也结束了.\n");
return 0;
}
运行结果:
根据结果中橙色方框的内容:回收了两次子进程,说明存在两个子进程;但程序中只有一个fork,为什么会有两个子进程呢?
注意:43行的system也会创建一个子进程!
方式2:
若父进程不关心子进程结束,则可使用sigaction或signal函数通知内核父进程会忽略SIGCHLD信号,则子进程结束后,内核会回收,并不再给父进程发送该信号。
示例:
本程序中,子进程先退出了,且父进程未使用wait或waipid回收。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
int main(int argc, const char* argv[]) {
pid_t pid = -1;
int ret = -1;
struct sigaction act;
act.sa_handler = SIG_IGN; // 忽略该信号
act.sa_flags = 0; // 使用旧的信号处理函数
/*忽略SIGCHLD信号,
则子进程结束后,由内核回收,不会再给父进程发信号*/
ret = sigaction(SIGCHLD, &act, NULL);
if (-1 == ret) {
perror("sigaction");
return 1;
}
pid = fork();
if (-1 == pid) {
perror("fork");
return 1;
}
if (0 == pid) { // 子进程
sleep(1);
printf("子进程已结束!\n");
exit(0);
} else { // 父进程
sleep(2);
system("ps -aux | grep Z"); // 查看是否有僵尸进程
}
printf("父进程也结束了.\n");
return 0;
}
运行结果:
ps命令看到无僵尸进程。