Copy-on-Write: How fork() Clones a Process Without Copying Memory

2026-05-02

When fork() creates a child process, the child gets a complete copy of the parent's address space. A naive implementation would duplicate every page of memory — for a process using 2 GB of RAM, that's copying 2 GB. But on any modern system, fork() returns almost instantly. The trick is copy-on-write (COW).

Instead of copying pages, the kernel marks all writable pages in both parent and child as read-only in their respective page tables, and increments a reference count on each physical page frame. Both processes share the same physical memory. Nothing is copied yet.

When either process tries to write to a shared page, the CPU raises a page fault. The kernel's fault handler checks the reference count:

This means pages that are never written — code segments, read-only data, pages the child never touches before calling exec() — are never copied. The classic fork()+exec() pattern in shells and servers benefits enormously: the child replaces its entire address space via exec(), so almost zero COW faults occur.

Real-world example: Redis uses fork() for background persistence (BGSAVE). The child process serializes the dataset to disk while the parent continues serving writes. Only pages the parent modifies during the save get copied. If Redis holds 10 GB of data and the write rate modifies 5% of pages during the save, COW limits the extra memory to roughly 500 MB instead of duplicating the full 10 GB.

Rule of thumb: After a fork(), your peak additional memory consumption equals (number of pages written by either process) × 4 KB, not the total process size. You can observe COW faults via /proc/[pid]/status — look at VmRSS (resident set) growing as writes trigger copies.

COW is also used beyond fork(). When you mmap() a file with MAP_PRIVATE, writes create private copies of the affected pages — the underlying file is never modified. The kernel uses the same COW machinery. Similarly, madvise(MADV_WIPEONFORK) lets you mark regions that should appear zeroed in the child rather than shared, useful for protecting secrets like encryption keys from leaking across a fork.

One pitfall: if a parent process touches all its memory after forking (e.g., a garbage collector scanning the heap), you pay the full copy cost plus the overhead of handling one page fault per page. In that scenario, posix_spawn() or vfork() may be better alternatives.

See it in action: Check out What is Copy-On-Write (CoW)? by simplyblock to see this theory applied.
Key Takeaway: Copy-on-write defers and often eliminates memory duplication at fork time by sharing pages read-only and copying individual pages only when a process actually writes to them.

All newsletters