If you instrument the program as discussed in comment 1 and comment 2, you end up with code like this (source: fork67.c, executable: fork67):
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
printf("PID = %5d, PPID = %5d\n", getpid(), getppid());
int a = 2;
int b = 3;
pid_t smith = fork();
if (smith == 0)
{
fork();
a++;
fork();
}
else if (smith > 0)
{
b++;
fork();
}
printf("PID = %5d, PPID = %5d: a = %d, b = %d\n", getpid(), getppid(), a, b);
int corpse;
int status;
int count = 0;
while ((corpse = wait(&status)) > 0)
{
printf("PID = %5d, PPID = %5d: child %5d exited with status 0x%.4X\n",
getpid(), getppid(), corpse, status);
count++;
}
printf("PID = %5d, PPID = %5d: reported on %5d children\n",
getpid(), getppid(), count);
return count;
}
With one example run, I got the output:
PID = 93578, PPID = 1320
PID = 93578, PPID = 1320: a = 2, b = 4
PID = 93580, PPID = 93578: a = 2, b = 4
PID = 93580, PPID = 93578: reported on 0 children
PID = 93579, PPID = 93578: a = 3, b = 3
PID = 93578, PPID = 1320: child 93580 exited with status 0x0000
PID = 93582, PPID = 93579: a = 3, b = 3
PID = 93581, PPID = 93579: a = 3, b = 3
PID = 93582, PPID = 93579: reported on 0 children
PID = 93583, PPID = 93581: a = 3, b = 3
PID = 93583, PPID = 93581: reported on 0 children
PID = 93581, PPID = 93579: child 93583 exited with status 0x0000
PID = 93579, PPID = 93578: child 93582 exited with status 0x0000
PID = 93581, PPID = 93579: reported on 1 children
PID = 93579, PPID = 93578: child 93581 exited with status 0x0100
PID = 93579, PPID = 93578: reported on 2 children
PID = 93578, PPID = 1320: child 93579 exited with status 0x0200
PID = 93578, PPID = 1320: reported on 2 children
My login shell has the PID 1320. You can see the processes' execution paths fairly clearly.
A still more instrumented version captures the return value from each fork() and prints that information too, leading to fork79.c and program fork79:
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
printf("PID = %5d, PPID = %5d\n", getpid(), getppid());
int a = 2;
int b = 3;
pid_t smith = fork();
printf("PID = %5d, PPID = %5d: c0 = %5d\n", getpid(), getppid(), smith);
if (smith == 0)
{
int c1 = fork();
printf("PID = %5d, PPID = %5d: c1 = %5d\n", getpid(), getppid(), c1);
a++;
int c2 = fork();
printf("PID = %5d, PPID = %5d: c2 = %5d\n", getpid(), getppid(), c2);
}
else if (smith > 0)
{
b++;
int c3 = fork();
printf("PID = %5d, PPID = %5d: c3 = %5d\n", getpid(), getppid(), c3);
}
printf("PID = %5d, PPID = %5d: a = %d, b = %d\n", getpid(), getppid(), a, b);
int corpse;
int status;
int count = 0;
while ((corpse = wait(&status)) > 0)
{
printf("PID = %5d, PPID = %5d: child %5d exited with status 0x%.4X\n",
getpid(), getppid(), corpse, status);
count++;
}
printf("PID = %5d, PPID = %5d: reported on %5d children\n",
getpid(), getppid(), count);
return count;
}
Sample run:
PID = 93985, PPID = 1320
PID = 93985, PPID = 1320: c0 = 93986
PID = 93985, PPID = 1320: c3 = 93987
PID = 93985, PPID = 1320: a = 2, b = 4
PID = 93986, PPID = 93985: c0 = 0
PID = 93987, PPID = 93985: c3 = 0
PID = 93987, PPID = 93985: a = 2, b = 4
PID = 93987, PPID = 93985: reported on 0 children
PID = 93986, PPID = 93985: c1 = 93988
PID = 93988, PPID = 93986: c1 = 0
PID = 93985, PPID = 1320: child 93987 exited with status 0x0000
PID = 93986, PPID = 93985: c2 = 93989
PID = 93986, PPID = 93985: a = 3, b = 3
PID = 93989, PPID = 93986: c2 = 0
PID = 93989, PPID = 93986: a = 3, b = 3
PID = 93988, PPID = 93986: c2 = 93990
PID = 93988, PPID = 93986: a = 3, b = 3
PID = 93989, PPID = 93986: reported on 0 children
PID = 93990, PPID = 93988: c2 = 0
PID = 93990, PPID = 93988: a = 3, b = 3
PID = 93986, PPID = 93985: child 93989 exited with status 0x0000
PID = 93990, PPID = 93988: reported on 0 children
PID = 93988, PPID = 93986: child 93990 exited with status 0x0000
PID = 93988, PPID = 93986: reported on 1 children
PID = 93986, PPID = 93985: child 93988 exited with status 0x0100
PID = 93986, PPID = 93985: reported on 2 children
PID = 93985, PPID = 1320: child 93986 exited with status 0x0200
PID = 93985, PPID = 1320: reported on 2 children
Because there is printing before and while the forking occurs, this code is vulnerable to the printf anomaly referenced in printf anomaly after fork(). Here's the output when its output is piped through cat:
$ fork79 | cat
PID = 94002, PPID = 1320
PID = 94002, PPID = 1320: c0 = 94004
PID = 94005, PPID = 94002: c3 = 0
PID = 94005, PPID = 94002: a = 2, b = 4
PID = 94005, PPID = 94002: reported on 0 children
PID = 94002, PPID = 1320
PID = 94004, PPID = 94002: c0 = 0
PID = 94004, PPID = 94002: c1 = 94006
PID = 94007, PPID = 94004: c2 = 0
PID = 94007, PPID = 94004: a = 3, b = 3
PID = 94007, PPID = 94004: reported on 0 children
PID = 94002, PPID = 1320
PID = 94004, PPID = 94002: c0 = 0
PID = 94006, PPID = 94004: c1 = 0
PID = 94008, PPID = 94006: c2 = 0
PID = 94008, PPID = 94006: a = 3, b = 3
PID = 94008, PPID = 94006: reported on 0 children
PID = 94002, PPID = 1320
PID = 94004, PPID = 94002: c0 = 0
PID = 94006, PPID = 94004: c1 = 0
PID = 94006, PPID = 94004: c2 = 94008
PID = 94006, PPID = 94004: a = 3, b = 3
PID = 94006, PPID = 94004: child 94008 exited with status 0x0000
PID = 94006, PPID = 94004: reported on 1 children
PID = 94002, PPID = 1320
PID = 94004, PPID = 94002: c0 = 0
PID = 94004, PPID = 94002: c1 = 94006
PID = 94004, PPID = 94002: c2 = 94007
PID = 94004, PPID = 94002: a = 3, b = 3
PID = 94004, PPID = 94002: child 94007 exited with status 0x0000
PID = 94004, PPID = 94002: child 94006 exited with status 0x0100
PID = 94004, PPID = 94002: reported on 2 children
PID = 94002, PPID = 1320
PID = 94002, PPID = 1320: c0 = 94004
PID = 94002, PPID = 1320: c3 = 94005
PID = 94002, PPID = 1320: a = 2, b = 4
PID = 94002, PPID = 1320: child 94005 exited with status 0x0000
PID = 94002, PPID = 1320: child 94004 exited with status 0x0200
PID = 94002, PPID = 1320: reported on 2 children
$
Note that the line PID = 94002, PPID = 1320 appears 6 times, once for each of the 6 processes that run: 94018,
94023,
94024,
94026,
94027,
94029.
There many variations that you can experiment with. On my machine, the original code (from the question) produces:
$ fork37
2 42 43 33 33 33 3$
because there's no newlines output, so the command prompt appears after the last 3 3. Curiously, I never got the prompt to appear before the last numbers — that surprises me. Adding the basic monitoring proposed in the comments gives output such as:
$ fork59
PID = 95109, PPID = 1320: a = 2 b = 4
PID = 95111, PPID = 1: a = 2 b = 4
PID = 95110, PPID = 1: a = 3 b = 3
PID = 95113, PPID = 95110: a = 3 b = 3
PID = 95112, PPID = 1: a = 3 b = 3
PID = 95114, PPID = 95112: a = 3 b = 3
$
When the parent PID (PPID) is 1, it means the original process that forked the current PID has already exited. With the wait loop, the processes are more synchronized.