即使wait 1的linux手册页很好地解释了你需要wait()
为他们的子进程而不是变成僵尸,但它根本没有说明原因。
我计划了我的程序(这是我的第一个多线程程序,所以请原谅我的天真)围绕一个for(;;)
循环启动子进程,exec()
被删除并且肯定会自行终止
我不能使用wait(NULL)
,因为这使得并行计算变得不可能,因此我可能不得不添加一个存储子pids的进程表,并且必须使用waitpid
- 不是无聊的,而是在一些之后时间已过 - 这是一个问题,因为孩子们的运行时间从几微秒到几分钟不等。如果我过早使用waitpid
,我的父进程将被阻止,当我太晚使用它时,我会被僵尸淹没而不能再fork()
,这不仅对我的进程有害,而且可以导致整个系统出现意外问题。
我可能必须编写一些使用一些最大数量的子节点的逻辑,并在达到该数量时阻止父节点 - 但这不应该是必要的,因为大多数子节点很快终止。我能想到的另一个解决方案(创建一个生成并发子进程的双层父进程,而这些进程同时生成并为孙子进行生成wait
)对我来说太复杂了。可能我还可以找到一个非阻塞功能来检查孩子,并且只有在他们终止时才使用waitpid
。
然而问题是:
为什么Linux会保留僵尸?为什么我要等我的孩子?这是否对父进程强制执行纪律?在使用Linux的几十年里,我从来没有从僵尸进程中获得任何有用的东西,我不太了解僵尸作为“特征”的用处。
如果答案是父进程需要有办法找出他们孩子的遭遇,那么就上帝而言,没有理由将僵尸视为正常进程,并禁止创建非僵尸进程,因为那里僵尸太多了。在我正在开发的系统上,我只能生成400到500个进程才能停止一切(这是一个维护得很差的CentOS系统,运行在我能找到的最便宜的VServer上 - 但仍有400个僵尸不到几KB的信息)
答案 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基本上是为了稍后将其退出代码提供给其父进程,
我不得不抱怨那里有一些糟糕的设计(但已经成为历史并且不可改变)。
i_don_care_status_of( pid )
在Windows操作系统上,我们有close( processHandle )
来实现此效果。
HANDLE aProcessHandle = CreateProcess(.....);
CloseHandle( aProcessHandle )
为了克服这个问题,有一些非完美的方法(来自Wiki):
在类似现代UNIX的系统上(在这方面符合SUSv3规范),以下特殊情况适用:如果父通过将其处理程序设置为SIG_IGN(而不是默认忽略信号)显式忽略SIGCHLD,或者设置SA_NOCLDWAIT标志后,将丢弃所有子退出状态信息,不会留下任何僵尸进程。[1]
当一个进程死了,如果没有对pid的引用,那么内核可以立即删除它。
只有父级才能获得pid的退出代码,这太荒谬了。 没有可靠的方式来等待无关的pid。
(使用NETLINK + PROC_CONNECTOR可以异步监听任何pid 的退出事件。)
在Windows上,可以通过WaitForSingleObject
HANDLE aProcessHandle = OpenProcess( pid... );
WaitForSingleObject(aProcessHandle, ...);
这些缺点显然存在,但Unix / Linux的设计非常简单,所以我们不得不裸露它。
答案 4 :(得分:0)
为了向您提供流程的“exitcode”,系统应该为您保留“流程数据库”。只有退出代码的这种数据库称为“僵尸”。您可以使用单独的进程来定期查询“僵尸进程”以获取其“exitcode”,从而有效地释放此内存。 Windows和其他操作系统也是如此。 Linux在这里并不特别。您无需等待进程,只需在进程完成后询问其“退出代码”即可。