最近得空翻看博客,才猛然发现最近一篇文章居然是在2022年,都已经三年过去了,真是时光飞逝。。。
想想曾经的“誓言”:每周至少写一篇,真是不堪回首。
现计划,从本篇开始,再次重启曾经的“誓言”,再加一篇,每周1到2篇,看能否坚持下去,题材就以最近三年的“奋斗”成果,车联网TBOX开发,计划作为一个专栏,涉及范围广,业务多,1到2篇应该不成问题;
废话就不多写了,开篇就以通用技术或者通用问题开始吧,后续再从Autosar,MCU,TSP,TLS,APN拨号,Selinux,休眠唤醒,电源管理,远程车控,诊断升级,数字钥匙等等方面展开吧
说到Linux系统子进程回收,相信熟悉一点Linux进程管理知识的同学应该都清楚,不就注册一个SIGCHLD信号,然后执行wait/waitpid不就行了?对,是如此简单,但也不仅仅如此;
这是一个在实际项目开发过程中遇到的问题,主进程A (以守护进程运行)启动后fork了BCDEF等5个子进程,然后A 也注册了SIGCHLD信号,在信号处理函数中也执行了waitpid,如下:
void _sig_hand(int sig)
{
waitpid(-1, &status, WNOHANG); //这么写只代表效果如此,当然在信号处理函数中这么做不合适
...
}
A_main()
{
signal(SIGCHLD, _sig_hand);
...
}
发生的问题:我们偶尔会发现系统中,存在BCDE或CDEF等僵尸进程,无论A是否在运行,A不在BCDE或CDEF的父进程是1号进程,但状态也是僵尸态;
疑问:A在运行,也已经执行了waitpid,为什么子进程还是僵尸态,也就是为什么没有被回收?
A不在运行,那也被1号进程接管了,为什么也不能回收?这跟A在与不在 没有关系;
分析1:子进程退出,A一定能捕获到SIGCHLD信号,也一定能调用waitpid,也就是一定可以回收,只要A没有卡死,实际A也没有卡死,因为设计上A就没什么业务,从日志看A也正常
分析2:既然回收了,还存在僵尸态,那就是没有回收全
分析3:为什么没有回收全?那就是没有调用waitpid
分析4:A如果有问题或者来不及调用waitpid,那么1号进程也存在这个问题(从现象看如此)
分析5:那么症结就在于什么来不及调用waitpid?
分析6:一般正常来说,太快了或者太多了就会来不及,跟并发处理一个道理
分析7:为什么太快或者太多? 那就是BCDEF等子进程一起挂了
到这里,问题基本就明了了。
既然BCDEF等子进程能一起挂,但是A又不能一起回收,那只有分多次了,最简单的方法就是循环,改动如下:
void hand_all_child() //注意这不是在信号处理回调中
{
while(1) {
pid_t pid = waitpid(-1, &status, WNOHANG);
if(pid == 0) break;
if(pid == -1) {
if(EINTR == errno) continue;
else break;
}
...
}
void _sig_hand(int sig)
{
//设置回收flag,触发hand_all_child
child_flag = 1;
...
}
A_main()
{
while(1) {
if(child_flag)
hand_all_child();
...
}
}
总结:
- waitpid需要循环调用,配置WNOHANG,非阻塞模式
- 什么都交给1号进程也不靠谱,最好自己干,要相信自我
扩展:
- 信号处理程序中,哪些事儿不能干,需要心里有数,类似此问题,子进程同一时间退出,信号处理程序的重入问题肯定会更加明显的爆发
- 如果A挂了来不及回收完,1号也回收不全,怎么办?有什么办法?
可能的办法(未经验证):只要触发1号的回收动作即可,即干掉任意一个1号的子进程;