3

I have a question about the following code. This is an example found on this page, not my code.

The parent process forks 2 child processes, each of them counting to 200 then exiting. What I don't understand is why aren't the children printing their messages immediately after they are forked and they allow their father to enter waiting status? Also, how is the wait system call returning the pid of the child that finishes first?

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

Output for 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 ***

Why *** Parent enters waiting status ..... line is displayed at the beginning and not somewhere after the childs start printing?

Shury
  • 468
  • 3
  • 15
  • 49
  • What do you mean by "why aren't the children printing their messages immediately after they are forked and they allow their father to enter waiting status?" exactly. Its not entirely clear what your question is right now. – Spaceman Spiff Jan 30 '15 at 19:03
  • Are you aware that `fork()` is creating new processes (without destroying the first), and that multiple processes can run concurrently? If so I'm not sure what you're asking. – Dmitri Jan 30 '15 at 19:05
  • @ian-sellar @dmitri `fork()` creates another process that in our case, because it is a child, enters `ChildProcess("First", " ");`. Right now, why isn't it printing from 1 to 200? Why the message `*** Parent enters waiting status .....` isn't displayed somewhere between 1-200 or at the end? – Shury Jan 30 '15 at 19:12
  • @Shury it might help if you put in the question exactly the output you're getting, and what part is confusing. – Spaceman Spiff Jan 30 '15 at 19:17
  • After including 3 missing headers (`sys/wait.h`, `stdlib.h`, and `unistd.h`), and fixing the return type of main (should be `int`), it works as expected for me... all three processes run, their output is interleaved, etc. It takes a moment for the new processes to get going (for example, the first child counted to 147 before the second started counting)... but that's expected because of the overhead involved in starting the new process. – Dmitri Jan 30 '15 at 19:29

2 Answers2

3

Fork makes a new process, which can execute simultaneously (or interleaved) with the parent process. It does not make the parent process stop. After the fork call, both processes are "runnable", and it is quite possible that both of them are running. If the parent process is lucky, it will be the first one to be able to output. In general, it is unpredictable.

There are a number of bugs in that code, so I wouldn't treat it as a good source of learning how to use fork().

For example, the parent process uses printf to write the messages:

*** Parent is about to fork process 1 ***
*** Parent is about to fork process 2 ***

However, it is quite possible that stdout is not line-buffered (for example, it might be redirected.) In that case, the output will only be copied into the in-memory output buffer, and will be printed to stdout when the buffer fills up or the stdout file descriptor is closed. However, the children will be forked with the same in-memory output buffer, so both the parent and child one will output the *** Parent is about to fork process 1 *** message and all three processes will output the *** Parent is about to fork process 2 *** message.

Also, if the stdout is redirected to a seekable stream not opened in append mode, then each process's file descriptor 1 will have an associated file position which each will manipulate independently. That may result in the two child processes overwriting each other's output. (On my system, the overwriting happens about 80% of the time.)

rici
  • 234,347
  • 28
  • 237
  • 341
  • Just as a point of reference. On an older dual core x86_64 w/openSuSE, First child process (fcp) starts & outputs 1-9 before Parent enters waiting. fcp continues output sequentially 10-200. fcp is about to exit and Second child process (scp) starts with sequential output 1-174 where parent detects fcp was done. scp continues sequentially 175-200. I suspect this is completely processor/scheduler/load dependent for every machine. – David C. Rankin Jan 30 '15 at 20:04
  • @DavidC.Rankin: completely. Without even mentioning the phase of the moon. " In general, it is unpredictable." – rici Jan 30 '15 at 21:31
0

On a non-busy multicore or multiprocessor system, it is highly likely that the children began executing almost instantly. On Unix and Linux, forking is a fast and easy operation: copy some memory descriptors, duplicate some resources, fix up some signal logic and leave both processes runnable. The scheduler is not predictable (at this level of observation): it is designed to be fair and equitable.

Besides, what the children do is more computationally heavy—formatting text, buffering it in the CRTL, possibly transferring it to the i/o system—than what the parent does: fork(), compare a number, wait().

So it is not surprising that the parent quickly gets to its wait() at which point it is no longer competing for CPU time.

Following each printf() with a fflush(stdout) might (or might not) improve consistency by removing two levels of i/o buffering from the mix.

wallyk
  • 56,922
  • 16
  • 83
  • 148
  • The code in the children doesn't printf; it sprintf()s and then write()s. The intention is to avoid intermingled output and buffering issues, but it does not actually solve the problem as I explained in my answer. At any rate, flushing or line buffering would be a good idea, but it only applies to a few lines printed by the parent. And the first thing done by the parent and the child is roughly the same: format text into a buffer and then call write on that buffer (which does not do additional buffering in userland) so the production of the first output line should be competitive. – rici Jan 30 '15 at 21:29