In advanced Linux programming, a Process is defined as an instance of a computer program in execution. Unlike shell scripts which run sequential command-line tools, system programming involves writing compiled C programs that directly invoke POSIX operating system primitives called System Calls.
In Linux, new processes are created through duplication using the fork() system call. The child process may then replace its memory space with an entirely new executable using execve(), while the parent monitors its lifecycle using wait():
Figure 4: Complete Linux process lifecycle and system call interaction.
fork() System CallThe fork() system call creates a new process by duplicating the calling process. It returns twice:
In the Child Process: fork() returns 0.
In the Parent Process: fork() returns the PID of the child (> 0).
On Failure: fork() returns -1.
/*
* fork_demo.c - Demonstrates process creation using fork()
* Compile: gcc fork_demo.c -o fork_demo
* Run : ./fork_demo
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid;
printf("Parent process started (PID: %d)\n", getpid());
// Fork the process
pid = fork();
if (pid < 0) {
perror("Fork failed");
return 1;
} else if (pid == 0) {
// Child process code block
printf("[CHILD] Hello from Child! PID: %d, Parent PID: %d\n", getpid(), getppid());
sleep(2);
printf("[CHILD] Child process finishing.\n");
return 42; // Exit status
} else {
// Parent process code block
int status;
printf("[PARENT] Spawned child with PID: %d. Waiting for child to terminate...\n", pid);
// Wait for child to exit to prevent zombie state
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("[PARENT] Child terminated normally with exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
exec() Family of System CallsWhile fork() creates a clone, the exec() family replaces the entire memory space of the process (code text, data segment, stack, and heap) with a fresh executable binary file from disk:
/*
* exec_demo.c - Executes the 'ls -l' command using execlp()
*/
#include <stdio.h>
#include <unistd.h>
int main() {
printf("About to execute 'ls -l' using execlp...\n");
// execlp searches PATH for "ls", passing arguments terminated by NULL
execlp("ls", "ls", "-l", "/home", NULL);
// This line ONLY executes if execlp encounters an error
perror("execlp execution failed");
return 1;
}
| Property | Zombie Process (Defunct) | Orphan Process |
|---|---|---|
| Definition | Child has terminated via exit(), but parent hasn't collected status via wait(). |
Parent process terminated or crashed while the child is still actively running. |
| Process Table Status | Shows status Z in ps aux or top. |
Active, running normally. |
| Parent Process ID | Original Parent PID. | Adopted by PID 1 (systemd / init). |
| How to Clean Up | Parent must call wait() or waitpid(). Killing parent forces init to reap it. |
Cleaned up automatically by PID 1 upon child termination. |
Problem Statement: Write a C program on Linux that creates 2 child processes simultaneously. Child 1 prints its PID and exits with code 10. Child 2 sleeps for 1 second, prints its PID, and exits with code 20. The parent must wait for both children and display each child's exit status.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t c1, c2;
int status;
printf("Parent PID: %d\n", getpid());
if ((c1 = fork()) == 0) {
printf("Child 1 (PID: %d) executing.\n", getpid());
exit(10);
}
if ((c2 = fork()) == 0) {
sleep(1);
printf("Child 2 (PID: %d) executing.\n", getpid());
exit(20);
}
// Parent waits for both children
pid_t terminated_pid;
while ((terminated_pid = wait(&status)) > 0) {
printf("Parent reaped Child PID %d with exit code: %d\n",
terminated_pid, WEXITSTATUS(status));
}
printf("All children reaped successfully.\n");
return 0;
}
In modern Linux kernels, fork() does not immediately copy all physical RAM pages of the parent to the child. Instead, both processes share the same read-only pages. A page is only duplicated in physical memory when either process writes to it. This optimization makes fork() extraordinarily fast.
Run the command: ps aux | grep 'Z' or top (check the 'zombie' count in the top summary line). Defunct zombie processes show as <defunct> in process listings.
Explore open, read, write, close, and lseek system calls in Class 26: Low-Level File I/O in C.
Continue to Class 26: Low-Level File I/O in C → Previous: ← String Operations | Full Syllabus