Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

BitNameMeaning
0PEProtected Mode Enable (must stay 1)
16WPWrite Protect (kernel can’t write to read-only pages)
31PGPaging 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.