【Linux】SIGCHLD信号解决僵尸进程问题

1. 基本信息

SIGCHLD信号产生的条件:

  • 子进程终止时
  • 子进程接收到SIGSTOP信号停止时
  • 子进程处在停止态,接受到SIGCONT后唤醒时
    以上三种条件都会给父进程发送SIGCHLD信号,父进程默认会忽略该信号。

2.僵尸进程的产生

  1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/types.h>4 #include<sys/stat.h>5 6 int main() {7 8         pid_t pid;9         for(int i = 0; i < 20; ++i){10                 pid = fork();11                 if (pid == 0)12                         break;13         }14 15         if (pid > 0) {16                 while(1){17                         printf("父进程ID=%d\n", getpid());18                         sleep(2);19                 }20         } else if(pid == 0) {21 22                 printf("子进程ID=%d\n", getpid());23         }24 }

创建20个子进程,父进程每隔2秒循环输出一句话。
在这里插入图片描述
在这里插入图片描述
可以看到当子进程结束后,父进程没有及时回收子进程的资源,所以导致子进程都变成了僵尸进程。

3.SIGCHLD信号解决僵尸进程问题

3.1 初级版本

//信号捕捉处理函数8 void myfun(int num) {9         printf("捕捉到的信号编号:%d\n", num);10         wait(NULL);11 }//在父进程添加信号捕捉
21 if (pid > 0) {
22   struct sigaction act;
23   act.sa_flags = 0;
24   act.sa_handler = myfun;
25   sigemptyset(&act.sa_mask);
26   sigaction(SIGCHLD, &act, NULL);
27   while(1){
28      printf("父进程ID=%d\n", getpid());
29      sleep(2);
30   }
31 } else if(pid == 0) {
32  	printf("子进程ID=%d\n", getpid());
33 }

sigaction是配置信号捕捉,要捕捉的信号是SIGCHLD,捕捉之后进入处理函数myfun,在函数中输出信号编号和执行wait函数。

wait函数
子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。父进程调用wait后,会将父进程挂起,然后分析释放当前某个子进程已经退出。如果它找到了一个僵尸子进程,就会收集该子进程的退出状态,并释放资源,返回对应的子进程id。如果它没有找到这样一个子进程,就会一直等待。wait函数一次只能回收一个子进程的资源。
在这里插入图片描述
把子进程产生数量改为10个,20个太多了,看花眼。从上图可以看到,信号捕捉处理函数的确捕捉到了17号信号,就是SIGCHLD信号。子进程的ID是从34-43共10个。
在这里插入图片描述
使用命令ps aux查看进程。发现34、35成功回收了,其他还是僵尸进程。为什么会出现这个原因呢?

信号集
在这里插入图片描述
图源自《牛客大学》。在内核区存在两个表来记录信号的状态,其中一个叫未决信号集,另外一个叫堵塞信号集。当SIGCHLD信号产生,内核会将第17位置1,代表SIGCHLD信号处于未决状态,即产生了但是还没处理。

用户态产生信号后,内核会接收该信号,并且转向用户态的处理函数,就是那个myfun函数。当执行完myfun函数后,再回到内核将未决信号集第17位置0,内核负责回到产生信号时的代码原位置。
在这里插入图片描述
当用户在执行myfun函数时,其他子进程,其他子进程也是正常运行的。所以当某一个子进程终止,也会产生SIGCHLD信号。内核同样也会接受到该信号,但是发现未决信号集第17位已经是1,就会忽略该信号。子进程产生的信号被忽略后,是不会重复发送的,就会变成僵尸进程,得不到释放。
在这里插入图片描述
从输出也能看到,父进程只成功捕捉到了2个信号,释放两个子进程,其他子进程的信号都被忽略掉了。

3.2 改进版本

  8 void myfun(int num) {9   printf("捕捉到的信号编号:%d\n", num);10   while(1) {11     int ret = waitpid(-1, NULL, WNOHANG);12     if (ret > 0)13         printf("终止子进程的id:%d\n", id);14     else if (ret == 0)15         break;16     else if (ret == -1)17         break;18   }19 }

将数据捕捉处理函数改了使用waitpid函数,并且加了while循环,所以如果在处理中间,有其他子进程终止了,waitpid函数也能在循环中帮忙回收资源。

waitpid函数
waitpid函数和wait函数很像,只是功能上更加灵活。当第一个参数取-1时,代表等待任何一个子进程退出,等于wait函数。第三个参数WNOHANG代表是非挂起状态,这是与wait函数不同的点,即waitpid函数没等到终止的子进程,不会一直等待。

当正常返回,并释放了某个子进程,返回该子进程id。
当该父进程存在子进程,但是没有终止状态的,就返回0.
当压根不存在子进程,返回-1 。
在这里插入图片描述
在这里插入图片描述
55087是父进程,可以看到已经没有子进程了,所有子进程都已经释放了。

3.3 为什么加了while可以回收之前被忽略掉SIGCHLD的僵尸进程?

小伙伴们不要有这样的误解,A子进程产生信号,调用了myfun函数,waitpid(wait函数同理)就只会去回收A进程。waitpid函数是个劳模,它只要见到僵尸进程就忍不住要回收,但能力有限,一次只能回收一次。只要给它机会,它可以把所有的僵尸进程一网打尽。所以只要有while循环,就可以不断执行waitpid函数,直到break。

3.4 为什么捕捉到了信号后没有进行处理就直接继续执行父进程后面的程序了呢?

在这里插入图片描述
观察输出图可以看到一个问题,父进程第一次输出【捕捉到的信号编号:17】,代表进入了信号捕捉处理函数,然后在里面循环终止了10个子进程,这个时候已经没有子进程了。但是后面还是输出【捕捉到的信号编号:17】,代表父进程再次进入了信号捕捉处理函数,那为什么已经不存在了子进程,父进程还是捕捉到了信号。

信号产生,内核中未决信号集SIGCHLD信号置1,内核调用信号捕捉函数myfun的同时把该信号置0,也就是说进入myfun函数后,内核依然是可以接收到SIGCHLD信号的。但是Linux为了防止某一个信号重复产生,在myfun函数进行多次递归导致堆栈空间爆了,它在调用myfhun函数会自动(内核自己完成)堵塞同类型信号。当然也可以用参数,自己指定要堵塞其他类型的信号。要注意的是,这里堵塞不是不接收信号,而是接收了不处理。当myfun函数结束,堵塞就会自动解除,该信号会传递给父进程。想象一个场景,20个子进程,先瞬间终止10个,父进程捕获到信号,进入myfun函数wait回收。这里有个点就是,父进程在执行myfun函数的时候,其他子进程不是挂起的,也是会运行的,至于怎么调度,那就看神秘莫测的调度算法了。在回收过程中,其余10个子进程也终止了,发出呼喊:“爹,快来回收我!”。父进程:“我没空,我还在myfun函数中干活”。于是内核将未决集中SIGCHLD信号置1等待处理,父进程在myfun函数中使用waitpid函数回收僵尸,”怎么越回收越多呀”,在while函数的加持下,他成功回收了20个僵尸。当它回到主函数打算休息下,内核叮的一声,有你的SIGCHLD信号,父进程以为有僵尸再次进入myfun函数,执行waipid函数,发现压根没有僵尸(上一次都回收完了),甚至儿子都没了(返回-1,break),骂骂咧咧返回了主函数。这就是为什么父进程捕获到了信号,进入了myfun函数,一个僵尸都没回收的真相。

3.5 为什么出现段错误?

该实例有可能意外终止,并显示段错误。段错误是个迷,有的人碰到过几次,有的人怎么也碰不到,这是由于神秘莫测的调度算法导致的。究其原因是调用了不可重入的函数。《Linux/UNIX系统编程手册》第21.1.2节 对可重入函数进行了详细的解释,有兴趣的可以去翻一下。

可重入函数的意思是:函数由两条或多条线程调用时,即便是交叉执行,其效果也与各线程以未定义顺序依次调用时一致。通俗点讲,就是存在一个函数,A线程执行一半,B线程抢过CPU又来调用该函数,执行到1/4倍A线程抢回执行权。在这样不断来回执行中,不出问题的,就是可重入函数。多线程中每个线程都有自己的堆栈,所以如果函数中只用到局部变量肯定是可重入的,没问题的。但是更新了全局变量或静态数据结构的函数可能是不可重入的。假设某线程正在为一个链表结构添加一个新的链表项,而另外一个线程也视图更新同一链表。由于中间涉及多个指针,一旦另一线程中断这些步骤并修改了相同指针,结果就会产生混乱。但是并不是一定会出现,一定是A线程刚好在修改指针,另外一线程又去修改才会出现。这就是为什么该问题复现难度较高的原因。

作者在文中指出,将静态数据结构用于内部记账的函数也是不可重入的。其中最明显的例子就是stdio函数库成员(printf()、scanf()等),它们会为缓冲区I/O更新内部数据结构。所以,如果在捕捉信号处理函数中调用了printf(),而主程序又在调用printf()或其他stdio函数期间遭到了捕捉信号处理函数的中断,那么有时就会看到奇怪的输出,设置导致程序崩溃。虽然printf()不是异步信号安全函数,但却频频出现在各种示例中,是因为在展示对捕捉信号处理函数的调用,以及显示函数中相关变量的内容时,printf()都不失为一种简单而又便捷的方式。真正的应用程序应当避免使用该类函数。

printf函数会使用到一块缓冲区,这块缓冲区是使用malloc或类似函数分配的一块静态内存。所以它是不可重入函数。

牛客大学
《Linux/UNIX系统编程手册》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/45426.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux最常用命令:能解决95%以上的问题

操作系统概述 操作系统 Operating System 简称 OS&#xff0c;通俗讲就是一款软件&#xff0c;不过和一般的软件不同&#xff0c;操作系统是管理和控制计算机硬件与软件资源的计算机程序&#xff0c;是直接运行在“裸机”上的最基本的系统软件&#xff0c;任何其他的软件都必须…

服务器卡顿排查方法(Linux服务器)

文章目录 一、分析思路二、详细分析方法1、排除本机自身原因2、服务器性能分析&#xff08;1&#xff09;查看服务器的内存和服务器核数大小&#xff08;2&#xff09;服务器的负载 3、虚拟机分析4、数据库分析&#xff08;MySql&#xff09;&#xff08;1&#xff09;进程列表…

红队常用命令速查

下载方式&#xff1a;干货|红队常用命令速查https://mp.weixin.qq.com/s/kQyj3aumrcUYSYV_I5pdqQ

一文练就命令行高手: Linux命令大全

文章目录 1 前言2 Linux文件系统简介3 文件管理相关命令3.1 路径切换: cd命令3.2 查看文件列表: ls命令3.3 新建文件/文件夹: mkdir命令3.4 删除文件: rm命令3.5 拷贝复制: cp命令3.6 移动move: mv命令3.7 文件查找: find命令3.8 更改文件权限: chmod命令 4 文本内容操作命令4.…

分享巧记Linux命令的方法

最近有些学弟经常私信问我说&#xff0c;他们自己是Linux方面的小白&#xff0c;对于Linux的命令了解十分的少&#xff0c;虽然每次跟着我推荐的学习视频教程可以进行操作&#xff0c;但是离开了视频&#xff0c;就又是两眼抓瞎&#xff0c;什么也想不起来。不知道怎么样通过命…

【玩转Linux】史上最详细的Linux命令大全和线上问题排查手册

文章目录 一、基本命令1、重启2、关机3、清屏4、显示当前的工作目录5、帮助命令6、显示目录下的文件和子目录&#xff08;ls&#xff09; 二、文件操作1、新建文件2、新建文件夹3、复制文件或目录4、删除文件或目录5、移动文件6、查看文件内容7、文件权限8、改变文件或目录的权…

开发者友好的Linux常见命令整理

系统本身 uname -a # 查看内核/操作系统/CPU信息 cat /etc/redhat-release 查看服务器版本 cat /proc/cpuinfo # 查看CPU信息 hostname # 查看计算机名 lspci -tv # 列出所有PCI设备 lsusb -tv # 列出所有USB设备 lsmod # 列出加载的内核模块 env # 查看环境变量 查看…

打听同事工资,我被离职了

薪资是职场红线。 你所在的公司可以自由交流薪资吗&#xff1f; 最近&#xff0c;苹果公司宣布员工之间可以公开讨论薪资、工作时间和环境&#xff0c;引发了网友对秘薪制的讨论。同一家公司同一个岗位&#xff0c;有人拿1万月薪&#xff0c;有人却拿1.5万&#xff0c;在互联…

某公司招了一个大专生,候选人要一万月薪,HR给申请一万二,HR领导说:给一万三!...

无良公司看多了&#xff0c;不禁让人怀疑&#xff1a;这个世界上还有好公司和好hr吗&#xff1f; 来看看这位hr的故事&#xff1a; 最近我们招了一个大专生&#xff0c;5年经验。他的工资是7000&#xff0c;候选人想要10000&#xff0c;我给申请了12000&#xff0c;结果审批不通…

谷歌软件工程师基本年薪达 71.8 万美元,此外还有股权和奖金!

整理 | 朱珂欣 出品 | CSDN程序人生&#xff08;ID&#xff1a;coder_life&#xff09; 树大好乘凉&#xff0c;大厂之下&#xff0c;员工的薪酬也很「可观」。 7 月 19 日&#xff0c;据 Business Insider 报道&#xff0c;2022 年谷歌员工总薪酬中位数为 279802 美元&…

为什么你的年薪只是别人的月薪? 你需要了解这些...

2019 年 5 月 26 - 27 日&#xff0c;由中国 IT 社区 CSDN 与数字经济人才发展中心联合主办的第一届 CTA核心技术及应用峰会将在杭州国际博览中心召开。近 500 名开发者将齐聚于此&#xff0c;共同交流探讨机器学习和知识图谱的技术及行业落地趋势。 会议将聚焦机器学习和知识图…

同一条好友邀请信息给大量的人发,会导致领英账号被封吗?

做外贸的领英新人经常有一个问题&#xff1a;领英上添加好友时&#xff0c;同一条好友邀请信息给大量的人发&#xff0c;会导致领英账号被封吗&#xff1f; 这是一个被一部分人所忽略&#xff0c;也在被一部分人所担心的问题&#xff0c;因为很多领英新手都是在复制粘贴发送相…

腾讯短信平台对接

水一篇文章。。。 腾讯短信平台网址&#xff1a;https://console.cloud.tencent.com/ 微信扫码登录后搜索短信服务 点击添加应用 得到应用 创建签名很严格但不复杂&#xff0c;按照正常流程即可 sdk下载列表。 到此为止&#xff0c;介绍完毕

和中国移动对接短信平台

项目需求 2017年4月12日&#xff0c;正式收到领导通知打算和中国移动对接短信平台&#xff0c;要做的项目当然就是给人发短信&#xff0c;这个短信平台和第三方提供的短信接口不同&#xff0c;这个平台可以二次开发&#xff0c;别人收到短信进行回复的话&#xff0c;我这边可以…

国际短信平台接口调用的方法步骤,简单5步快速教程

如果您需要使用国际短信平台来满足您的业务需求&#xff0c;那么您需要了解短信平台接口调用的方法步骤。 接口调用是指您的应用程序通过网络接口与短信平台进行通信&#xff0c;从而实现向全世界发送短信的功能。 下面是国际短信平台接口调用的方法步骤&#xff1a; 第一步&a…

短信宝接入发送短信实测 Java

1丶注册短信宝 2 代码分为授权跟参数实体 授权实体 Data public class SmsBaoAuthorization {//http://api.smsbao.com/sms private String url;//在短信宝注册的用户名private String username;//在短信宝注册的密码 private String password; } 参数实体 Data pub…

采用API方式发送,接收短信(嘉信)

API方式: 首选要在jx01移动代理服务器中进行如下配置 系统管理-->接口管理-->短信接口模块增加如下配置信息 接口编码&#xff1a; 接口名称&#xff1a; 接口类型&#xff1a;DB接口 API接口 WEB SERVICE接口 SOCKET接口 通信能力&#xff1a;短信 WAP 业务类型…

免费短信接口

话不多说&#xff0c;自己短信量太多了&#xff0c;快过期了&#xff0c;提供自己的免费云短信接口和测试窗口&#xff0c;想用的直接调我接口就行。 ps&#xff1a;不打马赛克不能发出来图&#xff1b;参数传递的id说一下&#xff0c;注册就传1173965&#xff0c;登录传&#…

解决微信“聊天界面中的新消息通知”设置按钮不见了问题

原因 解决微信“聊天界面中的新消息通知”设置按钮不见了问题 解决方法 开启“接收新消息通知”后&#xff0c;“聊天界面中的新消息通知”设置按钮就会出现

Android使用通知(Notification)——方法过时的解决办法

今天照着《Android第一行代码第二版-郭霖》把通知那部分的代码实现了一下&#xff0c;结果发现 Notification notification new NotificationCompat.Builder(this)过时了。 我查了一下开发者文档&#xff0c;根据文档可以看到public Builder (Context context)已经过时了&…