当前位置: 首页 > article >正文

【Linux】SIGCHLD信号

文章目录

  • SIGCHLD信号

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


http://www.kler.cn/a/2869.html

相关文章:

  • springboot 之 整合springdoc2.6 (swagger 3)
  • AtomicInteger 和 AtomicIntegerFieldUpdater的区别
  • Android 配置默认输入法
  • 金价大跌,特朗普胜选或成导火索
  • 【Spring】@Autowired与@Resource的区别
  • adb shell常用命令
  • 基于微信小程序的小区疫情防控小程序
  • 【算法】核心排序算法之堆排序原理及实战
  • 第七讲 贪心
  • Vue.js语法详解:从入门到精通
  • 如何利用WDM波分复用技术来扩展光纤容量?
  • Vector - CAPL - 检测报文周期
  • Vue2.x源码:new Vue()做了啥?
  • 给程序加个进度条吧,1行Python代码,快速添加~
  • c++ 一些常识 2
  • XILINX关于DDR2的IP的读写控制仿真
  • 【Spring Cloud Alibaba】2.服务注册与发现(Nacos安装)
  • 部署私有npm 库
  • 水文-编程命令快查手册
  • 支持RT-Thread最新版本的瑞萨RA2E1开发板终于要大展身手了
  • 学习 Python 之 Pygame 开发魂斗罗(十)
  • 如何系统型地学习深度学习?
  • Python日志logging实战教程
  • 利用Cookie劫持+HTML注入进行钓鱼攻击
  • 服务端测试知识汇总
  • 基于原生Javascript的放大镜插件的设计和实现