Search code examples
cfork

fork() in C ? Cannot Comprehend The Source Code


Source Code :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main() {
  if(fork() == 0)
    if(fork())
      printf("Hello world!!\n");
  exit(0);
}

OUTPUT :
┌──(kali㉿kali)-[~/Desktop/System]
└─$ ./a.out

Hello world!!


Can anyone please explain me the flow of the Program ? I know what fork() does but I can't comprehend this source Code . Can anyone please share what exactly happens when fork() is invoked apart from saying it's just a copy.


Solution

  • fork() is a system call. That means it's a part of the native library made available to the user by the underlying operating system (in this case, most likely the Linux kernel).

    Processes under Linux are organized in a tree structure, such that every process has a parent (apart from the root one, called init), and every process may have children. Every process is also uniquely idientified by a PID, just a number really.

    Say you launch process 100, which executes the code you provided. While running, process 100 calls fork(). What happens is the following:

    • The kernel spawns a new process, say with PID 200, whose parent is process 100. This includes allocating a whole new private memory area for the new process to use, as it won't share any memory with its parent. The memory of the parent is copied into that newly allocated child memory.
    • Process 200, the child process, starts execution exactly after the fork() call, whose return value is 0. fork() returning 0 indicates that the execution is continuing as part of the newly spawned process.
    • Process 100, the parent process, also resumes execution exactly after the fork() call. But in this process, fork() will have returned 200. fork() returning a non-zero value indicates that the execution is continuing as part of the parent process, and the returned value is the PID of the new child process.

    In any practical case, the PIDs won't be 100 or 200 but that's to get a rough idea. Let's continue with that idea anyway, and let's also rewrite your code as follows:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
     
    int main()
    {
        int forkValue1 = fork();
        if(forkValue1 == 0)
        {
            int forkValue2 = fork();
            if(forkValue2)
            {
                printf("Hello world!!\n");
            }
        }
        exit(0);
    }
    

    Now let's break down what this does at runtime, instruction by instruction, assuming your code is run by process 100 again.


    Process 100:

    int forkValue1 = fork();
    
    • fork() is called! Process 200 is spawned by the kernel.
    • 200, the PID of the child process, is returned by fork() into forkValue1, because we're in the parent process, the one which issued the call to fork() in the first place.
    if(forkValue1 == 0)
    
    • Since forkValue1 is 200, the condition evaluates to false, and the whole block is skipped.
    exit(0);
    
    • Process 100 exits cleanly.

    Now what happens from the perspective of process 200?

    • Process 200 is spawned into existence by the kernel when process 100 calls fork(). Execution starts immediately after the originating fork() call, which in this case returns 0 since we're in the child process. That value goes into forkValue1.
    if(forkValue1 == 0)
    
    • Since forkValue1 is 0, the condition evaluates to true, and the block is executed.
    int forkValue2 = fork();
    
    • fork() is called! Process 300 is spawned by the kernel.
    • 300, the PID of the child process, is returned by fork() into forkValue2, because we're in the parent process, the one which issued the call to fork() in the first place.
    if(forkValue2)
    
    • What this really means is "if forkValue2 is non-zero". Since forkValue2 is 300, the condition evaluates to true, and the block is executed.
    printf("Hello world!!\n");
    
    • Process 200 prints "Hello world!!\n" to the standard output.
    exit(0);
    
    • Process 200 exits cleanly.

    Finally, let's see how it goes for process 300

    • Process 300 is spawned into existence by the kernel when process 200 calls fork(). Execution starts immediately after the originating fork() call, which in this case returns 0 since we're in the child process. That value goes into forkValue2.
    if(forkValue2)
    
    • forkValue2 is 0, so the condition evaluates to false, and the block is skipped.
    exit(0);
    
    • Process 300 exits cleanly.

    The key takeaway in here is that the processes are basically duplicated from a system point of view, and the only difference in their execution flow is that for one of them, fork() will have returned 0, and for the other, it will have returned something else.
    This leads to a very common idiom in Linux programming:

    if (fork() == 0)
    {
        /* Code for the newly spawned child process */
    }
    else
    {
        /* Code for the parent process */
    }
    

    See this question for additional info.