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

Identity Mapping

The Problem of Enabling Paging

There’s a chicken-and-egg problem when enabling paging:

  • Before paging: The CPU uses physical addresses directly
  • After paging: The CPU translates every address through the page tables
  • The problem: The kernel is running at a physical address (e.g. 0x00102000). When paging is enabled, if virtual address 0x00102000 isn’t mapped to physical 0x00102000, the next instruction the CPU tries to fetch will cause a page fault.

In other words: if the virtual address space doesn’t match the physical address space for the code that enables paging, enabling paging crashes the kernel immediately.

The Solution: Identity Mapping

Identity mapping maps virtual addresses to the same physical addresses:

Virtual 0x00000000 → Physical 0x00000000
Virtual 0x00001000 → Physical 0x00001000
Virtual 0x00002000 → Physical 0x00002000
...
Virtual 0x003FF000 → Physical 0x003FF000

The kernel continues executing without noticing the transition — the virtual addresses it uses map to exactly the same physical locations.

Our Identity Mapping

We identity-map the first 4MB (1024 pages):

0xFFFFFFFF ──┬─────────────┐
    4GB      │             │
             │/            │/
   (unmapped) (unmapped)

 0x00400000 ──┼─────────────┤  ← End of identity-mapped region
    4MB      │             │    Virtual = Physical
             │  KERNEL     │
             │  CODE       │
  0x00100000 ├─────────────┤  ← Kernel loads here (1MB)
    1MB      │  BIOS/hw    │
  0x000B8000 │  VGA buf    │  ← Identity mapped!
  0x00000000 └─────────────┘
             Virtual = Physical

4MB ensures we cover:

  • The kernel code and data (0x00100000 onwards)
  • The VGA buffer (0x000B8000)
  • The stack (just below 1MB in some configurations)
  • The GDT, IDT, and page tables themselves

Implementation

#![allow(unused)]
fn main() {
// Allocate a page table (one table covers 4MB)
let pt_frame = allocate_frame().expect("Cannot allocate page table");
let page_table = pt_frame as *mut u32;

// Map 1024 pages: virtual 0x00000000-0x003FFFFF = physical same
for i in 0usize..1024 {
    let phys_addr = (i as u32) * 0x1000;
    *page_table.add(i) = phys_addr | PAGE_PRESENT | PAGE_WRITABLE;
}

// Install this page table at PD index 0
// (PD index 0 covers virtual 0x00000000-0x003FFFFF)
(*page_dir).entries[0] = pt_frame | PAGE_PRESENT | PAGE_WRITABLE;
}

Why Only 4MB?

The subject specifies the kernel is limited to 4MB. We check this at startup:

#![allow(unused)]
fn main() {
if kernel_end > 0x00400000 {
    panic!("Kernel is larger than 4MB! Cannot identity map.");
}
}

If the kernel ever exceeded 4MB, we’d need a second page table for the second 4MB region (PD index 1).

After Paging Is Enabled

Once paging is enabled:

CPU fetches instruction at 0x00102000
    ↓
Virtual 0x00102000 → PD[0] → PT[258] → Physical 0x00102000
    ↓
Same physical address — execution continues normally

The kernel never notices paging was enabled.