fork() 和 wait() 调用

3
我有一个与以下代码相关的问题。这是在这个页面上找到的一个示例,而不是我的代码。
父进程分叉出两个子进程,每个子进程计数到200然后退出。我不理解的是为什么孩子们不会在他们被分叉后立即打印他们的消息,而是允许他们的父亲进入等待状态?此外,等待系统调用如何返回先完成的子进程的pid? pid = wait(&status);
#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  ChildProcess(char [], char []);    /* child process prototype  */

void  main(void)
{
     pid_t   pid1, pid2, pid;
     int     status;
     int     i;
     char    buf[BUF_SIZE];

     printf("*** Parent is about to fork process 1 ***\n");
     if ((pid1 = fork()) < 0) {
          printf("Failed to fork process 1\n");
          exit(1);
     }
     else if (pid1 == 0) 
          ChildProcess("First", "   ");

     printf("*** Parent is about to fork process 2 ***\n");
     if ((pid2 = fork()) < 0) {
          printf("Failed to fork process 2\n");
          exit(1);
     }
     else if (pid2 == 0) 
          ChildProcess("Second", "      ");

     sprintf(buf, "*** Parent enters waiting status .....\n");
     write(1, buf, strlen(buf));
     pid = wait(&status);
     sprintf(buf, "*** Parent detects process %d was done ***\n", pid);
     write(1, buf, strlen(buf));
     pid = wait(&status);
     printf("*** Parent detects process %d is done ***\n", pid);
     printf("*** Parent exits ***\n");
     exit(0);
}

void  ChildProcess(char *number, char *space)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     pid = getpid();
     sprintf(buf, "%s%s child process starts (pid = %d)\n", 
             space, number, pid);
     write(1, buf, strlen(buf));
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "%s%s child's output, value = %d\n", space, number, i); 
          write(1, buf, strlen(buf));
     }
     sprintf(buf, "%s%s child (pid = %d) is about to exit\n", 
             space, number, pid);
     write(1, buf, strlen(buf));     
     exit(0);
}

输出MAX_COUNT为40的结果。

*** Parent is about to fork process 1 ***
*** Parent is about to fork process 2 ***
*** Parent enters waiting status .....
   First child process starts (pid = 3300)
   First child's output, value = 1
      Second child process starts (pid = 3301)
      Second child's output, value = 1
   First child's output, value = 2
      Second child's output, value = 2
      Second child's output, value = 3
   First child's output, value = 3
      Second child's output, value = 4
   First child's output, value = 4
      Second child's output, value = 5
   First child's output, value = 5
      Second child's output, value = 6
   First child's output, value = 6
      Second child's output, value = 7
   First child's output, value = 7
      Second child's output, value = 8
   First child's output, value = 8
      Second child's output, value = 9
   First child's output, value = 9
      Second child's output, value = 10
   First child's output, value = 10
      Second child's output, value = 11
   First child's output, value = 11
      Second child's output, value = 12
   First child's output, value = 12
      Second child's output, value = 13
   First child's output, value = 13
      Second child's output, value = 14
   First child's output, value = 14
      Second child's output, value = 15
   First child's output, value = 15
      Second child's output, value = 16
   First child's output, value = 16
      Second child's output, value = 17
   First child's output, value = 17
      Second child's output, value = 18
   First child's output, value = 18
      Second child's output, value = 19
   First child's output, value = 19
      Second child's output, value = 20
   First child's output, value = 20
      Second child's output, value = 21
   First child's output, value = 21
      Second child's output, value = 22
   First child's output, value = 22
      Second child's output, value = 23
   First child's output, value = 23
      Second child's output, value = 24
   First child's output, value = 24
      Second child's output, value = 25
   First child's output, value = 25
      Second child's output, value = 26
   First child's output, value = 26
      Second child's output, value = 27
   First child's output, value = 27
      Second child's output, value = 28
   First child's output, value = 28
      Second child's output, value = 29
   First child's output, value = 29
      Second child's output, value = 30
   First child's output, value = 30
      Second child's output, value = 31
   First child's output, value = 31
      Second child's output, value = 32
   First child's output, value = 32
      Second child's output, value = 33
   First child's output, value = 33
      Second child's output, value = 34
   First child's output, value = 34
      Second child's output, value = 35
   First child's output, value = 35
      Second child's output, value = 36
   First child's output, value = 36
      Second child's output, value = 37
   First child's output, value = 37
      Second child's output, value = 38
   First child's output, value = 38
      Second child's output, value = 39
   First child's output, value = 39
      Second child's output, value = 40
   First child's output, value = 40
      Second child (pid = 3301) is about to exit
   First child (pid = 3300) is about to exit
*** Parent detects process 3300 was done ***
*** Parent detects process 3301 is done ***
*** Parent exits ***

为什么在孩子开始打印之后而不是之前,*** Parent enters waiting status .....行在开头显示?


“为什么孩子们在被分叉后不立即打印消息,而是允许他们的父亲进入等待状态?”这句话的意思是什么?目前你的问题并不完全清楚。 - Spaceman Spiff
你是否知道fork()会创建新的进程(而不是销毁第一个进程),并且多个进程可以同时运行?如果是这样,我不确定你在问什么。 - Dmitri
@ian-sellar @dmitri fork() 创建了另一个进程,在我们的情况下,因为它是子进程,所以进入了 ChildProcess("First", " ");。现在,为什么它没有从1打印到200呢?为什么消息 *** Parent enters waiting status ..... 没有在1-200之间或最后显示? - Shury
@Shury,如果您在问题中精确描述您得到的输出以及哪一部分令您困惑,可能会有所帮助。 - Spaceman Spiff
在包含了3个缺失的头文件(sys/wait.hstdlib.hunistd.h)并修复了main函数的返回类型(应该是int)之后,它对我来说可以正常工作...所有三个进程都运行,它们的输出交错等。新进程需要一段时间才能启动(例如,第一个子进程在第二个开始计数之前计数到147)...但这是预期的,因为启动新进程涉及开销。 - Dmitri
2个回答

3
Fork会创建一个新的进程,该进程可以与父进程同时执行(或交错执行)。它不会使父进程停止。在fork调用之后,两个进程都是“可运行”的,很可能两者都在运行。如果父进程幸运的话,它将成为第一个能够输出的进程。总的来说,这是不可预测的。
代码中存在许多错误,因此我不会将其视为学习如何使用fork()的良好来源。
例如,父进程使用printf来写入消息:
*** Parent is about to fork process 1 ***
*** Parent is about to fork process 2 ***

然而,stdout可能不是按行缓冲的(例如,它可能已被重定向)。在这种情况下,输出将仅被复制到内存输出缓冲区中,并且只有当缓冲区填满或关闭stdout文件描述符时,才会将其打印到stdout中。然而,子进程将使用相同的内存输出缓冲区分叉,因此父进程和子进程都将输出*** Parent is about to fork process 1 ***信息,并且所有三个进程都将输出*** Parent is about to fork process 2 ***信息。
另外,如果stdout被重定向到未以追加模式打开的可寻址流,则每个进程的文件描述符1将具有相关联的文件位置,每个进程将独立地操作该位置。这可能导致两个子进程互相覆盖输出。(在我的系统上,覆盖发生的概率约为80%。)

仅供参考,以一台旧的双核x86_64处理器运行的openSuSE系统为例。首个子进程(fcp)启动并在父进程进入等待状态之前输出1-9。fcp继续按顺序输出10-200。当fcp完成后,父进程检测到并开始第二个子进程(scp),scp按顺序输出1-174。scp继续按顺序输出175-200。我怀疑这完全取决于处理器/调度程序/负载,因此对于每台机器都有差异。 - David C. Rankin
@DavidC.Rankin:完全正确。甚至不用提及月相。“总的来说,它是不可预测的。” - rici

0
在一个非繁忙的多核或多处理器系统上,孩子们几乎立即开始执行是非常可能的。在Unix和Linux上,分叉是一种快速且简单的操作:复制一些内存描述符,复制一些资源,修复一些信号逻辑并使两个进程都可运行。调度程序不可预测(在这个观察级别上):它被设计为公平和公正。
此外,孩子们所做的计算量更大——格式化文本,在CRTL中缓冲它,可能将其传输到I/O系统——比父进程所做的工作更少:fork(),比较数字,等待()。
因此,父进程很快就到达了它的等待点,在这一点上,它不再竞争CPU时间。
在每个printf()后面加上fflush(stdout)可以通过从混合物中删除两个级别的I/O缓冲来改善一致性(也可能不会)。

子进程中的代码不使用printf,而是使用sprintf()和write()。这样做的目的是避免混杂的输出和缓冲问题,但实际上并没有解决问题,正如我在答案中所解释的那样。无论如何,刷新或行缓冲都是一个好主意,但它只适用于父进程打印的几行代码。而父进程和子进程的第一件事情大致相同:将文本格式化到缓冲区中,然后调用该缓冲区上的write(在用户空间不进行额外的缓冲),因此第一行输出的产生应该是有竞争力的。 - rici

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