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.

Bash
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:

  1. Write the physical address of PML4 into CR3
  2. Set the PG bit (bit 31) of CR0 to 1

Bash
# 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:

Bash
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:

Bash
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

Bash

# 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]
Last modified: 2024年1月5日

Author

Comments

Write a Reply or Comment

Your email address will not be published.