You've written a fork bomb. This is when a process spawns children which then spawn children which then spawn children... until all memory is consumed or the operation system kills it.
fork
can be difficult to get your head around. When its called, both the parent and child processes continue on executing the code. Because of this it's important to separate the child code and make sure the child exits. If the parent and child are running the same code, how do you ensure the child stops and the parent continues?
fork
in the parent returns the process ID of the child. fork
in the child returns 0. And if there's an error it will return -1. So the typical idiom looks like this:
pid_t pid = fork();
if( pid == 0 ) {
// child
exit(0);
}
else if( pid < 0 ) {
// error
perror("fork failed");
}
// parent
Once done you have to wait for all children to complete. wait
will return when any child process completes, returning the child pid. You need to keep calling wait
until it returns with < 0 indicating there were no more child processes.
void wait_all() {
while(wait(NULL) > 0);
}
Putting them all together, and removing the compilation of doing it 15 times...
for(int j=0; j<10; j++){
pid_t pid = fork();
if( pid == 0 ) {
// print
printf("%d", j);
// job's done
exit(0);
}
else if( pid < 0 ) {
perror("fork failed");
}
}
wait_all();
printf("\n");
Even so I still get them in order. Probably because each child is executing the exact same very simple and predictable thing. They all take roughly the same amount of time to execute, so they're probably going to come out in the same order.
Side Note: stdout
is line buffered, meaning it will only display, or flush, its contents when it sees a newline. printf("%d", j);
won't display until a newline is printed or stdout
gets flushed. exit
will flush and close all streams, so that's ok.
However, the child inherits the parent's buffer. This can lead to some pretty weird behavior if the parent leaves anything on the buffer. For example...
for(int j=0; j<10; j++){
pid_t pid = fork();
if( pid == 0 ) {
printf("child: %d, ", j);
exit(0);
}
else if( pid < 0 ) {
perror("fork failed");
}
printf("parent: %d, ", j);
}
wait_all();
printf("\n");
We get:
child: 0, parent: 0, child: 1, parent: 0, parent: 1, child: 2, parent: 0, parent: 1, parent: 2, child: 3, parent: 0, parent: 1, parent: 2, parent: 3, child: 4, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, child: 5, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, child: 6, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, child: 7, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, child: 8, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, child: 9, parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, parent: 9,
What's going on? This boggled me for a while. It's a little clearer if we put a newline in the child's print.
for(int j=0; j<10; j++){
pid_t pid = fork();
if( pid == 0 ) {
printf("child: %d\n", j);
exit(0);
}
else if( pid < 0 ) {
perror("fork failed");
}
printf("parent: %d, ", j);
}
wait_all();
printf("\n");
child: 0
parent: 0, child: 1
parent: 0, parent: 1, child: 2
parent: 0, parent: 1, parent: 2, child: 3
parent: 0, parent: 1, parent: 2, parent: 3, child: 4
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, child: 5
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, child: 6
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, child: 7
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, child: 8
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, child: 9
parent: 0, parent: 1, parent: 2, parent: 3, parent: 4, parent: 5, parent: 6, parent: 7, parent: 8, parent: 9,
Each time the child is forked it gets a copy of the parent's stdout
buffer. Each time through the loop the parent adds parent: %d,
to that buffer without flushing. When the child does printf("child: %d\n", j);
it adds to the existing buffer and then flushes it.
- The first child copies
""
from the parent, adds child: 0\n
, and flushes.
- The parent adds
parent: 0,
to stdout
.
- The second child copies
parent: 0,
, adds child: 1\n
, and flushes.
- The parent adds
parent: 1,
to stdout
, its now parent: 0, parent: 1,
- The third child copies
parent: 0, parent: 1,
, adds child: 2\n
, and flushes.
And so on.
Flushing stdout
after the parent does a partial print avoids this, and ensures everything is displayed as it happens.
for(int j=0; j<10; j++){
pid_t pid = fork();
if( pid == 0 ) {
printf("child: %d\n", j);
exit(0);
}
else if( pid < 0 ) {
perror("fork failed");
}
printf("parent: %d, ", j);
fflush(stdout);
}
wait_all();
printf("\n");
parent: 0, child: 0
parent: 1, child: 1
parent: 2, child: 2
parent: 3, child: 3
parent: 4, child: 4
parent: 5, child: 5
parent: 6, child: 6
parent: 7, child: 7
parent: 8, child: 8
parent: 9, child: 9