The idea: fork() copies the page table, not the memory
fork() makes a new process that is a copy of the caller: same code, same variables, same heap. Copying all of that memory would be slow, and usually wasted, because the child often calls exec() right away and throws it out. So Linux does not copy memory at fork. It copies the page table, the map from the process's virtual pages to physical frames, and lets both processes share every frame. Each shared page is marked read-only and copy-on-write (COW). A page is copied only when one of them writes to it, and only that one page.
The canvas shows the parent (redis-server, pid 100) and its page table on the left, the 12 physical frames in the middle, and the child (pid 101) on the right. Each frame has its own colour, and a page-table entry's frame cell takes that colour, so two entries with the same colour share memory. Pick an action, a process and a page, then press Do It, or run a demo.
Page tables, the COW mark and the reference count
Every memory access goes through the page table (see Virtual Memory). An entry says which frame holds the page and what is allowed: read, write, execute. At fork() the kernel:
- creates the child and copies the parent's page-table entries into it, so both point at the same frames;
- clears the write bit of every writable page, in both processes, and marks them COW;
- raises each frame's reference count (how many entries point at it) from 1 to 2.
Code is already read-only, so it is simply shared and never copied. Demo 2: reads never copy: both processes read the same frames, with no fault at all.
| fork copies | work at fork() | memory right after fork() |
|---|---|---|
| every page (old Unix) | every writable page, 4 KiB each | doubled |
| copy-on-write (Linux) | the page-table entries only | unchanged |
The write fault, step by step
When a process writes to a COW page, the CPU sees the write bit is off and raises a page fault. The kernel's fault handler sees the COW mark and checks the frame's reference count:
- refs > 1: the frame is still shared. The handler takes a free frame, copies the 4 KiB into it, points the writer's entry at the new frame with the write bit on, and drops the old frame's count. Then the CPU runs the same write again, and it succeeds (Demo 1).
- refs = 1: the other process already made its own copy (or is gone). Nothing needs copying; the handler just turns the write bit back on (Demo 3: both write the same page).
A write to a page that was never writable, like code, is not a COW fault. The handler finds nothing that allows it and sends SIGSEGV (Demo 7).
Why fork + exec is cheap
A shell runs a command with fork() then exec() in the child. exec() drops every mapping the child had and maps the new program (see Program Loading). With copy-on-write, nothing was copied in between: the cost of fork is just the page table (Demo 4: fork + exec copies nothing). The parent's pages go back to refs 1. They are still marked COW, so the parent's next write to each one faults once and only flips the write bit. Before COW, fork copied every page and exec threw them away (Demo 5: old Unix copies every page).
Even copying the page table costs time for a process with many gigabytes mapped. vfork() skips it: the child borrows the parent's memory and the parent waits until the child calls exec() or _exit(). posix_spawn() wraps that pattern safely, and is what many runtimes use to start programs. Both are system calls like any other (see System Calls).
Redis BGSAVE, memory growth and overcommit
Redis uses COW on purpose. To save a snapshot, BGSAVE forks: the child sees a frozen copy of the whole dataset and writes it to disk, while the parent keeps serving clients. Every page the parent changes during the save gets copied, so memory grows with the write rate (Demo 6: Redis BGSAVE: 3 writes, 5 → 8 frames in use; when the child exits, back to 5).
In the worst case every page is written and memory doubles. That is why a fork of a large process can succeed and the system can still run out of memory later, at a write. Linux allows the fork by overcommitting (vm.overcommit_memory; Redis recommends setting it to 1). If no frame is free when a COW fault needs one, the kernel reclaims or swaps pages (see Page Replacement) and, as a last resort, the OOM killer ends a process. Huge pages make each COW copy 2 MiB instead of 4 KiB, which is why Redis also asks for transparent huge pages to be off.
PostgreSQL forks one backend per connection, see PostgreSQL Process Architecture.
See also: What happens to the child after it exits (zombies, wait() and orphans) is on A Linux Process from fork() to Reaping. fork() also copies the fd table, but the open files are shared, offsets included; see What open() Does.
See also Process vs Thread: the same fork() next to pthread_create(), which shares the page table instead of copying it.
What the page leaves out
Multi-level page tables and the TLB flush after the write bits change, shared memory and MAP_SHARED mappings (never COW), private file mappings and the zero page (also COW), lazy loading of the program by exec(), threads (only the calling thread is copied), madvise(MADV_DONTFORK), and how the kernel actually counts references (struct page map counts). Five pages and twelve frames stand in for millions.