When you launch a
Demand paging is what makes virtual memory feel free — until the day the pages a program actively needs no longer fit in RAM. Then the system tips over an edge into thrashing, where it spends all its time shuttling pages to and from disk and gets almost no real work done. This lesson is about both: the elegant laziness that makes memory abundant, and the cliff it walks up to.
"Page fault" sounds like an error; it is not. It is the ordinary mechanism by which the OS lazily populates memory. The crucial distinction — the one that separates a fast program from a slow one — is where the page comes from:
| Kind | What happened | Cost |
|---|---|---|
| Minor (soft) fault | page is already in RAM — just not mapped into this process (e.g. shared page, page cache hit, zero page) | microseconds — no I/O |
| Major (hard) fault | page must be read from disk/swap | milliseconds — thousands× slower |
| Invalid access | address not in any valid mapping (a real bug) | SIGSEGV — the program dies |
A minor fault is a bookkeeping blip; a major fault stalls the process for milliseconds while a disk (or, today, an SSD or swap device) delivers the page. The ratio of major to minor faults is one of the first things you look at when a system feels slow: many major faults means the working data does not fit in RAM.
How much memory does a process "need"? Not its whole address space, and not one page — it needs whatever
it is actively using right now. Denning's working-set model makes this precise:
the working set
This is the basis of a real allocation policy: the OS estimates each process's working-set size (by sampling the accessed bits over a window) and tries to keep that many frames allocated to it. If it cannot — if the working sets do not all fit — some process must be suspended entirely and swapped out, because running it would only thrash everyone.
Here is the counter-intuitive shape every OS student must know. Add more processes (raise the degree of multiprogramming) and at first throughput rises — more processes means the CPU always has something to run while others wait on I/O. But each process needs its working set resident. Past the point where the working sets stop fitting in RAM, adding one more process forces eviction of pages that are still in use, so every process starts faulting, the disk queue saturates, and throughput falls off a cliff. Drag the working-set slider: fatter working sets move the cliff to the left, because fewer processes fill memory.
The system is worst exactly when it is busiest: at the peak of thrashing the CPU is idle most of the time, waiting on disk, even though the run queue is full. The fix is not "work harder" — it is to reduce the degree of multiprogramming: suspend a process, free its frames, and let the survivors keep their working sets. This is the page-fault-frequency (PFF) policy in one sentence.
Working sets are expensive to track exactly, so real systems use a feedback signal that is trivial to measure: each process's page-fault rate. PFF sets an upper and lower band:
It is a thermostat: fault rate is the temperature, frames are the heat, and the OS holds each process in a comfortable band. When the whole machine is over-committed and even PFF cannot keep everyone in band, the last resort is swapping: evict an entire process's pages to the swap device and mark it non-runnable until memory frees up. Swapping a process out to rescue the rest is the OS admitting that no replacement policy can help when the demand simply exceeds the RAM.
Once thrashing sets in, everything slows down — including the code that is supposed to fix it. The memory manager, the shell you are typing into, even the mouse cursor's handler must all be paged back in to run, and they are stuck behind a saturated disk queue. So the machine that most needs to suspend a process is the one least able to, because the tool for suspending it is itself paged out. This "swap death" is why an over-committed server can become totally unresponsive rather than merely slow, and why the kernel eventually invokes the blunt instrument of last resort — the OOM killer, which simply picks a memory hog and terminates it to break the spiral.
Demand paging is only cheap because major faults are rare. To see how brutally a small fault rate
hurts, compute the effective access time. Let a RAM access take
Plug in a fault on just one access in a thousand (
Two slips travel together. First, "fault" does not mean error: the overwhelming majority of page faults are the intended way memory gets populated, and the process resumes as if nothing happened. The genuine error — touching an address in no valid mapping — is a segmentation fault, a different beast. Second, do not assume every fault hits the disk. A minor fault — mapping in a page that is already in RAM (a shared library other processes loaded, a file already in the page cache, a copy-on-write or demand-zero page) — costs microseconds and never queues an I/O. When you profile, the number that predicts pain is the major-fault count, not the total. Conflating the two will make you fear cheap faults and miss the expensive ones.