2. x86分页模式历史:从386到x86-64,分页模式的演进与兼容性
x86架构的分页机制从1985年80386处理器引入至今,经历了从32位到64位、从两级页表到四级页表、从硬件TLB到PCID/SMEP/SMAP等安全扩展的漫长演进。理解这段历史,是掌握现代Linux内核MMU实现的基础。
2.1 分页模式的演进路线图
下表总结了x86分页模式的主要版本及其关键特性:
| 分页模式 | 引入处理器 | 页表级数 | 虚拟地址宽度 | 物理地址宽度 | 页大小支持 | 关键特性 |
|---|---|---|---|---|---|---|
| 32-bit 分页 | 80386 (1985) | 2级 | 32位 | 32位 | 4KB | 基本分页、页级保护 |
| PAE 分页 | Pentium Pro (1995) | 3级 | 32位 | 36位 | 4KB / 2MB | 物理地址扩展、NX位 |
| PSE-36 分页 | Pentium III (1999) | 2级 | 32位 | 36位 | 4MB | 大页支持 |
| x86-64 分页 | AMD Opteron (2003) | 4级 | 48位 | 52位 | 4KB / 2MB / 1GB | 长模式、4级页表、PCID |
| 5级分页 | Ice Lake (2019) | 5级 | 57位 | 52位 | 4KB / 2MB / 1GB | 更大虚拟地址空间 |
2.2 32位分页模式(386/486)
80386首次引入硬件分页单元,采用两级页表结构:页目录(Page Directory, PD)和页表(Page Table, PT)。控制寄存器CR3指向页目录基址。
2.3 PAE分页模式(Pentium Pro)
PAE(Physical Address Extension)将物理地址从32位扩展到36位,支持最大64GB物理内存。页表从2级变为3级:页目录指针表(PDPT)、页目录(PD)、页表(PT)。
2.4 x86-64长模式分页(4级页表)
AMD64架构引入长模式(Long Mode),虚拟地址扩展到48位(高16位为符号扩展),物理地址支持到52位。页表变为4级:PML4(Page Map Level 4)、PDPT(Page Directory Pointer Table)、PD(Page Directory)、PT(Page Table)。
- 每级页表包含512个表项(9位索引),每个表项8字节,页表大小为4KB。
- 虚拟地址空间分为用户空间(0x0000000000000000 - 0x00007FFFFFFFFFFF)和内核空间(0xFFFF800000000000 - 0xFFFFFFFFFFFFFFFF)。
- 高16位必须为符号扩展位,否则触发一般保护异常(#GP)。
- Linux内核将PML4表项0用于用户空间,表项256用于内核空间映射。
2.5 分页模式兼容性与切换
现代x86-64处理器支持多种分页模式,操作系统在启动过程中需要完成从实模式到保护模式再到长模式的切换。以下是各模式之间的兼容关系:
| 当前模式 | 兼容的分页模式 | 切换条件 | 典型使用场景 |
|---|---|---|---|
| 实模式 | 无分页 | CR0.PE=0 | BIOS、引导加载程序 |
| 保护模式(32位) | 32-bit分页 / PAE | CR0.PG=1, CR4.PAE=0/1 | 32位操作系统、兼容模式 |
| 长模式(64位) | 4级分页 / 5级分页 | IA32_EFER.LME=1, CR0.PG=1, CR4.PAE=1 | 64位操作系统 |
| 兼容模式 | 32-bit分页 / PAE | CS.L=0, IA32_EFER.LMA=1 | 运行32位应用程序 |
- 从保护模式切换到长模式时,必须先启用PAE(CR4.PAE=1),然后设置IA32_EFER.LME=1,最后设置CR0.PG=1。
- 切换过程中必须禁用中断,并确保页表内容在切换瞬间有效。
- 长模式下无法直接使用32位分页,必须使用4级或5级页表。
- Linux内核在启动时使用临时页表(identity mapping)完成模式切换,然后建立最终的内核页表。
2.6 分页模式演进对Linux内核的影响
Linux内核通过arch/x86/include/asm/pgtable_types.h和arch/x86/mm/pgtable.c等文件抽象不同分页模式的差异。关键抽象层包括:
- 页表类型定义:使用
pgd_t、pud_t、pmd_t、pte_t分别对应PML4、PDPT、PD、PT。 - 页表操作宏:
pgd_offset()、pud_offset()、pmd_offset()、pte_offset()用于遍历页表。 - 配置选项:
CONFIG_X86_PAE、CONFIG_X86_64、CONFIG_PGTABLE_LEVELS控制编译时的页表级数。
/* Linux内核页表层级抽象示例 (arch/x86/include/asm/pgtable-2level_types.h) */
typedef struct { pteval_t pte; } pte_t; /* 32位PTE */
typedef struct { pgdval_t pgd; } pgd_t; /* 32位PGD */
/* 4级页表类型定义 (arch/x86/include/asm/pgtable-4level_types.h) */
typedef struct { pteval_t pte; } pte_t; /* 64位PTE */
typedef struct { pmdval_t pmd; } pmd_t; /* 页中间目录 */
typedef struct { pudval_t pud; } pud_t; /* 页上层目录 */
typedef struct { pgdval_t pgd; } pgd_t; /* 页全局目录 (PML4) */
/sys/kernel/debug/page_tables接口查看当前进程的页表结构。使用crash工具或gdb调试内核时,info pgd命令可以显示页表层级。理解分页模式的演进历史,有助于快速定位与页表相关的内核崩溃(如Oops: page fault)的根本原因。
2.7 本章小结
x86分页模式从386的2级页表发展到x86-64的4级页表,物理地址空间从32位扩展到52位,虚拟地址空间从4GB扩展到256TB(48位)甚至128PB(57位)。每次演进都保持了向后兼容性,使得操作系统可以在同一硬件上支持32位和64位应用程序。Linux内核通过精巧的页表抽象层,屏蔽了不同分页模式的差异,为上层提供了统一的虚拟内存管理接口。