分叉和等待 - 如何等待所有孙子进程完成

6
我正在做一个构建简单shell的任务,并且尝试添加一些目前不需要的功能,但是在使用管道时遇到了问题。
一旦我的命令被解析,我就会派生一个进程来执行它们。这个进程是一个子程序,如果只剩下一个命令,它将执行该命令,否则它将分叉。父进程将执行第一个命令,子进程将处理其余部分。管道被设置并正常工作。
然后,我的主进程调用wait(),然后输出提示符。当我执行像ls -la | cat这样的命令时,提示符会在cat的输出之前打印出来。
我尝试为应执行的每个命令调用wait()一次,但第一次调用有效,所有后续调用都返回ECHILD
如何强制我的主线程等待所有子进程(包括子进程的子进程)退出?
3个回答

7
你做不到。要么让你的子进程等待它的子进程并在它们被等待完之前不退出,要么从同一个进程fork所有子进程。

1
好的,我明白bash只是从主进程分叉出所有子进程。这会使管道设置变得更加复杂,但应该可以工作。 - Dan

5
请看这个答案如何使用wait()等待子进程:如何等待fork()调用的所有子进程完成? 无法等待孙子进程;您需要在每个进程中实现等待逻辑。这样,每个子进程只有在其所有子代进程退出后才会退出(这将包括所有孙代递归退出)。

好的 - 但是对于一个shell来说,每个子进程都会调用execvp或类似的函数 - 那么我该如何让它等待该进程的子进程呢? - Dan
1
正如我所说:“你不能等待孙子辈”。所有正常的shell都会自行分割管道,创建所有子进程和它们之间的管道,然后等待每个子进程。 - Aaron Digulla

0

由于您正在谈论孙子,显然您是以级联方式生成子进程的。这是实现管道的一种可能方式。

但请记住,从管道返回的值(在终端中执行echo $?时获得的值)是从最右边的命令返回的值。

这意味着您需要从右到左生成子进程来实现级联。您不想失去那个返回值。

现在假设我们只谈论内置命令,为了简单起见(没有额外调用fork()和execve()),有趣的事实是,在某些shell(如“zsh”)中,最右边的命令甚至没有被分叉。我们可以通过一个简单的管道命令来看到这一点:

export stack=OVERFLOW | export overflow=STACK

使用命令env,我们可以看到环境变量中的overflow=STACK是持久的。这表明右侧的命令没有在子shell中执行,而export stack=OVERFLOW是在子shell中执行的。

注意:在像“sh”这样的shell中不是这种情况。

现在让我们使用基本的管道命令来为此级联实现提供可能的逻辑。

cat /dev/random | head

注意:尽管cat /dev/random被认为是一个永无止境的命令,但它会在命令head读取cat /dev/random输出的第一行后停止。这是因为当head完成时,stdin被关闭,而命令cat /dev/random中止,因为它正在写入一个破损的管道。
逻辑:
  1. 父进程(您的shell)发现有一个管道需要执行。然后它将分叉成两个进程。父进程保持为您的shell,等待子进程返回并存储返回值。

  2. 在第一代子进程的上下文中:(尝试执行管道的最右边的命令) 它看到该命令不是最后一个命令,它将再次fork()(我称之为“级联实现”)。 现在分叉完成后,父进程首先要执行其任务(head -1),然后关闭其stdin和stdout,然后等待其子进程。首先关闭stdin和stdout非常重要,然后调用wait()。关闭stdout会向父进程发送EOF,如果在stdin上读取。关闭stdin可以确保试图写入管道的孙子进程中止,并出现“破损的管道”错误。

  3. 在孙子进程的上下文中: 它看到它是管道的最后一个命令,它只需执行该命令并返回其值(它关闭stdin和stdout)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接