Page tables promise everything โ sparse spaces, no external fragmentation, per-page protection โ and then present two bills.
18.2 Where Are Page Tables Stored?
Do the arithmetic for a real address space โ 32 bits, 4KB pages. The offset takes 12 bits, leaving a 20-bit VPN:
Far too much for on-chip MMU storage โ so, unlike base/bounds pairs or segment registers, the page table lives in plain memory. (For now, in physical memory the OS manages; later even page tables get virtualized and swapped, which is too confusing right now, so we ignore it.) Hereโs our tiny table, at home in the OSโs frame:
click a region marked โ for details
18.3 Whatโs Actually In The Page Table?
The simplest organization is the linear page table linear page table The simplest page table: a flat array indexed by VPN, one PTE per virtual page โ the structure whose 4MB-per-process cost motivates smarter tables. defined in ch. 18 โ open in glossary : an array, indexed by VPN, yielding a page-table entry (PTE) page-table entry One page table record (PTE): the physical frame number plus bits โ valid, protection, present, dirty, reference โ that the hardware checks and the OS exploits. defined in ch. 18 โ open in glossary โ the PFN plus a set of bits worth knowing:
| what it means | |
|---|---|
| Valid | is this translation usable at all? |
| Protection | read / write / execute permissions |
| Present | in physical memory, or swapped to disk? |
| Dirty | modified since it was brought in? |
| Reference (accessed) | has this page been touched? |
The valid bit valid bit PTE bit marking a translation as usable; unused address-space pages are marked invalid and get NO physical frames โ the trick that makes sparse address spaces cheap (access โ trap, likely termination). defined in ch. 18 โ open in glossary deserves the spotlight: itโs what makes paging handle sparse address spaces gracefully โ every unused page between heap and stack is marked invalid and consumes no physical frame at all. The present bit present bit PTE bit saying whether the page is in physical memory or swapped to disk; the hinge on which chapter 21's swapping turns (x86 fuses it with validity into a single P bit). defined in ch. 18 โ open in glossary , dirty bit dirty bit PTE bit set when a page has been modified since it was brought into memory โ a clean page can be evicted without writing it back. defined in ch. 18 โ open in glossary , and reference bit reference bit PTE bit (a.k.a. accessed bit) recording that a page was touched โ the raw signal replacement policies use to guess which pages are popular. defined in ch. 18 โ open in glossary are down-payments on the swapping and replacement chapters ahead.
The real thing, from x86 โ find each flag:
Figure 18.5: an x86 page-table entry (hover any cell)
Aside: Why No Valid Bit?
x86 fuses valid and present into the single P bit. P=1: present and valid. P=0: maybe swapped out, maybe invalid โ the hardware traps either way, and the OS, consulting its own structures, decides whether to swap the page in or terminate the illegal access. Hardware provides the minimal feature; the OS builds the full service on top โ a division of labor youโll see again and again.18.4 Paging: Also Too Slow
The second bill. To translate VA 21, the hardware must first fetch the PTE โ and it finds the table via the page-table base register page-table base register Hardware register (PTBR) holding the physical address where the running process's page table starts; the hardware computes PTEAddr = PTBR + VPN ร sizeof(PTE) on every reference. defined in ch. 18 โ open in glossary (PTBR), which holds the tableโs starting physical address. Figure 18.6โs full per-reference protocol:
// Extract the VPN from the virtual address
VPN = (VirtualAddress & VPN_MASK) >> SHIFT
// Form the address of the page-table entry (PTE)
PTEAddr = PTBR + (VPN * sizeof(PTE))
// Fetch the PTE
PTE = AccessMemory(PTEAddr)
// Check if process can access the page
if (PTE.Valid == False)
RaiseException(SEGMENTATION_FAULT)
else if (CanAccess(PTE.ProtectBits) == False)
RaiseException(PROTECTION_FAULT)
else
// Access is OK: form physical address and fetch it
offset = VirtualAddress & OFFSET_MASK
PhysAddr = (PTE.PFN << PFN_SHIFT) | offset
Register = AccessMemory(PhysAddr)
Count the AccessMemory calls: two. Every load, store, or fetch
now costs an extra memory reference to the page table first โ likely a
2ร slowdown or more. Two real problems, then: without careful
design, paging makes the machine too slow and fills memory with
tables that are too big.
18.5 A Memory Trace
Watch the tax collected, reference by reference. The program couldnโt be humbler:
int array[1000];
...
for (i = 0; i < 1000; i++)
array[i] = 0;
Disassembled (gcc -O), the loop is four instructions at virtual addresses 1024โ1036:
1024 movl $0x0,(%edi,%eax,4) ; array[i] = 0 (edi = base, eax = i)
1028 incl %eax ; i++
1032 cmpl $0x03e8,%eax ; i == 1000?
1036 jne 0x1024 ; not yet โ loop
Setup: 64KB address space, 1KB pages, linear page table at physical 1024. Code sits on VPN 1 โ PFN 4; the 4000-byte array spans virtual 40000โ44000, VPN 39โ42 โ PFN 7โ10. Try the exact translations the hardware will make:
virtual address
1 KB (1024)
0000010000000000
MMU (paging)
1. VPN = top 6 bits = 1 ยท offset = 0
2. PTEAddr = 1024 + 1ร4 = 1028
3. PT[1] โ PFN 4 โ
4. PA = 4ร1024 + 0 = 4096
physical address
4 KB (4096)
| VPN | โ PFN | valid |
|---|---|---|
| 1 โ | 4 | 1 |
| 39 | 7 | 1 |
| 40 | 8 | 1 |
| 41 | 9 | 1 |
| 42 | 10 | 1 |
| โฆ every unlisted VPN: valid = 0 | ||
Fetching the first instruction: VA 1024 โ VPN 1, PTE at 1024 + 1ร4 = 1028, PFN 4 โ PA 4096. The array store: VA 40000 โ VPN 39, PTE at 1180, PFN 7 โ PA 7232. 41000 lands in VPN 40 (the array spans four pages: VPN 39โ42). And 45000? VPN 43 โ valid bit 0 โ SEGMENTATION_FAULT, paging-style.
Each iteration: four instruction fetches, one store โ and five page-table reads, one before each. Ten memory accesses to zero a single array element:
Iteration 0: the fetch of movl needs translating first โ read PT[1] at physical 1024 + 1ร4 = 1028.
The patterns beg for exploitation: PT[1] is read four times per iteration with the same answer every time; the array marches predictably through VPN 39, then (around i=250) crosses into VPN 40, 41, 42. What kind of hardware would notice such repetition โ and short-circuit it? Thatโs the next chapter.
18.6 Summary
Paging solves memory virtualization without external fragmentation and with genuine support for sparse address spaces. But done naively itโs too slow (an extra memory reference per access) and too big (megabytes of table per process). The next two chapters fix each in turn: a translation cache, then smarter table structures.
Homework: paging-linear-translate.py
First study how linear page-table SIZE scales โ grow the address space (1m โ 4m), then grow the page size (1k โ 4k), and explain both trends (why not just use big pages?). Then translate: vary the fraction of allocated pages (-u 0โฆ100) and watch valid/invalid results shift; finish with the deliberately crazy configs (-P 8, -P 1m) and say which are unrealistic, and why. Get it at ostep-homework.Check yourself
1.Why do page tables live in ordinary memory rather than in dedicated MMU hardware, as base/bounds and segment registers did?
2.How does the valid bit make sparse address spaces nearly free?
3.x86 has no separate valid bit โ only present (P). When P=0 and a process touches the page, what must happen?
4.What does naive paging cost on EVERY memory reference, and what are the two crucial problems left standing at chapter's end?
5.In the array.c trace (four instructions + one store per iteration), how many memory accesses does ONE loop iteration generate?