Forget segments β attack the waste directly: why keep the invalid regions of the page table in memory at all?
20.3 Multi-level Page Tables
The multi-level page table multi-level page table A page table chopped into page-sized pieces with entirely-invalid pieces simply not allocated β a page directory tracks which pieces exist. Space grows with the USED address space, at the cost of an extra memory load per TLB miss (x86's design). defined in ch. 20 β open in glossary turns the linear array into a tree, and it is so effective that most modern systems use it (x86 included). Two moves: chop the page table into page-sized units; if an entire page of PTEs is invalid, donβt allocate that page at all. A new structure β the page directory page directory The top structure of a multi-level page table: one entry per page of the page table, saying whether that piece holds any valid PTEs and, if so, which frame it lives in (its base register: the PDBR β x86's CR3). defined in ch. 20 β open in glossary β records which pieces exist: each page-directory entry (PDE) page-directory entry One page-directory record (PDE): a valid bit plus a PFN; valid means at least one PTE on the pointed-to page of the page table is valid β invalid means that whole slice of address space costs nothing. defined in ch. 20 β open in glossary holds a valid bit and a PFN, where valid means βat least one PTE on the page I point to is valid.β Invalid PDE β that whole slice of the table simply isnβt there. The multi-level table makes parts of the linear page table disappear, freeing those frames for real work.
Two structural advantages: page-table space is allocated in proportion to the address space you actually use β sparse spaces, finally handled honestly; and each piece fits neatly in a page, so growing a table means grabbing any free frame β no hunt for 4MB of contiguous memory, because the directoryβs indirection lets pieces live anywhere. The costs: on a TLB miss, two memory loads (PDE, then PTE) rather than one β a time-space trade-off β and undeniably more complexity in the lookup, wherever it runs.
A Detailed Multi-Level Example
A 16KB address space with tiny 64-byte pages: 14-bit addresses, 8-bit VPN, 6-bit offset. A linear table needs 256 entries (1KB = sixteen 64-byte pages of 16 PTEs each) β for this:
click a region marked β for details
The VPN now splits in two: the top 4 bits index the directory (16 entries, one per page of the table), the bottom 4 index within the chosen page:
PDEAddr = PageDirBase + (PDIndex * sizeof(PDE))
// if PDE valid:
PTEAddr = (PDE.PFN << SHIFT) + (PTIndex * sizeof(PTE))
Only three pages exist where sixteen would have been: the directory, and the two pieces of the table with valid mappings. Walk any address through it:
virtual address
16256
11111110000000
page directorymemory access #1
1. PDIndex = top 4 bits = 15
2. PD[15] β PT page @ PFN 101 β
page of the page tablememory access #2
3. PTEAddr = (101 βͺ 6) + 14Γ4 = 6520
4. PTE β PFN 55, rw- β
physical address
3520
| Page Directory | ||
|---|---|---|
| idx | PFN | v |
| 0 | 100 | 1 |
| 1 | β | 0 |
| 2 | β | 0 |
| 3 | β | 0 |
| 4 | β | 0 |
| 5 | β | 0 |
| 6 | β | 0 |
| 7 | β | 0 |
| 8 | β | 0 |
| 9 | β | 0 |
| 10 | β | 0 |
| 11 | β | 0 |
| 12 | β | 0 |
| 13 | β | 0 |
| 14 | β | 0 |
| 15 β | 101 | 1 |
| Page of PT (@PFN:100) | |||
|---|---|---|---|
| idx | PFN | v | prot |
| 0 | 10 | 1 | r-x |
| 1 | 23 | 1 | r-x |
| 2 | β | 0 | β |
| 3 | β | 0 | β |
| 4 | 80 | 1 | rw- |
| 5 | 59 | 1 | rw- |
| 6 | β | 0 | β |
| 7 | β | 0 | β |
| 8 | β | 0 | β |
| 9 | β | 0 | β |
| 10 | β | 0 | β |
| 11 | β | 0 | β |
| 12 | β | 0 | β |
| 13 | β | 0 | β |
| 14 | β | 0 | β |
| 15 | β | 0 | β |
| Page of PT (@PFN:101) | |||
|---|---|---|---|
| idx | PFN | v | prot |
| 0 | β | 0 | β |
| 1 | β | 0 | β |
| 2 | β | 0 | β |
| 3 | β | 0 | β |
| 4 | β | 0 | β |
| 5 | β | 0 | β |
| 6 | β | 0 | β |
| 7 | β | 0 | β |
| 8 | β | 0 | β |
| 9 | β | 0 | β |
| 10 | β | 0 | β |
| 11 | β | 0 | β |
| 12 | β | 0 | β |
| 13 | β | 0 | β |
| 14 β | 55 | 1 | rw- |
| 15 | 45 | 1 | rw- |
The tour: 16256 (0x3F80) β PDIndex 1111 β PD[15] β PT page @101; PTIndex 1110 β PFN 55; PA = (55 βͺ 6) | 0 = 3520 = 0x0DC0 β the book's worked example. 0 β code at 640. 320 (VPN 5, heap) β 3776. 128 (VPN 2) β the PT page EXISTS but the PTE is invalid: level-2 fault. 8192 (VPN 128) β PD[8] invalid: level-1 fault β the entire middle was never allocated. Note the two violet "memory access" badges: that's the time half of the time-space trade.
More Than Two Levels
The goal is that every piece of the structure fits in a page β the directory included. Take a 30-bit space with 512-byte pages: 21-bit VPN, 9-bit offset; 128 PTEs fit per page, so the page-table index takes 7 bitsβ¦ leaving 14 bits of directory index. A 2ΒΉβ΄-entry directory spans 128 pages β goal destroyed. The remedy is recursion: split the directory itself and add a level above it:
Each added level is another load on a miss β x86-64 walks four. Whew: a lot of work, all just to look something up in a tree.
The Translation Process: Remember The TLB
The full per-reference control flow, TLB first (Figure 20.6):
VPN = (VirtualAddress & VPN_MASK) >> SHIFT
(Success, TlbEntry) = TLB_Lookup(VPN)
if (Success == True) // TLB Hit
if (CanAccess(TlbEntry.ProtectBits) == True)
Offset = VirtualAddress & OFFSET_MASK
PhysAddr = (TlbEntry.PFN << SHIFT) | Offset
Register = AccessMemory(PhysAddr)
else
RaiseException(PROTECTION_FAULT)
else // TLB Miss
PDIndex = (VPN & PD_MASK) >> PD_SHIFT
PDEAddr = PDBR + (PDIndex * sizeof(PDE))
PDE = AccessMemory(PDEAddr)
if (PDE.Valid == False)
RaiseException(SEGMENTATION_FAULT)
else
PTIndex = (VPN & PT_MASK) >> PT_SHIFT
PTEAddr = (PDE.PFN << SHIFT) + (PTIndex * sizeof(PTE))
PTE = AccessMemory(PTEAddr)
if (PTE.Valid == False)
RaiseException(SEGMENTATION_FAULT)
else if (CanAccess(PTE.ProtectBits) == False)
RaiseException(PROTECTION_FAULT)
else
TLB_Insert(VPN, PTE.PFN, PTE.ProtectBits)
RetryInstruction()
On a hit, nothing changed. Only a miss pays the two extra
AccessMemory calls β the smaller tableβs whole price, collected only
when the cache fails.
Tip: Be Wary Of Complexity
A good systems builder implements the least complex system that achieves the task. As Saint-ExupΓ©ry wrote: βPerfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away.β (What he didnβt write: itβs a lot easier to say something about perfection than to achieve it.) Here, complexity was purchased deliberately β deeper lookups in exchange for precious memory.20.4 Inverted Page Tables
The extreme point in the design space: keep one table for the whole system, with an entry per physical frame recording which process uses it and which of that processβs virtual pages maps there. Finding a translation becomes a search β in practice a hash table built over the structure (the PowerPCβs approach). The inverted page table inverted page table One system-wide table with an entry per PHYSICAL frame (which process, which virtual page), found by search β in practice hashed (PowerPC). The extreme reminder that page tables are just data structures. defined in ch. 20 β open in glossary makes the chapterβs moral explicit: page tables are just data structures, and data structures can be made smaller, bigger, faster, slower β whatever the constraints reward.
20.5 Swapping The Page Tables To Disk
One last assumption quietly relaxed: even shrunken tables might not fit in physical memory. Some systems therefore place page tables in kernel virtual memory, letting the tables themselves be swapped to disk under pressure. How anything moves between memory and disk is the next chaptersβ story β and the VAX/VMS case study will show this trick in production.
20.6 Summary
Real page tables are built as trees, hashes, and hybrids β not just arrays β each choice trading time against space: bigger tables serve misses faster; smaller tables give memory back. Memory-tight systems choose small structures; memory-rich systems running page-hungry workloads may prefer big flat ones. And with software-managed TLBs, the entire data-structure universe belongs to the OS innovator β thatβs you. Dream the big dreams only operating-system developers can dream.
Homework: paging-multilevel-translate.py
Count the registers needed to locate a two-level table, then a three-level one; translate addresses under random seeds and verify with -c, counting memory references per lookup; then reason about how page-table accesses behave in the hardware CACHES (hits or misses? why?). Get it at ostep-homework.Check yourself
1.State the multi-level page table's basic idea in two moves.
2.In the 16KB example (64-byte pages, 8-bit VPN), translate 0x3F80 β the 0th byte of VPN 254.
3.What does the multi-level structure pay for its compactness, and when is the bill collected?
4.With 512-byte pages and a 30-bit address space, two levels aren't enough. Why, and what's the fix?
5.The inverted page table turns the whole design inside out. How?