ยง18.2โ€“18.6Where Are Page Tables Stored? โ€ฆ Summary

Part I OSTEP pp. 189โ€“195 ยท ~9 min read

  • page-table entry
  • linear page table
  • valid bit
  • present bit
  • dirty bit
  • reference bit
  • page-table base register

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:

220โ‰ˆ106ย PTEsร—4ย bytes=4โ€‰MBย perย process2^{20} \approx 10^6 \ \text{PTEs} \times 4 \ \text{bytes} = 4\,\text{MB per process} 100ย processesโ‡’400โ€‰MBย ofย pureย translation100 \ \text{processes} \Rightarrow 400\,\text{MB of pure translation}

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:

Figure 18.4: the page table itself lives in (OS) physical memory โ€” spot the tiny set of translations
OS โ€” page table: 3, 7, 5, 2 โ“˜0(unused)16page 3 of AS32page 0 of AS48(unused)64page 2 of AS80(unused)96page 1 of AS112128

click a region marked โ“˜ for details

18.3 Whatโ€™s Actually In The Page Table?

The simplest organization is the linear page table : an array, indexed by VPN, yielding a page-table entry (PTE) โ€” the PFN plus a set of bits worth knowing:

The PTE's standard bits โ€” each one funds a later chapter
what it means
Validis this translation usable at all?
Protectionread / write / execute permissions
Presentin physical memory, or swapped to disk?
Dirtymodified since it was brought in?
Reference (accessed)has this page been touched?
Dotted-underlined cells have explanations โ€” click one.

The valid bit 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 , dirty bit , and reference bit 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)

31 โ€ฆ 12PFN11โ€“9โ€”8G7PAT6D5A4PCD3PWT2U/S1R/W0P

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 (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:

The ยง18.5 trace configuration, live: 64KB space, 1KB pages, linear PT at physical 1024 โ€” with the PTBR arithmetic shown

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โ†’ PFNvalid
1 โ—€41
3971
4081
4191
42101
โ€ฆ 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:

Figure 18.7: the first three loop iterations โ€” every stripe in the top lane is the paging tax
t =
Page table (PA)
Array
Code
tick 1 / 30 ยท one memory access per tick โ€” 10 per loop iteration (5 useful + 5 page-table)page-table read (the tax)instruction fetcharray store

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?

5 questions