Learning Objectives
- Understand why operating systems create processes.
- Learn the purpose of fork().
- Understand Parent and Child processes.
- Study process creation inside Linux.
- Understand PID allocation.
- Prepare for advanced process management topics.
1. Introduction
Modern operating systems execute multiple programs simultaneously. A web browser, music player, terminal, and editor can all run at the same time because each program executes as an independent process.
Whenever Linux needs another process, it creates one using the fork() system call.
The fork() system call is one of the most fundamental process management operations in UNIX-like operating systems.
2. Why Do We Need fork()?
Suppose a terminal starts another program. If Linux had no mechanism to create another process, the terminal itself would have to stop executing permanently. Instead, Linux creates a new process.
Because of fork(), the terminal remains available after the command finishes.
3. Definition
The fork() system call creates a new process called the
Child Process from the currently executing
Parent Process.
Conceptually, the child starts with the same program, memory contents,
open file descriptors, and execution context as the parent.
In modern Linux systems, this is implemented efficiently using
Copy-on-Write (COW). Instead of immediately copying all
physical memory pages, the parent and child initially share them as
read-only pages. A private copy of a page is created only when either
process modifies that page.
After fork() returns, both processes execute independently.
4. Parent and Child Process
The child process is created by the kernel. It receives a unique Process ID (PID), while the parent retains its original PID.
5. What Does the Child Receive?
When fork() succeeds, the child process begins execution
with the same logical process state as the parent. In modern Linux,
physical memory pages are shared initially using
Copy-on-Write (COW), and private copies are created only
when a page is modified.
| Resource | Inherited by Child |
|---|---|
| Program Code | Yes |
| Global Variables | Yes |
| Heap | Yes |
| Stack | Yes |
| Environment Variables | Yes |
| Open File Descriptors | Yes |
| Signal Dispositions | Mostly Yes |
| PID | No (new PID assigned) |
6. Process Creation Flow
7. Return Value of fork()
The return value of fork() allows a program to determine whether it is executing in the parent process or the child process.
| Return Value | Meaning |
|---|---|
| > 0 | Returned in the parent process. The value represents the PID of the child. |
| 0 | Returned in the child process. |
| -1 | Process creation failed. |
8. First fork() Program
#include#include int main() { pid_t pid; pid = fork(); if(pid < 0) { printf("fork failed\n"); return 1; } if(pid == 0) { printf("Child Process\n"); } else { printf("Parent Process\n"); } return 0; }
9. Output
Parent Process Child Process
The order of these messages is not guaranteed. After fork(), the operating system scheduler decides when each process executes.
10. Execution Timeline
11. Process ID (PID)
Every process executing in Linux is uniquely identified by a Process ID (PID). The operating system assigns this identifier when a new process is created. The PID allows the kernel to distinguish one process from another and manage CPU scheduling, memory allocation, signals, and resource ownership.
12. Parent Process ID (PPID)
Every process (except the initial system process) is created by another process. The identifier of the process that created it is called the Parent Process ID (PPID).
| Process | PID | PPID |
|---|---|---|
| init | 1 | 0 |
| bash | 450 | 1 |
| my_program | 600 | 450 |
13. getpid()
The getpid() system call returns the Process ID of the calling process.
#include <unistd.h> pid_t getpid(void);
Example
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("PID = %d\n", getpid());
return 0;
}
Sample Output
PID = 3245
14. getppid()
The getppid() system call returns the Process ID of the parent process.
#include <unistd.h> pid_t getppid(void);
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("PID = %d\n", getpid());
printf("PPID = %d\n", getppid());
return 0;
}
15. Parent and Child Process IDs
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid == 0)
{
printf("Child\n");
printf("PID = %d\n", getpid());
printf("PPID = %d\n", getppid());
}
else
{
printf("Parent\n");
printf("PID = %d\n", getpid());
printf("Child PID = %d\n", pid);
}
return 0;
}
16. Execution Flow
17. Why Does fork() Return Twice?
This is one of the most frequently asked interview questions. Although the fork() system call is invoked once, it returns in two different processes:
- Once in the parent process.
- Once in the newly created child process.
The return value allows the program to determine which process is executing and perform different tasks accordingly.
18. Process Tree
Each process can create additional child processes, forming a hierarchical process tree.
19. Observing Processes in Linux
ps -ef ps -f pstree top htop
These commands display currently running processes, their parent-child relationships, and resource usage.
20. Practice Exercise
Write a program that:
- Calls
fork(). - Prints the PID of the parent.
- Prints the PID of the child.
- Prints the PPID of the child.
- Observe the output using
pstreewhile the program is running.
21. Process Memory Layout Before fork()
Before understanding what fork() does, it is important to understand how a process is organized in memory.
Every running process has its own virtual address space, which is divided into multiple logical sections. Each section stores a different type of information required during program execution.
22. Memory Segments
| Segment | Purpose |
|---|---|
| Text | Stores executable machine instructions. |
| Data | Stores initialized global and static variables. |
| BSS | Stores uninitialized global and static variables. |
| Heap | Dynamically allocated memory requested at runtime. |
| Stack | Function calls, parameters, return addresses, and local variables. |
23. What Happens When fork() is Called?
When a process calls fork(), the kernel creates a new child
process.
From the application's point of view, the child begins execution with
the same program, variables, open files, and memory contents as the
parent at the instant fork() returns.
Internally, modern Linux avoids immediately copying all physical memory.
Instead, both processes initially share the same memory pages using
Copy-on-Write (COW). Only when either process writes to a
shared page does the kernel create a private copy of that page.
After the system call returns, the parent and child continue executing
independently.
24. Are Parent and Child Sharing Memory?
Immediately after fork(), the parent and child appear to have identical memory contents.
However, they execute as separate processes. A modification made by one process does not change the memory seen by the other process.
25. Example Program
#include <stdio.h>
#include <unistd.h>
int main()
{
int value = 100;
pid_t pid = fork();
if(pid == 0)
{
value = 200;
printf("Child : %d\n", value);
}
else
{
printf("Parent : %d\n", value);
}
return 0;
}
26. Possible Output
Parent : 100 Child : 200
The parent still prints 100, while the child prints 200. Although both processes started with the same variable value, each process updates its own copy after fork().
27. Memory Independence
28. Heap Memory Example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int *num;
num = malloc(sizeof(int));
*num = 50;
pid_t pid = fork();
if(pid == 0)
{
*num = 500;
printf("Child Heap = %d\n", *num);
}
else
{
printf("Parent Heap = %d\n", *num);
}
free(num);
return 0;
}
The heap memory is logically independent for the parent and child after fork(). Changes made by one process are not visible in the other process.
29. Key Observations
- Both processes begin execution immediately after
fork(). - Both processes initially observe the same memory contents.
- Each process has its own stack and heap.
- Updating a variable in one process does not change the value seen by the other process.
- The parent and child execute independently under the control of the scheduler.
- Modern Linux implements
fork()efficiently using Copy-on-Write (COW), so physical memory pages are copied only when they are modified.
30. Interview Questions
- Why do the parent and child initially print the same variable value after
fork()? - Why doesn't changing a variable in the child affect the parent?
- What memory segments exist in a Linux process?
- Does the child receive a new Process ID?
- Can the parent directly access the child's stack variables?
31. Copy-on-Write (COW)
When learning fork(), it is common to imagine that the operating system immediately duplicates the entire memory of the parent process.
Modern Linux systems use a more efficient technique called Copy-on-Write (COW).
Instead of copying every memory page during fork(), the parent and child initially share the same physical memory pages. These shared pages are marked as read-only. If either process later attempts to modify a shared page, the kernel creates a private copy of that page for the writing process.
fork(), especially when the child process soon executes another program using exec().
32. Memory Before fork()
33. Memory Immediately After fork()
Immediately after fork(), both processes refer to the same physical memory pages.
No additional copies are created at this stage.
RO = Read Only
34. What Happens When the Child Writes?
Suppose the child modifies a variable stored on one of the shared pages. The write cannot proceed because the page is currently marked read-only. This triggers a page fault, allowing the kernel to allocate a new page and copy the original contents before completing the write.
35. Memory After Copy-on-Write
Only the modified page is copied. All unmodified pages continue to be shared.
36. Why Copy-on-Write?
| Without COW | With COW |
|---|---|
| Entire process memory copied | Only modified pages copied |
| Slow process creation | Fast process creation |
| Large memory consumption | Efficient memory usage |
| Unnecessary copying | Copy only when required |
37. Example
#include#include int main() { int value = 100; pid_t pid = fork(); if(pid == 0) { value = 500; printf("Child : %d\n", value); } else { sleep(1); printf("Parent : %d\n", value); } return 0; }
Initially, both processes observe the value 100. When the child changes the variable to 500, the kernel creates a private copy of the affected memory page for the child. The parent's copy remains unchanged.
38. Copy-on-Write and Page Faults
The first write to a shared page generates a page fault because the page is marked read-only. The kernel then:
- Allocates a new physical page.
- Copies the original page contents.
- Updates the page table of the writing process.
- Marks the new page writable.
- Restarts the interrupted instruction.
39. Why COW Works Well with exec()
A common programming pattern is:
In this situation, the child quickly replaces its address space with a different program. Because of Copy-on-Write, Linux avoids copying the parent's memory unnecessarily before the replacement occurs.
40. Interview Questions
- What problem does Copy-on-Write solve?
- Does Linux copy the entire process memory during
fork()? - When is a memory page actually copied?
- Why is
fork()followed byexec()in many programs? - What role do page faults play in Copy-on-Write?
41. Copy-on-Write (COW) Execution Example
The following example demonstrates how Copy-on-Write works in practice. Initially, both parent and child processes appear to have identical memory. However, when one process modifies a variable, the kernel creates a private copy of the affected memory page for that process.
#include <stdio.h>
#include <unistd.h>
int main()
{
int number = 100;
pid_t pid = fork();
if(pid == 0)
{
printf("Child Before = %d\n", number);
number = 500;
printf("Child After = %d\n", number);
}
else
{
sleep(1);
printf("Parent = %d\n", number);
}
return 0;
}
42. Sample Output
Child Before = 100 Child After = 500 Parent = 100
The parent process continues to observe the original value because it still references its own logical view of memory. The child's modification causes the kernel to create a private copy of the modified page.
43. Step-by-Step Kernel Execution
| Step | Kernel Activity |
|---|---|
| 1 | Parent process executes normally. |
| 2 | Parent calls fork(). |
| 3 | Kernel creates the child process. |
| 4 | Parent and child initially share the same physical memory pages. |
| 5 | Shared pages are marked read-only. |
| 6 | Child attempts to modify a variable. |
| 7 | A page fault occurs. |
| 8 | Kernel allocates a new physical page. |
| 9 | Original page contents are copied. |
| 10 | Child page table is updated. |
| 11 | Child resumes execution. |
| 12 | Parent continues using the original page. |
44. Complete Memory Transition
45. Why Read-Only Pages?
If both processes were allowed to write directly to the same physical page, one process could accidentally overwrite the other process's data. Marking shared pages as read-only ensures that the kernel is notified before any modification occurs. This notification is provided through a page fault, allowing the kernel to create a private copy for the writing process.
46. Performance Benefits of Copy-on-Write
| Without Copy-on-Write | With Copy-on-Write |
|---|---|
| Entire address space copied immediately. | Pages copied only when modified. |
| High memory usage. | Efficient memory utilization. |
| Longer process creation time. | Faster process creation. |
| Many unnecessary memory copies. | Only modified pages are copied. |
| Poor scalability. | Better scalability. |
47. Important Points
- Copy-on-Write is an optimization technique used by modern Linux systems.
- The child initially appears to have the same memory as the parent.
- Physical memory pages are shared until a write operation occurs.
- The first write generates a page fault.
- The kernel creates a private copy only for the modified page.
- The parent process remains unaffected by changes in the child.
- Unmodified pages continue to be shared, improving memory efficiency.
48. Interview Questions
- Why are shared pages marked read-only after
fork()? - What event triggers Copy-on-Write?
- Why doesn't Linux immediately duplicate all memory pages?
- Which process receives the copied page after a write?
- Can both parent and child continue sharing unmodified pages?
- Why is Copy-on-Write considered a performance optimization?
49. The exec() System Call
The fork() system call creates a new process. However, the newly created child process initially executes the same program as its parent. Most applications do not want the child to continue running the same program. Instead, they want the child to execute a completely different program. This is the purpose of the exec() family of system calls.
50. Why Do We Need exec()?
Consider a Linux terminal. When a user types a command such as:
ls
The terminal itself should continue running after the command finishes.
If the terminal directly became the ls program, it would no
longer exist after ls terminated.
Instead, Linux follows these steps:
51. What Does exec() Do?
The exec() system call replaces the current program image
with a new program.
After a successful exec():
- The old program no longer exists.
- The new program starts executing.
- The Process ID (PID) remains the same.
- No new process is created.
52. fork() vs exec()
| fork() | exec() |
|---|---|
| Creates a new process. | Does not create a new process. |
| Child gets a new PID. | PID remains unchanged. |
| Parent continues executing. | Current program is replaced. |
| Both processes execute. | Only the new program executes. |
| Returns twice. | Returns only if an error occurs. |
53. Execution Flow
54. Memory Before exec()
55. Memory After exec()
The process remains the same, but its address space is replaced with the new program.
56. Does exec() Return?
If exec() successfully loads the new program, control never
returns to the original program.
The original process image has already been replaced.
The function returns only if an error occurs.
| Situation | Return Value |
|---|---|
| Success | Does not return |
| Failure | -1 |
57. Why fork() and exec() Are Used Together
Using fork() followed by exec() allows the shell
to continue running while the requested command executes in a separate
process.
58. Real Linux Example
When the following command is entered in the terminal:
gcc hello.c -o hello ./hello
The shell creates a child process using fork().
The child replaces itself with the hello executable using
exec().
After the program exits, the shell continues accepting new commands.
59. Key Points
- fork() creates a child process.
- exec() replaces the currently executing program.
- The Process ID remains unchanged after
exec(). - No new process is created by
exec(). - On success,
exec()never returns. - The combination of
fork()andexec()forms the basis of process creation in UNIX-like systems.
60. Interview Questions
- What is the difference between
fork()andexec()? - Why does
exec()not create a new process? - Does the Process ID change after
exec()? - Why does
exec()normally never return? - Why do shells use
fork()before callingexec()?
61. The exec() Family of Functions
Linux provides several functions that belong to the exec() family. Although their names are different, all of them perform the same basic operation:
- Replace the current program with a new executable.
- Do not create a new process.
- Keep the same Process ID (PID).
The differences between these functions are mainly in how program arguments and environment variables are passed.
62. Members of the exec() Family
| Function | Description |
|---|---|
| execl() | Arguments passed as a list. |
| execv() | Arguments passed as an array. |
| execlp() | Searches executable using PATH. |
| execvp() | Uses PATH and argument array. |
| execle() | Allows custom environment variables. |
| execve() | Underlying system call interface used by Linux. |
63. Understanding the Function Names
| Letter | Meaning |
|---|---|
| l | Arguments supplied as a list. |
| v | Arguments supplied as an array (vector). |
| p | Search executable using the PATH environment variable. |
| e | Provide a custom environment. |
64. exec() Family Overview
65. execl()
The execl() function accepts program arguments one by one.
#include <unistd.h>
int execl(const char *path,
const char *arg0,
const char *arg1,
...
NULL);
Example
execl("/bin/ls",
"ls",
"-l",
NULL);
The executable path must be specified explicitly.
66. execv()
The execv() function receives arguments through an array.
char *args[] =
{
"ls",
"-l",
NULL
};
execv("/bin/ls", args);
This approach is useful when the number of arguments is determined at runtime.
67. execlp()
The execlp() function searches the executable using the
PATH environment variable.
execlp("ls",
"ls",
"-l",
NULL);
Unlike execl(), the full path does not need to be specified.
68. execvp()
The execvp() function combines two features:
- Arguments supplied as an array.
- Executable searched using PATH.
char *args[] =
{
"ls",
"-l",
NULL
};
execvp("ls", args);
69. execle()
The execle() function allows a program to specify a custom
set of environment variables for the new program.
char *env[] =
{
"USER=guest",
NULL
};
execle("/bin/printenv",
"printenv",
NULL,
env);
70. execve()
The execve() function is the underlying interface used by the
kernel to execute a new program.
Most other members of the exec() family eventually invoke
execve() after preparing the required arguments.
int execve(const char *pathname,
char *const argv[],
char *const envp[]);
71. Choosing the Correct Function
| If You Need... | Recommended Function |
|---|---|
| Arguments one by one | execl() |
| Arguments in an array | execv() |
| Automatic PATH search | execlp() |
| PATH search with array arguments | execvp() |
| Custom environment variables | execle() |
| Direct kernel interface | execve() |
72. Summary
- All exec() functions replace the current program.
- None of them creates a new process.
- The PID remains unchanged.
- The differences are only in argument passing, PATH searching, and environment handling.
- Most applications commonly use
execvp()because it supports PATH searching and array-based arguments.
73. Interview Questions
- What is the difference between
execl()andexecv()? - When should
execvp()be preferred overexecv()? - Which function allows custom environment variables?
- Which function is ultimately invoked by most members of the
exec()family? - Why is
execvp()commonly used in shells?
74. The wait() System Call
After creating a child process using fork(), the parent
process often needs to wait until the child finishes execution.
Linux provides the wait() system call for this purpose.
When a parent calls wait(), it is suspended until one of its
child processes terminates.
wait() system call synchronizes the execution of parent
and child processes.
75. Why Do We Need wait()?
Suppose a parent process creates a child that writes data into a file.
If the parent immediately starts reading the file before the child has
finished writing, incomplete or incorrect results may occur.
Using wait() ensures that the parent proceeds only after the
child has completed its work.
76. Syntax of wait()
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);
| Parameter | Description |
|---|---|
| status | Pointer used to receive the child's exit status. |
77. Return Value of wait()
| Return Value | Meaning |
|---|---|
| > 0 | PID of the terminated child process. |
| -1 | No child exists or an error occurred. |
78. Example Program
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid == 0)
{
printf("Child Running\n");
sleep(3);
printf("Child Finished\n");
}
else
{
printf("Parent Waiting...\n");
wait(NULL);
printf("Parent Continues\n");
}
return 0;
}
79. Sample Output
Parent Waiting... Child Running Child Finished Parent Continues
The parent process does not continue until the child terminates.
80. Execution Timeline
81. Process State During wait()
| Process | State |
|---|---|
| Parent | Waiting (Blocked) |
| Child | Running |
| After Child Exits | Parent becomes Ready and resumes execution. |
82. Kernel Activity During wait()
83. Complete Process Flow
84. Key Points
wait()suspends the parent until a child process terminates.- The kernel wakes the parent after the child exits.
- The child's exit status can be collected.
wait()helps synchronize parent and child execution.- Most command-line shells use
wait()afterfork()andexec().
85. Interview Questions
- Why is
wait()required afterfork()? - What happens if the parent never calls
wait()? - Does
wait()consume CPU while waiting? - What value does
wait()return? - Can one parent wait for multiple child processes?
86. Child Process Termination using exit()
Every process eventually completes its execution.
When a process finishes, it informs the operating system that it is
terminating.
This is performed using the exit() library function,
which eventually invokes the kernel to terminate the process.
The process may also terminate by returning from the
main() function.
main() is equivalent to calling
exit() with the same return value.
87. Syntax of exit()
#include <stdlib.h> void exit(int status);
| Parameter | Description |
|---|---|
| status | Exit status returned to the parent process. |
88. Exit Status
Every process returns an integer value when it terminates.
The parent process can retrieve this value using
wait().
| Status | Meaning |
|---|---|
| 0 | Program completed successfully. |
| Non-zero | Program terminated with an error or a specific application-defined status. |
89. Example Program
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Program Started\n");
exit(0);
printf("This statement never executes\n");
return 0;
}
After exit(0) is executed, the process terminates immediately.
Any statements following exit() are not executed.
90. Parent Receiving Child Exit Status
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int status;
pid_t pid = fork();
if(pid == 0)
{
printf("Child Running\n");
exit(25);
}
wait(&status);
printf("Child Finished\n");
return 0;
}
91. Kernel Activity During exit()
92. What Happens Inside the Kernel?
| Step | Kernel Operation |
|---|---|
| 1 | Stop executing the process. |
| 2 | Close all open file descriptors. |
| 3 | Release process memory. |
| 4 | Release other allocated kernel resources. |
| 5 | Save exit status. |
| 6 | Notify the parent process. |
| 7 | Keep a minimal process entry until the parent collects the status. |
93. Why Doesn't Linux Remove the Process Immediately?
The parent process may need to know whether the child completed
successfully or failed.
For this reason, Linux temporarily retains a small amount of information
about the terminated process, including its Process ID (PID) and exit
status.
Once the parent calls wait(), this information is collected
and the remaining process entry is removed.
94. Complete Process Life Cycle
95. Important Points
exit()terminates the calling process.- Returning from
main()has the same effect as callingexit(). - The parent can obtain the child's exit status using
wait(). - The kernel temporarily retains termination information until the parent collects it.
- This mechanism prepares the foundation for understanding zombie processes.
96. Interview Questions
- What is the difference between
return 0;andexit(0);? - Why is the exit status stored by the kernel?
- What resources are released when a process exits?
- Why doesn't Linux immediately remove a terminated process?
- Which system call allows the parent to collect the child's exit status?
97. Zombie Process
A Zombie Process is a process that has finished executing,
but whose termination status has not yet been collected by its parent
using wait() or waitpid().
Although the process has stopped executing and most of its resources have
already been released, the kernel retains a small amount of information
about it so that the parent can obtain its termination status.
98. Why Does Linux Create Zombie Processes?
When a child process terminates, the parent may still need to determine:
- Whether the child completed successfully.
- Why the child terminated.
- The exit status returned by the child.
To preserve this information, the kernel keeps a minimal process
descriptor until the parent retrieves it using
wait() or waitpid().
99. Process Life Cycle Including Zombie State
100. Resources Released During exit()
| Resource | Released Immediately? |
|---|---|
| User-space memory | Yes |
| Open file descriptors | Yes |
| Kernel resources | Most are released |
| Process table entry | No (retained until wait()) |
| Exit status | No (retained until wait()) |
| Process ID (PID) | Retained until the zombie is reaped |
101. Zombie Process Example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
printf("Child exiting...\n");
exit(0);
}
sleep(30);
return 0;
}
The child terminates immediately.
The parent intentionally does not call
wait().
For approximately 30 seconds, the child remains a zombie process.
102. Viewing Zombie Processes
ps -el or ps -aux
A zombie process is typically displayed with the process state Z. Example:
PID PPID STAT COMMAND 2480 2475 Z a.out
103. Kernel View of a Zombie Process
104. What Happens If wait() Is Never Called?
If the parent process never calls
wait() or
waitpid(),
the zombie remains in the process table until one of the following occurs:
- The parent process terminates.
- The parent later collects the child's termination status.
If the parent terminates before collecting the child's status,
the zombie is adopted and eventually reaped by the system's child-reaper
process (typically init or systemd, depending on
the system).
105. Key Observations
- A zombie process has already finished execution.
- It does not execute instructions.
- It does not consume CPU time.
- Only minimal kernel bookkeeping information remains.
- The parent should call
wait()orwaitpid()to remove the zombie.
106. Interview Questions
- What is a zombie process?
- Why does Linux keep zombie processes?
- Do zombie processes consume CPU time?
- Which process resources remain after a child exits?
- How can a zombie process be removed?
- Which process eventually reaps an unreaped zombie if the parent exits?
107. Orphan Process
An Orphan Process is a running process whose parent
process terminates before the child finishes execution.
The child process continues running normally even though its original
parent no longer exists.
To ensure that every process always has a parent, the kernel immediately
reassigns the orphan process to the system's child-reaper process
(typically init or systemd, depending on the
Linux system).
108. How Does an Orphan Process Occur?
109. Example Program
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{
sleep(5);
printf("Child PID = %d\n", getpid());
printf("Parent PID = %d\n", getppid());
exit(0);
}
printf("Parent Exiting...\n");
return 0;
}
The parent terminates immediately.
The child continues executing.
When the child later calls getppid(), it reports the Process
ID of its new parent.
110. Example Output
Parent Exiting... Child PID = 4210 Parent PID = 1
On many Linux systems, the child is re-parented to PID 1, which is
typically systemd or another process acting as the system's
child-reaper.
111. Kernel Activity
| Step | Kernel Operation |
|---|---|
| 1 | Parent process exits. |
| 2 | Kernel checks for active child processes. |
| 3 | Each running child is re-parented to the system's child-reaper process. |
| 4 | The child continues executing without interruption. |
| 5 | When the child later terminates, its new parent collects its exit status. |
112. Orphan Process Life Cycle
113. Zombie vs Orphan Process
| Zombie Process | Orphan Process |
|---|---|
| Already terminated. | Still executing. |
| Waiting to be reaped. | Has lost its original parent. |
| Consumes no CPU time. | May continue using CPU normally. |
| Retains a minimal process table entry. | Executes like any other process. |
Removed after wait() or waitpid(). |
Adopted by the system's child-reaper process. |
114. Real Linux Example
Many server applications intentionally create orphan processes. For example, a server may create a background worker process and then allow the original parent process to terminate. The background process continues running independently under the supervision of the operating system.
115. Important Points
- An orphan process is still running.
- It is automatically re-parented by the kernel.
- The child continues execution normally.
- Re-parenting ensures that every process always has a parent.
- The system's child-reaper process eventually collects the child's exit status after it terminates.
116. Interview Questions
- What is an orphan process?
- How is an orphan process different from a zombie process?
- Who becomes the new parent of an orphan process?
- Does an orphan process stop executing?
- Why does Linux re-parent orphan processes?
117. The waitpid() System Call
The wait() system call waits for any one child
process to terminate.
However, many applications create multiple child processes and need to
wait for a specific child.
For this purpose, UNIX and Linux provide the
waitpid() system call.
waitpid() gives the parent process finer control over which
child process it waits for and how it waits.
118. Why Do We Need waitpid()?
Consider a server that creates three child processes.
Using wait(), the parent cannot choose which child finishes
first.
Using waitpid(), the parent can wait for a specific child
identified by its Process ID (PID).
119. Syntax
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,
int *status,
int options);
120. Parameters
| Parameter | Description |
|---|---|
pid |
Selects which child process(es) to wait for. |
status |
Receives the child's termination status. |
options |
Controls the behavior of waitpid(). |
121. Meaning of the pid Parameter
| pid Value | Meaning |
|---|---|
| > 0 | Wait for the child whose PID exactly matches this value. |
| -1 | Wait for any child process (similar to wait()). |
| 0 | Wait for any child process in the same process group as the caller. |
| < -1 | Wait for any child whose process group ID equals the absolute value of pid. |
122. Common Options
| Option | Description |
|---|---|
0 |
Block until the selected child changes state. |
WNOHANG |
Return immediately if no selected child has changed state. |
WUNTRACED |
Also report children that have stopped. |
WCONTINUED |
Also report children that were resumed after being stopped (if supported). |
123. Example Program
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
pid_t child;
child = fork();
if(child == 0)
{
sleep(2);
exit(0);
}
printf("Waiting for child %d\n", child);
waitpid(child, NULL, 0);
printf("Child Finished\n");
return 0;
}
124. Execution Flow
125. wait() vs waitpid()
| wait() | waitpid() |
|---|---|
| Waits for any child. | Can wait for a specific child. |
| Less control. | More control. |
| No options. | Supports multiple options. |
| Simple programs. | Servers, shells, and multi-process applications. |
126. Key Points
waitpid()extends the functionality ofwait().- It allows a parent to wait for a specific child process.
- It supports blocking and non-blocking operation.
- It can monitor different child state changes through option flags.
- Most UNIX shells and server applications use
waitpid()instead ofwait()because it provides greater flexibility.
127. Interview Questions
- Why is
waitpid()preferred overwait()in server applications? - What is the purpose of the
pidparameter? - What does
WNOHANGdo? - How does
waitpid()support non-blocking behavior? - When would you use
wait()instead ofwaitpid()?
128. Understanding Child Exit Status
When a child process terminates, the parent can determine
how the child finished by examining the status value
returned by wait() or waitpid().
The status value is an encoded integer interpreted using macros defined
in <sys/wait.h>.
Applications should use these macros instead of attempting to interpret
the integer value directly.
status is implementation-defined.
Portable applications should always use the POSIX macros provided in
<sys/wait.h>.
129. Why Are Macros Required?
The integer returned by wait() contains multiple pieces of
information, such as:
- Whether the child exited normally.
- Whether the child was terminated by a signal.
- The child's exit code.
- Whether the child was stopped or resumed (when requested).
Because this information is encoded into a single integer, the operating system provides standard macros to decode it safely.
130. Common Status Macros
| Macro | Purpose |
|---|---|
WIFEXITED(status) |
True if the child terminated normally. |
WEXITSTATUS(status) |
Returns the child's exit code (valid only if WIFEXITED() is true). |
WIFSIGNALED(status) |
True if the child terminated because of a signal. |
WTERMSIG(status) |
Returns the terminating signal number. |
WIFSTOPPED(status) |
True if the child is currently stopped. |
WSTOPSIG(status) |
Returns the signal that stopped the child. |
WIFCONTINUED(status) |
True if a previously stopped child resumed execution (when supported). |
131. Normal Process Termination
If a program finishes by:
- calling
exit(), or - returning from
main(),
then the parent can detect this using
WIFEXITED(status).
132. Example Program
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int status;
pid_t pid = fork();
if(pid == 0)
{
exit(25);
}
wait(&status);
if(WIFEXITED(status))
{
printf("Exit Code = %d\n",
WEXITSTATUS(status));
}
return 0;
}
133. Sample Output
Exit Code = 25
134. Child Terminated by a Signal
Not every process ends by calling exit().
A process may terminate because it receives a signal, such as
SIGSEGV, SIGABRT, or SIGKILL.
The parent can detect this using
WIFSIGNALED(status).
135. Example
if(WIFSIGNALED(status))
{
printf("Signal = %d\n",
WTERMSIG(status));
}
136. Summary Table
| Question | Macro |
|---|---|
| Did the child exit normally? | WIFEXITED() |
| What was the exit code? | WEXITSTATUS() |
| Did a signal terminate the child? | WIFSIGNALED() |
| Which signal terminated the child? | WTERMSIG() |
| Was the child stopped? | WIFSTOPPED() |
| Which signal stopped it? | WSTOPSIG() |
137. Important Points
- Never interpret the raw status integer directly.
- Always use the macros provided by
<sys/wait.h>. WEXITSTATUS()is valid only ifWIFEXITED()returns true.WTERMSIG()is valid only ifWIFSIGNALED()returns true.- These macros provide a portable interface across POSIX systems.
138. Interview Questions
- Why should you never interpret the raw status value directly?
- What is the difference between
WIFEXITED()andWEXITSTATUS()? - When is
WTERMSIG()valid? - Which macro determines whether a child terminated because of a signal?
- Why does POSIX provide status macros instead of exposing the status format directly?
139. How a UNIX Shell Executes a Command
One of the most common uses of the
fork(),
exec(), and
waitpid() system calls is inside a UNIX shell.
Whenever a user enters a command, the shell creates a child process,
executes the requested program in the child, and waits for the child to
finish before displaying the next command prompt (for foreground
commands).
$ ls -lThe shell itself does not become the
ls program.
Instead, it creates a child process that executes
ls.
140. Command Execution Flow
141. Step-by-Step Execution
| Step | Description |
|---|---|
| 1 | User enters a command. |
| 2 | The shell parses the command line. |
| 3 | The shell calls fork(). |
| 4 | The child process calls an exec() function to replace its program image. |
| 5 | The requested program executes. |
| 6 | The program terminates using exit() or by returning from main(). |
| 7 | The parent shell calls waitpid() to collect the child's termination status. |
| 8 | The shell displays the next prompt. |
142. Simplified Shell Implementation
while(1)
{
read_command();
pid_t pid = fork();
if(pid == 0)
{
execvp(command[0], command);
exit(EXIT_FAILURE);
}
waitpid(pid, NULL, 0);
}
This simplified example demonstrates the fundamental control flow used by many command-line shells for executing foreground commands.
143. Complete Process Life Cycle
144. Key Concepts Learned in This Chapter
- Creating a new process with
fork(). - Understanding parent and child processes.
- Copy-on-Write (COW) optimization.
- Replacing the process image with the
exec()family. - Synchronizing processes using
wait()andwaitpid(). - Handling normal termination with
exit(). - Retrieving child termination status.
- Zombie and orphan processes.
- Using status macros such as
WIFEXITED()andWEXITSTATUS(). - Understanding how a UNIX shell launches programs.
145. Interview Questions
- Why does a shell use
fork()before callingexec()? - Why doesn't the shell call
exec()directly? - Why is
waitpid()preferred by shells? - What happens if the shell never waits for foreground child processes?
- How does the shell regain control after a command finishes?
147. Best Practices
- Always check the return value of
fork()for errors. - Call an
exec()function only in the child process unless replacing the current process is the intended behavior. - If an
exec()call fails, handle the error and terminate the child process appropriately. - Ensure that every terminated child process is reaped using
wait()orwaitpid()to avoid zombie processes. - Use
waitpid()when an application manages multiple child processes. - Always interpret the child status using the macros defined in
<sys/wait.h>. - Close file descriptors that are no longer needed after
fork(), especially before executing another program.
148. Common Programming Mistakes
| Mistake | Possible Result |
|---|---|
Ignoring the return value of fork() |
Failure to detect process creation errors. |
Not calling wait() or waitpid() |
Zombie processes accumulate. |
Using WEXITSTATUS() without first checking WIFEXITED() |
Undefined or meaningless results. |
Assuming parent and child share writable memory after fork() |
Incorrect program logic. |
Continuing execution after a failed exec() without handling the error |
Unexpected behavior. |
149. Performance Considerations
- Modern Linux uses Copy-on-Write (COW), so
fork()does not immediately duplicate all memory pages. - The cost of
fork()increases with the size and complexity of the process because page tables and kernel data structures must still be created. - Replacing the child immediately with an
exec()function is a common and efficient pattern. - Long-running server applications often use
waitpid()with non-blocking options to manage multiple child processes.
150. Complete Chapter Review
151. Quick Revision
| Concept | Purpose |
|---|---|
fork() |
Create a child process. |
exec() |
Replace the current process image. |
wait() |
Wait for any child process. |
waitpid() |
Wait for a specific child process. |
exit() |
Terminate a process. |
| Zombie | Terminated child awaiting reaping. |
| Orphan | Running child whose parent has terminated. |
| Copy-on-Write | Delay copying memory until modification. |
152. Practice Exercises
- Write a program that creates one child process and prints the PIDs of both the parent and child.
- Create two child processes and wait for them using
waitpid(). - Modify a variable in the child after
fork()and observe Copy-on-Write behavior. - Execute the
lscommand usingexecvp(). - Create a zombie process and remove it using
wait(). - Create an orphan process and observe the new parent process using
getppid(). - Write a simple shell that repeatedly reads a command, executes it using
fork()andexecvp(), and waits for completion.
153. Next Chapter
The next chapter explores how the operating system decides which process runs next. Topics include:
- CPU Scheduling
- Scheduling Goals
- Scheduling Criteria
- FCFS
- SJF
- Priority Scheduling
- Round Robin
- Multilevel Queue
- Multilevel Feedback Queue
- Linux Completely Fair Scheduler (CFS)
- Real-Time Scheduling
- Context Switching
Concept Mastery Assessment — Module 15
Verify your conceptual understanding with 12 targeted technical assessment questions. Instant feedback and explanations are provided for every choice.