JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Handling a Page Fault, Step by Step

The previous guide gave the page fault its name and its trigger. Now we open the hood and trace the whole rescue, instruction by instruction: the trap, the disk read, the table update, and the one quiet requirement that makes it all possible — being able to take an instruction back and run it again as if nothing happened.

Picking up where the fault left off

In the previous guide we built demand paging: pages live on disk and are pulled into memory only when first touched. Each entry in the page table carries a valid/invalid bit that says "this page is really in a frame right now" or "this page is not in memory." When the MMU tries to translate an address whose entry is marked invalid, the hardware raises a trap. That trap is the page fault. Guide 2 stopped right there, at the doorbell. This guide is about who answers the door and what they do.

It helps to be precise about what a page fault is and is not. It is not an error, despite the unlucky name — a fault is simply a polite, expected interruption that says "the data you want is real, it just is not in RAM yet; please pause while I fetch it." Compare it to an illegal access (touching memory outside your space), which really is fatal and gets your process killed. The OS tells the two apart by looking at the same table: if the address is legal for this process but the page is currently on disk, the OS services the fault and resumes you; if the address was never part of your address space at all, it kills you with a segmentation fault.

The full service sequence

Here is the whole rescue, walked through end to end. Imagine your process executes an instruction that reads a byte at some logical address, the MMU finds the page invalid, and the doorbell rings. The kernel — the building manager — now runs a fixed routine, the page fault service sequence. Read it once at full speed, then we will slow down on the surprising parts.

  1. Trap to the kernel. The faulting instruction stops mid-flight. The hardware saves enough state (the program counter, registers) and jumps into the kernel's page-fault handler, exactly like answering an interrupt. Your process is now blocked, not running.
  2. Check the address. The kernel looks up the faulting address against the process's records. Legal but not in memory? Service it. Truly illegal? Kill the process. We assume the legal case from here on.
  3. Find a free frame. The kernel grabs an empty physical frame from its free list. If none is free, it must first choose a victim page and evict it — that is page replacement, the subject of a later rung.
  4. Schedule the disk read. The kernel asks the disk to copy the needed page from its home in swap or in the file into that frame. A disk read is slow — millions of CPU cycles — so the kernel does not busy-wait.
  5. Let the CPU work on something else. While the page travels, the kernel runs another ready process. The disk will signal completion later with an interrupt, so no cycles are wasted spinning.
  6. Disk finishes, interrupt arrives. The transfer completes and the disk raises an interrupt. The kernel updates the page table: it points the entry at the new frame and flips the valid/invalid bit to valid.
  7. Restart the faulting instruction. The kernel marks your process ready again. When it next runs, it re-executes the very instruction that faulted — and this time the translation succeeds, the byte is there, and your program never knew anything happened.

Two things in that list deserve a second look. Step five is the same trick you met in the I/O and scheduling rungs: a slow device should never freeze the whole CPU, so the kernel parks the waiting process and hands the processor to someone else. A page fault, then, is not just a memory event — it triggers a full context switch away from your process and (when the disk is done) back again. Step seven is the quiet miracle, and it gets its own section.

The restartable-instruction requirement

Step seven assumes something deep about the hardware: that a half-finished instruction can be taken back and run again from scratch, with exactly the same effect as if it had never run the first time. This is the restartable-instruction requirement, and the whole edifice of demand paging rests on it. If an instruction could fault partway through and leave the machine in a half-changed state, re-running it would double some of its effects, and your data would silently corrupt.

Here is the trap, made concrete. Picture an instruction that copies a block of bytes from one place to another, and suppose the source straddles a page boundary. It copies the first half fine, then faults reaching the second source page. The OS fetches that page and restarts the instruction — which now copies the whole block again, redoing the first half on top of bytes it already wrote. If the source and destination overlapped, the second copy reads the wrong values, and the result is wrong. The fix is purely a hardware-design discipline: CPUs that support demand paging guarantee restartability, either by checking all the pages an instruction will touch before changing anything, or by recording enough to undo a partial step.

Counting the cost: effective access time

A page fault is correct, but it is brutally slow, and it pays to see exactly how slow. A normal memory access takes maybe 100 nanoseconds. A page fault that reaches the disk takes something like 8 milliseconds — that is 8,000,000 nanoseconds, roughly 80,000 times slower than a hit. The effective access time model blends these into one number you can reason about. Let p be the page fault rate, the fraction of accesses that fault. Then the average cost is the weighted mix of the two cases.

EAT = (1 - p) * 100 ns  +  p * 8,000,000 ns

   p = 0          ->  EAT = 100 ns          (no faults)
   p = 1/1000     ->  EAT ~= 8100 ns        (81x slower!)
   p = 1/400000   ->  EAT ~= 120 ns         (a tolerable 20% hit)

   one fault in a thousand accesses already
   wrecks performance by a factor of ~80
Even a tiny fault rate dominates, because a single fault costs ~80,000 normal accesses.

Stare at those numbers, because they carry the central lesson of this whole topic. To keep the slowdown under, say, ten percent, you need fewer than one fault in roughly four hundred thousand accesses — an astonishingly low rate. The reason demand paging works at all in practice is not that faults are cheap; they are ruinously expensive. It works because faults are rare, and they are rare for one specific reason that the next guide is entirely about: locality of reference. Programs do not poke at memory randomly; they revisit the same small neighborhood for a while, so once a page is fetched it tends to serve thousands of accesses before the next fault.

Where the page comes from, and one shortcut

Step four said "copy the page from its home," so where is home? For a page backed by a file (your program's own code, or a memory-mapped file), home is that file on disk. For anonymous pages — your stack and heap, which correspond to no file — home is a dedicated region of disk called swap space. When memory gets tight and the OS needs the frame back, it writes a page out to swap; this is the page-out half of the story, the mirror image of the fault you just traced. Together, page-in on a fault and page-out under pressure are how the disk acts as a slow, enormous extension of RAM.

There is a clever shortcut hidden in the eviction half, and the page table entry makes it possible. Each entry carries a dirty bit that the hardware sets the moment the page is written to. When the OS evicts a page, it checks that bit: if the page is clean (never modified since it was read in), an identical copy still sits on disk, so the OS can simply drop the frame with no write-back at all. Only dirty pages must actually be written to swap. This single bit can cut eviction traffic roughly in half, since read-only code and freshly loaded pages are usually clean.