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.