为什么vim变成孤儿进程会崩溃?

Sta*_*123 6 c linux vim fork exec

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int pid = fork();
    
    if (pid) {
        sleep(5);
        // wait(NULL); // works fine when waited for it.
    } else {
        execlp("vim", "vim", (char *)NULL);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我运行这段代码时,vim 运行正常,然后在 5 秒后崩溃(即当它的父退出时)。当我等待它(即不让它成为孤儿进程)时,代码完全正常。

为什么成为孤儿进程在这里成为一个问题?它是vim特有的吗?

为什么这甚至是 vim 可见的东西?我认为只有父母知道它的孩子什么时候死。但是在这里,我看到不知何故,孩子会注意到当它被收养时,会发生一些事情并以某种方式崩溃。当他们的父进程也死亡时,子进程会得到通知吗?

当我运行这段代码时,我在崩溃后得到这个输出:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int pid = fork();
    
    if (pid) {
        sleep(5);
        // wait(NULL); // works fine when waited for it.
    } else {
        execlp("vim", "vim", (char *)NULL);
    }
}
Run Code Online (Sandbox Code Playgroud)

fil*_*den 3

这实际上是因为shell正在执行 forks Vim 的二进制文件!

当 shell 运行前台命令时,它会创建一个新的进程组,并使其成为附加到 shell 的终端的前台进程组。在 bash 5.0 中,您可以在 中找到转移此职责的代码give_terminal_to(),该代码用于tcsetpgrp()设置前台进程组。

需要正确设置终端的前台进程组,以便前台运行的程序能够从终端获取信号(例如Ctrl+C发送中断信号、Ctrl+Z发送终端停止信号暂停进程)进程),并以全屏程序(例如 Vim)通常执行的方式更改终端设置。(前台进程组的主题有点超出了这个问题的范围,只是在这里提到它,因为它在响应中起作用。)

当 shell 执行的进程(更准确地说是管道)终止时,shell 将通过使用shell的进程组give_terminal_to()调用相同的代码来收回前台进程组。

这通常很好,因为在执行的管道完成时,该进程组上通常没有进程,或者如果有的话,它们通常不会保留在终端上(例如,如果您正在启动来自 shell 的后台守护进程,该守护进程通常会关闭 stdin/stdout/stderr 流以放弃对终端的访问。)

但您提出的设置实际上并非如此,Vim 仍然附加到终端和前台进程组的一部分。当父进程退出时,shell 假定管道已完成,并将前台进程组设置回自身,从 Vim 所在的前前台进程组中“窃取”它。因此,下次 Vim 尝试从终端读取时,读取将失败,Vim 将退出并显示您报告的消息。

亲自查看父进程退出不会影响 Vim 本身的一种方法运行它strace。例如,使用以下命令(假设./vim-launcher是您的二进制文件):

$ strace -f -o /tmp/vim-launcher.strace ./vim-launcher
Run Code Online (Sandbox Code Playgroud)

由于strace运行时可以选择-f跟踪分叉,因此它在启动时也会开始跟踪 Vim。shell 将正在执行strace(而不是vim-launcher),因此其前台管道仅在strace停止运行时才会结束。并且strace不会停止运行直到Vim退出。Vim 在 5 秒后仍能正常工作,即使它已被重新设置为 init。

曾经还有一个fghack工具(daemontools 的一部分),它完成了相同的阻塞任务,直到所有分叉的子进程退出为止。它将通过创建一个新管道并让其生成的进程继承该管道来实现这一点,以一种自动被所有其他分叉子进程继承的方式。这样,它可能会阻塞,直到该管道文件描述符的所有副本都关闭为止,这通常仅在所有进程退出时才会发生(除非后台进程不遗余力地关闭所有继承的文件描述符,但这本质上表明它们不这样做)不想被跟踪,到那时他们很可能已经放弃了对终端的访问。)