Understanding Linux Process Creation, Waiting, and Execution: fork, wait, and exec
This article explains how Linux processes are created, managed, and replaced using the fork, wait (including waitpid), and exec system calls, covering their prototypes, return values, copy‑on‑write optimization, and practical C code examples that demonstrate their coordinated use in real‑world scenarios.
In Linux, a process is the basic unit of resource allocation and scheduling, represented by a task_struct (PCB). Each process has its own address space, file descriptors, and registers, and the kernel manages them via the PCB.
Processes enable multitasking; for example, each terminal window runs as an independent process, allowing concurrent execution without interference.
The three essential system calls for process programming are fork (create a new process), wait (parent waits for child termination), and exec (replace the current program image).
1. Process Creation: fork Function Analysis
1.1 fork Basics
fork is declared in <unistd.h> as pid_t fork(void); . It creates a child process that is almost an exact copy of the parent, assigning a new PID while sharing some resources such as open file descriptors.
1.2 fork Return Values and Execution Logic
The call returns twice: in the parent it returns the child's PID, in the child it returns 0, and on failure it returns -1 and sets errno . This allows the program to distinguish parent and child execution paths.
#include
#include
#include
int main() {
pid_t pid;
// Create child process
pid = fork();
// Check return value
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// Child process
printf("I am the child process, my pid is %d, my parent's pid is %d\n", getpid(), getppid());
} else {
// Parent process
printf("I am the parent process, my pid is %d, and my child's pid is %d\n", getpid(), pid);
}
return 0;
}Running the program prints the PID of both parent and child, clearly showing their separate execution paths.
1.3 Copy‑On‑Write Mechanism
Instead of copying the entire address space immediately, Linux marks shared pages as read‑only and duplicates them only when a write occurs. This reduces memory usage and speeds up fork for large processes.
2. Process Waiting: wait Function Analysis
2.1 Purpose of wait
wait blocks the parent until one of its children terminates, then reclaims the child's resources and provides its exit status, preventing zombie processes.
2.2 Prototype and Parameters
Declared in <sys/types.h> and <sys/wait.h> as pid_t wait(int *status); . If status is NULL, the parent only waits; otherwise, the exit information is stored in the pointed integer.
WIFEXITED(status): true if child exited normally.
WEXITSTATUS(status): returns the child's exit code.
WIFSIGNALED(status): true if child was terminated by a signal.
WTERMSIG(status): returns the terminating signal number.
2.3 wait Example
#include
#include
#include
#include
#include
int main() {
pid_t pid;
int status;
// Create child process
pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// Child process
printf("I am the child process, my pid is %d\n", getpid());
sleep(2);
exit(3);
} else {
// Parent process
printf("I am the parent process, my pid is %d, and my child's pid is %d\n", getpid(), pid);
wait(&status);
if (WIFEXITED(status)) {
printf("The child process exited normally, exit status is %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("The child process was terminated by a signal, signal number is %d\n", WTERMSIG(status));
}
}
return 0;
}The output shows the parent and child PIDs and the child's exit status.
2.4 waitpid Extension
waitpid (prototype pid_t waitpid(pid_t pid, int *status, int options); ) adds flexibility:
Specify which child to wait for (positive PID, 0 for same process group, -1 for any child, < -1 for any child in a specific group).
Use options such as WNOHANG for non‑blocking wait.
Retrieve additional states with WUNTRACED and WCONTINUED .
Example of non‑blocking waitpid :
#include
#include
#include
#include
#include
int main() {
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
printf("I am the child process, my pid is %d\n", getpid());
sleep(5);
exit(3);
} else {
printf("I am the parent process, my pid is %d, and my child's pid is %d\n", getpid(), pid);
while (1) {
pid_t ret = waitpid(pid, &status, WNOHANG);
if (ret == 0) {
printf("The child process is still running, I can do other things\n");
sleep(1);
} else if (ret == pid) {
if (WIFEXITED(status)) {
printf("The child process exited normally, exit status is %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("The child process was terminated by a signal, signal number is %d\n", WTERMSIG(status));
}
break;
} else {
perror("waitpid error");
break;
}
}
}
return 0;
}This demonstrates that the parent can continue other work while periodically checking the child's status.
3. Program Replacement: exec Function Analysis
3.1 exec Family Overview
The exec family replaces the current process image with a new program without creating a new PID. It is commonly used after fork to run a different command, such as a shell executing ls .
3.2 Prototypes and Parameters
#include
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);Key parameters:
path : absolute path to the executable.
file : name searched in PATH if it contains no '/'.
arg/argv : arguments passed to the new program; the last element must be NULL .
envp : optional environment array; if omitted, the child inherits the parent's environment.
3.3 exec Example
#include
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// Child process executes "ls -l" via execlp
if (execlp("ls", "ls", "-l", NULL) == -1) {
perror("execlp error");
exit(EXIT_FAILURE);
}
} else {
// Parent waits for child
wait(NULL);
printf("Child process has finished.\n");
}
return 0;
}The child replaces its image with ls -l , the parent waits, and then prints a completion message.
4. Coordination of fork, wait, and exec
4.1 Common Application Scenarios
In a shell, the user command triggers fork to create a child, the child calls exec to run the command (e.g., ls -l ), and the parent uses wait to reap the child. Web servers use the same pattern to handle client connections with separate child processes.
4.2 Full Code Demonstration
#include
#include
#include
#include
#include
int main() {
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// Child process executes "ls -l"
char *argv[] = {"ls", "-l", NULL};
if (execvp("ls", argv) == -1) {
perror("execvp error");
exit(EXIT_FAILURE);
}
} else {
// Parent process
printf("I am the parent process, my pid is %d, and my child's pid is %d\n", getpid(), pid);
wait(&status);
if (WIFEXITED(status)) {
printf("The child process exited normally, exit status is %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("The child process was terminated by a signal, signal number is %d\n", WTERMSIG(status));
}
}
return 0;
}The parent calls fork to create a child.
The child receives a return value of 0, builds an argument array, and calls execvp to run ls -l . On success, the child's memory image is replaced.
The parent receives the child's PID, prints both PIDs, and calls wait to block until the child finishes, then reports the child's exit status.
This example clearly shows how fork , exec , and wait work together to create a new process, replace its program, and clean up resources.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.