linux僵尸进程理解,聊聊Linux系统中的僵尸进程

车祸现场

今天下午,笔者正在认真搬砖,日志集群中有一台机器忽然报init进程占用100% CPU。strace之,发现疯狂输出如下系统调用。

~ strace -p 1

rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [], 8) = 0

clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7cc15789d0) = -1 ENOMEM (Cannot allocate memory)

rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0

close(9) = 0

close(10) = 0

pipe([9, 10]) = 0

rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [], 8) = 0

clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7cc15789d0) = -1 ENOMEM (Cannot allocate memory)

rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0

close(9) = 0

close(10) = 0

pipe([9, 10]) = 0

rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [], 8) = 0

clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7cc15789d0) = -1 ENOMEM (Cannot allocate memory)

rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0

close(9) = 0

close(10) = 0

pipe([9, 10]) = 0

然后top和ps发现大量PPID为1的僵尸进程,说明init进程不知为何不再回收僵尸进程了。

b9a5c861090a

b9a5c861090a

原因未查明(高度怀疑CentOS 6系统/内核bug,或是硬件问题?下图是查找到的一种可能性),并且僵尸进程还在不断增多,急呼运维同学reboot,遂恢复正常。

b9a5c861090a

折腾了半天,今晚简单说说僵尸进程吧。

僵尸进程、产生原因及危害

"zombie process"或者"defunct process",是类Unix系统中的概念,指那些实际运行已经完成或终止[如通过exit()系统调用,或者发生错误、收到终止信号],但是在系统进程表中仍然残留着对应的进程项,没有完全被清理的进程。僵尸进程已经释放了除进程表项外的所有内存空间,无法再被调度执行,只是在等待其他进程来收集它的退出状态信息而已。

在正常情况下,父进程会通过wait()或waitpid()系统调用进行善后处理,即获取其子进程的退出状态。一旦成功获取,该僵尸子进程就会被销毁(reaped)而不复存在,其进程表项也会被释放。所以子进程退出时,应该只会在exit()和wait()之间很短暂地处于僵尸状态。

但是,如果父进程没有通过wait()或waitpid()系统调用获取其子进程的退出状态,也没有显式地处理或者忽略掉SIGCHLD信号[该信号是子进程结束时发送给父进程的],并且父进程保持运行,那么它的子进程结束后就一直处于僵尸状态了,此时就可以被用户观察到。在top命令下,僵尸进程的状态会显示为"Z",在ps命令下则会带上""标记。

b9a5c861090a

很显然,如果僵尸进程一直不被销毁,那么它们将永远占用PID和进程表中的空间。Linux系统中的PID取值范围是有限制的(在/proc/sys/kernel/pid_max下,默认值32768),过多的僵尸进程会导致系统PID耗尽,无法再创建新的进程。

如何手动清理?

僵尸进程是无法被直接kill掉的,而造成僵尸进程无法销毁的罪魁祸首是它的父进程不做善后工作。所以,我们可以通过kill命令发送SIGKILL/SIGTERM信号直接干掉父进程,它的子进程就会成为所谓“孤儿进程”(orphan process)。孤儿进程会被根进程init收养,并且init进程会在后台执行wait()或waitpid()系统调用,代替它们的父进程完成清理工作。

但是在极端情况下,仍然有可能出现init出现大量僵尸子进程的情况(比如本文开头),这时就只能干掉init进程——即重启系统了。

如何避免?

父进程一定要尽职尽责,避免出现长时间僵尸进程的方法有:

在signal()系统调用中设定SIGCHLD信号的handler回调,并显式wait()处理之。

在signal()系统调用中设定handler为SIG_IGN,即显式忽略该信号,子进程的回收会由内核直接负责。

在产生子进程时做两次fork(),并立即杀掉一级子进程,令二级子进程(即真正的子进程)成为孤儿并被init收养。没那么“负责任”,但是也比较安全。

The End

民那晚安晚安。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值