Reading — step 1 of 5
Read
fork() and Copy-on-Write
The Processes lesson told you fork() clones a process — address space and all — and the shell course builds its whole execution model on calling it constantly. Take that literally and it's absurd: cloning a 2 GB browser process to run ls would copy two gigabytes… and then exec would throw the copy away three microseconds later. Real fork copies almost nothing. The trick is copy-on-write, and it's the single best demonstration of what page tables make possible.
The lazy bargain
At fork time, the kernel copies only the page tables, not the pages: parent and child map the same physical frames. Then two bookkeeping moves make the sharing safe:
- Every shared page is marked read-only in both processes' page tables — even pages that were writable.
- Every shared frame gets a reference count — how many processes map it.
Reads proceed at full speed forever — most of a process's memory (the code, the libraries, that browser's 2 GB of tabs) is never written by either side, so it's simply never copied. Fork's cost drops from "copy the address space" to "copy the page tables": milliseconds become microseconds. And the fork+exec pattern becomes retroactively brilliant — the child that immediately execs discards mappings, not copies, because copies never existed.
The write is where the magic fires
When either process writes a shared page, the CPU raises a page fault — the page is marked read-only, remember? The fault handler (paging lesson!) checks: this isn't a crash, it's COW. It:
- Allocates a fresh frame (frame allocator!),
- copies the page's contents into it,
- remaps the writer's page table entry to the new frame, writable,
- decrements the old frame's refcount,
- retries the faulting instruction — which now succeeds, the process none the wiser.
The write costs one page copy — 4 KiB, not 2 GB — and only for pages actually written. Payment precisely on use: the lazy-allocation philosophy of demand paging, applied to process creation.
The refcount subtlety (your exercise's sharpest test)
Track the counts through a sequence. Parent and child share page P (refcount 2). Parent writes:
- Parent gets a private copy (its new frame, refcount 1). Old frame's refcount drops to 1 — only the child maps it now.
- Child then writes the same page. Its mapping's refcount is 1 — nobody else shares it. The handler doesn't copy; it just flips the page writable in place.
OK, notCOPY.
Miss that rule and you copy pages nobody shares — a real bug that real kernels optimize against explicitly (the "last sharer reclaims the page" path). Your simulator's third test is exactly this sequence; model refcounts per physical frame, not per process, and it falls out naturally.
Why this lesson sits where it does
COW is the course converging: fork (Processes) creates the sharing, page tables + fault handling (Virtual Memory) detect the write, the frame allocator (last chapter) supplies the copy, and the refcounts need synchronization (last lesson!) the moment two cores fault on the same frame simultaneously — real kernels take a lock exactly there. One feature, five lessons of machinery, cooperating. That's what a kernel is.
Your exercise: COW Page Accounting
PAGES k births process 1; FORK shares mappings (free!); WRITE prints COPY or OK per the refcount rules; COUNT reports physical pages. The graded sequence everyone gets wrong once is test 3's parent-write-then-child-write (COPY then OK — refcounts, not "was it ever forked"). Keep frames as the unit of truth — pid→page→frame mappings pointing into frame→refcount — and every case, including fork-after-copy chains, is just arithmetic on that table.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…