我找到了这个问题的正确解释on Geeks for Geeks:
fork() 系统调用将进程生成为生长二叉树的叶子。如果我们调用 fork() 两次,它将产生 22 = 4 个进程。所有这 4 个过程形成了二叉树的叶子子节点。一般来说,如果我们是第 l 级,并且无条件调用 fork(),我们将在第 (l+1) 级有 2l 个进程。相当于一棵二叉树在第(l+1)层的最大子节点数。
作为另一个例子,假设我们已经无条件地调用了 3 次 fork() 调用。我们可以使用具有 3 个级别的完整二叉树来表示生成的进程。在第 3 级,我们将有 23 = 8 个子节点,这对应于正在运行的进程数。
关于 C/C++ 逻辑运算符的说明:
逻辑运算符 && 比 || 具有更高的优先级,并且具有从左到右的关联性。左操作数执行后,将估计最终结果,右操作数的执行取决于左操作数的结果以及操作类型。
在 AND (&&) 的情况下,在对左操作数求值后,只有当左操作数的求值结果为非零时,才会对右操作数求值。在 OR (||) 的情况下,在对左操作数求值后,只有当左操作数求值为 0 时才会对右操作数求值。
fork()的返回值:
fork() 的手册页引用了以下关于返回值的摘录,
“成功时,父进程返回子进程的PID,子进程返回0。失败时,在父进程中返回-1,不创建子进程,并适当设置errno。”
PID 类似于进程的句柄,表示为无符号整数。我们可以得出结论,fork() 将在父项中返回非零值,在子项中返回零值。让我们分析一下程序。为了便于记号,如下所示标记每个 fork(),
#include <stdio.h>
int main()
{
fork(); /* A */
( fork() /* B */ &&
fork() /* C */ ) || /* B and C are grouped according to precedence */
fork(); /* D */
fork(); /* E */
printf("forked\n");
return 0;
}
前两个 fork() 调用是无条件调用的。
在级别 0,我们只有主进程。主程序(图中的 m)将创建子 C1,并且两者都将继续执行。孩子们按照他们被创造的升序编号。
在级别 1,我们有 m 和 C1 正在运行,并准备执行 fork() – B。(注意 B、C 和 D 被命名为 && 和 || 运算符的操作数)。初始表达式 B 将在该级别运行的每个子进程和父进程中执行。
在级别 2,由于 m 和 C1 执行了 fork() – B,我们将 m 和 C1 作为父级,将 C2 和 C3 作为子级。
fork() 的返回值 - B 在父节点中非零,在子节点中为零。由于第一个运算符是 &&,由于返回值为零,子 C2 和 C3 将不会执行下一个表达式 (fork()-C)。父进程 m 和 C1 将继续执行 fork() - C。子进程 C2 和 C3 将直接执行 fork() - D,以评估逻辑 OR 操作的值。
在第 3 级,我们将 m、C1、C2、C3 作为正在运行的进程,将 C4、C5 作为子进程。表达式现在简化为 ((B && C) || D),此时 (B && C) 的值是显而易见的。在父母中它是非零的,而在孩子中它是零。因此,父母知道整体 B && C 的结果 || D,将跳过执行 fork() – D。因为在孩子 (B && C) 评估为零时,他们将执行 fork() – D。我们应该注意,在级别 2 创建的孩子 C2 和 C3,也将运行 fork() - D 如上所述。
在第 4 级,我们将 m、C1、C2、C3、C4、C5 作为正在运行的进程,将 C6、C7、C8 和 C9 作为子进程。所有这些进程都无条件地执行 fork() - E,并产生一个子进程。
在第 5 级,我们将运行 20 个进程。该程序(在 Ubuntu Maverick,GCC 4.4.5 上)打印了 20 次“分叉”。一次由根父母(主要)和孩子休息。总共将产生 19 个进程。