Why Understanding fork, exec, and COW Is Crucial for Linux Developers
This article demystifies Linux's fork, exec, and copy‑on‑write mechanisms, explaining their inner workings, performance trade‑offs, and how they cooperate during typical command execution, while providing clear code examples and practical insights for developers and interview preparation.
Introduction
Many programmers claim to be familiar with the Linux kernel, yet when asked about the low‑level logic of fork, exec and the Copy‑On‑Write (COW) optimization, they often stumble. Understanding these mechanisms separates superficial API knowledge from true kernel insight.
1. Linux fork mechanism
1.1 What is fork?
fork()is defined in <unistd.h> and creates a new process that is a near‑identical copy of the calling (parent) process. The child receives a new PID, its own task_struct, and initially shares most resources (code, data, file descriptors) with the parent.
1.2 How fork works internally
The system call is a thin wrapper around sys_clone(), which eventually invokes do_fork(). The kernel validates permissions, allocates a fresh task_struct, copies the parent’s descriptor fields, and sets up a new virtual memory space. Instead of copying all pages, the kernel marks them read‑only and enables COW, so the parent and child share the same physical pages until one of them writes.
1.3 Return values
On success, fork() returns twice: the child's PID in the parent, and 0 in the child. On failure it returns -1 and sets errno.
1.4 Example
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
printf("I am the child, pid=%d, ppid=%d
", getpid(), getppid());
} else {
printf("I am the parent, pid=%d, child pid=%d
", getpid(), pid);
}
return 0;
}The parent can later use wait or waitpid to reap the child.
1.5 Performance considerations
Fork incurs CPU cycles for descriptor copying, permission changes, and possible context switches. Without COW, a large parent would require a full memory copy, dramatically increasing both time and space overhead. COW mitigates this by deferring actual page duplication until a write occurs.
2. Linux exec mechanism
2.1 What is exec?
execreplaces the current process image with a new program while keeping the same PID. It is not a new process creation; it merely swaps the code and data segments.
2.2 exec function family
execl : int execl(const char *path, const char *arg, ...) execlp : searches PATH for the program name.
execv : int execv(const char *path, char *const argv[]) execvp : combines execlp and execv.
2.3 Example of execl
#include <stdio.h>
#include <unistd.h>
int main() {
execl("/bin/ls", "ls", "-l", NULL);
perror("execl failed");
return 1;
}2.4 How exec works
The kernel parses the executable format (typically ELF), creates a new virtual memory layout, maps the program’s segments, resolves dynamic libraries, updates the task_struct, and jumps to the entry point. All previous user‑space resources (heap, stack, data) are discarded unless explicitly preserved (e.g., open file descriptors).
2.5 Typical usage pattern
Shells first fork() to create a child, then the child calls an exec variant to run the requested command. The parent waits for the child to finish, preventing zombie processes.
3. Copy‑On‑Write (COW) mechanism
3.1 Why COW exists
Early UNIX duplicated the entire address space on fork, wasting memory and CPU. Modern Linux uses COW: pages are shared read‑only; a write triggers a page‑fault, after which the kernel allocates a new page, copies the data, and updates the faulting process’s page table.
3.2 COW workflow
During fork, the kernel marks all writable pages of the parent as read‑only and increments a reference count.
If either process writes to a shared page, the CPU raises a page‑fault.
The kernel’s fault handler allocates a fresh physical page, copies the original content, updates the page‑table entry to point to the new page, and clears the read‑only flag.
3.3 Kernel implementation snippet
In mm/copy_on_write.c, the function do_cow_fault() performs the allocation ( alloc_pages_current), copies data ( memcpy), and updates the PTE with set_pte.
static int do_cow_fault(struct vm_area_struct *vma, struct page *page, unsigned long address)
{
struct page *new_page = alloc_pages_current(GFP_KERNEL, 0);
if (!new_page)
return -ENOMEM;
memcpy(page_address(new_page), page_address(page), PAGE_SIZE);
set_pte(vma, address, mk_pte(new_page, vma->vm_page_prot));
return 0;
}3.4 Performance impact
COW dramatically reduces memory usage for short‑lived processes (e.g., web servers spawning many workers). Benchmarks show 30‑50% lower memory consumption under high concurrency. However, write‑intensive workloads suffer extra page‑fault overhead, potentially degrading throughput by up to 15% in write‑heavy scenarios.
4. Interaction between fork, exec and COW
4.1 fork ↔ COW
When fork() creates a child, the kernel immediately enables COW, allowing parent and child to share pages without copying. Only when one side writes does the kernel duplicate the affected page.
4.2 exec ↔ COW
Calling exec discards the current user‑space mappings, so previously shared COW pages become irrelevant and are reclaimed. If no writes occurred before exec, the pages are freed without ever being copied.
4.3 Full workflow example (shell command ls -l )
The shell (bash) parses the command, calls fork() to create a child, the child inherits the parent's memory via COW, then the child invokes execlp("ls", "ls", "-l", NULL). The kernel loads /bin/ls, replaces the child's address space, and the shared pages are released. The parent waits for the child to finish.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
printf("Parent (bash): waiting for command
");
pid_t pid = fork();
if (pid == 0) {
printf("Child: executing ls -l
");
execlp("ls", "ls", "-l", NULL);
perror("exec failed");
exit(EXIT_FAILURE);
} else if (pid > 0) {
wait(NULL);
printf("Parent: child finished
");
} else {
perror("fork failed");
}
return 0;
}This example demonstrates how COW saves memory during fork, while exec swaps in the new program, and the parent reaps the child.
5. Takeaways for developers
Grasping the internals of fork, exec and COW equips developers to write more efficient code, diagnose performance bottlenecks, and succeed in technical interviews that frequently probe these concepts.
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.
