为什么我必须为子进程“等待()”

  • 本文关键字:等待 子进程 linux fork
  • 更新时间 :
  • 英文 :


尽管等待 1 的 Linux 手册页很好地解释了您需要为子进程wait(),以便它们不会变成僵尸,但它根本没有说明原因。

我计划我的程序(这是我的第一个多线程程序,所以请原谅我的天真(围绕一个for(;;)循环启动子进程,这些子进程exec()消失并且肯定会自行终止。

我不能使用wait(NULL),因为这会使并行计算变得不可能,因此我可能不得不添加一个存储子 pid 的处理表,并且必须使用 waitpid - 不是立即使用,而是在一段时间后 - 这是一个问题,因为子项的运行时间从几微秒到几分钟不等。如果我过早使用waitpid,我的父进程就会被阻塞,当我使用得太晚时,我会被僵尸淹没,无法再fork(),这不仅对我的进程不利,而且会导致整个系统出现意想不到的问题。

我可能必须编写一些逻辑来使用一些最大数量的子项,并在达到该数量时阻止父项 - 但这应该是不必要的,因为大多数子项会很快终止。我能想到的另一个解决方案(创建一个两层父进程,生成并发子进程,进而同时生成和wait孙子(现在对我来说太复杂了。可能我还可以找到一个非阻塞函数来检查孩子并仅在它们终止时使用waitpid

然而问题:

为什么Linux会保留僵尸?为什么我必须等待我的孩子?这是为了对父流程强制执行纪律吗?在使用Linux的几十年里,我从来没有从僵尸进程中得到任何有用的东西,我不太明白僵尸作为一个"功能"的有用性。

如果答案是父进程需要有一种方法来找出发生在他们的孩子身上的事情,那么看在上帝的份上,没有理由仅仅因为僵尸太多而将僵尸算作正常进程并禁止创建非僵尸进程。在我目前正在开发的系统上,我只能生成 400 到 500 个进程,然后一切都停止(这是一个维护不善的 CentOS 系统,运行在我能找到的最便宜的 VServer 上 - 但仍然 400 个僵尸不到几 KB 的信息(

我可能必须添加一个存储子 pid 的流程表 并且必须使用waitpid - 不是立即使用,而是一段时间后 通过 - 这是一个问题,因为孩子们的运行时间 从几微秒到几分钟不等。如果我也使用waitpid 早点,我的父进程将被阻止

查看文档以了解waitpid。您可以使用WNOHANG选项告诉waitpid不阻止(即,如果没有孩子可以收获,则立即返回(。 此外,您无需给waitpid PID。 您可以指定 -1 ,它将等待任何子项。 因此,按如下所示调用waitpid符合您的无阻塞约束和无保存 pids 约束:

waitpid( -1, &status, WNOHANG );

如果你真的不想正确处理流程创建,那么你可以通过分叉两次、收割子项并将exec交给孙子项来将收获责任交给init

pid_t temp_pid, child_pid;
temp_pid = fork();
if( temp_pid == 0 ){
    child_pid = fork();
    if( child_pid == 0 ){
        // exec()
        error( EXIT_FAILURE, errno, "failed to exec :(" );
    } else if( child_pid < 0 ){
        error( EXIT_FAILURE, errno, "failed to fork :(" );
    }
    exit( EXIT_SUCCESS );
} else if( temp_pid < 0 ){
    error( EXIT_FAILURE, errno, "failed to fork :(" );
} else {
    wait( temp_pid );
}

在上面的代码片段中,子进程分叉自己的子进程,立即存在,然后立即被父进程收割。 孙子是孤儿,被init收养,并将自动收获。

为什么Linux会保留僵尸?为什么我必须等待我的 孩子?这是为了对父流程强制执行纪律吗?在 使用Linux的几十年,我从来没有从僵尸中得到任何有用的东西 过程中,我不太明白僵尸作为"功能"的用处。 如果答案是父进程需要有办法找出答案 他们的孩子发生了什么,那么看在上帝的份上,没有 将僵尸视为正常进程并禁止创建 非僵尸进程只是因为僵尸太多。

您还建议如何有效地检索进程的退出代码? 问题是PID<=>退出代码(等(的映射必须是一对一的。 如果内核在进程退出后立即释放了该进程的 PID,无论是否收获,然后一个新进程继承了相同的 PID 并退出,您将如何处理为一个 PID 存储两个代码? 感兴趣的进程如何检索第一个进程的退出代码? 不要仅仅因为您不关心就认为没有人关心退出代码。 您认为是滋扰/错误的东西被广泛认为是有用和干净的。

在我目前正在开发的系统上,我只能生成 400 到 500 个 一切都停止之前的进程(这是一个维护不善的问题 CentOS 系统运行在我能找到的最便宜的 VServer 上 - 但仍然 400个僵尸不到几kB的信息(

让一个被广泛接受的内核行为成为替罪羊,因为显然是对一个维护不善/廉价的系统感到沮丧,这似乎是不对的。

通常,最大进程数仅受内存限制。 您可以通过以下方式查看您的限制:

cat /proc/sys/kernel/threads-max

你的推理是倒退的:内核保留僵尸是因为它们存储了你可以通过wait()和相关系统调用检索的状态。

处理异步子终止的正确方法是使用一个SIGCHLD处理程序,该处理程序执行清理子进程wait()

当程序退出时,它会向内核返回返回代码。僵尸进程只是一个保存返回代码的地方,直到父进程可以获取它。wait()调用让内核知道不再需要该 pid 的返回代码,并且僵尸被删除。

虽然在

进程表中保留死 pid 基本上是为了稍后将其退出代码提供给其父级,

我不得不抱怨那里有一些糟糕的设计(但已经成为历史并且不可更改(。

1.不能预先声明i_don_care_status_of( pid )

在Windows操作系统上,我们有一个close( processHandle )来实现这种效果。

HANDLE aProcessHandle = CreateProcess(.....);
CloseHandle( aProcessHandle )

为了克服这个问题,有一些不完美的方法(来自Wiki(:

在现代类 UNIX 系统上(在这方面符合 SUSv3 规范(,以下特殊情况适用:如果父级通过将其处理程序设置为 SIG_IGN(而不是默认忽略信号(来显式忽略 SIGCHLD,或者设置了 SA_NOCLDWAIT 标志,则所有子退出状态信息都将被丢弃,并且不会留下僵尸进程。[1]

2. 没有基于参考计数器的PID处理。

当一个进程死了,如果没有对pid的引用,那么内核可以立即删除它。

3.无法获取不相关pid的退出代码

只有父级才能获得pid的退出代码,这太荒谬了。没有可靠的方法来等待不相关的 pid。

(使用 NETLINK + PROC_CONNECTOR可以异步侦听任何 pid 的退出事件(。

在Windows上,可以通过WaitForSingleObject来完成

HANDLE aProcessHandle = OpenProcess( pid... );
WaitForSingleObject(aProcessHandle, ...);

这些缺点显然是存在的,但是Unix/Linux的设计非常简单,所以我们必须裸露它。

为了向您提供进程的"退出代码",系统应为您保留"进程数据库"。这种只有一个退出代码的数据库称为"僵尸"。您可以使用单独的进程,该进程将定期查询"僵尸进程"以获取其"退出代码",从而有效地释放此内存。Windows和其他操作系统也是如此。Linux在这里并不特别。您无需等待进程,只需在进程完成后询问其"退出代码"即可。

最新更新