SIGCHLD的产生条件
子进程状态发生变化就会产生SIGCHLD信号
1、子进程终止时
2、子进程接收到SIGSTOP信号停止时
3、子进程处在停止态,接受到SIGCONT后唤醒时
借助 SIGCHLD 信号回收子进程 (重要)
子进程执行结束之后,父进程如果不对其进行回收,子进程就会变为僵尸进程。
父进程可以通过调用wait()函数和waitpid()函数去回收子进程。目前有两种方法来进行回收僵尸进程
1.wait阻塞方式 2.waitpid的轮询模式。
这两种方式都会导致父进程无法干别的事。
子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
void cat_sigchild(int sig) //有子进程终止,发送SIGCHLD信号时,该函数会被内核回调
{
int status;
pid_t pid;
//if((wpid = wait(NULL)) != -1) //常规信号不支持排队,不能这么写
while((pid = waitpid(0,&status,WNOHANG)) > 0) //循环回收,防止僵尸进程出现
{
if(WIFEXITED(status)) //获取子进程退出状态,并打印
{
printf("child exit with %d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("child kill by sig %d\n",WTERMSIG(status));
}
}
}
int main(int argc,char *argv[])
{
pid_t pid;
//阻塞SIGCHLD信号
sigset_t newset,oldset;
sigemptyset(&newset);
sigaddset(&newset,SIGCHLD);
sigprocmask(SIG_BLOCK,&newset,&oldset);
//如果在信号捕捉函数设置之前,子进程先结束,那么如果提前不阻塞SIGCHLD信号的话,
//就会导致先结束的子进程无法回收,所以在设置信号捕捉函数之前应该先阻塞SIGCHLD信号。
int i;
for(i=0;i<10;i++)
{
pid = fork(); //创建多个子进程
if(pid == 0)
{
break;
}
else if(pid <0)
{
perror("fork:");
exit(1);
}
}
if(pid == 0)
{
printf("i am %d child pid is %d\n",i+1,getpid());
return i+1;
}
else if(pid >0)
{
int n = 10000000;
//sleep(4);
//阻塞SIGCHLD信号 为什么可以在这?
/*sigset_t newset,oldset;
sigemptyset(&newset);
sigaddset(&newset,SIGCHLD);
sigprocmask(SIG_BLOCK,&newset,&oldset);*/
while(n --) //模拟程序执行,让子进程先结束。
{}
//父进程中注册捕获信号函数
struct sigaction act,oldact;
act.sa_handler = cat_sigchild;
act.sa_flags = 0;
sigemptyset(&act.sa_mask); //信号捕捉函数执行期间,sa_mask会被替换掉
sigaction(SIGCHLD,&act,NULL);
//解除阻塞
sigprocmask(SIG_SETMASK,&oldset,NULL); //如果不解除阻塞,回调函数没有执行机会
while(1)
{
printf("parent id %d\n",getpid());
sleep(1);
}
}
return 0;
}
注意点:
这里需要注意的是:我们在设置SIGCHLD信号的信号捕捉函数之前为了程序的严谨性,要先使用系统函数sigprocmask()去阻塞SIGCHLD信号,在设置完SIGCHLD信号的信号捕捉函数之后再解除阻塞。
原因:
如果我们的子进程先于父进程执行,假如在父进程设置完SIGCHLD的信号捕捉函数之前所有子进程都执行结束了,那么父进程就不会再收到子进程发送的SIGCHLD信号,信号捕捉函数就不会执行,进而回收子进程的系统函数waitpid()就不会被调用,那么就会造成所有的子进程变为僵尸进程。
解决办法:
1:设置信号阻塞,在父进程设置完SIGCHLD信号的信号捕捉函数之前阻塞SIGCHLD信号。这样即使所有的子进程都结束了,由于信号处于阻塞状态,当我们设置完SIGCHLD信号的捕捉函数之后,解除阻塞,就可以正常捕获到子进程发送的SIGCHLD信号,就可以进入捕捉函数调用waitpid去回收 死去的子进程 的资源。
注意: 这里想要回收所有子进程调用waitpid必须使用while循环结构,不能使用if结构。因为在执行SIGCHLD信号捕捉函数期间,如果两个或多个子进程同时结束,那么SIGCHLD信号只记录一次,此时如果使用if结构就会导致同时结束的子进程只回收一个。
2:我们可以人为的使用kill -17 (父进程号) 命令给父进程发送SIGCHLD信号,就会触发信号捕捉函数,在捕捉函数内部利用
while((pid = waitpid(0,&status,WNOHANG)) > 0) 结构去回收所有死掉的子进程。
注意:在父进程之前死掉的子进程发送的SIGCHLD信号默认动作为忽略,所以可以将之前先于父进程死掉的子进程回收靠的并不是之前子进程的SIGCHLD信号,而是靠的后来收到的SIGCHLD信号,和waitpid函数.
在这种人为发送信号回收的情况下:
SIGCHLD信号捕捉函数只调用执行一次,waitpid函数执行多次,才可以将之前死掉的所有子进程回收。
SIGCHLD信号注意问题
- 子进程继承父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending。
- 注意注册信号捕捉函数的位置。
- 应该在fork之前,阻塞SIGCHLD信号。注册完捕捉函数后解除阻塞。