---
title: Address Spaces and Translation
module: Virtual Memory
moduleNumber: 7
lessonNumber: 1
order: 701
summary: >
  Every process runs as if it owns a private, contiguous span of memory — its
  virtual address space — while the hardware maps those addresses onto a
  single shared physical memory. We fix virtual memory's three jobs (a cache for
  disk, a memory manager, a protection boundary), the page as the unit of
  mapping, and the MMU replacing the virtual page number while the offset passes
  through untouched — then run one translation end to end at the bit level and
  trace the control flow of a page hit against a page fault.
topics: [Virtual Memory]
sources:
  - book: Bryant & O'Hallaron
    ref: "CS:APP — §9 Virtual Memory"
  - book: Bistriceanu
    ref: "Computer Architecture Notes — §10 Virtual Memory"
---

The [memory hierarchy](/computer-architecture/memory-hierarchy/storage-technologies-and-the-latency-gap)
ended with a convenient fiction: that a program addresses one flat array of bytes
that is entirely its own. On a real machine that array does not exist. Physical
memory, the DRAM chips, is a single shared resource, smaller than the address
each program is allowed to name, and used at the same time by dozens of
processes. **Virtual memory** is the mechanism that makes the fiction true for each
process: every program gets its own private array of bytes, and hardware
translates each access into a location in the one physical memory underneath.
This lesson sets up the two address spaces and the translation that connects
them; the next two build the data structures that make it work.

## Two address spaces

A program issues **virtual addresses**: the addresses in its code, its pointers,
its `%rip`. The set of all $2^n$ such addresses on an $n$-bit machine is the
**virtual address space**. Underneath, DRAM is named by **physical addresses**,
and the set of $2^m$ of those (with $m$ usually smaller than $n$) is the
**physical address space**. The processor's [word size](/computer-architecture/foundations/bits-bytes-and-words)
fixes $n$; the amount of installed RAM fixes the useful range of $m$.

> **Definition (Virtual / physical address).** A **virtual address** is the
> address a program emits; it indexes that process's private virtual address
> space. A **physical address** is the address of an actual byte in main memory.
> The two are connected only through **address translation**, performed by
> hardware on every reference.

The sizes matter. A modern x86-64 processor uses 48-bit virtual
addresses: $2^{48}$ bytes is 256 TB of nameable space, per process. A machine
with 16 GB of DRAM has physical addresses covering about $2^{34}$ bytes. Every
process can name sixteen thousand times more memory than exists, and dozens of
processes do so simultaneously; address translation is what makes this workable.

## The three jobs of virtual memory

Virtual memory solves three problems at once, all with the same mechanism: a
level of indirection on every address. The machinery of the next two lessons
exists to serve exactly these jobs.

**A cache for disk.** A process can name far more bytes than the machine has
DRAM, so the full contents of its address space live on disk, and DRAM holds
only the subset in active use. This makes DRAM one more level of the memory
hierarchy: a cache whose backing store is disk. But the numbers force an
extreme design. A DRAM access costs tens of nanoseconds; a read from an SSD
costs tens of microseconds, and from a spinning disk around
$10\,\text{ms}$ — a miss penalty of three to five orders of magnitude, dwarfing
the roughly 100x gap that [SRAM caches](/computer-architecture/memory-hierarchy/cache-memories-direct-mapped)
bridge. Consequently the blocks are large (4 KB pages rather than 64-byte
lines, to amortize the transfer), placement is fully associative (any page may
occupy any frame, so no placement constraint ever forces a bad
eviction), the write policy is always write-back (writing through to disk on
every store would be absurd), and replacement is decided by operating-system
software, because when a miss costs ten million cycles, spending a few thousand
choosing the right victim is free.

The magnitudes force every one of those design
choices. Line up the levels a reference might reach: an L1 hit
is about 1 ns, a DRAM access about 100 ns (a $100\times$ gap, the one SRAM caches
exist to bridge), an SSD read about $50\,\mu\text{s} = 50{,}000$ ns, and a
spinning-disk read about $10\,\text{ms} = 10{,}000{,}000$ ns. So a page fault to
a spinning disk is roughly **100,000 times** slower than the DRAM access it stands
in for, and to an SSD still about 500 times slower. If a DRAM access were one
second, a disk page fault would be over a day. When a single miss costs that much,
it pays to spend freely both avoiding it and handling it: large blocks (one
10 ms seek should return a lot of data), full associativity (never evict a
useful page over a placement technicality), and a software replacement policy
(a few thousand cycles of deliberation is invisible against ten million cycles of
disk) all follow directly from the size of that number.

**A memory manager.** Because each address space is private, every process can
use the _same_ layout: on x86-64 Linux, code starting at `0x400000`, the heap
growing up from the end of the data segment, shared libraries in the middle,
the stack growing down from the top. The [linker](/computer-architecture/machine-level-x86-64/the-machines-view)
bakes absolute addresses into every executable without knowing, or caring,
where in DRAM the program will land. Allocation gets simpler too: when a
process asks for $k$ contiguous pages of heap, the kernel can satisfy it with
$k$ page frames scattered anywhere in DRAM, because contiguity in virtual space
never requires contiguity in physical space. And sharing falls out for free:
the kernel keeps one physical copy of the C library's code and maps the same
frames into every process that uses it, at whatever virtual address each
expects.

**A protection boundary.** Isolation holds by construction: a process cannot
read or clobber another's memory because no virtual address it can form _maps_
there. Within a process, the mapping is the natural place to hang finer
permissions — this page is read-only code, that one is non-executable stack,
those belong to the kernel — checked by hardware on every single reference.
The [next lesson](/computer-architecture/virtual-memory/page-tables-and-page-faults)
shows that these checks cost nothing extra, because they ride along with a
lookup translation was doing anyway.

## Pages and page frames

Translating each byte individually would be hopeless: there are $2^{48}$ of
them. Instead virtual memory works in fixed-size chunks. The virtual space is cut
into **pages** and physical memory into equally-sized **page frames**, and the
unit of mapping is one page into one frame. A typical page is $P = 2^p = 4\,\text{KB}$,
so $p = 12$.

> **Definition (Page / page frame).** A **page** is a fixed-size, aligned block
> of the virtual address space — the granularity at which virtual memory is
> mapped, transferred, and protected. A **page frame** is a physical-memory block
> of the same size that can hold one page. Mapping a page to a frame is what
> translation records.

Because the page size is a power of two, a virtual address splits cleanly into two
fields, exactly as a cache address split into [tag, index, and offset](/computer-architecture/memory-hierarchy/cache-memories-direct-mapped).
The low $p$ bits are the **virtual page offset (VPO)**: which byte _within_ the
page. The high $n - p$ bits are the **virtual page number (VPN)**: which page.

$$
% caption: An n-bit virtual address splits into the high virtual page number
% caption: (VPN) and the low p-bit virtual page offset (VPO). The VPN names a page;
% caption: the VPO names a byte inside it. Page size P = 2^p, so p = log2(P).
\begin{tikzpicture}[font=\footnotesize,>=stealth]
  \definecolor{acc}{HTML}{2348F2}
  \node[draw, fill=acc!8, minimum width=48mm, minimum height=8mm, inner sep=0pt]
    (vpn) at (0,0) {VPN};
  \node[draw, minimum width=24mm, minimum height=8mm, inner sep=0pt,
    anchor=west] (vpo) at (vpn.east) {VPO};
  \node[anchor=south west] at (vpn.north west) {bit n-1};
  \node[anchor=south east] at (vpo.north east) {bit 0};
  \draw[<->] ($(vpn.south west)+(0,-0.25)$) -- ($(vpn.south east)+(0,-0.25)$)
    node[midway,below] {n-p bits};
  \draw[<->] ($(vpo.south west)+(0,-0.25)$) -- ($(vpo.south east)+(0,-0.25)$)
    node[midway,below] {p bits};
\end{tikzpicture}
$$

## The MMU translates the page number, not the offset

Translation is done in hardware by the **memory management unit (MMU)**, a block
that sits between the CPU's address output and main memory. On every memory
reference the CPU hands the MMU a virtual address; the MMU returns the physical
address, and only then does DRAM see anything.

$$
% caption: The MMU sits between the CPU and physical memory. The CPU emits a
% caption: virtual address; the MMU translates it to a physical address; only the
% caption: physical address reaches DRAM. Translation happens on every reference.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  box/.style={draw, minimum height=11mm, inner sep=3pt, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box, minimum width=16mm] (cpu) at (0,0) {CPU};
  \node[box, minimum width=18mm, fill=acc!8] (mmu) at (3.4,0) {MMU};
  \node[box, minimum width=22mm] (mem) at (7.4,0) {physical\\memory};
  \draw[->] (cpu.east) -- (mmu.west)
    node[midway,above] {VA};
  \draw[->] (mmu.east) -- (mem.west)
    node[midway,above] {PA};
  \draw[->] (mem.south) |- ($(cpu.south)+(0,-0.75)$) -- (cpu.south)
    node[pos=0.25,below] {data};
\end{tikzpicture}
$$

The translation itself is the lesson's central fact, and it is simpler than it
looks. The MMU maps the **virtual page number** to a **physical page number
(PPN)**; that is the only lookup. The offset is _not_ translated: because a page
and a frame are the same size and the same alignment, byte $k$ of a page is byte
$k$ of its frame. So the **physical page offset (PPO) equals the VPO, bit for
bit.** The MMU replaces the high field and copies the low field through unchanged.

$$
% caption: Translation replaces the VPN with the PPN and copies the offset
% caption: through unchanged. The physical page offset (PPO) equals the virtual
% caption: page offset (VPO) exactly, because a page and a frame share size and
% caption: alignment. Only the page number is looked up.
\begin{tikzpicture}[font=\footnotesize,>=stealth]
  \definecolor{acc}{HTML}{2348F2}
  % virtual address (top)
  \node[draw, fill=acc!8, minimum width=40mm, minimum height=8mm, inner sep=0pt]
    (vpn) at (0,1.6) {VPN};
  \node[draw, minimum width=24mm, minimum height=8mm, inner sep=0pt,
    anchor=west] (vpo) at (vpn.east) {VPO};
  \node[anchor=east] at (vpn.west) {VA};
  % physical address (bottom)
  \node[draw, fill=acc!8, minimum width=30mm, minimum height=8mm, inner sep=0pt]
    (ppn) at (1.0,-1.0) {PPN};
  \node[draw, minimum width=24mm, minimum height=8mm, inner sep=0pt,
    anchor=west] (ppo) at (ppn.east) {PPO};
  \node[anchor=north east] at (ppn.south west) {PA};
  % VPN -> MMU -> PPN
  \node[draw, circle, minimum size=9mm, fill=acc!8] (mmu) at (-1.7,0.3) {MMU};
  \draw[->] (vpn.west) -| (mmu.north);
  \draw[->] (mmu.south) |- (ppn.west);
  % of\/fset copied straight down, unchanged
  \draw[->,acc, thick] (vpo.south) -- (ppo.north)
    node[midway,right] {unchanged};
\end{tikzpicture}
$$

So a virtual address `VPN | VPO` becomes the physical address `PPN | VPO`. Two
properties follow immediately. First, the number of offset bits — and therefore
the page size — is shared by both spaces; only the page-number widths can differ
($n - p$ virtual bits map to $m - p$ physical bits). Second, the _whole_ job of
the address-translation machinery reduces to one question: **given a VPN, what is
the PPN?** That mapping is held in a per-process table.

## A translation, bit by bit

Run one translation end to end on a
small machine (the example system of CS:APP §9.6.4): $n = 14$ virtual address
bits, $m = 12$ physical address bits, and $P = 64$-byte pages, so $p = 6$.
The derived widths follow mechanically:

- **VPO = PPO = 6 bits**, because $p = 6$;
- **VPN** $= n - p = 8$ bits, so the virtual space has $2^8 = 256$ pages;
- **PPN** $= m - p = 6$ bits, so physical memory has $2^6 = 64$ frames.

Notice $m < n$: this machine's virtual space (16 KB) is four times larger than
its physical memory (4 KB), so at most a quarter of the virtual pages can be
resident at once. Now let the CPU read virtual address `0x03D4`.

1. **Write out the 14 bits.** `0x03D4` is `00 0011 1101 0100`.
2. **Split at bit 6.** The high 8 bits, `0000 1111`, are the VPN: page
   `0x0F`. The low 6 bits, `01 0100`, are the VPO: byte `0x14` within the
   page.
3. **Look up VPN `0x0F`.** The page table (next lesson's subject) says this
   page is resident in frame `0x0D`, so the PPN is `0x0D`; in bits,
   `00 1101`.
4. **Form the physical address.** Concatenate PPN and the untouched offset:
   `0011 0101 0100`, which reads as `0x354`. That 12-bit address goes to
   memory.

$$
% caption: Virtual address 0x03D4 on the example machine (n = 14, m = 12,
% caption: P = 64). The high 8 bits are VPN 0x0F; the low 6 are VPO 0x14. The page
% caption: table maps VPN 0x0F to PPN 0x0D, and the physical address 0x354 is that
% caption: PPN with the same six offset bits appended, untouched.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  bit/.style={draw, minimum size=6mm, inner sep=0pt}]
  \definecolor{acc}{HTML}{2348F2}
  % virtual address bits: 00001111 010100
  \foreach \i/\v in {0/0,1/0,2/0,3/0,4/1,5/1,6/1,7/1}
    \node[bit, draw=acc, fill=acc!8] at (\i*0.6,2.6) {\v};
  \foreach \i/\v in {8/0,9/1,10/0,11/1,12/0,13/0}
    \node[bit] at (\i*0.6,2.6) {\v};
  \node[anchor=south] at (2.1,3.0) {VPN = \texttt{0x0F}};
  \node[anchor=south] at (6.3,3.0) {VPO = \texttt{0x14}};
  \node[anchor=east] at (-0.55,2.6) {VA \texttt{0x03D4}};
  % physical address bits: 001101 010100 (of\/fset column-aligned with VPO)
  \foreach \i/\v in {0/0,1/0,2/1,3/1,4/0,5/1}
    \node[bit, draw=acc, fill=acc!8] at (\i*0.6+1.2,0.4) {\v};
  \foreach \i/\v in {6/0,7/1,8/0,9/1,10/0,11/0}
    \node[bit] at (\i*0.6+1.2,0.4) {\v};
  \node[anchor=north] at (2.7,0.0) {PPN = \texttt{0x0D}};
  \node[anchor=north] at (6.3,0.0) {PPO = \texttt{0x14}};
  \node[anchor=east] at (0.65,0.4) {PA \texttt{0x354}};
  % page-table lookup arrow (VPN group down to PPN group)
  \draw[->] (2.1,2.25) -- (2.7,0.75);
  \node[anchor=east, align=right] at (1.75,1.5)
    {page table:\\PTE \texttt{0x0F} = PPN \texttt{0x0D}};
  % of\/fset copied down unchanged
  \draw[->,acc,thick] (6.3,2.25) -- (6.3,0.75)
    node[midway,right] {unchanged};
\end{tikzpicture}
$$

Every translation the rest of this module performs — through page tables, TLBs,
and multi-level walks — is this same four-step skeleton. Only step 3, the
lookup, gets more elaborate.

One more property of the split, worked through with the same numbers because it is
a common source of confusion: **which bits move and which do not.** The offset
`0x14` appears verbatim in both the virtual address (`0x03D4`) and the physical
address (`0x354`) — the low six bits are `01 0100` in each. Only the high field
changed, from VPN `0x0F` to PPN `0x0D`. So two virtual addresses in the _same_
page always land in the _same_ frame at the same relative position: `0x03D4` and
`0x03D5` (offsets `0x14` and `0x15` of page `0x0F`) become `0x354` and `0x355`,
adjacent in memory just as they were adjacent in the program. Crossing a page
boundary, though, is a discontinuity: `0x03FF` is the last byte of page `0x0F`,
and `0x0400` is byte 0 of page `0x10`, which the table may map to any frame at
all — physically nowhere near frame `0x0D`. Contiguity in virtual space survives
translation only _within_ a page; across pages it is the table's to decide, and
usually it scatters.

## Page hit, page fault

Step 3 hides one assumption: that the page _was_ resident. The lookup structure
records, for each virtual page, whether a frame currently holds it, and the
two answers produce two very different control flows.

A **page hit** is the common case, and it is handled entirely in hardware. The
CPU sends the virtual address to the MMU; the MMU reads the page's **page-table
entry (PTE)** from memory, finds the page resident, forms the physical address,
and the access completes. The program never knows any of this happened.

$$
% caption: A page hit runs entirely in hardware. (1) The CPU sends the virtual
% caption: address to the MMU. (2) The MMU requests the page-table entry from
% caption: memory (PTEA is the entry's address) and (3) receives the PTE. (4) The
% caption: entry supplies the PPN, and the MMU sends the physical address to
% caption: memory, which (5) returns the data.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  box/.style={draw, minimum height=16mm, inner sep=3pt, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box, minimum width=16mm] (cpu) at (0,0) {CPU};
  \node[box, minimum width=18mm, fill=acc!8] (mmu) at (3.6,0) {MMU};
  \node[box, minimum width=26mm] (mem) at (8.2,0) {cac\/he /\\memory};
  \draw[->] (cpu.east) -- (mmu.west) node[midway,above] {(1) VA};
  \draw[->] ([yshift=5mm]mmu.east) -- ([yshift=5mm]mem.west)
    node[midway,above,font=\scriptsize] {(2) PTEA};
  \draw[<-] (mmu.east) -- (mem.west)
    node[midway,above,font=\scriptsize] {(3) PTE};
  \draw[->] ([yshift=-5mm]mmu.east) -- ([yshift=-5mm]mem.west)
    node[midway,above,font=\scriptsize] {(4) PA};
  \draw[->] (mem.south) |- ($(cpu.south)+(0,-0.7)$) -- (cpu.south);
  \node[anchor=south] at (3.0,-1.45) {(5) data};
\end{tikzpicture}
$$

A **page fault** is what happens when the PTE says the page is _not_ resident.
Hardware cannot fix that — fetching a page from disk, choosing which resident
page to evict from its frame, and updating the bookkeeping are policy
decisions — so the MMU raises an exception that transfers control to a software
routine in the kernel, the **page-fault handler**. The handler brings the page
into a frame, updates the lookup structure, and returns. Then the CPU
**re-executes the faulting instruction from scratch**,
and this time the reference hits. The program cannot tell the fault occurred,
except by the clock.

$$
% caption: A page fault diverts to software. Steps 1-3 proceed as in a hit, but
% caption: the PTE comes back not-resident, so (4) the MMU raises an exception
% caption: into the OS page-fault handler. (5) The handler evicts a victim if
% caption: needed and reads the missing page from disk into a frame, (6) the page
% caption: lands in memory and the PTE is updated, and (7) control returns and the
% caption: CPU restarts the faulting instruction, which now hits.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  box/.style={draw, minimum height=16mm, inner sep=3pt, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \node[box, minimum width=16mm] (cpu) at (0,0) {CPU};
  \node[box, minimum width=18mm, fill=acc!8] (mmu) at (3.6,0) {MMU};
  \node[box, minimum width=26mm] (mem) at (8.2,0) {cac\/he /\\memory};
  \node[box, minimum width=30mm, minimum height=11mm] (os) at (3.6,2.6)
    {OS page-fault\\handler};
  \node[box, minimum width=18mm, minimum height=11mm] (disk) at (8.2,2.6)
    {disk\\(swap)};
  \draw[->] (cpu.east) -- (mmu.west) node[midway,above] {(1) VA};
  \draw[->] ([yshift=5mm]mmu.east) -- ([yshift=5mm]mem.west)
    node[midway,above,font=\scriptsize] {(2) PTEA};
  \draw[<-] (mmu.east) -- (mem.west)
    node[midway,above,font=\scriptsize] {(3) PTE: v=0};
  \draw[->] (mmu.north) -- (os.south) node[midway,right] {(4) exception};
  \draw[->] (os.east) -- (disk.west)
    node[midway,above,font=\scriptsize] {(5) read page};
  \draw[->] (disk.south) -- (mem.north) node[midway,right] {(6) new page};
  \draw[->] (os.west) -| (cpu.north);
  \node[anchor=south, font=\scriptsize] at (0.75,2.68) {(7) return, restart};
\end{tikzpicture}
$$

The division of labor is deliberate. Hardware handles the fast, common
case (hits) with no software in the loop; software handles the slow, rare case
(faults) with full policy freedom. The
[next lesson](/computer-architecture/virtual-memory/page-tables-and-page-faults)
fills in both halves: the structure the MMU reads, and what the handler
actually does.

## One physical memory, many virtual spaces

Putting the pieces together: each running process has its own virtual address
space, its own VPN-to-PPN mapping, and the freedom to use identical virtual
layouts. Those mappings funnel into the single physical memory, where the kernel
hands out frames as it sees fit. Two processes may map their page 5 to entirely
different frames (isolation), or — for shared libraries — deliberately to the
same frame (sharing).

$$
% caption: Two processes each have a private virtual address space; their pages
% caption: map, through per-process translations, into the one shared physical
% caption: memory. The two page 0s land in different frames (isolation), while A's
% caption: page 1 and B's page 0 deliberately share frame 2 - a shared library
% caption: mapped at different virtual addresses. Frame 1 is currently free.
\begin{tikzpicture}[font=\footnotesize,>=stealth,
  pg/.style={draw, minimum width=15mm, minimum height=6mm, inner sep=0pt},
  fr/.style={draw, minimum width=15mm, minimum height=6mm, inner sep=0pt}]
  \definecolor{acc}{HTML}{2348F2}
  % process A virtual pages (left top)
  \node[anchor=south] at (-4,2.3) {pro\/cess A};
  \node[pg] (a0) at (-4,1.7) {page 0};
  \node[pg] (a1) at (-4,1.0) {page 1};
  % process B virtual pages (left bottom)
  \node[anchor=south] at (-4,-0.5) {pro\/cess B};
  \node[pg] (b0) at (-4,-1.1) {page 0};
  \node[pg] (b1) at (-4,-1.8) {page 1};
  % physical frames (right)
  \node[anchor=south] at (2.4,2.3) {physical memory};
  \node[fr] (f0) at (2.4,1.7) {frame 0};
  \node[fr] (f1) at (2.4,1.0) {frame 1};
  \node[fr, fill=acc!8] (f2) at (2.4,0.3) {frame 2};
  \node[fr] (f3) at (2.4,-0.4) {frame 3};
  % mappings (monotone: no crossings; f2 shared by both processes)
  \draw[->,acc] (a0.east) -- (f0.west);
  \draw[->,acc] (a1.east) -- ([yshift=1.5mm]f2.west);
  \draw[->] (b0.east) -- ([yshift=-1.5mm]f2.west);
  \draw[->] (b1.east) -- (f3.west);
\end{tikzpicture}
$$

## From Atlas to five-level paging

Virtual memory is one of the oldest ideas in computer architecture that is still
exactly as described here, and the history explains why the design looks the way
it does.

The mechanism was invented on the **Atlas** computer at the University of
Manchester (Kilburn, Edwards, Lanigan, and Sumner, "One-Level Storage System,"
1962, _IRE Trans._). Atlas gave programmers a single flat store of 1 M words
while backing it with 16 K words of core and a drum, paging between them
automatically — the "one-level storage" of the title is precisely the convenient
fiction this lesson opened with. Every element here, the page, the page fault, the
automatic fetch from backing store, is in that paper. What Atlas lacked was a
fast way to do the lookup on every reference; that arrived as the **translation
lookaside buffer**, and its principle is the third lesson of this module.

The design also keeps expanding along the one axis the arithmetic exposes: address
width. This lesson's 48-bit x86-64 space ($2^{48}$ = 256 TB) was generous when it
shipped, but large servers outgrew it, so Intel and AMD added **5-level paging**
(a fifth table level lifting virtual addresses to 57 bits, $2^{57}$ = 128 PB),
supported in Linux since 2017 and shipping on Ice Lake servers. The
[page-table lessons](/computer-architecture/virtual-memory/page-tables-and-page-faults)
that follow show why adding reach is as cheap as adding one more level to the
walk. And the isolation this lesson credits to "no virtual address maps there"
has a hardware caveat: the **Meltdown** attack (Lipp et al., 2018,
USENIX Security) showed that speculative execution could momentarily read across
the protection boundary the page table is supposed to enforce, and the fix,
**kernel page-table isolation**, gives the kernel its own set of page tables so
a user process's tables cannot even name kernel memory — protection by
construction, taken one level further than this lesson's version.

> **Takeaway.** Each process emits **virtual addresses** into its own space; the
> **MMU** translates every one into a **physical address** in the single shared
> DRAM. Translation works on **pages** (size $P = 2^p$): the high **VPN** is
> looked up and replaced by a **PPN**, while the low **VPO** is copied through
> unchanged as the **PPO** — same size, same alignment, so the offset never
> moves. On the 14-bit example machine, `0x03D4` splits into VPN `0x0F` and
> offset `0x14`, and PPN `0x0D` makes the physical address `0x354`. A resident
> page is a **page hit**, handled entirely in hardware; a missing one is a
> **page fault**, handled by the OS, after which the instruction restarts. The
> whole arrangement buys a cache for disk, painless memory management, and
> protection.

The mapping from VPN to PPN lives in a structure the MMU consults on every access:
the [page table](/computer-architecture/virtual-memory/page-tables-and-page-faults),
and what happens when a page is not in DRAM at all.
