我研究 Linux 内核行为已经有一段时间了,我一直很清楚:
当一个进程死亡时,它的所有子进程都会返回给该
init
进程(PID 1),直到它们最终死亡。
然而,最近,一个比我更有内核经验的人告诉我:
当一个进程退出时,它的所有子进程也会死亡(除非您使用
NOHUP
在这种情况下它们会返回init
)。
现在,尽管我不相信这一点,但我仍然编写了一个简单的程序来确保这一点。我知道我不应该依赖时间(sleep
)进行测试,因为它完全取决于进程调度,但对于这个简单的情况,我认为这已经足够了。
int main(void){
printf("Father process spawned (%d).\n", getpid());
sleep(5);
if(fork() == 0){
printf("Child process spawned (%d => %d).\n", getppid(), getpid());
sleep(15);
printf("Child process exiting (%d => %d).\n", getppid(), getpid());
exit(0);
}
sleep(5);
printf(stdout, "Father process exiting (%d).\n", getpid());
return EXIT_SUCCESS;
}
这是程序的输出,以及ps
每次printf
对话的相关结果:
$ ./test &
Father process spawned (435).
$ ps -ef | grep test
myuser 435 392 tty1 ./test
Child process spawned (435 => 436).
$ ps -ef | grep test
myuser 435 392 tty1 ./test
myuser 436 435 tty1 ./test
Father process exiting (435).
$ ps -ef | grep test
myuser 436 1 tty1 ./test
Child process exiting (436).
现在,正如您所看到的,这与我的预期完全一致。孤儿进程(436)被返回给init
(1),直到它死亡。
但是,是否有任何基于 UNIX 的系统默认情况下不应用此行为?是否存在任何系统,进程的死亡会立即触发其所有子进程的死亡?
答案1
当一个进程退出时,它的所有子进程也会死亡(除非您使用 NOHUP 在这种情况下它们会返回到 init)。
这是错误的。大错特错了。说这句话的人要么是错误的,要么是将特定情况与一般情况混淆了。
进程的死亡有两种方式间接地导致其子女死亡。它们与终端关闭时发生的情况有关。当终端消失时(历史上是因为串行线路由于调制解调器挂断而被切断,现在通常是因为用户关闭了终端仿真器窗口),会出现 SIGHUP 信号已发送到控制过程在该终端中运行 - 通常,初始 shell 在该终端中启动。 Shell 通常会通过退出来对此作出反应。在退出之前,用于交互式使用的 shell 会将 HUP 发送到它们启动的每个作业。
从 shell 启动作业会nohup
破坏 HUP 信号的第二个源,因为作业将忽略该信号,因此不会在终端消失时被告知终止。中断 HUP 信号从 shell 到作业的传播的其他方法包括使用 shell 的disown
内置函数(如果有的话)(该作业从 shell 的作业列表中删除),以及双分叉(shell 启动一个子进程,该子进程又启动一个子进程)它自己的并立即退出;shell 不知道它的孙子)。
同样,在终端中启动的作业死亡不是因为它们的父进程(shell)死亡,而是因为它们的父进程在被告知杀死它们时决定杀死它们。终端中的初始 shell 死亡不是因为其父进程死亡,而是因为其终端消失(这可能是巧合,也可能不是巧合,因为终端是由 shell 的父进程的终端仿真器提供的)。
答案2
当一个进程退出时,它的所有子进程也会死亡(除非您使用 NOHUP 在这种情况下它们会返回到 init)。
如果进程是会话领导者,则这是正确的。当会话领导者死亡时,会向该会话的所有成员发送 SIGHUP。实际上,这意味着它的孩子及其后代。
进程通过调用使自己成为会话领导者setsid
。贝壳就用这个。
答案3
我在答案中遗漏了一点与垂死的父母略有相关的内容:当一个进程在不再有读取进程的管道上写入时,它会收到一个 SIGPIPE。 SIGPIPE 的标准操作是终止。
这确实会导致进程死亡。事实上,这是程序yes
终止的标准方式。
如果我执行
(yes;echo $? >&2)|head -10
在我的系统上,答案是
y
y
y
y
y
y
y
y
y
y
141
141 确实是 128+SIGPIPE:
SIGPIPE 13 Term Broken pipe: write to pipe with no
readers
从man 7 signal
。
答案4
所以上面的海报所说的是,孩子们不会死,父母会杀死他们(或向他们发送终止信号)。因此,如果您对父级进行编程以(1)保留其所有子级的记录,并且(2)向其所有子级发送信号,那么您就可以得到您所要求的。
这就是 Shell 所做的事情,也应该是您的父进程所做的事情。可能有必要捕获父级中的 HUP 信号,以便您仍然有足够的控制权来杀死子级。