c语言 - 为什么 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);
}
}

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

为什么成为孤儿过程在这里成为一个问题?是特定于 vim 的东西吗?

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

当我运行此代码时,我在崩溃后得到以下输出:

Vim: Error reading input, exiting...
Vim: preserving files...
Vim: Finished.

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

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

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

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

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

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

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

$ strace -f -o /tmp/vim-launcher.strace ./vim-launcher

由于strace运行时-f选项可以跟踪分叉,因此它也将在启动时开始跟踪 Vim。shell 将执行strace(而不是vim-launcher),因此其前台管道仅在strace停止运行时结束。在 Vim 退出之前,strace不会停止运行。Vim 在 5 秒后工作得很好,即使它已被重新设置为初始化。

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

最新更新