2. x86分页模式历史:从386到x86-64,分页模式的演进与兼容性

x86架构的分页机制从1985年80386处理器引入至今,经历了从32位到64位、从两级页表到四级页表、从硬件TLB到PCID/SMEP/SMAP等安全扩展的漫长演进。理解这段历史,是掌握现代Linux内核MMU实现的基础。

学习目标:掌握x86分页模式的四个关键里程碑(386/486/Pentium Pro/x86-64),理解各级页表结构的差异、地址翻译流程的变化以及操作系统如何利用分页实现虚拟内存、保护和隔离。

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 更大虚拟地址空间
兼容性要点:现代x86-64处理器在加电后默认进入32位保护模式,必须通过设置CR0.PG、CR4.PAE、IA32_EFER.LME等控制寄存器,才能逐步切换到4级页表的长模式。操作系统必须同时支持32位兼容模式和64位原生模式的分页切换。

2.2 32位分页模式(386/486)

80386首次引入硬件分页单元,采用两级页表结构:页目录(Page Directory, PD)和页表(Page Table, PT)。控制寄存器CR3指向页目录基址。

32位分页地址翻译流程 32位分页地址翻译流程(2级页表) 虚拟地址 (32位) 页目录索引 (10位) 页表索引 (10位) 页内偏移 (12位) 页目录 (PD) CR3 → 1024项 页表 (PT) 1024项 物理页 (4KB) 说明 • 页目录项(PDE)指向页表 • 页表项(PTE)指向物理页 • 每项4字节,共4MB页表
关键数据结构:页目录项(PDE)和页表项(PTE)均为32位。高20位存放物理页帧号(PFN),低12位存放标志位(Present、R/W、User/Supervisor、Dirty、Accessed等)。操作系统通过修改PTE的Present位实现页面换入换出。

2.3 PAE分页模式(Pentium Pro)

PAE(Physical Address Extension)将物理地址从32位扩展到36位,支持最大64GB物理内存。页表从2级变为3级:页目录指针表(PDPT)、页目录(PD)、页表(PT)。

PAE分页地址翻译流程 PAE分页地址翻译流程(3级页表) 虚拟地址 (32位) PDPT索引 (2位) PD索引 (9位) PT索引 (9位) 页内偏移 (12位) PDPT 4项 PD 512项 PT 512项 物理页 (4KB) PAE特性 • 页表项64位 • 物理地址36位 • 支持NX位 • CR3指向PDPT
PAE兼容性陷阱:PAE模式下页表项从32位变为64位,导致页表大小翻倍。操作系统必须为每个进程分配4KB的PDPT(仅使用前4项),且CR3必须指向32字节对齐的PDPT基址。Linux内核在支持PAE时,需要额外处理页表项的64位原子更新。

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)。

x86-64 4级页表地址翻译流程 x86-64 4级页表地址翻译流程 虚拟地址 (48位,高16位符号扩展) PML4 (9位) PDPT (9位) PD (9位) PT (9位) 偏移 (12位) PML4 512项 PDPT 512项 PD 512项 PT 512项 物理页 4级页表特性 • 每级页表512项 • 每项8字节 • 页表大小4KB/级 • 支持1GB大页 • CR3指向PML4
4级页表关键参数:
  • 每级页表包含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位应用程序
切换注意事项:
  1. 从保护模式切换到长模式时,必须先启用PAE(CR4.PAE=1),然后设置IA32_EFER.LME=1,最后设置CR0.PG=1。
  2. 切换过程中必须禁用中断,并确保页表内容在切换瞬间有效。
  3. 长模式下无法直接使用32位分页,必须使用4级或5级页表。
  4. 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内核通过精巧的页表抽象层,屏蔽了不同分页模式的差异,为上层提供了统一的虚拟内存管理接口。