Copy-on-Write Memory During Linux fork
Linux uses copy-on-write (COW) during fork so the parent and child can initially share the same physical memory without allowing one process to change the other’s data. A physical page is copied only when one process actually tries to write to it.
The problem fork has to solve
A process uses virtual memory: the addresses in its code and pointers are not direct locations in RAM. The processor translates those virtual addresses through page tables, data structures that map virtual pages to physical pages. A page is a fixed-size block of memory, commonly 4 KiB.
When a process calls fork, Linux creates a second process with an address space that initially looks like the first one. The child needs the parent’s code, stack, heap, and other memory contents at the instant of the fork.
Without copy-on-write, Linux could copy every memory page immediately. That would be expensive for a large process, even when the child soon calls exec to replace its entire address space with a new program. It would also copy pages that neither process ever changes.
COW avoids that upfront work. The parent and child initially use the same physical pages, and Linux delays copying until a write makes sharing unsafe.
What fork sets up
Conceptually, the sequence looks like this:
- Linux creates the child’s process metadata and page-table structures.
- It gives the child mappings corresponding to the parent’s mappings.
- For memory that is private to the process, Linux marks the shared mappings as read-only in both processes.
- Linux records that the physical pages may be referenced by more than one mapping.
- Both processes continue execution, each believing it has its own normal address space.
The page contents are not duplicated at this point. Both page tables can contain entries that point to the same physical page:
Parent virtual page ─┐
├──> physical page containing "counter = 10"
Child virtual page ─┘
The read-only marking is important even if the original mapping was writable. It ensures that the processor will trap a write attempt instead of silently modifying a page that the other process can see.
This is not the same as making the processes share one ordinary writable variable. They share the physical storage temporarily, but neither is allowed to write through that shared mapping. Reads are safe because they do not change the contents.
Linux also maintains a reference count, a number recording how many mappings or users refer to a physical page. The count lets the kernel know whether a page is still shared and whether it can be reclaimed when mappings disappear.
The write-triggered page fault
Suppose the child executes an instruction that increments a value on one of these pages. The processor looks up the child’s virtual address and finds a read-only page-table entry. Because the operation is a write, the processor raises a page fault: a transfer to the kernel caused by a memory access that cannot proceed under the current page-table permissions.
A page fault is not automatically an error. Operating systems also use faults to implement demand paging, memory mapping, and COW. The kernel examines the fault’s address and the reason for the fault to decide whether it is an expected operation.
For a valid COW write, Linux generally does the following:
- Allocates a new physical page for the writing process.
- Copies the old page’s contents into the new page.
- Updates the faulting process’s page table so its virtual page points to the new page and is writable.
- Adjusts the reference counts: the old page has one fewer reference, and the new page is now referenced by the writing process.
- Retries the interrupted instruction.
After that retry, the child’s write changes only its new page:
Parent virtual page ───> original physical page: "counter = 10"
Child virtual page ───> copied physical page: "counter = 11"
The parent’s mapping remains read-only while the original page is still shared. If the parent later writes instead, it takes its own COW fault and receives a private copy. The first writer does not have to be the child; either process can trigger the split.
Once a page has been copied, later writes by that process usually proceed normally because its page-table entry is writable. A page may therefore stay shared for the entire lifetime of both processes, or it may split into two independent pages after the first write.
What is and is not copied
fork still has costs. Linux must create the child’s bookkeeping and copy or construct page-table structures, and it must inspect the process’s memory mappings. But it avoids copying the contents of every physical page up front. The eventual cost is proportional to pages that are actually written, not simply pages that exist.
COW applies to memory that has private semantics: typical process heap, stack, and other private anonymous mappings, as well as private file-backed mappings. A MAP_SHARED mapping has different rules: writes are deliberately visible through the shared mapping, so it is not turned into an independent COW copy in the same way.
COW also explains why a process can appear to use a large amount of memory after fork even before it has written much: the virtual address spaces look large, and page-table memory and accounting may be charged separately from the still-shared page contents. As writes occur, private physical copies consume additional memory.
In practice, this mechanism is behind the familiar pattern of a server calling fork and then having the child call exec. The child can replace its address space without first forcing Linux to duplicate every page of the parent. It is also why a write-after-fork can produce a page-fault trace even though the address is valid: the fault is Linux’s signal to create the private copy that the process’s memory model requires.