#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 可见的东西?我认为只有 parent 知道它的 child 什么时候死。但是在这里,我看到不知何故, child 会注意到当它被收养时,会发生一些事情并以某种方式崩溃。当他们的父进程也死亡时,子进程会得到通知吗?
当我运行这段代码时,我在崩溃后得到这个输出:
Vim: Error reading input, exiting...
Vim: preserving files...
Vim: Finished.
最佳答案
这实际上是因为 shell 正在执行 fork Vim 的二进制文件!
当 shell 运行一个前台命令时,它会创建一个新的进程组并使其成为连接到 shell 的终端的前台进程组。在 bash 5.0 中,你可以在 give_terminal_to()
中找到转移这个职责的代码,它使用 tcsetpgrp()
来设置前台进程组。
需要正确设置终端的前台进程组,以便前台运行的程序可以从终端获取信号(例如Ctrl+C发送中断信号,Ctrl+Z发送终端停止信号暂停进程) process),并以 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
选项运行以跟随 fork,所以它也将在启动时开始跟踪 Vim。 shell 将执行 strace
(不是 vim-launcher
),所以它的前台管道只会在 strace
停止运行时结束。而且 strace
不会停止运行,直到 Vim 退出。 Vim 将在 5 秒后正常工作,即使它已重新设置为 init。曾经还有一个
fghack
tool ,它是 daemontools 的一部分,它完成了相同的阻塞任务,直到所有 fork 的 child 都退出。它将通过创建一个新管道并使其产生的进程继承管道来实现这一点,这种方式将自动由所有其他 fork 子进程继承。这样,它可以阻塞,直到该管道文件描述符的所有副本都关闭,这通常只在所有进程退出时发生(除非后台进程不惜一切代价关闭所有继承的文件描述符,但这实际上表明它们没有) t 想要被跟踪,到那时他们很可能已经放弃了对终端的访问权限。)
关于c - 为什么vim变成孤儿进程会崩溃?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63502153/