使用SIGCHLD信号完成对子进程的回收
1、SIGCHLD信号
1.1 信号作用
子进程退出后,内核会给它的父进程发送SIGCHLD信号,父进程收到这个信号后可以对子进程进行回收。
使用SIGCHLD信号完成对子进程的回收可以避免父进程阻塞等待而不能执行其他操作,只有当父进程收到SIGCHLD信号之后才去调用信号捕捉函数完成对子进程的回收,未收到SIGCHLD信号之前可以处理其他操作。
1.2 产生条件
- 子进程结束时
- 子进程收到SIGSTOP信号
- 子进程停止时,收到SIGCONT信号
2、练习
要求:父进程创建出三个子进程,然后令父进程捕获SIGCHLD信号完成对子进程的回收。
2.1 代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
//SIGCHLD信号处理函数
void waitChild(int sign)
{
pid_t wpid;
while(1)
{
wpid = waitpid(-1,NULL,WNOHANG); //调用waitpid函数回收子进程
if(wpid>0)
{
printf("child is quit,wpid==%d\n",wpid);
}
else if(wpid==0)
{
printf("child is living,wpid==%d\n",wpid);
}
else if(wpid<0)
{
printf("no child is living,wpid==%d\n",wpid);
break;
}
}
}
int main()
{
int i= 0;
//将SIGCHLD信号阻塞
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask,SIGCHLD);
sigprocmask(SIG_BLOCK,&mask,NULL);
//循环创建三个子进程
for( i = 0;i<3;i++)
{
//pid_t fork(void)
pid_t pid = fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid>0)//父进程
{
printf("father: pid == [%d],fpid==[%d]\n",getpid(),getppid());
}
else if(pid==0) //子进程
{
printf("child: pid==[%d],fid==[%d]\n",getpid(),getppid());
break; //防止子进程循环创建子进程
}
}
//第一个子进程
if(i==0)
{
printf("[%d]:child\n",i);
}
//第二个子进程
if(i==1)
{
printf("[%d]:child\n",i);
}
//第三个子进程
if(i==2)
{
printf("[%d]:child\n",i);
}
//父进程
if(i==3)
{
printf("[%d]:father\n",i);
//注册SIGCHLD信号处理函数
struct sigaction act;
act.sa_handler = waitChild;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sleep(5); //保证在信号处理函数注册完成前子进程先退出产生僵尸进程
sigaction(SIGCHLD,&act,NULL);
//在注册信号处理函数后解除对SIGCHLD的阻塞
sigprocmask(SIG_UNBLOCK,&mask,NULL);
//保证父进程不退出
while(1)
{
sleep(1);
}
}
return 0;
}
2.2 注意点
- 有可能还未完成信号处理函数的注册三个子进程都退出了。这样会导致僵尸进程的产生。
解决办法:可以在fork之前先将SIGCHLD信号阻塞,当完成信号处理函数的注册后在解除阻塞。 - 当SIGCHLD信号函数处理期间, SIGCHLD信号若再次产生是被阻塞的,而且若产生了多次, 则该信号只会被处理一次, 这样可能会产生僵尸进程。
解决办法:可以在信号处理函数里面使用while(1)循环回收, 这样就有可能出现捕获一次SIGCHLD信号但是回收了多个子进程的情况,从而可以避免产生僵尸进程。