为什么我必须为子进程`wait()`?

时间:2011-12-29 08:46:40

标签: linux fork

即使wait 1的linux手册页很好地解释了你需要wait()为他们的子进程而不是变成僵尸,但它根本没有说明原因。

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

我不能使用wait(NULL),因为这使得并行计算变得不可能,因此我可能不得不添加一个存储子pids的进程表,并且必须使用waitpid - 不是无聊的,而是在一些之后时间已过 - 这是一个问题,因为孩子们的运行时间从几微秒到几分钟不等。如果我过早使用waitpid,我的父进程将被阻止,当我太晚使用它时,我会被僵尸淹没而不能再fork(),这不仅对我的进程有害,而且可以导致整个系统出现意外问题。

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

然而问题是:

为什么Linux会保留僵尸?为什么我要等我的孩子?这是否对父进程强制执行纪律?在使用Linux的几十年里,我从来没有从僵尸进程中获得任何有用的东西,我不太了解僵尸作为“特征”的用处。

如果答案是父进程需要有办法找出他们孩子的遭遇,那么就上帝而言,没有理由将僵尸视为正常进程,并禁止创建非僵尸进程,因为那里僵尸太多了。在我正在开发的系统上,我只能生成400到500个进程才能停止一切(这是一个维护得很差的CentOS系统,运行在我能找到的最便宜的VServer上 - 但仍有400个僵尸不到几KB的信息)

5 个答案:

答案 0 :(得分:9)

  

我可能需要添加一个存储子pids的进程表   并且必须使用waitpid - 不是无聊的,但过了一段时间了   通过 - 这是一个问题,因为孩子的运行时间   从几微秒到几分钟不等。如果我也使用waitpid   早,我的父进程将被阻止

查看documentation for waitpid。您可以使用waitpid选项告诉WNOHANG不阻止(即,如果没有孩子需要立即返回)。而且,您不需要给waitpid一个PID。您可以指定-1,它将等待任何子项。所以调用waitpid如下所示适合你的无阻塞约束和no-saving-pids约束:

waitpid( -1, &status, WNOHANG );

如果您确实不想正确处理流程创建,那么您可以通过分叉两次,收割孩子并提供{{1}来将收割责任交给init到孙子:

exec

在上面的代码片段中,子进程分叉自己的子进程,立即存在,然后由父进程立即获取。孙子是孤儿,由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 ); } 采用,并将自动获得。

  

为什么Linux会保留僵尸?为什么我要等我的   孩子?这是否对父进程强制执行纪律?在   几十年来使用Linux我从来没有从僵尸中得到任何有用的东西   过程中,我不太了解僵尸作为“特征”的用处。   如果答案是父进程需要找到方法   他们的孩子发生了什么事,然后为了上帝的缘故,没有   将僵尸视为正常过程并禁止创建的原因   非僵尸进程只是因为有太多的僵尸。

您如何建议可以有效地检索流程的退出代码?问题是PID&lt; =&gt;的映射。退出代码(等)必须是一对一的。如果内核在退出时立即释放了进程的PID,是否收到了,然后新进程继承了相同的PID并退出,您将如何处理为一个PID存储两个代码?感兴趣的进程如何检索第一个进程的退出代码?不要认为没有人只关心退出代码,因为你没有。您认为是一种令人讨厌的错误/错误被广泛认为是有用和干净的。

  

在我正在开发的系统上,我只能生成400到500个   在一切都停止之前的过程(这是一个严重维护   在我能找到的最便宜的VServer上运行的CentOS系统 - 但仍然如此   400个僵尸不到几KB的信息)

关于使一个被广泛接受的内核行为成为明显受到严重维护/廉价系统挫折的替罪羊的事情似乎并不合适。

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

init

答案 1 :(得分:3)

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

虽然在进程表中保留死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的设计非常简单,所以我们不得不裸露它。

答案 4 :(得分:0)

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