Enabling Paging
The Two-Step Activation
Enabling paging requires configuring two CPU control registers in the correct order:
#![allow(unused)]
fn main() {
unsafe fn enable_paging(page_dir_phys: u32) {
// Step 1: Tell the CPU where the page directory is
core::arch::asm!(
"mov cr3, {0:e}",
in(reg) page_dir_phys,
);
// Step 2: Set the PG bit in CR0 to enable paging
core::arch::asm!(
"mov eax, cr0",
"or eax, 0x80000000",
"mov cr0, eax",
out("eax") _,
);
}
}
CR3 — Page Directory Base Register
CR3 (32-bit):
Bits 31-12: Physical address of page directory (4KB-aligned)
Bits 11-0: Flags (cache control, etc.) — we use 0
Writing to CR3 also flushes the entire TLB. The CPU starts fresh with no cached translations.
mov cr3, {page_dir_phys}
After this instruction, the CPU knows where to find the page directory, but paging is not yet active. The CPU still uses physical addresses directly.
CR0 — Control Register 0
CR0 controls fundamental CPU operation modes. We only want to set bit 31 (the PG bit):
CR0 bit 31: PG (Paging Enable)
0 = Paging disabled (physical addressing)
1 = Paging enabled (virtual addressing via page tables)
mov eax, cr0 ; Read current CR0 (preserve all other bits)
or eax, 0x80000000 ; Set bit 31 only
mov cr0, eax ; Write back
The OR is essential. CR0 has other critical bits:
| Bit | Name | Meaning |
|---|---|---|
| 0 | PE | Protected Mode Enable (must stay 1) |
| 16 | WP | Write Protect (kernel can’t write to read-only pages) |
| 31 | PG | Paging Enable ← we set this |
Using MOV instead of OR would overwrite these bits and crash the CPU.
The Point of No Return
The instruction mov cr0, eax (with PG bit set) is the point of no return. The CPU immediately starts translating all virtual addresses — including the very next instruction fetch.
Before this instruction: CPU uses physical address 0x00102ABC directly
After this instruction: CPU translates virtual 0x00102ABC
→ PD[0] → PT[258] → physical 0x00102ABC
→ Same address (identity mapping works!)
If the identity mapping is wrong, the CPU page-faults on the instruction immediately following mov cr0, eax. Since there’s no page fault handler set up yet (paging wasn’t enabled until now), this becomes a double fault, then a triple fault, then a CPU reset.
This is why the identity mapping is set up entirely before calling enable_paging — you get exactly one shot.
After Paging Is Enabled
init_paging returns
↓
kernel_main continues executing
↓
Every memory access (instruction fetch, stack push, variable read)
goes through the page tables
↓
Unmapped addresses → page fault → rust_page_fault() → "EXCEPTION #14"
Mapped addresses → transparent hardware translation
The kernel doesn’t need to change any of its pointer arithmetic — identity mapping ensures all existing pointers still work. The paging system is invisible unless something accesses unmapped memory.