Identity Mapping Implementation
This chapter covers the actual code that creates the identity mapping. For the theory of why we identity map, see Identity Mapping.
Creating the Page Table
#![allow(unused)]
fn main() {
// Allocate a 4KB frame to use as our first page table
let pt_frame: u32 = allocate_frame().expect("Cannot allocate page table");
let page_table: *mut u32 = pt_frame as *mut u32;
}
A page table is just 1024 u32 entries packed into a 4KB frame. We don’t need a named struct — a raw pointer to the frame works fine.
Filling the Page Table
#![allow(unused)]
fn main() {
// Identity map 1024 pages = 4MB
for i in 0usize..1024 {
let phys_addr = (i as u32) * 0x1000; // 0x000000, 0x001000, 0x002000, ...
*page_table.add(i) = phys_addr | PAGE_PRESENT | PAGE_WRITABLE;
}
}
Each entry maps virtual page N to physical page N. Page 0 → physical 0x000000, page 1 → physical 0x001000, and so on up to page 1023 → physical 0x3FF000.
Every page is marked present and writable. The kernel needs write access to everything in this region — including the VGA buffer, the stack, and the page tables themselves once they’re allocated above the kernel.
Installing in the Page Directory
#![allow(unused)]
fn main() {
// PD entry 0: covers virtual 0x00000000–0x003FFFFF
(*page_dir).entries[0] = pt_frame | PAGE_PRESENT | PAGE_WRITABLE;
}
Page directory index 0 covers the virtual range 0x00000000 – 0x003FFFFF (first 4MB). Pointing it to our page table completes the identity mapping.
What Gets Covered
Virtual Range PD Index PT Frame Coverage
0x00000000-0x003FFFFF [0] pt_frame Identity mapped:
- Low memory
- 0x000B8000 (VGA buffer)
- 0x00100000 (kernel start)
- Kernel code + data
- Page directory frame
- Page table frame
- Future frame allocations
Note that the page directory frame and page table frame themselves fall within this identity-mapped range (they’re allocated after the kernel end, which is below 4MB). So we can access them both before and after enabling paging using the same addresses.
The Critical Moment
After installing PD[0] but before enabling paging:
Virtual addresses: Not translated (paging off)
Physical addresses: CPU uses directly
Kernel executing at physical 0x00102000
PT[0] = pt_frame | PRESENT | WRITABLE (physical 0x00100FFF or wherever)
PD[0] = pt_frame | PRESENT | WRITABLE
After enable_paging():
Virtual addresses: Translated through PD → PT
Physical addresses: Hidden behind MMU
CPU fetches from virtual 0x00102000
→ PD[0] → pt_frame
→ PT[258] → physical 0x00102000
→ Same physical memory!
Execution continues without interruption.