Copy-on-write — how fork() avoids actually copying memory
Creating a new process by duplicating an existing one sounds like it should mean copying that process's entire memory — copy-on-write is the trick that makes it nearly instant instead, by not actually copying anything until it's genuinely needed.
Beginner
3 min read
fork(): the system call that duplicates a process
On Unix-like systems, the standard way to create a new process is the
fork() system call, which creates a near-exact duplicate of the calling
process — same code, same variables, same open files — that continues
running from the exact point fork() was called, as a separate process.
Naively, this sounds expensive: if a process is using a gigabyte of memory,
duplicating it should mean copying a full gigabyte, every single time a new
process is forked. In practice, fork() is fast — often a few
milliseconds, even for a process using gigabytes — because the data itself
never gets copied. (The kernel still has to copy the process's page
tables, the bookkeeping that maps addresses to memory, so forking a very
large process does take measurably longer than forking a small one — but
that's a tiny fraction of the cost of copying the data.)
The trick: share the memory, copy nothing yet
Copy-on-write (COW) is what makes this possible. Instead of actually
duplicating memory at fork time, both the parent and child processes are set
up to point at the exact same physical memory pages, each marked
read-only. Nothing has actually been copied — both processes are, for the
moment, sharing one copy of the data.
The moment either process actually tries to write to one of those shared
pages, the read-only protection triggers a page fault (the exact
mechanism from the paging lesson earlier in this domain) — and only at that
point does the kernel actually copy that one specific page, giving the
writing process its own private copy. Every other page neither process has
touched yet stays shared, uncopied, for as long as neither side writes to
it.
Why this is such a good trade
Most real uses of fork() are immediately followed by loading a
completely different program (the exec system call) — starting a new
shell command, for instance — which means almost none of the parent's
original memory would ever have needed copying anyway. Copy-on-write means
the system never pays for copying memory that turns out to be unnecessary;
it only ever copies the specific pages that actually get modified, exactly
when they get modified, and not a page before.
Why this matters day to day
Copy-on-write is a genuine example of a broader, recurring idea in system
design: defer expensive work until you're certain it's actually needed,
rather than doing it eagerly up front "just in case." The same lazy pattern
shows up in plenty of other places once you know to look for it — lazy
evaluation in programming languages, lazy loading of images on a web page,
or a database only computing a query result once it's actually requested.
Recognizing "this could be done lazily instead of eagerly" as a real
optimization technique, not just a coincidence specific to fork(), is the
transferable skill here.