Worked examples — Page tables and multi-level paging
This page drills the parent topic through concrete numbers. We touch every kind of input the translation machinery can face: normal addresses, addresses that sit exactly on a page boundary, the very first and very last byte of a page, invalid (unmapped) entries, single-level vs multi-level walks, a 64-bit monster, a word problem, and an exam twist. Prerequisites we lean on: Virtual Memory, Bit Manipulation and Masking, and TLB and Translation Caching.
The scenario matrix
Every cell here is a distinct situation the reader must survive. Each example below is tagged with the cell(s) it covers.
| Cell | Scenario class | What makes it tricky |
|---|---|---|
| A | Normal single-level translation | The baseline — chop, look up, reassemble |
| B | Offset = 0 (page boundary, low edge) | Address sits exactly at a page start |
| C | Offset = P−1 (last byte of a page) | The maximum offset; off-by-one traps |
| D | Two-level walk, all fields non-zero | Splitting the VPN itself |
| E | Invalid PTE (unmapped page), both fault points | Translation fails → page fault at L1 or L2 |
| F | Degenerate: address 0x0 | Every field is zero |
| G | 64-bit, 4-level walk | Large-address scaling |
| H | Word problem (memory saved) | Real-world sizing decision |
| I | Exam twist: given PA, recover VPN | Running translation backwards |
Setup we reuse
Unless a problem says otherwise: 32-bit virtual addresses (), page size bytes, so offset bits , and VPN bits . Two-level split is 10 / 10 / 12 (outer index = 10 bits, inner index = 10 bits, offset = 12 bits).
Figure s01 shows this chop applied to a 32-bit address: the same knife-cut we will perform, by hand, in every example below. Notice the orange offset field on the right — that is the slice that rides through untranslated in Examples A–I.

Example A — Normal single-level translation (Cell A)
Steps.
- offset . Why this step? The low 12 bits name a byte inside the page; they ride through untranslated.
- VPN . Why this step? The remaining high bits say which page — the key into the table.
- Look up
table[3]→ PFN =0x00042, valid, so translation succeeds. Why this step? The array position is the VPN; the stored value is the frame. - PA . Why this step? Reassemble: frame number in the high bits, original offset in the low bits.
Verify: the low three hex digits ABC are unchanged (offset untouched — exactly the parent's rule). Only the page portion 003 → 042 moved. . ✓
Example B — Offset zero, page boundary (Cell B, F-flavour)
Steps.
- offset .
Why this step?
0x7000is a multiple of0x1000= 4096 = , so it sits exactly at the start of a page. Offset 0 means "byte 0 of the frame." - VPN . Why this step? Discarding the low 12 bits leaves the page number — the key we index the table with, exactly as in Example A.
- Look up
table[7]→ PFN0x00100. Why this step? The array position 7 is the VPN; the stored value0x100is the frame that page lives in. - PA . Why this step? Reassemble: slide the PFN into the high bits, drop the offset (here 0) into the low bits.
Verify: a boundary address maps to a boundary address — PA also ends in 000. . ✓ This is the smallest address in that frame.
Example C — Last byte of a page, offset = P−1 (Cell C)
Steps.
- offset .
Why this step?
0xFFFis the largest value 12 bits can hold — the very last byte of the page. - VPN .
Why this step? Crucial: even though the address looks big, everything up to
0x7FFFstill belongs to page 7. The next byte,0x8000, is the first byte of page 8. table[7]→ PFN0x00100. Why this step? Same page number 7 as Example B, so it indexes the same row and yields the same frame — the maximum offset did not change which page we're in.- PA .
Why this step? Reassemble: the frame is unchanged from Example B; only the offset (now
0xFFF) rides into the low bits, landing us at the last byte of the frame.
Verify: Example B (0x7000) and Example C (0x7FFF) share PFN 0x100; their physical addresses differ only in the offset 000 vs FFF. Both stay inside frame 0x100. Add one more byte (0x8000) and the VPN becomes 8 — a different table row. ✓ This is the off-by-one that traps people: 0x8000 is not in page 7.
Example D — Two-level walk, all fields non-zero (Cell D)
Steps.
- offset . Why this step? Low 12 bits, as always.
- VPN . Why this step? We now have the 20-bit page number that we must split again.
- inner index .
Why this step? The inner index is the lowest VPN slice, so shift (nothing below it);
0x3FFkeeps its 10 bits. - outer index . Why this step? The outer index sits above the 10 inner bits, so shift first, then keep 10 bits — the high slice of the VPN.
- Walk: read
L1[1]→ gives L2 base; readL2[2]→ PFN0x0009C. Why this step? Two memory accesses, one per level, matching the parent's cost formula ( reads for levels). - PA . Why this step? Reassemble, exactly as in the single-level case: the final-level PFN slides into the high bits, the untranslated offset drops into the low bits — the tree only changed how we found the PFN, not how we build the address from it.
Verify: Reassemble the VPN from the two indices: . ✓ Matches the parent note's worked walk exactly.
Figure s02 draws this exact walk as a tree: L1[1] (plum) hands us the L2 base, L2[2] (teal) hands us frame 0x9C (orange), and the offset 0xABC selects the byte inside it. Follow the two arrows — each one is a memory read.

Example E — Invalid PTE: translation fails at either level (Cell E)
Steps (Case E1 — top-level fault).
- offset . Why this step? Same first move as every example: peel off the low 12 bits so the rest is a pure page number, whether or not it turns out to be mapped.
- VPN . Why this step? We need the page number to compute the indices — a missing mapping is discovered during the walk, so we still chop first.
- ; . Why this step? Same chop as Example D — the mechanics don't change just because an entry is missing.
- Read
L1[2]: valid = 0. Stop immediately — there is no L2 table to walk into. Why this step? The parent's "omit empty subtrees" insight lives here — an unallocated branch is exactly an invalid top-level entry. We never even read L2.
Steps (Case E2 — second-level fault).
- Chop as in Example D: , , offset
0xABC. - Read
L1[1]: valid = 1 → gives a real L2 base. Proceed. Why this step? The top branch exists, so we take the second memory read — unlike E1. - Read
L2[2]: valid = 0. Stop. The L2 table exists but this leaf maps no frame. Why this step? Sparsity can occur at any level; a present inner table may still have unmapped rows. - Hardware raises a page fault — there is no PFN to return, so the CPU cannot build a PA and must hand control to the OS. Why this step? A valid = 0 leaf means "this virtual page currently maps to no frame"; the only correct response is to signal the OS (map a frame on demand, or kill the process) rather than fabricate an address.
Both cases: the OS decides — map a new frame (demand paging) or kill the process (illegal access). See Page Faults and Demand Paging.
Verify: neither case yields a PA — that absence is the answer. E1 costs 1 table read before faulting; E2 costs 2. A valid-bit of 0 must never be treated as PFN 0 (that would silently read frame 0). ✓ Together E1 and E2 cover every fault point a two-level walk has.
Example F — The degenerate address 0x0 (Cell F)
Steps.
- offset . Why this step? Even for the smallest address we still peel off the offset — the recipe never changes, and confirming offset = 0 tells us we're at the very first byte of a page.
- VPN , so , . Why this step? Zero shifts and masks to zero — the smallest possible everything.
L1[0]→L2[0]→ PFN0x00000. Why this step? We still walk both levels: index 0 of the root points to an L2 table, index 0 of which stores the frame — zero indices are ordinary array positions, not a special case.- PA . Why this step? Reassemble as always; with PFN 0 and offset 0 the high and low bits are both empty, so the physical address is 0.
Verify: PA = 0x0. Note this is a choice of the mapping, not a law: virtual 0 maps to physical 0 only because that PTE happened to store PFN 0. In real systems address 0 is usually left unmapped (so null-pointer dereferences fault) — which would instead be an Example-E situation. ✓ Both readings are legitimate; the difference is entirely in the valid bit.
Example G — 64-bit, 4-level walk (Cell G)
Steps.
- offset . Why this step? Page size is still 4 KiB, so regardless of address width.
- VPN . Each level now uses a 9-bit slice, so the mask is
0x1FF() and the shift jumps by 9 per level (generalising the "" rule to 9-bit levels: , counting from the bottom):- — lowest slice, , nothing below it.
- — skip the 9 bits of first.
- — skip the 18 bits of .
- — skip the 27 bits below it. Why this step? Same knife as the 32-bit case, just narrower teeth. Each shift discards exactly the index bits below the slice we want, then the mask keeps the 9 bits we want and drops everything above. bits total — the split must exhaust the address.
- Numerically this gives (computed in Verify).
- Walk
L1[i1] → L2[i2] → L3[i3] → L4[i4]= 4 memory reads, then 1 more for the data. Why this step? Parent's rule: -level table ⇒ reads per translation before caching. Here , so a TLB miss is expensive — exactly why TLB and Translation Caching matters more as levels grow.
Verify: = ✓. And reassembling: should return the original 48-bit address (checked below). ✓
Example H — Word problem: how much memory did the tree save? (Cell H)
Steps.
- Flat table size . Why this step? One entry per possible page, whether used or not.
- Two-level: top table = always 1 table of 4 KiB. Why this step? The root must exist to hold pointers.
- Two-level: inner tables = 3 (worst case, one per distinct top-level entry) × 4 KiB = 12 KiB. Why this step? Each used branch gets exactly one inner table; the other 1021 top entries are invalid → no table.
- Total tree .
- Savings .
Verify: ratio smaller. The tree uses about 0.39% of the flat table's memory here. ✓ Units check: bytes vs bytes throughout. If all 3 pages shared one top entry, only 1 inner table is needed → 8 KiB total (even cheaper) — the worst case above bounds it.
Example I — Exam twist: run translation backwards (Cell I)
Steps.
- Extract offset from PA . Why this step? The offset is shared by virtual and physical addresses — the one field we can trust across the divide.
- The trap: the PA gives us the frame number
0x9C, but not the VPN. Many virtual pages can map to the same frame (shared memory). So PA alone is insufficient — we needed the walk info . Why this step? Translation is many-to-one in general; reversing needs extra data. - Rebuild VPN from the walk indices . Why this step? The indices are the VPN, split; concatenating them restores it.
- Rebuild virtual address . Why this step? This is the reassemble move of our chop→look up→reassemble recipe, run in reverse: having recovered the VPN in Step 3, we slide it left by to put it back in the page-number position and drop the untranslated offset into the low bits — exactly the inverse of the Step-1 chop.
Verify: this is exactly Example D's original virtual address — the round trip closes. ✓ Forward: 0x00402ABC → 0x0009CABC; backward (with walk data): 0x0009CABC + → 0x00402ABC. The offset ABC survives both directions untouched.
Recall Quick self-test
In Example C, is address 0x8000 in page 7? ::: No — it is the first byte of page 8; 0x7FFF was the last byte of page 7.
In Example E, what are the two fault points of a two-level walk? ::: An invalid L1 entry (no inner table, fault after 1 read) or a valid L1 entry whose L2 leaf is invalid (fault after 2 reads).
In Example H, how many times smaller is the tree than the flat table? ::: About 256× (16 KiB vs 4 MiB).
In Example I, why can't PA alone give the VPN? ::: Many virtual pages can share one frame — translation is many-to-one, so it isn't uniquely reversible.
What does PFN stand for? ::: Physical Frame Number — which physical frame a page maps to; stored in the PTE.