Frontend Labs
⋯

Chapter 11 — Paging, page faults, and the TLB

In one sentence

Physical memory is managed in fixed-size chunks called pages, and when a process touches a virtual address that isn't currently mapped to a physical page, the CPU raises a page fault — which the kernel may resolve instantly, slowly (by reading from disk), or fatally (by killing the process).

Why this matters

"Why did my program's first request feel slow, but the second one was fast" and "why does swapping make everything grind to a halt" are both, directly, page-fault stories.

How it actually works

   PAGES: physical RAM and virtual address space are both divided into
   fixed-size chunks -- PAGES -- commonly 4KB on Linux/x86 (larger "huge
   pages," 2MB or 1GB, exist for specific performance-sensitive
   workloads). The kernel's PAGE TABLE records, for every virtual page a
   process uses, WHERE (or whether) it's currently backed by a physical
   page.

   A PAGE FAULT happens when a process accesses a virtual address whose
   page ISN'T currently mapped to physical RAM. There are several very
   different flavours:

     MINOR FAULT (cheap, common, totally normal)
       the page exists somewhere accessible but isn't yet mapped into
       THIS process (e.g. a shared library page another process already
       loaded) -- the kernel just updates the mapping. Fast, invisible.

     MAJOR FAULT (expensive)
       the data has to be read FROM DISK -- either it's part of a
       memory-mapped file not yet loaded, or (the painful case) it was
       previously SWAPPED OUT to disk to free up RAM for something else,
       and now has to be swapped back IN. This is orders of magnitude
       slower than RAM access (Chapter 12's hierarchy).

     SEGMENTATION FAULT (fatal)
       the process touched a virtual address that ISN'T VALID AT ALL for
       it -- not "not yet loaded," but genuinely not part of its address
       space (a wild pointer, a buffer overrun). The kernel delivers
       SIGSEGV (Chapter 20) and the process usually dies. This is the
       infamous "segfault."

   THE TLB (Translation Lookaside Buffer) is a small, very fast CACHE,
   right on the CPU, of recent virtual-to-physical translations -- so the
   full page-table lookup (itself potentially several memory accesses)
   doesn't have to happen on EVERY single memory access. A "TLB miss"
   means falling back to the slower, full page-table walk.

Real-world example

   THRASHING is the well-documented, classic failure mode this chapter's
   mechanism produces under memory pressure: when a system's actively-
   used memory (its "working set") exceeds available RAM, the kernel
   starts SWAPPING pages out to disk to make room, and swapping others
   BACK IN as they're needed -- generating a storm of expensive MAJOR
   PAGE FAULTS. If the working set is large enough relative to RAM, the
   system can spend almost ALL of its time swapping pages in and out
   and almost NONE of its time doing actual work -- CPU usage looks
   deceptively LOW (everything is stuck waiting on disk I/O, D-state,
   Chapter 5) while the system feels completely unresponsive. This
   remains one of the most common real production incidents reported in
   SRE/ops communities: "the server isn't doing much CPU-wise but is
   totally unresponsive" is, very often, a thrashing system -- and it's
   exactly why cloud providers and Kubernetes (Chapter 58) generally
   prefer to KILL a process/pod that's exceeding its memory budget
   (Chapter 13's OOM killer) rather than let it swap into oblivion.

Try it yourself

# See page fault counts for a command -- minor vs major
/usr/bin/time -v ls / 2>&1 | grep -i "page faults"
# (on some systems: /usr/bin/time -v cat /proc/self/maps 2>&1 | grep fault)

# Watch major faults spike under real memory pressure (careful, only on
# your disposable VM -- this deliberately stresses memory)
free -h                          # baseline
vmstat 1 5                       # watch the "si"/"so" (swap in/out) columns

# See your system's page size
getconf PAGE_SIZE                # almost always 4096 on x86_64 Linux

Common mistakes

  • Treating all page faults as bad. Minor faults are constant, cheap, and completely normal — only SUSTAINED major faults (especially swap activity) are a red flag.
  • Confusing a segmentation fault with "the program is out of memory." A segfault means an INVALID memory access (a bug), not memory exhaustion — those are different failures with different causes (Chapter 13 covers actual memory exhaustion).
  • Assuming more swap space fixes a thrashing system. It usually just makes the thrashing last longer before the system becomes truly unusable — the real fix is more RAM or a smaller working set.

Check yourself

  1. What's the difference between a minor page fault and a major page fault?
  2. What does the TLB cache, and why does it exist?
  3. What is thrashing, and why does a thrashing system often show LOW CPU usage despite being unresponsive?

Further reading

  • Book (free): OSTEP, "Paging" and "Swapping" chapters.
  • Reference: man 2 mmap, man 8 vmstat.
  • Article: search "Linux memory thrashing troubleshooting" for current, concrete production writeups.

Generated from real-life-os-guide.md · 8 Oct 2026 16:36