C语言中的fork()系统调用

3
    #include <stdio.h>
    #include <unistd.h>
    int main()
    {
       fork();
       fork() && fork() || fork();
       fork();

     printf("forked\n");
     return 0;
    }

如何计算程序执行后产生的进程数量,这可能会让人感到困惑。请帮我找出答案。

平台--UBUNTU 10.04


5
为什么不先编译并运行它,看看你会得到多少个“forked”消息? - Paul R
5
那是作业吗?没有理智的人会写 fork() && fork() || fork()。你可以分开这一行并添加更多的调试输出。 - ThiefMaster
1
我已经编译了很多次,每次都得到不同的答案... 在这里看看 http://ideone.com/CXlkR - Anil Arya
我猜这是一种“面试题”。真正的问题是在复合行中会发生什么。我模糊地记得,如果成功,fork()将返回TRUE,并且几乎总是成功,除非您达到了进程的分叉限制,因此第二行的前两个分叉调用将执行,但不是第三个,由于短路。[啊,现在我记起来了!——fork对于一个进程返回true,对于另一个进程返回false,并且您将在两个进程中都从上次离开的地方继续。可能结果将是一种分形扩展,也许难以预测。] - Hot Licks
是的,但我在想(模糊的回忆可以追溯到几十年前),当主进程触发“退出”时,它会杀死所有其他进程。因此,实际打印出来的内容是不确定的。 - Hot Licks
显示剩余3条评论
5个回答

10

让我们跟随 fork 树,假设没有一个分支失败

fork();

现在我们有两个进程,目前谁是子进程,谁是父进程并不重要,称它们为 p1 和 p2

fork()

这两个进程都生成了另一个子进程,所以我们有了四个进程,对于其中的两个进程 (p3 和 p4),结果是零,而对于另外两个进程 (p1 和 p2),结果是非零

   && fork()
p1和p2再次进行分叉,得到p5和p6,总共有六个进程。对于p1和p2,&&的值为真,因此它们在这一行不会再次分叉。对于p3、p4、p5、p6,&&的值为假,因此它们会进行分叉。
              || fork();

在这里,生成四个新进程,总共有6 + 4 = 10个。

fork();

每个10个进程都再次分叉,总共变成20个。


3

不应该像这样使用fork()。绝对不要这样做。然而,在现实生活中,你也不需要这样做。

如何正确使用:

int main() {
    /* code */
    pid_t pid = fork();
    if (pid < 0) {
        /* error, no child process spawned */
    }
    if (pid > 0) {
        /* we are the parent process, pid is the process ID of the ONE child process spawned */
    }
    /* else, we are the child process, running exactly one command later the fork() was called in the parent. */
    /* some more code */
    return 0;
}

你可能是对的,但这并没有真正回答问题。 - Paul R
嘿,别那么快就点踩啊!你显然没看过我的回答。在评论里,已经列出了执行或未执行子进程的编号。我是确实想回答他的问题的。 - user529758

3
fork();

fork系统调用返回一个整数:在父进程中是子进程的PID,在子进程中为0。如果发生错误,则不创建进程并返回-1。

||&&是逻辑运算符。

如果在评估左操作数后已知运算符的结果,则必须短路(即不评估右操作数):

  • 对于||运算符,如果左操作数!= 0,则不评估其右操作数
  • 对于&&运算符,如果左操作数== 0,则不评估其右操作数

1

保存文件,命名为fork-count.c。然后使用gcc fork-count.c -o fork-count进行编译。接着你可以运行它,并用./fork-count | wc -l来计算输出的行数。


我已经编译了很多次,但是结果却不同。 - Anil Arya
你的意思是说可以连续运行程序并获得不同的结果吗? - Michael Mior
如果你使用ideone,你会得到不可靠的结果,因为它不能正确地捕获分叉进程的输出。 - Michael Mior

0

我已经在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(||)的情况下,在评估左操作数后,仅当左操作数评估为零时才评估右操作数。

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,以计算逻辑或操作的值。

在第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次“forked”。其中一次是由根父进程(主进程)执行,其余的是由子进程执行。总共将产生19个子进程。

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