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 address0x00102000isn’t mapped to physical0x00102000, 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 (
0x00100000onwards) - 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.