Skip to content
Lesson 13 of 24

Step 1 of 5 · Reading · ~3 min

Read

Processes & Scheduling

fork() and Copy-on-Write

fork promises a child with a private copy of the parent's whole address space. Copying 4 GiB of memory on every fork would make a shell, which forks for every command and usually replaces the child with a new program a moment later, unbearably slow. Real kernels never copy at fork. They share the frames, mark them read-only, and copy a single page only if somebody tries to write to it. In this lesson your program is the part of the kernel that does this: page tables with flag bits, a reference count per frame, and the page-fault handler.

The idea

  1. At fork: give the child its own page table whose entries point at the same physical frames as the parent's. Increase each frame's reference count. For every writable page, clear the writable bit in both page tables and set a software flag, COW, meaning "this is read-only only because it is shared; a write should get a private copy".
  2. On read: nothing special, both processes read the shared frame.
  3. On write: the CPU raises a page fault, because the PTE is read-only. The handler decides:
SituationAction
address not mapped at allgenuine error: kill the process (SEGV)
page is read-only and not COW (code, constants)genuine error: SEGV
COW page, frame's reference count is above 1allocate a new frame, copy the contents, point this PTE at the copy, set writable, clear COW, decrement the old frame's count
COW page, reference count is 1nobody else shares it any more: just make it writable and clear COW, no copy

The faulting instruction then re-executes and succeeds; the process never learns anything happened.

Why the reference count matters

Consider parent P and child C sharing frame 5 (count 2). C writes: it gets frame 6 (count 1), and frame 5's count drops to 1. P's PTE still says read-only + COW, but P is now the only owner. When P writes it must not copy again: that would waste a frame and a copy for nothing. The count is how the handler tells the cases apart. When a process exits, every frame it maps loses one reference, and a frame whose count reaches 0 returns to the free pool.

PTE flag bits for this: x86 reserves bits 9-11 of every entry for the OS, which is where Linux and others keep their COW marker.

A worked trace

Four frames. Process 1 has virtual pages 0 (rw) and 1 (ro, code).

CommandEffectOutput
MAP 1 0 rwframe 0frame 0
MAP 1 1 roframe 1frame 1
FORK 1 2share both; page 0 becomes W=0, COW=1 in both processes; frame 0 and 1 have 2 referencesOK
WRITE 2 0COW, count 2: copy to frame 2COPY 0 -> 2
WRITE 1 0COW, count 1: reuseREUSE 0
WRITE 1 1read-only, not COWSEGV

Only one extra frame was ever allocated.

Where you meet this

  • A shell forks and execs: nearly nothing is ever copied.
  • Redis forks to take a snapshot: the child holds a frozen view, and the parent pays one page copy for each page it modifies during the snapshot, so a write-heavy workload can nearly double memory use.
  • Zero-page tricks, shared libraries and mmaped files use the same machinery.

Common mistakes

  • Forgetting to clear the writable bit in the parent as well as the child.
  • Copying on every write to a COW page instead of checking the reference count first.
  • Decrementing the count on the wrong frame (the new one rather than the old one).
  • Making read-only code pages COW. A write to them is an error, not a copy.

Your exercise

The starter holds the page tables and prints results. You write fork (sharing, flags, counts) and the write-fault handler.

Up nextPipes & Inter-Process CommunicationProcesses & Scheduling

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…