AlgoMaster Logo

Copy-on-Write

30 min readUpdated August 7, 2026
Listen to this chapter
Unlock Audio

A process holds 400 MiB of initialized heap memory and calls fork().

The child must initially observe the same bytes as the parent, but immediately copying all 400 MiB would be wasteful. The child may soon call exec() and discard the inherited address space, or it may read most of the data without changing it.

The operating system can initially map the parent's and child's virtual pages to the same physical frames. It protects privately writable pages from direct modification. If either process later writes one of those pages, a page fault lets the kernel create a private copy for the writer.

This strategy is copy-on-write, or CoW.

Copy-on-write shares physical contents while they remain unchanged and performs a private copy only when a write requires the contents to diverge.

CoW turns a potentially large eager copy into a collection of smaller deferred copies. Pages that neither process modifies never need to be duplicated.

The Semantics That Must Be Preserved

After fork(), the parent and child have separate virtual address spaces. They begin with the same user-space memory contents:

If the child later writes:

the parent must continue seeing:

while the child sees:

The simplest implementation would duplicate every private frame during fork(). That preserves isolation, but its cost is proportional to the complete copied memory image, including pages the child never changes.

CoW separates the logical promise from the immediate physical implementation:

The optimization is invisible to correct application code. Parent and child behave as though private memory was copied at fork() time, even though physical copying happens page by page on demand.

Initial CoW Setup

Suppose the parent maps virtual page 0x500 to physical frame 42 with read-and-write permission:

Before the fork, the parent's virtual page 0x500 maps to physical frame 42 with read and write permission.

During fork(), the kernel creates the child's address-space metadata and establishes a mapping to the same frame:

If both mappings remained hardware-writable, either process could modify frame 42 directly and the other would observe the change. That would violate private-memory semantics.

The kernel therefore marks both mappings read-only at the hardware level while remembering that they represent logically writable private memory:

Reads succeed because read permission remains. A write cannot silently change the shared frame; the read-only PTE forces a protection page fault.

The kernel distinguishes this deliberate CoW protection from genuinely read-only memory. That distinction is essential:

A page-table read-only bit alone is not enough to decide the outcome. The kernel also consults the virtual region's higher-level policy.

The Write-Fault Path

Suppose the child writes one byte in the shared page.

The MMU finds a read-only translation and raises a protection page fault. The kernel checks the faulting address and sees that:

The kernel can then resolve the fault:

After resolution:

The sharing ended for this one page only. Every other page the two processes have in common is still a single frame.

Only the child's page-table entry changes. The parent's virtual address and physical frame remain unchanged.

The processor retries the faulting store. It now reaches the child's writable frame 73, so the child modifies its private copy.

Copy Before Write

The new frame must initially contain the old page's complete bytes. The child expects every location it did not modify to retain its pre-fork value.

Suppose a 4 KiB page contains:

The child writes one byte at offset 0x100. The kernel cannot create a frame containing only that byte. It copies the complete page, then retries the original one-byte write.

The copy happens before the application write is allowed to complete:

This preserves all untouched offsets.

CoW granularity follows the mapping's page granularity. A tiny write can therefore allocate and copy much more memory than the modified object itself.

Read Behavior Under Copy-on-Write

The CoW mappings remain readable. Parent and child can both read frame 42 without a fault caused by CoW protection:

No private copy is needed because reads do not change the shared contents.

This makes CoW effective for workloads that create processes after initializing a large mostly read-only state:

  1. Load the shared model or index.
  2. Fork the worker processes.
  3. The workers mostly read those pages.
  4. The physical frames stay shared.

Only pages that a worker modifies become private to that worker.

The benefit depends on actual writes, not on how the application conceptually labels its data. A runtime can break sharing by updating object headers, counters, allocator metadata, or garbage-collector state inside otherwise read-mostly pages.

Page-Granularity Copy-on-Write

Suppose a parent has 100,000 resident 4 KiB pages:

After fork(), assume the child writes to 100 distinct pages.

Ignoring page-table and kernel metadata:

The extra physical data memory is:

An eager full copy would have required another 390.625 MiB.

Writing 100 bytes does not necessarily copy only 100 bytes. If each byte lies in a different page, the child creates 100 page copies. If all 100 bytes lie in one page, only one page copy is required.

Data layout therefore affects CoW efficiency. Closely grouping frequently modified state can preserve sharing for neighboring read-only pages.

Frame Reference Counts

The kernel must know how many mappings depend on a physical frame.

In the simplified example:

The count prevents the old frame from being freed while another mapping still uses it.

It also enables an optimization. Suppose the child already copied away, leaving the parent as the only owner of frame 42. If the parent later writes:

No other private address space can observe the change through that CoW relationship. The kernel may restore write permission on the parent's existing mapping instead of allocating and copying another frame.

Conceptually:

The second branch costs no copy at all. A page that was shared and is now privately owned can simply be marked writable again.

Real reference accounting is more nuanced because frames can participate in several kinds of mappings and kernel references. The core safety rule remains: a frame cannot be modified as private state while another private mapping still relies on its old contents.

CoW and TLB Consistency

Changing page-table permissions and frame mappings requires translation-cache coordination.

During initial CoW setup, an old writable TLB entry must not let the parent bypass the new read-only PTE. The kernel invalidates or otherwise prevents use of stale writable translations.

During fault resolution, the writer's old read-only translation must give way to the new writable translation:

On a multicore system, relevant stale entries can exist on more than one CPU. The kernel follows architecture-specific invalidation and shootdown rules before treating the transition as complete.

This is part of CoW's cost. The operation is not merely a memory copy:

Concurrent Writes

Parent and child can fault on the same shared frame at nearly the same time on different CPUs.

Both must receive correct private contents:

The kernel synchronizes mapping and frame state so one fault cannot free, reuse, or incorrectly modify a frame while another fault handler is examining it.

A valid result is:

The exact number of copies can depend on which fault completes first and whether one process becomes the old frame's sole owner. Correctness cannot depend on that timing.

Threads within one process behave differently. They share one page table and are supposed to observe the same writable process memory. CoW does not give each thread a private copy when it writes. Once the process's mapping becomes writable, all its threads use the same frame.

CoW follows address-space boundaries, not thread boundaries.

Why fork() Becomes Cheap

Without CoW, fork() time and initial memory demand would scale with the parent's complete private resident image.

With CoW, the kernel can:

  1. Create the child's process and address-space metadata.
  2. Reproduce the parent's virtual mappings.
  3. Point eligible private mappings at the same physical frames.
  4. protect those mappings against direct writes.
  5. Return control to parent and child.

The actual data copies are deferred to later write faults.

This is especially valuable for the common pattern:

exec() discards the child's inherited user address space and replaces it with a new program image. If the child avoids modifying inherited pages before exec(), most user-data frames never need to be copied.

CoW makes fork() cheaper, not free. The kernel still creates process state, duplicates or constructs mapping metadata, updates page-table permissions, increments frame references, and performs translation invalidations. A process with an enormous number of mappings can still make fork() expensive even if the child writes nothing.

Loading simulation...

What Is Shared After fork()?

Different kinds of mappings have different semantics.

Private writable mappings

These are the main CoW case. Parent and child initially share frames under read-only hardware permissions. A writer receives private contents.

Read-only mappings

Pages that are genuinely read-only can remain shared directly. No CoW write recovery is needed because a write is not logically permitted.

Intentionally shared writable mappings

If a mapping was created for shared communication, parent and child continue observing the same physical storage. Writes are meant to be visible, so private CoW behavior would violate the sharing contract.

Kernel-managed resources

Open-file state and other process resources follow their own fork() sharing or duplication rules. CoW describes virtual-memory contents, not every type of operating-system object.

The private-versus-shared policy comes from the virtual region. Equal physical sharing immediately after fork() does not mean every mapping has CoW semantics.

CoW Beyond fork()

Copy-on-write is a general optimization, not a feature limited to process creation.

Shared zero pages

Many untouched anonymous virtual pages can read from one shared physical page containing zeros. A write fault allocates a private zero-initialized frame for the writer.

Private views of common content

Multiple private mappings can begin with common file-derived bytes. A process that modifies its private view receives different physical contents without changing what other mappings observe.

Snapshots

A system can preserve an old view while allowing a new view to change. Both views initially share unchanged storage; modified units are copied as they diverge.

The unit does not always have to be a virtual-memory page in other systems. The general pattern is:

In virtual memory, hardware page protection and page faults provide the interception mechanism.

Copy-on-Write Memory-Accounting Pitfalls

After fork(), both processes map the shared physical frames. A per-process resident metric can include those pages for both processes:

Adding them produces 800 MiB, but the two processes may still share almost all 400 MiB of physical data.

Proportional set size, or PSS, divides the cost of a shared page among the processes mapping it. If exactly two processes share every page:

As the child writes pages, new private frames increase unique physical consumption. Those pages are no longer divided between the two mappings.

On Linux, /proc/<pid>/smaps_rollup provides aggregate fields useful for this distinction:

Common fields include:

Exact attribution can be subtle, especially for shared libraries, file-derived pages, and pages mapped by more than two processes. The main rule is:

Summed per-process RSS is not unique physical memory usage when CoW or other sharing is present.

Minor Copy-on-Write Faults

A normal CoW write fault already has the old page contents resident. The kernel allocates a frame and copies from RAM rather than reading the contents from storage.

It is therefore normally classified as a minor page fault.

Minor does not mean free. A CoW fault pays for:

  • Exception entry and kernel fault handling
  • Physical frame allocation
  • Copying a page
  • Page-table and reference-count updates
  • Translation invalidation
  • Retrying the instruction

If the system is under memory pressure, obtaining a frame can trigger reclaim or fail. The deferred nature of CoW means fork() can succeed before the system has enough physical capacity for every future private copy.

If parent and child eventually modify nearly every private page, CoW approaches the physical memory cost of an eager copy while also paying fault overhead page by page. The benefit comes from pages that stay shared or are discarded before being written.

CoW and Backend Workloads

CoW behavior often explains surprising service memory growth.

Prefork workers

A parent process loads a large read-mostly model, index, or application state, then forks worker processes. Workers can share the initial frames.

If workers modify only small private regions, total physical use remains far below the sum of their RSS values.

Garbage-collected runtimes

A runtime may update object headers, mark bits, reference counts, or allocator metadata across many pages. Even if application-level data appears read-only, these writes can break CoW sharing page by page.

Cache mutation

Workers that independently update inherited caches create private copies. A cache that saved memory before fork() can multiply physical usage afterward if every worker writes throughout it.

Forking a large service to run a command

The child may quickly call exec(), so data-page copying remains small. Page-table construction and runtime activity before exec() can still make the operation noticeable.

Latency spikes

A phase that writes broadly across inherited pages can produce a burst of minor CoW faults and memory-bandwidth use. The same code can be fast when pages are already private and slower on the first post-fork write pass.

The useful questions are:

A Runnable CoW Demonstration

The following Linux program allocates and initializes 32 MiB before fork(). The child then writes one byte per page.

Compile and run:

A typical result is:

With 4 KiB pages:

The child's write loop can therefore cause roughly 8192 CoW faults. Exact counts vary with page size, allocator behavior, large-page use, runtime activity, and kernel optimizations.

The values demonstrate isolation: the child sees its writes, while the parent's mapping retains the old contents.

When CoW Saves Little

CoW provides little memory saving when both processes quickly overwrite most private pages.

Suppose a parent has N private pages and the child eventually writes all of them:

The final data footprint resembles eager copying. CoW has delayed the allocation and spread it across faults rather than avoiding it.

This can still help if memory becomes available over time or the child exits before modifying everything, but it is not a universal reduction.

CoW can also increase latency unpredictability. An ordinary-looking store can take a minor-fault path and copy a page on its first post-sharing write. Code that requires consistent latency may need to account for this first-write cost.

Failure During a CoW Write

A CoW fault needs a physical frame for the private copy. That allocation can fail under severe memory pressure or a strict memory-control limit.

This creates an important timing distinction:

The operating system can spend substantial effort reclaiming memory, and a process may ultimately be terminated under out-of-memory policy if capacity cannot be provided.

CoW defers allocation; it does not guarantee that every logically possible private copy already has physical backing.

Applications should not interpret successful fork() as proof that parent and child can both overwrite the entire inherited address space without further memory cost.

Summary

Copy-on-write lets separate private address spaces initially share physical frames. The kernel marks logically writable shared pages read-only so the MMU intercepts the first write with a protection fault.

For a shared CoW page, the kernel allocates a frame, copies the complete old page, remaps the writer to the new frame with write permission, updates reference accounting and translation state, then retries the write. Reads cause no copies, and a sole remaining owner can sometimes regain write permission without copying.

CoW makes fork() efficient when the child calls exec(), exits quickly, or modifies only a small subset of inherited pages. Its benefits depend on page-level write patterns: broad writes break sharing, increase private memory, and create minor-fault and memory-bandwidth costs. RSS can double logically while physical frames remain shared, so CoW-aware diagnosis must consider PSS and private-versus-shared memory.

Quiz

Copy-on-Write Quiz

5 quizzes