僵尸进程的成因以及僵尸可以被“杀死”吗?

僵尸不可能被杀死,因为它已经死了,不存在再死一次的问题。死的对立面是活,死者已死。只有活的进程才可能被杀死。
什么是僵尸
首先要明确一点,僵尸进程的含义是:子进程已经死了,但是父进程还没有 wait 它的一个中间状态,这个时候子进程是一个僵尸。正常情况下子死,父 wait,清理掉子进程的 task_struct,释放子进程的 PID:

编译上述程序,运行,我们看到 2 个 a.out 进程:

杀死子进程 4578,看到父进程的打印:

之后,4578 会消失,因为父进程执行到了 wait,也知道了子进程是被信号 2 杀掉的。
但是如果子进程死了,父进程不执行到 wait,比如把上图中的"#if 0"改为"#if 1",杀死子进程后,子进程就会是一个僵尸:

我们重新运行,当我们用 kill -2 杀掉子进程 4628 后,我们发现 4628 成为一个僵尸,状态变为 Z+,名字上也加了一个棺材[],成为[a.out]:

僵尸不可能被杀死
我们看到上面 4628 是个僵尸很不爽,所以我们想把它干掉,据说 Linux 有个信号 9,神挡杀神,佛挡杀佛,我们现在来用 kill -9 干掉 4628:

从上图可以看出,我们把 4628 用 kill -9 捅了好多刀,但是最后看 4628 这个僵尸,还是没有消失。
因为僵尸已经是死了,它不可能再次被杀死,你给它捅一万刀,它也是个死人,不可能再次死!
僵尸不可能被杀死,因为它已经死了!只等父进程来 wait 清理尸体了。
这个时候我们能够把僵尸消失掉的方法,就是杀死僵尸进程的父进程 4627。
一个僵尸可以被杀死的假象
下面的这个程序证明“僵尸可以被杀死”:

我们在主线程里面,pthread_create()创建线程后,pthread_exit()退出,这个时候我们会发现,在 ps 命令里面,a.out 显示为一个僵尸:

这个时候我们来杀死 4730 这个僵尸:
kill -9 4730
我们会惊奇地发现,4730 真地会从 ps 命令里面消失!

我们把时间轴拉回调用"kill -9 4730"之前。刚才我们“看起来”能杀死僵尸的本质原因是,当主线程 4730 调用 pthread_exit()退出后,主线程 4730 的状态确实是僵尸了,但是该进程里面的 4731 线程,却没有死:


看看 4731:

4731 是活着的,证明整个进程并没有挂。所以 4730 的退出,只是让整个进程半死。而由于 ps 这些命令的误会,4730 凑巧又是整个进程的 PID,它显示地好像整个 4370 成了僵尸一样。

那么,根据 POSIX 标准关于信号(signal)的定义,当我们执行 kill -9 4730(4730 是 4730 和 4731 的 TGID,也是整个进程用户态视角的 PID)的时候,是要杀死整个 4730 进程的,所以这个时候 4731 被我们杀死,整个进程就都死了,这个时候,执行到父进程的 wait 逻辑,导致僵尸消失。
所以,在本例中,kill -9 4730 看起来是"杀死了僵尸”,实际是杀死了 4730 整个进程(里面的每个线程),导致整个进程死。在次之前,整个进程实际还是活的。
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
关注公众号 『 java 烂猪皮 』,不定期分享原创知识。
同时可以期待后续文章 ing🚀
.关注后回复【666】扫码即可获取学习资料包

文章来自公众号:Linux 阅码场,作者:宋宝华
评论