Skip to content
Lesson 4 of 24

Step 1 of 5 · Reading · ~4 min

Read

Boot & Memory

Virtual Memory & Paging

Every process believes it owns a clean address space starting at zero. They are all wrong, and wrong safely: the lie is told per process, enforced by the MMU and maintained by the kernel. Paging is the machinery. In this lesson your program plays the MMU of a 32-bit x86 CPU: it is handed physical memory containing real page tables and a CR3 register, and for each memory access it either produces the physical address or raises a page fault with the exact error code the hardware would push.

Splitting the address

Memory is managed in 4 KiB pages, so the low 12 bits of an address are the offset and pass through untouched. The classic 32-bit scheme has two levels, so the rest splits in two:

 31          22 21          12 11           0
+--------------+--------------+--------------+
| directory ix |   table ix   |    offset    |
+--------------+--------------+--------------+
    10 bits        10 bits         12 bits

CR3 holds the physical address of the 4 KiB page directory: 1024 entries of 4 bytes. Each present directory entry (PDE) points at a page table of 1024 page-table entries (PTE). The PTE names the physical frame.

The entry format

Both levels use the same layout. The frame address is in bits 12-31; the flags are below it:

BitNameMeaning
0Ppresent; clear it and any access faults
1R/Wwrites allowed
2U/Suser mode (ring 3) allowed
5Aaccessed: the CPU sets it when the entry is used
6Ddirty: the CPU sets it on a write (in the PTE)
7PSin a PDE: this entry maps a 4 MiB page directly

An access is allowed only if both levels allow it: a write needs R/W set in the PDE and the PTE, a user access needs U/S in both. The kernel uses this to hide kernel memory from user mode: U/S clear.

A worked walk

CR3 = 0x1000. The directory entry at 0x1004 is 0x00002007 (frame 0x2000, flags P, R/W, U/S). The table entry at 0x200C is 0x00009005 (frame 0x9000, flags P and U/S, read-only). User read of 0x00403025:

  1. directory index = 0x00403025 >> 22 = 1, so the PDE is at 0x1000 + 1*4 = 0x1004.
  2. table index = (0x00403025 >> 12) & 0x3FF = 0x403 & 0x3FF = 3, so the PTE is at 0x2000 + 3*4 = 0x200C.
  3. offset = 0x025. Physical address = 0x9000 | 0x025 = 0x9025.

A user write to the same address finds R/W clear in the PTE. That is a protection fault.

Page faults are a feature

When translation fails the CPU puts the faulting address in CR2 and pushes an error code before entering vector 14:

BitClearSet
0 (P)page not presentprotection violation on a present page
1 (W)the access was a readthe access was a write
2 (U)the CPU was in supervisor modethe CPU was in user mode

The example fault is error code 0x7 (present, write, user). A user read of an unmapped page is 0x4. The kernel reads the code and CR2 and decides: allocate a frame (demand paging), copy the page (copy-on-write), or kill the process (the segfault you know).

The accessed and dirty bits are how the kernel learns what is cold and what must be written back before eviction. Your MMU sets them in memory, and only when the walk succeeds.

Common mistakes

  • Checking permissions on the PTE only. The PDE's R/W and U/S matter too.
  • Forgetting the offset, or using the PTE's flag bits as part of the frame address (mask with 0xFFFFF000).
  • Using the wrong error-code bit for user mode (bit 2) or write (bit 1), or setting bit 0 for a not-present fault.
  • Treating a CR3 value with low bits set as an unaligned directory. The low 12 bits are control flags, not address.

The real TLB caches finished translations, which is why changing a mapping needs invlpg and writing CR3 flushes. x86-64 repeats the same walk four times, 9 bits per level.

Your exercise

The starter reads memory and CR3 for you. You write the translation, including the 4 MiB PS case, the hardware-set accessed/dirty bits and the fault code.

Up nextGDT, IDT & Mode SwitchingBoot & Memory

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…