Search code examples
linuxkernelforkscheduler

Who runs first in fork, with contradicting results


I have this simple test:

int main() {
    int res = fork();
    if (res == 0) { // child
        printf("Son running now, pid = %d\n", getpid());
    }
    else { // parent
        printf("Parent running now, pid = %d\n", getpid());
        wait(NULL);
    }
    return 0;
}

When I run it a hundred times, i.e. run this command,

for ((i=0;i<100;i++)); do echo ${i}:; ./test; done

I get:

0:
Parent running now, pid = 1775
Son running now, pid = 1776
1:
Parent running now, pid = 1777
Son running now, pid = 1778
2:
Parent running now, pid = 1779
Son running now, pid = 1780

and so on; whereas when I first write to a file and then read the file, i.e. run this command,

for ((i=0;i<100;i++)); do echo ${i}:; ./test; done > forout
cat forout

I get it flipped! That is,

0:
Son running now, pid = 1776
Parent running now, pid = 1775
1:
Son running now, pid = 1778
Parent running now, pid = 1777
2:
Son running now, pid = 1780
Parent running now, pid = 1779

I know about the scheduler. What does this result not mean, in terms of who runs first after forking? The forking function, do_fork() (at kernel/fork.c) ends with setting the need_resched flag to 1, with the comment by kernel developers saying, "let the child process run first."

I guessed that this has something to do with the buffers that the printf writes to.

Also, is it true to say that the input redirection (>) writes everything to a buffer first and only then copies to the file? And even so, why would this change the order of the prints?

Note: I am running the test on a single-core virtual machine with a Linux kernel v2.4.14.

Thank you for your time.


Solution

  • When you redirect, glibc detects that stdout is not tty turns on output buffering for efficiency. The buffer is therefore not written until the process exits. You can see this with e.g.:

    int main() {
      printf("hello world\n");
      sleep(60);
    }
    

    When you run it interactively, it prints "hello world" and waits. When you redirect to a file, you will see that nothing is written for 60 seconds:

    $ ./foo > file & tail -f file
    (no output for 60 seconds)
    

    Since your parent process waits for the child, it will necessarily always exit last, and therefore flush its output last.