TL;DR — Copy‑on‑write (CoW) lets the kernel share physical pages between parent and child after a
fork(), postponing actual copies until a write occurs. This strategy slashes memory consumption, accelerates process creation, and improves cache and TLB behavior on modern systems.
Process creation is the most frequent operation in a multitasking operating system. Every time a user launches a program, the kernel must allocate a new address space, copy the executable image, and set up bookkeeping structures. Historically, this was an expensive, memory‑hungry step. Modern kernels—Linux, BSD, macOS—have converged on a single powerful technique: copy‑on‑write. By deferring copies until they are truly needed, CoW turns a potentially linear‑time, memory‑intensive operation into a near‑constant‑time, memory‑light one. The following sections unpack the mechanics, the performance wins, and the practical limits of CoW in today’s kernels.
The Fundamentals of Copy‑on‑Write
Historical Context
Before CoW, the classic fork() implementation performed a deep copy of the parent’s entire address space. On a system with 8 GiB of RAM, for example, a naïve fork could temporarily require an additional 8 GiB of physical memory just to duplicate page tables and data. Early Unix variants mitigated this with swap and overcommit, but the cost remained prohibitive for high‑frequency process spawning (e.g., web servers handling thousands of requests per second).
The breakthrough came in the early 1990s when the Linux kernel introduced CoW for page tables (see the original patch by Linus Torvalds in 1992). The idea was borrowed from the virtual memory subsystem of the Mach microkernel and the COW semantics of the mmap() system call, which already allowed multiple processes to map the same file pages read‑only.
How CoW Works at the Page Level
At the hardware level, virtual memory is organized into pages (commonly 4 KiB). Each page table entry (PTE) contains flags such as present, read/write, and dirty. When a fork() occurs:
- The kernel clones the parent’s page tables, but instead of copying every physical page, it marks each shared page as read‑only in both the parent and child.
- Both processes receive identical PTEs that point to the same physical frame, and a reference count for that frame is incremented.
- The first time either process attempts to write to a shared page, the CPU raises a page‑fault because the page is not writable.
- The kernel’s fault handler checks the reference count. If it is greater than one, the handler allocates a new page, copies the original contents, updates the PTE to point to the new page with write permission, and decrements the reference count on the original frame.
This lazy copying mechanism is the essence of CoW. The heavy lifting—allocating a new page and copying memory—only happens on a write to a previously shared page. If a process never writes to most of its memory, those pages stay shared for its entire lifetime.
Memory Savings in Practice
Forked Processes and Page Sharing
Consider a typical web server that spawns a new worker process for each incoming connection. Each worker loads the same binary and libraries, which occupy many megabytes of read‑only code and data. With CoW, all workers share the same physical pages for the executable and shared libraries:
- Before CoW: 10 workers × 100 MiB ≈ 1 GiB of memory.
- After CoW: 100 MiB (shared) + 10 × 10 MiB (private dirty pages) ≈ 200 MiB.
The savings become dramatic when the shared portion dominates, as is the case for containerized workloads that often run identical images.
Reducing Page Fault Overhead
When a process accesses a page for the first time, the kernel may need to populate it from swap or a file. CoW reduces the number of write page faults because many pages remain read‑only. A study of the NGINX web server on a 16‑core Linux box showed a 30 % reduction in major page faults after enabling CoW‑friendly fork patterns (see the LWN article “The fork() story”).
Fewer page faults translate to less I/O pressure on the storage subsystem and lower latency for latency‑sensitive services.
Performance Benefits Beyond Memory
Cache Locality
Modern CPUs rely on multi‑level caches (L1, L2, L3) to bridge the speed gap between registers and main memory. When two processes share a page, the cache line containing that page may be present in the core’s private caches. If the child reads the same data the parent just read, the cache hit rate improves dramatically. Because CoW delays copying, the temporal locality of read‑only data is preserved across processes.
A benchmark on an Intel Xeon Scalable processor demonstrated a 12 % increase in L3 cache hit rate for a workload that heavily reused read‑only data after a mass fork() (source: Intel Developer Zone).
Reduced TLB Pressure
The Translation Lookaside Buffer (TLB) caches recent virtual‑to‑physical translations. Each new page mapping consumes a TLB entry. By sharing pages, CoW reduces the number of distinct physical frames that need to be mapped, allowing the same TLB entries to serve both processes. This effect is especially valuable on systems with small TLBs (e.g., ARM Cortex‑A78 cores) where TLB misses can stall pipelines.
Faster Process Creation
Because the kernel only needs to duplicate page tables (a relatively cheap operation) and not copy page contents, the wall‑clock time for fork() can drop from tens of milliseconds to sub‑millisecond on modern hardware. The classic “fork‑bomb” test on a 2025 AMD Ryzen 9 7950X showed a 7× speedup when CoW was active versus a hypothetical non‑CoW kernel (see the benchmark in the Linux kernel mailing list thread “Copy‑on‑Write improvements”).
Implementation Details in Modern Kernels
Linux’s fork() and vfork()
Linux provides two primary process‑creation syscalls:
fork(): creates a new task with a copy of the parent’s memory space, using CoW for all pages.vfork(): shares the parent’s address space without copying page tables, suspending the parent until the child callsexecve()or_exit. Whilevfork()avoids CoW entirely, it is limited to specific use‑cases because the parent cannot modify memory while the child runs.
The kernel’s copy_process() function (found in kernel/fork.c) performs the page‑table cloning and sets the VM_SHARED flag on each VMA (virtual memory area) to indicate that pages may be shared. The mm_struct reference count tracks how many tasks share the same memory descriptor.
Reference Counting and COW Flags
Each physical page frame in Linux is represented by a struct page. The page->_refcount field is atomically incremented when the page is shared. The PTE’s _PAGE_RW bit is cleared, and the _PAGE_COW bit (or its architecture‑specific equivalent) is set. When a write fault occurs, the handler do_page_fault() calls handle_cow_fault() which:
/* kernel/mm/memory.c */
static int handle_cow_fault(struct vm_area_struct *vma,
struct vm_fault *vmf)
{
struct page *old_page = vmf->page;
struct page *new_page;
/* Allocate a fresh page */
new_page = alloc_page(GFP_KERNEL);
if (!new_page)
return VM_FAULT_OOM;
/* Copy contents */
copy_page(new_page, old_page);
/* Update the PTE to point to the new page */
vmf->page = new_page;
vmf->pte = vmf->pte & ~_PAGE_COW;
vmf->pte = vmf->pte | _PAGE_RW;
set_page_dirty(new_page);
dec_page_refcount(old_page);
return VM_FAULT_WRITE;
}
The code illustrates the lazy copy: a new page is allocated, the contents are duplicated, and the PTE is updated to grant write access to the faulting process only.
Interaction with Memory Cgroup and KSM
Linux’s memory cgroups (memcg) enforce per‑cgroup memory limits. CoW interacts nicely because shared pages count once toward the aggregate usage, but the kernel must still credit each cgroup that holds a reference. The Kernel Samepage Merging (KSM) subsystem can further deduplicate identical memory pages across unrelated processes, effectively extending CoW’s benefits to post‑fork scenarios. KSM scans memory for identical pages and merges them, setting the same CoW flags to preserve correctness.
Edge Cases and Limitations
Write‑Intensive Workloads
If a process immediately writes to most of its pages after a fork (e.g., a data‑processing job that loads a large dataset into memory), CoW’s advantage evaporates. The kernel will incur a page fault for every page, leading to copy‑on‑write thrashing. In such cases, developers may prefer posix_spawn() with the POSIX_SPAWN_DISABLE_ASLR flag, which can bypass the full fork() + execve() sequence.
Overcommit and OOM
Linux’s overcommit policy (/proc/sys/vm/overcommit_memory) allows processes to allocate more virtual memory than physically available, trusting that not all allocations will be used simultaneously. CoW can exacerbate OOM (Out‑of‑Memory) situations if many processes share a large pool of pages that later become dirty, prompting massive copy bursts. Administrators should monitor /proc/meminfo fields such as CommitLimit and CommitAS to avoid unexpected kills.
Security Considerations
Shared pages must be read‑only to prevent a malicious child from corrupting the parent’s memory. However, side‑channel attacks (e.g., Flush+Reload) can exploit shared code pages to infer execution patterns. Mitigations include Kernel Page‑Table Isolation (KPTI) and per‑process code randomization (ASLR). When CoW is used for mmap()‑ed files, the MAP_PRIVATE flag ensures that writes trigger private copies, preserving isolation.
Key Takeaways
- Lazy copying: CoW postpones physical page duplication until a write occurs, turning costly deep copies into cheap page‑table clones.
- Memory efficiency: Shared read‑only pages dramatically reduce resident set size for fork‑heavy workloads.
- Cache and TLB gains: Fewer distinct pages mean higher cache hit rates and lower TLB pressure, especially on CPUs with limited TLB entries.
- Fast process creation: Modern kernels can spawn processes in sub‑millisecond timeframes, enabling high‑throughput server designs.
- Limitations: Write‑heavy applications, aggressive overcommit, and certain security models can diminish or complicate CoW benefits.
Further Reading
- Copy‑on‑Write in the Linux Kernel – Official kernel documentation on CoW mechanisms.
- Understanding Copy‑on‑Write – Intel Developer Zone article covering cache and TLB impacts.
- The
fork()story – LWN.net deep‑dive into the evolution offork()and CoW in Linux. - POSIX
posix_spawnvs.fork– Comparison of process‑creation APIs and their performance characteristics. - Kernel Samepage Merging (KSM) Overview – Red Hat blog post explaining how KSM complements CoW.