355 | Virtual Memory: From CR3 to Complete Page Table Setup
Key Insight: Without virtual memory, the kernel cannot even protect itself. The security foundation of the entire modern OS rests on a single register — CR3.
1. There Must First Be Someone, Then There Is a Virtual Address
The previous article covered how the MMU works: the CPU sends a virtual address, the MMU looks up the page table, translates it to a physical address, and reads/writes memory.
But the question is: Who builds this page table?
The answer is: The kernel.
In the very early stage of booting (just after the Bootloader hands control to the kernel), the first thing the kernel does is not allocating memory, not scheduling processes, but:
Build a set of page tables so that virtual addresses “point to” physical memory.
Without page tables, the MMU can only be in a disabled state (or only perform crude real-mode address mapping). Only with page tables can we talk about user-mode/kernel-mode isolation, and independent address spaces between processes.
2. What Is CR3?
In the x86-64 architecture, there are 8 control registers (CR0~CR7), among which CR3 is the most special.
Content of CR3: A physical address pointing to the base of the PML4 table (Page Map Level 4)When the CPU translates a virtual address, the hardware directly reads CR3, obtains the PML4 base address, and then traverses down through the 4-level page table.
So enabling paging essentially boils down to two steps:
- Write the physical address of PML4 into CR3
- Set the PG bit (bit 31) of CR0 to 1
# Minimal code to enable paging (pseudo)
mov eax, [pml4_physical_addr] # Put PML4 physical address into eax
mov cr3, eax # Write to CR3
mov eax, cr0
or eax, 0x80000000 # PG bit = 1
mov cr0, eax # Enable paging!Once paging is enabled, all addresses issued by the CPU become virtual addresses, including the kernel’s own code and data.
This leads to a critical question: How do the kernel’s virtual addresses correspond to physical addresses?
3. Kernel Space Layout: Identity Mapping + High Address Mapping
The Linux/x86-64 virtual address space layout is as follows:
0x0000000000000000 ~ 0x00007FFFFFFFFFFF → User Space (128TB)
0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF → Kernel Space (128TB)Within kernel space, there are two segments that actually map physical memory:
3.1 Identity Mapping
Also called direct mapping. Feature: Virtual address == Physical address.
In a segment below 0xFFFF800000000000 (starting around 0xFFFF880000000000), the kernel creates a direct mapping:
Virtual Address 0xFFFF880000000000 → Physical Address 0x0000000000000000
Virtual Address 0xFFFF880000001000 → Physical Address 0x0000000000001000
...The advantage of this region: You can directly access physical memory without looking up page tables, which greatly simplifies the kernel initialization phase.
3.2 vmalloc Area
Used to allocate physically non-contiguous memory blocks (such as registers mapped for I/O devices), where virtual addresses are contiguous but physical addresses are not.
3.3 Fixmap Area
Fixed-purpose mappings determined at compile time, such as early page tables, early console, etc.
4. Four-Level Page Table Setup Process
x86-64 uses four-level page tables: PML4 → PDPT → PD → PT.
Each level is a 512-entry table (each entry is 8 bytes = 4096 bytes total, one page size).
Let's walk through the complete kernel initialization flow.
Step 1: Allocate PML4 Table
# Space reserved in the kernel linker script
.align 4096
pml4_table:
.fill 4096, 1, 0
pdpt_table:
.fill 4096, 1, 0
pd_table:
.fill 4096 * 4, 1, 0 # 4 PD tables (each 512GB)
pt_table:
.fill 4096 * 204... [truncated]
Comments