Linux 为什么我必须等待子进程?

Linux 为什么我必须等待子进程?,linux,fork,Linux,Fork,尽管等待的linux手册页很好地解释了您需要wait()子进程才能将no变成僵尸,但它根本不说明原因 我计划了我的程序(这是我的第一个多线程程序,请原谅我的疏忽)围绕一个for(;)ever循环,该循环启动子进程,这些子进程被exec()ed带走,并且一定会自行终止 我不能使用wait(NULL),因为这使得并行计算变得不可能,因此我可能必须添加一个存储子PID的进程表,并且必须使用waitpid——不是立即使用,而是经过一段时间后使用——这是一个问题,因为孩子们的跑步时间从几微秒到几分钟不等。

尽管等待的linux手册页很好地解释了您需要
wait()
子进程才能将no变成僵尸,但它根本不说明原因

我计划了我的程序(这是我的第一个多线程程序,请原谅我的疏忽)围绕一个
for(;)
ever循环,该循环启动子进程,这些子进程被
exec()
ed带走,并且一定会自行终止

我不能使用
wait(NULL)
,因为这使得并行计算变得不可能,因此我可能必须添加一个存储子PID的进程表,并且必须使用
waitpid
——不是立即使用,而是经过一段时间后使用——这是一个问题,因为孩子们的跑步时间从几微秒到几分钟不等。如果我过早地使用
waitpid
,我的父进程将被阻塞,如果我使用得太晚,我将被僵尸淹没,无法再使用
fork()
,这不仅对我的进程有害,而且可能会在整个系统上造成意外问题

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

然而,问题是:

为什么Linux会保留僵尸?为什么我要等我的孩子?这是为了对父进程强制执行规程吗?在使用Linux的几十年中,我从来没有从僵尸进程中得到任何有用的东西,我不太明白僵尸作为一种“特性”的用处


如果答案是父进程需要找到一种方法来找出它们的子进程发生了什么,那么看在上帝的份上,没有理由将僵尸计算为正常进程,也没有理由仅仅因为僵尸太多就禁止创建非僵尸进程。在我目前为之开发的系统上,在一切都停止之前,我只能生成400到500个进程(这是一个维护糟糕的CentOS系统,运行在我能找到的最便宜的VServer上,但仍然有400个僵尸不到几kB的信息)

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

您的推理是反向的:内核保留僵尸,因为它们存储可以通过
wait()
和相关系统调用检索的状态


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

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

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

看看这本书。您可以使用
WNOHANG
选项告诉
waitpid
不要阻塞(即,如果没有要收割的子对象,则立即返回)。此外,您不需要给出
waitpid
PID。您可以指定
-1
,它将等待任何子级。因此,如下调用
waitpid
符合您的无阻塞约束和无保存PID约束:

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 );
}

虽然在进程表中保留死pid基本上是为了以后向其父进程提供其退出代码

我不得不抱怨那里有一些糟糕的设计(但已经成为历史,无法改变)

1.不能预先声明(pid)的
i\u don\u care\u status\u
在Windows操作系统上,我们有一个
close(processHandle)
来实现这个效果

HANDLE aProcessHandle = CreateProcess(.....);
CloseHandle( aProcessHandle )
要克服这一点,有一些非完美的方法(来自:

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

2.没有基于参考计数器的pid处理。 当进程死机时,如果没有对pid的引用,那么内核可以立即删除它

3.无法获取无关pid的退出代码 只有家长才能获得pid的退出代码,这太荒谬了。 没有可靠的方法等待不相关的pid

(使用NETLINK+PROC_连接器可以异步侦听任何pid的退出事件)

在Windows上,可以通过
WaitForSingleObject

HANDLE aProcessHandle = OpenProcess( pid... );
WaitForSingleObject(aProcessHandle, ...);
这些缺点显然是存在的,但是
HANDLE aProcessHandle = OpenProcess( pid... );
WaitForSingleObject(aProcessHandle, ...);