Topic 0 › Chapter 0.8

Virtual memory

Pages, page tables, TLBs: every address you've ever used was a fiction.

You already know · 0.1 Bytes and addresses · 0.5 Caches

The problem › A confession

Every address so far was a lie

Chapter 0.1 told you memory is one flat byte array and an address is an index into it. That model earned its keep for seven chapters, and it was a fiction the whole time. The addresses your program sees, prints, and does arithmetic on are virtual: the CPU emits them, but they are not the numbers that reach DRAM. On every single access, a piece of hardware called the MMU (memory management unit) translates the virtual address into a physical one, using tables the operating system maintains.

1CS:APP 3e §9.1 · pp. 839–840

You can catch the lie red-handed: run two programs at once and have both print the address of their first local variable. They can print the same number, and write to it, without touching each other. Two indices, one physical byte array, no collision. That is only possible if each process gets its own private, per-process address space: a large, sparse illusion that holds all of its code and data, with the OS and hardware turning every reference into a physical location behind its back.

2OSTEP Ch. 13 · pp. 109–115

Why go to this trouble? Three jobs at once: isolation(no process can even name another's memory), simplicity (every program believes it starts at the same tidy addresses), and capacity(the illusion can be larger than the physical memory behind it). The flat array of 0.1 survives; there is just one per process, and it's a rented illusion.

3OSTEP Ch. 13 · p. 115

SourcesOSTEP Ch. 13 · pp. 109–115CS:APP 3e §9.1 · pp. 839–840

The mechanism › Pages

Translate in 4 KB chunks, not bytes

Translating every byte individually would need a mapping table as big as memory itself. So the mapping works in fixed-size chunks called pages: 4 KB (4096 bytes) on essentially every system you'll touch. Virtual memory is carved into 4 KB pages, physical memory into 4 KB page frames, and the map only records which page lives in which frame. Within a page, bytes stay in order.

4OSTEP Ch. 18 · pp. 169–170

That makes translation a bit-slicing operation. Since 4096 = 212, the low 12 bits of a virtual address are the offset within the page, and every bit above them is the virtual page number (VPN). The page table, one per process and maintained by the OS, maps each VPN to a physical frame number (PFN). The hardware rebuilds the physical address as (PFN << 12) | offset: the offset is never translated, because pages and frames have identical internal layout.

5OSTEP Ch. 18 · pp. 171–175

What exactly sits in one page table entry (PTE)? The PFN, plus a handful of bits that turn out to run the world: a valid bit (is this part of the address space mapped at all?), protection bits (read/write/execute; the hardware enforces them on every access), a present bit (is the page in physical memory right now, or out on disk?), plus dirty and accessed bits the OS uses to decide what to write back and what to evict, the same bookkeeping logic you met inside caches in 0.5, now in software.

6OSTEP Ch. 18, Fig. 18.5 · pp. 173–174
virtual
VPN 0x00403
offset 0x2A7
↓ page table lookup↓ unchanged
physical
PFN 0x1F2
offset 0x2A7
One translation: the page table swaps VPN for PFN; the 12-bit offset passes straight through (OSTEP Ch. 18, Fig. 18.3).

Before you feel the cost, feel the size. A 32-bit address space with 4 KB pages has a 20-bit VPN (about a million pages), and at 4 bytes per entry, that's a 4 MB page table per process. Run a hundred processes and the OS is spending hundreds of megabytes on nothing but translations. This is why the table lives in ordinary memory, not in some special on-chip bank, and that placement is about to cost us.

16OSTEP §18.1 · p. 172

Predict first

A 1 MB array sits in virtual memory. How many 4 KB pages does it span (assuming it starts on a page boundary)?

SourcesOSTEP Ch. 18 · pp. 169–178CS:APP 3e §9.3.2 · p. 842

The mechanism › The TLB

A cache for translations: you already know this one

Here is the catch: because the page table lives in memory, paging taken literally requires one extra memory reference to fetch the translationbefore every real load, store, or instruction fetch. Memory traffic doubles; OSTEP puts it bluntly: this alone would "slow down the process by a factor of two or more," and the memory wall from 0.4 gets twice as tall. The fix should feel inevitable by now: cache the translations. The TLB (translation lookaside buffer) is a small, very fast cache inside the MMU holding recently used VPN→PFN entries. OSTEP calls it what it really is, an address-translation cache, and credits it with making virtual memory practical at all.

7OSTEP Ch. 18 · p. 1768OSTEP Ch. 19 · p. 183

The access protocol is chapter 0.5 verbatim: extract the VPN, look it up; on a hit, concatenate the cached PFN with the offset and go, with no page-table visit; on a miss, walk the page table in memory, install the entry, and retry the instruction. And because one entry covers 4096 consecutive bytes, the TLB runs on the same fuel as every cache: spatial locality(0.6). OSTEP's own worked trace, a 10-int array straddling three pages, goes miss, hit, hit, miss, hit, hit, hit, miss, hit, hit: one miss per page touched, everything else rides free.

9OSTEP Ch. 19, Fig. 19.1 · p. 18410OSTEP §19.2 · pp. 185–186

But TLB reach is tiny. Even a generous 1,024-entry TLB at 4 KB per entry covers just 4 MB of address space, against gigabytes of RAM. Sweep sequentially and you miss once per 4 KB; hop randomly across a large heap and every access can miss the TLB and the data cache, stacking a page-table walk on top of a DRAM fetch.

One PTE bit deserves its own paragraph: present. Touch a page that is mapped but not in physical memory and the hardware raises a page fault; the OS steps in, finds the bytes (or allocates a fresh frame), updates the table, and restarts your instruction as if nothing happened: a cache miss handled in software, with the OS as the miss handler. Pages + page tables + present-or-not is the exact machinery vLLM's PagedAttention borrows in chapter 3.6 to manage the KV cache. Same idea, different bytes. Segmentation, swap-replacement policy, and malloc's free-list internals also live in this neighborhood; they exist, but they're not on our path.

11CS:APP 3e §9.3.4 · p. 844

interactive · coming soon

AddressTranslate: step-through walkthrough

Planned interactive: type a virtual address, watch it split into VPN and offset, walk the page table, hit or miss the TLB, and assemble the physical address, with a fault path when the page isn't resident.

SourcesOSTEP Ch. 19 · pp. 183–186CS:APP 3e §9.6.2 · p. 853CS:APP 3e §9.3.4 · p. 844

The number › Your turn

Translate 0x004032A7 by hand

Page size 4 KB, so the offset is the low 12 bits: exactly three hex digits. Suppose the page table says VPN 0x403 maps to PFN 0x1F2.

Compute the number › one address, start to finish

virtual address0x004032A7
offset = low 12 bits (3 hex digits)0x2A7
VPN = remaining bits0x403
page table lookup: VPN 0x403 →PFN 0x1F2
physical = (PFN << 12) | offset0x1F2000 | 0x2A7
Resultphysical address 0x1F22A7

Notice what stayed fixed: the last three hex digits. Any address ending in 2A7 lands 679 bytes into some page; translation only decides which frame that page occupies. The recipe (mask off the offset, shift out the VPN, index the table, OR the pieces back together) is literally how the hardware does it.

12OSTEP Ch. 18 · p. 175
The full story › multi-level tables and huge pages

A flat table for a 64-bit address space is absurd (OSTEP's arithmetic: even 32 bits costs 4 MB per process), so real x86-64 page tables are a sparse 4-level tree; only the regions you actually use get table nodes. The price: a TLB miss now triggers up to four dependentmemory reads (a "page walk"), a textbook 0.9 dependency chain, which is why TLB misses hurt more than they sound. The standing mitigation: huge pages(2 MB on x86-64), each covering 512× more memory per TLB entry. OSTEP notes large pages exist mainly to relieve TLB pressure (databases and other large-footprint systems lean on them), and Bakhvalov's measurement tooling enables huge pages precisely to keep TLB effects out of latency measurements. File that away for Topic 3.

13OSTEP Ch. 20 · pp. 201–20214OSTEP Ch. 20 · p. 20215Bakhvalov 2e §8.4.3 · p. 204

Your addresses are per-process fictions, translated page by page through an OS-owned table, with a TLB caching translations because even the fiction's bookkeeping must obey the memory hierarchy. Pages, page tables, present-or-not: you'll meet this exact machinery again managing KV caches in 3.6.