Share on LinkedInBack to deep dives

Linux memory

Copy-on-Write

The core idea

Copy-on-write is a memory optimization built around one simple observation: copying is wasteful until someone actually writes. If two processes can safely read the same page, the kernel lets them share it.

The classic case is fork(). A parent process creates a child with what looks like the same address space. But Linux does not eagerly duplicate every physical page. Instead, both processes point to the same frames, and those mappings are protected so a later write can be detected.

Playground 1

Fork first, copy later

After fork(), parent and child can point at the same physical pages. A private copy is made only when one process writes.

Page 0shared
Page 1shared
Page 2shared
Page 3shared
state: before forkshared pages: 0private copies: 0

Copy-on-write is lazy: sharing is cheap until a write proves the page must become private.

What fork really shares

After fork(), parent and child have separate page tables, but many entries can point to the same physical frames. From each process's point of view, its virtual addresses still look private. Underneath, the RAM can be shared.

To make this safe, the kernel marks shared writable pages as read-only in both processes. That sounds strange, but it is the trick that makes copy-on-write work. A read succeeds. A write causes a controlled fault.

Playground 2

Write fault timeline

Copy-on-write is implemented through page protection. The first write is intentionally trapped so the kernel can make a private page.

Mappingshared read-only
Faultnone

Both processes map the same physical page as read-only.

The write fault

When the child writes a shared page, the CPU sees a write to a read-only mapping and raises a page fault. This is not necessarily an error. The kernel checks the page table metadata, recognizes a copy-on-write case, allocates a new physical frame, copies the old bytes, and updates the child's page table to point at the new frame.

After that, the faulting instruction is retried. The parent still sees the original page. The child sees its private modified copy. From user space, it feels like the child always had independent memory, but the expensive copy happened only at first write.

Playground 3

RSS can look scary, PSS tells the truth

After fork, both processes can show mappings for the same pages. PSS divides shared pages fairly, while private pages count fully.

Page 0shared
Page 1shared
Page 2shared
Page 3shared
Page 4shared
Page 5shared
Page 6shared
Page 7shared
shared: 8 pagesparent PSS: 4.0 pageschild PSS: 4.0 pages

The more the child writes, the fewer pages are shared. That is why a forked process can start cheap and become expensive as it mutates memory.

Why RSS can mislead

Copy-on-write can make memory accounting look confusing. If parent and child both map the same physical page, RSS can count that page in both processes. That does not mean two physical pages exist.

PSS, or proportional set size, gives a clearer view by splitting shared pages across processes. If two processes share one page, each gets half of that page charged to its PSS. Once one process writes and gets a private copy, that private page counts fully.

Where copy-on-write shows up

  • Process creation: fork() can be fast because it copies page tables and shares frames.
  • Program startup: a child can quickly call exec() without paying for a full address-space copy.
  • Shared libraries: code pages can be mapped by many processes while private writable data is copied only when needed.
  • Snapshots: filesystems and virtual machines use the same idea: share old data until a write needs a new copy.

Summary

  • Before write: parent and child can share the same physical frame.
  • Protection: shared writable pages are temporarily mapped read-only.
  • First write: page fault, allocate new frame, copy bytes, update page table, retry.
  • Memory cost: cheap at fork time, more expensive as processes mutate shared pages.