【Linux】SIGCHLD信号

文章介绍了SIGCHLD信号在处理子进程结束时的作用,避免僵尸进程的产生。通过设置信号处理函数,父进程可以在子进程终止时得到通知,使用waitpid函数非阻塞地清理子进程。此外,还提到了忽略SIGCHLD信号作为另一种避免僵尸进程的方法。
摘要由CSDN通过智能技术生成

文章目录

SIGCHLD信号

回忆:

为了避免出现僵尸进程,父进程需要使用wait或waitpid函数等待子进程结束,父进程可以阻塞等待子进程结束,也可以非阻塞地查询的是否有子进程结束等待清理,即轮询的方式

  • 如果采用阻塞等待:父进程阻塞就不能处理自己的工作了
  • 如果采用非阻塞等待:父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂

引入

子进程在终止时会给父进程发生SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理动作,这样父进程就只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait或waitpid函数清理子进程即可


例子:

这里写一个监视脚本:

while :; do ps axj | head -1 && ps axj | grep signal | grep -v grep; sleep 1; echo "################"; done

下述中对SIGCHLD信号进行了捕捉,并将在该信号的处理函数中调用了waitpid函数对子进程进行了清理,如果不清理,子进程还是僵尸进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
void GetChild(int signo)
{
	waitpid(-1,NULL,WNOHANG);
	printf("get a signal:%d,pid:%d\n",signo,getpid());
}
int main()
{
	signal(SIGCHLD,GetChild);//捕捉SIGCHLD信号, 也可以写成:singal(17,GetChild)
	pid_t id = fork();
	if(id == 0)
	{
		//child
		int count = 5;
		while(count)
		{
			printf("我是子进程:%d\n",getpid());
			sleep(1);
			count--;
		}
		exit(0);
	}
	//father
	//子进程一旦退出,父进程在while循环期间也一定能够收到SIGCHLD信号
	//因为对SIGCHLD信号进行捕捉,所以执行自定义逻辑
	while(1) ;
	return 0;
}

image-20220817130653715


但是实际上:父进程在信号处理函数这样写会比较好:

void handle(int signo)
{
    printf("get a signal: %d\n", signo);
    int ret = 0;
    while ((ret = waitpid(-1, NULL, WNOHANG)) > 0) //WNOHANG:非阻塞等待
    {
        printf("wait child %d success\n", ret);
    }
}

含义:

  1. SIGCHLD属于普通信号,记录该信号的pending位只有一个,如果在同一时刻有多个子进程同时退出,那么在handler函数当中实际上只清理了一个子进程,因此在使用waitpid函数清理子进程时需要使用while不断进行清理

2)使用waitpid函数时,需要设置 WNOHANG 选项,即非阻塞式等待,否则当所有子进程都已经清理完毕时,由于while循环,会再次调用waitpid函数,此时就会在这里阻塞住

问题1:为什么要用循环等待

这样的写法能够满足各种子进程退出的情况,假设创建10个子进程, 它们同时退出了,每个子进程退出都向父进程发送信号, 但是pending位图只能有一个比特位记录这个SIGCHILD信号, 如果只wait一次,那么只能读取一个子进程,剩下的9个就没被读到,所以设置while循环, 不等待指定一个子进程->所以参数设定-1循环式的把所有子进程退出都读取到

问题2:为什么要非阻塞等待呢?

假设5个进程退出,5个没退出, 前五个退出的进程全读完了,还要读第6次, 我们刚才是站在上帝视角,实际我们不知道有多少个进程退出了, 当读取一个子进程退出,那就继续读,只有读取失败的时候才知道没有子进程退出了

如果用阻塞,上述情况下,当我们读取第6次的时候, 如果子进程不退出就在信号捕捉这里卡住了,所以使用非阻塞等待可以防止程序卡住的情况


下面代码中对SIGCHLD信号进行了捕捉,并将在该信号的处理函数中调用了waitpid函数对子进程进行了清理

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
void handler(int signo)
{
	printf("get a signal: %d\n", signo);
	int ret = 0;
	while ((ret = waitpid(-1, NULL, WNOHANG)) > 0)
	{
		printf("wait child %d success\n", ret);
	}
}

int main()
{
	signal(SIGCHLD, handler);//捕获SIGCHLD信号
	if (fork() == 0)
	{
		//child
		printf("child is running, pid: %d\n", getpid());
		sleep(3);
		exit(1);
	}
	//father
	while (1);
	return 0;
}

image-20220817135044146


要想不产生僵尸进程还有另外一种办法:父进程调用signal或sigaction函数将SIGCHLD信号的处理动作设置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程.系统默认的忽略动作和用户用signal或sigaction函数自定义的忽略通常是没有区别的,但这是一个特列.此方法对于Linux可用,但不保证在其他UNIX系统上都可用

例子:调用signal函数将SIGCHLD信号的处理动作自定义为忽略

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
    signal(SIGCHLD, SIG_IGN);//将SIGCHLD信号的处理动作自定义为忽略
    if (fork() == 0)
    {
        //child
        printf("child is running, child pid: %d\n", getpid());
        sleep(3);
        exit(1);
    }
    //father
    while (1);
    return 0;
}

此时我们发现:进程在终止时会自动被清理掉,不会产生僵尸进程,也不会通知父进程

image-20220817135331404

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芒果再努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值