子进程是否与其父进程一起死亡,是否有任何UNIX变体?


41

我已经研究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的系统?是否有任何系统可以使进程的死亡立即触发所有子进程的死亡?

Answers:


68

当进程退出时,它的所有子进程也会死亡(除非您使用NOHUP,在这种情况下,它们将返回到init)。

错了 完全错误。那个人说错了,或者将特殊情况与一般情况相混淆。

进程的死亡可以通过两种方式间接导致其子进程的死亡。它们与终端关闭时发生的情况有关。当终端消失时(从历史上讲,由于调制解调器挂断,串行线被切断,如今通常是由于用户关闭了终端仿真器窗口),SIGHUP信号被发送到在该终端运行的控制进程 -通常,初始外壳启动在那个终端。炮弹通常通过退出而对此做出反应。在退出之前,旨在交互使用的shell将HUP发送到它们启动的每个作业。

从外壳启动作业nohup会中断第二个HUP信号源,因为该作业将忽略该信号,因此在终端消失时不会被告知死亡。中断HUP信号从Shell到作业的传播的其他方法包括:使用Shell的disown内置命令(如果该作业有一个,则将其从Shell的作业列表中删除),以及双重派生(Shell会启动一个子级,然后再启动一个子级)本身并立即退出;外壳不知道其孙子)。

同样,在终端中启动的作业之所以死亡,并不是因为其父进程(shell)死亡,而是因为当告诉他们杀死它们时,其父进程决定要杀死它们。终端中的初始外壳死亡不是因为其父进程死亡,而是因为其终端消失了(这可能或可能不巧合,因为该终端由作为外壳父进程的终端仿真器提供)。


1
还有第三种方式,就是控制组。我所知道的是systemd使用它来执行彻底清除。
o11c 2014年

5
@JanHudec我想你混淆nohup使用disowndisown是某些Shell中的内置组件,可从作业表中删除正在运行的作业(采用像这样的参数%1),其主要有用的副作用是Shell不会将SIGHUP发送到子进程。nohup是一个外部实用程序,它将忽略SIGHUP(并执行其他一些操作),然后启动在其命令行上传递的命令。
吉尔(Gilles)'所以

@Gilles:不,我不是,但nohup实际上看strace 确实会exec在命令发出之前忽略该信号。
Jan Hudec 2014年

谢谢您的回答!我特别喜欢有关调制解调器挂断的一点历史背景。我开始担心我一直都在误解内核……
John WH Smith

3
还应注意,程序在收到SIGHUP时退出,因为SIGHUP的默认信号处理程序将退出。但是任何程序都可以实现其自己的SIGHUP处理程序,这可能导致它在父shell向其发送SIGHUP时不退出。
slebetman 2014年

12

当进程退出时,它的所有子进程也会死亡(除非您使用NOHUP,在这种情况下,它们将返回到init)。

如果该过程是会话负责人,则这是正确的。当会话主持人死亡时,会将SIGHUP发送给该会话的所有成员。实际上,这意味着它的孩子们及其后代。

进程通过调用使自己成为会话领导者setsid。炮弹使用这个。


我只是对此进行了测试,但无法重现您描述的行为...我产生了一个进程,为其赋予了一个新的会话(fork,杀死父进程,setsid),并将另一个孩子分叉到这个新的会话中。但是,当父进程(新会话负责人)死亡时,孩子没有收到SIGHUP,并且一直附着到init它最终退出。
约翰·史密斯

来自setpgid(2)联机帮助页:如果会话具有控制终端,并且未设置该终端的CLOCAL标志,并且发生了终端挂断,则向会话负责人发送SIGHUP。如果会话负责人退出,则还将SIGHUP信号发送到控制终端的前台进程组中的每个进程。
ninjalj 2014年

1
@ninjalj然后,我猜想,当且仅当会话领导者也是终端的控制过程时,此答案才有效。独立于任何终端的标准会议负责人都不会杀死其子级。这是正确的吗?
约翰·史密斯

-1

因此,以上张贴者所说的是,孩子们不会死,父母会杀死他们(或向他们发送终止他们的信号)。因此,如果您对父级进行编程,以便(1)保留其所有子级的记录,以及(2)向其所有子级发送信号,您就可以提出自己的要求。

这就是Shell所做的,也应该是您的父进程所做的。可能有必要在父母中捕获HUP信号,这样您仍然有足够的控制权杀死孩子。


许多原因会使父母丧命。例如,如果父母是SIGKILLed,则无法阻止孩子的生存。
EmilJeřábek在2014年

-1

我在与垂死的父母稍有关系的答案中遗漏了一点:当进程在不再有读取进程的管道上写入时,它将得到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


2
但是,从管道读取的进程不一定是(甚至通常是)其父进程。因此,这实际上与问题所要解决的情况截然不同。
Barmar 2014年
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.