SIGCHLD 可以用来解决僵尸进程的问题。
虽然父进程可以采用 wait()来回收子进程的资源,但是 wait ()函数是阻塞的,父进程也有自己的业务逻辑,不能老是为等待子进程而停留。父进程通过捕捉到 SIGCHLD ,并且对其进行信号的处理,便可以回收子进程的资源。
/*
SIGCHLD 信号产生的3个条件
1、子进程结束了
2、子进程暂停了
3、子进程继续运行
都会给父进程发送该信号,父进程会默认忽略该信号
使用 SIGCHLD 信号解决僵尸进程的问题。
*/
#include<stdio.h>
#include<wait.h>
#include<signal.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include<wait.h>
void myFun(int num){
//能够捕捉到信号,但是只靠这样的一句话真的能够回收资源吗?不能
printf("捕捉到的信号 : %d.\n",num);
//子进程结束后需要回收 PCB 的资源
wait(NULL);
}
int main(void){
//创建一些子进程
pid_t pid;
for(int i = 0;i < 20;i++){
pid = fork();
if(pid == 0){
break;
}
}
if(pid > 0){
//捕捉子进程挂掉的时候发出的信号,这样父进程还能够做自己的事情
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,NULL);
while (1)
{
printf("parent process : %d.\n",getpid());
sleep(2);
}
}
else if(pid == 0){
printf("child process : %d.\n",getpid());
}
return 0;
}
但是根据上面这个进程的运行的结果,还是有部分子进程没有被回收。
原因是父进程在回收某一个子进程的信号的时候,另外的子进程产生了信号,但是未决信号集只能保存一个未决信号,其他重复的相同的信号会被丢弃。
一个不好的解决方法,写一个while 死循环来回收子进程,这样做会导致 父进程回收了一个被阻塞。
void myFun(int num){
//能够捕捉到信号,但是只靠这样的一句话真的能够回收资源吗?不能
printf("捕捉到的信号 : %d.\n",num);
//子进程结束后需要回收 PCB 的资源
while(1){
wait(NULL);
}
}
我个人想法是计数,来一个 SIGCHLD 的信号就计数一次,这个计数会被当作 wait () 函数执行的次数。
😔哎真的要复习了,使用 waitpid循环回收子进程
void myFun(int num){
//能够捕捉到信号,但是只靠这样的一句话真的能够回收资源吗?不能
printf("捕捉到的信号 : %d.\n",num);
while (1)
{
int ret = waitpid(-1,NULL,WNOHANG);
if(ret == -1){
break;
//说明没有子进程了,那就更不用管了
}else if(ret > 0){
printf("child die, pid = %d.\n",ret);
}else if(ret == 0){
break;
//就是break,说明还有其他的子进程,但是先不用管,父进程先做好自己的事情
}
}
}
但是这样依然可能会出现段错误。
如果信号发生的时候在未决信号集当中注册,在这个程序中子进程众多,父进程还没有来得及改变未决信号集里面的状态,所有的子进程就已经运行完了。
改进方式,在子进程尚未运行完成时,对 SIGCHLD 信号阻塞,运行完成之后统一设置成不阻塞。由父进程统一对释放的子进程进行处理。
解决方法
#include<stdio.h>
#include<wait.h>
#include<signal.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include<wait.h>
void myFun(int num){
//能够捕捉到信号,但是只靠这样的一句话真的能够回收资源吗?不能
printf("捕捉到的信号 : %d.\n",num);
//子进程结束后需要回收 PCB 的资源
//1、不好,信号不能排队,会导致无法将所有的子进程回收完
// wait(NULL);
//2、不好,循环调用能够回所有的子进程,但是父进程会被阻塞
// while(1){
// wait(NULL);
// }
//3、使用waitpid()
while (1)
{
int ret = waitpid(-1,NULL,WNOHANG);
// If wstatus is not NULL, wait() and waitpid() store status
//information in the int to which it points. This integer can
//be inspected with the following macros (which take the integer
//itself as an argument, not a pointer to it, as is done in wait()
//and waitpid()!):
//第2个参数是用来存放子进程被回收的时候的状态来着。
if(ret == -1){
// perror("waitpid");
// exit(0);
break;
//说明没有子进程了,那就更不用管了
}else if(ret > 0){
printf("child die, pid = %d.\n",ret);
}else if(ret == 0){
///break;不能这样,说明还有其他的子进程
break;
//就是break,说明还有其他的子进程,但是先不用管,父进程先做好自己的事情
}
}
}
int main(void){
//提前设置好阻塞信号集,因为有可能进程很快结束,父进程还没有注册完信号捕捉。
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGCHLD);
sigprocmask(SIG_BLOCK,&set,NULL);
//创建一些子进程
pid_t pid;
for(int i = 0;i < 20;i++){
pid = fork();
if(pid == 0){
break;
}
}
if(pid > 0){
//捕捉子进程挂掉的时候发出的信号,这样父进程还能够做自己的事情
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD,&act,NULL);
//注册完信号捕捉以后解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
while (1)
{
printf("parent process : %d.\n",getpid());
sleep(2);
}
}
else if(pid == 0){
printf("child process : %d.\n",getpid());
}
return 0;
}