为什么僵尸在等孩子呢?


11

我正在研究不同的来源,但找不到关于儿童收割的解剖结构的良好描述。这是我想了解的一个简单案例。

$ cat <( sleep 100 & wait ) &
[1] 14247
$ ps ax -O pgid | grep $$
12126 12126 S pts/17   00:00:00 bash
14248 12126 S pts/17   00:00:00 bash
14249 12126 S pts/17   00:00:00 sleep 100
14251 14250 S pts/17   00:00:00 grep --color=auto 12126
$ kill -2 14248

$ ps ax -O pgid | grep $$
12126 12126 S pts/17   00:00:00 bash
14248 12126 Z pts/17   00:00:00 [bash] <defunct>
14249 12126 S pts/17   00:00:00 sleep 100
14255 14254 S pts/17   00:00:00 grep --color=auto 12126

僵尸为什么要等孩子?

你能解释一下吗?我是否需要了解C并阅读Bash源代码才能对此有更广泛的了解,或者是否有任何文档?我已经咨询过:

GNU bash版本4.3.42(1)-发行版(x86_64-pc-linux-gnu)

Linux 4.4.0-31-generic#50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 GNU / Linux


2
应该注意的是,这实际上与bash无关(除非您选择使用bash作为shell,否则它将启动很多进程)。其他shell(tcsh,ksh,zsh和&c)都启动进程,并运行本质上相同的OS函数来处理它们。
jamesqf

@jamesqf有趣。如果您想将评论扩展为完整的答案,那就太好了。

1
除了不是真正的答案,只是指出您一直在错误的地方寻找答案:-)任何有关* nix系统编程的好书都应该提供比我能写的更好的答案。
jamesqf

Answers:


17

僵尸不在等待孩子。像任何僵尸进程一样,它会一直存在直到其父级将其收集为止。

您应该显示所有涉及的过程,以了解发生了什么,并查看PPID。使用以下命令行:

ps -t $(tty) -O ppid,pgid

您要终止的进程的父级是cat。发生的是bash cat <( sleep 100 & wait )在子shell中运行background命令。由于此子shell唯一要做的就是设置一些重定向,然后运行外部命令,因此该子shell被外部命令替换。以下是摘要:

  • 原始bash(12126)调用forkcat <( sleep 100 & wait )在子级(14247)中执行后台命令。
    • 子项(14247)调用pipe以创建管道,然后fork创建子项以运行流程替换sleep 100 & wait
      • 孙子(14248)调用在后台fork运行sleep 100。由于孙子不是交互式的,因此后台进程不会在单独的进程组中运行。然后,孙子等待sleep退出。
    • 子级(14247)调用setpgid(这是交互式外壳程序中的后台作业,因此它获得了自己的进程组),然后execve运行cat。(对于后台进程组中没有发生进程替换,我感到有些惊讶。)
  • 您杀死了孙子(14248)。它的父级正在运行cat,它对任何子进程一无所知,也没有业务调用wait。由于孙子的父母没有收割,因此孙子仍然是僵尸。
  • 最终,cat退出-是因为您杀死了它,还是因为sleep返回并关闭了管道,所以cat看到了输入的结尾。到那时,僵尸的父母死了,所以僵尸由init收集并由init收割。

如果将命令更改为

{ cat <( sleep 100 & wait ); echo done; } &

然后cat在单独的进程中运行,而不是在原始bash进程的子进程中运行:第一个孩子必须留在后面才能运行echo done。在这种情况下,如果您杀死了孙子,那么它就不会像僵尸一样留下来,因为那个孩子(当时仍在运行bash)会收割它。

另请参见linux如何处理僵尸进程僵尸可以有孤儿吗?收割僵尸会打扰孤儿吗?


我也对流程组感到惊讶。看来这是一个错误,现在已在bash master分支中修复。
PSkocik

“原始的打击正在等待它的孩子(14247)。” 为什么或以什么方式?该孩子应该在后台运行,并且没有显式调用。等待14247的原始bash(14246)和cat未等待14248(正在等待sleep)的14247(正在运行)有什么区别?是否有谁在等待谁的记忆,孩子(14247)失去了,原始的重击(14246)没有,或者像SIGCHLD这样的信号列表应与谁联系,而14247(现在正在运行bash)则不再订阅关于14248?

1
@tomas我的意思是原始bash调用wait其子级,即它获得了它。我可以看到这会造成混乱,我已经删除了按时间顺序在正确的时间点上没有的句子。某个进程已死亡的信息将传递给该进程的父级,一个进程无法“订阅”以接收有关某个其他进程的死亡的信息。
吉尔斯(Gilles)'所以

6

僵尸不在等孩子。相反,僵尸是已经死亡(由您自己决定或被杀死)进程,如其示例所示,它的代码,数据和堆栈已被释放,而现在仅包含其退出代码,等待其父级调用wait(2)以检索它进程(并最终从进程表中彻底清除进程条目)

在您的示例中,当睡眠结束(或被杀死)时,父级将读取退出状态,然后收割僵尸。有关wait(2)详细信息,请参见上面提到的内容。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.