The fork()
is a magical C function that creates an exact copy of a program, making two identical versions of it, with one being the parent and the other the child.
- In the context of the terminal, when I type a command and press Enter, the shell executes the command by creating a child process using the
fork
system call. This child process is a copy of the shell process. - The child process then calls
execve
to replace its memory with the new program's memory (the command I typed). The new program starts executing from its entry point, and the child process now becomes that new program.
- Meanwhile, the original shell process (parent) continues to exist, waiting for the child process to complete. It does not die or terminate after
execve()
. - When the new program (child process) finishes its execution, the child process exits, and the parent process (shell) regains control, ready to accept the next command.
The terminal, duly forks and executes our commands each time with execve,
yet regains control at the end of forking, ready to accept the next command.
Now I try this code:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid; // Create a child process using fork()
pid = fork();
// Parent process
printf("Hello, I am the parent process! My PID is %d, and my child's PID is %d.\n", getpid(), pid);
return 0;
}
As I can see, fork()
creates a child process with PID 0, which tries to imitate the parent process, so both of them prints the given text, but with their own PIDs and their child’s.
Now, how about this:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
pid = 9;
printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
printf("Hello1, I am main process (before fork). My PID is %d.\n", getpid());
// Create a child process using fork()
pid = fork();
printf("------------------------------------\n");
printf("Hello1, I am main process (after fork). My PID is %d.\n", getpid());
if (pid < 0) {
// Fork failed
fprintf(stderr, "Fork2 failed.\n");
return 1;
} else if (pid == 0) {
// Child process
printf("Hello1, I am the child process! My PID is %d, and my child's PID is %d\n", getpid(), pid);
} else {
// Parent process
printf("Hello1, I am the parent process! My PID is %d, and my child's PID is %d.\n", getpid(), pid);
}
return 0;
}
So, we can always identify the child process with the return value of the fork()
call.
Thus, just like in the terminal shell, the forking process allows the main program process to persist and manage the execution of child, creating a multi-tasking environment.
Here if you can see, I tried to trick to initialize pid_t with a non-zero value (pid=9;) to seeif I can overtake the PID=0 for the child process. But no. During forking, its return value forces itself as 0 to the child process. This is basically to avoid the child process forking infinitely.
To reinforce the understanding, imagine propogating an Devil’s ivy plant via stem cutting. I cut a small branch out of the parent plant — parent plant has its own pid and gives away a new identity for the child (a new pid for child).
The child is ready to root itself (behaves like a parent), but not yet ready to propogate itself (child pid = 0)
That’s it for this chapter. In the next, I combine execve()
with fork()
.
Chapter 3: execve() with fork()