Search code examples
cforksystem-calls

How is the flow of control in this program involving fork() system call?


From what I read about fork() system call

Fork system call use for creates a new process, which is called child process, which runs concurrently with parent process

After a new child process created, both processes will execute the next instruction following the fork() system call

fork() returns 0 to the child process

fork() returns Process ID of newly created child process to parent process (Positive value)

fork() returns negative value if child process creation fails

In this piece of code

void foo() { 
if (fork() == 0) 
    printf("Hello from Child!\n"); 
else 
    printf("Hello from Parent!\n"); 
} 

int main() { 
    foo(); 
    return 0; 
} 

The output is

Hello from Parent!
Hello from Child!

The child process was created when the control was inside the condition of if-else of function foo in main process.

So from where (which instruction) did the child process start executing?

As it can be observed from the output, Hello from Parent is printed when fork() returns 0. So from my understanding Hello from Parent was actually printed by the Child Process

fork() returned a positive value to the parent process and the parent process printed Hello from Child. Is my understanding about this correct?

And from which instruction exactly did the child process started executing? The function call to fork() was given inside the condition section of a if-else. So the child should have started executing after that if-else but that is not what is happening?


Solution

  • Let's start by identifying a primary misconception here:

    As it can be observed from the output, Hello from Parent is printed when fork() returns 0. So from my understanding Hello from Parent was actually printed by the Child Process

    The child and the parent are two separate processes running concurrently. The order of these two outputs isn't well-defined, will vary based on your kernel and other timing considerations, and isn't correlated with the fact that your code contains the if/else block written as you have it.1

    Let's rewrite your code as a linear stream of "instructions" in an abstract sense:

    0: Function foo():
    1:  Invoke system call fork(), no arguments, store result to $1
    2:  If $1 is non-zero, jump to label #1.
    3:  Invoke C function printf(), argument "Hello from Child!"
    4:  Jump to label #2.
    5: Label #1:
    6:  Invoke C function printf(), argument "Hello from Parent!"
    7: Label #2:
    8: return control to calling function.
    

    Once your program reaches 1:, the system call is invoked, transferring control to the kernel. The kernel duplicates the process, puts the PID of the child into the return value of fork in the parent process, and puts 0 into the return value of fork in the child. On x86, the return value is stored in register eax (rax for x64) as part of the syscall calling convention.

    One of these two processes will eventually get scheduled to run by the kernel. In your case, the child process happened to be the first to get scheduled. Your user-mode code took control back from kernel mode, read the return value (out of eax/rax if on x86) which was zero, and did not jump to label #1. It printed Hello from Child!, and then returned from the function (to the caller of foo, since the child got a copy of the parent's stack).

    The same happened for the parent, except the parent got a non-zero value back from the system call, and printed Hello from Parent!. It got scheduled to run, and your user-mode code took control from the kernel at the same point, just with a different value returned by the system call.

    1 It's also possible that the two outputs might become interleaved in some way, but that's not as relevant to this discussion, and requires understanding how Linux processes perform I/O.