3. MMU硬件基础:MMU的组成与工作流程
内存管理单元(MMU)是CPU内部负责虚拟地址到物理地址转换的硬件模块。理解MMU的硬件组成、工作流程及其与CPU的交互机制,是掌握Linux内核页表管理的前提。本章从硬件视角剖析MMU的核心组件与运作原理。
3.1 MMU的硬件组成
现代MMU通常集成在CPU内部,由三个关键硬件单元构成:TLB(转译后备缓冲器)、页表遍历器(Page Table Walker)和控制寄存器组。三者协同完成地址转换与权限检查。
3.1.1 TLB(转译后备缓冲器)
TLB是MMU内部的高速缓存,用于存储最近使用的虚拟地址到物理地址的映射条目。其目的是避免每次内存访问都遍历页表,从而大幅降低地址转换延迟。
| TLB特性 | 说明 | 典型值(x86-64) |
|---|---|---|
| 条目数 | 存储的页表条目数量 | L1 TLB: 64~128条 |
| 层级 | 通常分为L1/L2 TLB | L1指令/数据分离,L2统一 |
| 关联度 | 全相联/组相联 | L1: 全相联,L2: 4~8路组相联 |
| 页大小支持 | 支持4KB/2MB/1GB大页 | 4KB基础页 + 大页 |
| 命中延迟 | TLB命中时的转换时间 | 1~2个CPU周期 |
3.1.2 页表遍历器(Page Table Walker)
页表遍历器是MMU内部的硬件状态机,负责在TLB未命中时自动遍历内存中的页表结构。它从页表基址寄存器(如x86的CR3、ARM的TTBR0_EL1)开始,逐级解析页表条目,直到找到最终的物理地址或触发缺页异常。
// 页表遍历器硬件行为伪代码(4级页表,x86-64)
// 输入:虚拟地址 VA,页表基址 CR3
// 输出:物理地址 PA 或 缺页异常
uint64_t page_table_walk(uint64_t va, uint64_t cr3) {
uint64_t pml4e = read_memory(cr3 + (va >> 39 & 0x1FF) * 8);
if (!pml4e & PRESENT) raise_page_fault(va);
uint64_t pdpte = read_memory(pml4e & PAGE_MASK + (va >> 30 & 0x1FF) * 8);
if (!pdpte & PRESENT) raise_page_fault(va);
if (pdpte & HUGE_PAGE) return (pdpte & HUGE_MASK) + (va & HUGE_OFFSET);
uint64_t pde = read_memory(pdpte & PAGE_MASK + (va >> 21 & 0x1FF) * 8);
if (!pde & PRESENT) raise_page_fault(va);
if (pde & HUGE_PAGE) return (pde & HUGE_MASK) + (va & HUGE_OFFSET);
uint64_t pte = read_memory(pde & PAGE_MASK + (va >> 12 & 0x1FF) * 8);
if (!pte & PRESENT) raise_page_fault(va);
return (pte & PAGE_MASK) + (va & 0xFFF);
}
3.1.3 控制寄存器组
MMU的行为由一组控制寄存器配置。这些寄存器在系统启动时由内核初始化,并在进程切换时更新。以下是关键控制寄存器及其功能:
| 寄存器 | x86-64名称 | ARMv8-A名称 | 功能描述 |
|---|---|---|---|
| 页表基址 | CR3 | TTBR0_EL1 / TTBR1_EL1 | 指向当前进程的页表物理基地址 |
| MMU使能 | CR0.PG | SCTLR_EL1.M | 开启/关闭MMU地址转换 |
| 页大小 | CR4.PSE | TCR_EL1.TG0/TG1 | 配置基础页大小(4KB/16KB/64KB) |
| 地址空间 | CR3.PCID | TTBR0_EL1.ASID | 进程地址空间标识,避免TLB刷全 |
| 异常级别 | — | SCR_EL3 / HCR_EL2 | 控制虚拟化/安全扩展的MMU行为 |
3.2 MMU的工作流程
MMU的地址转换流程可概括为以下步骤。每次CPU发出内存访问请求(取指令或数据读写),MMU都会执行此流程:
- 虚拟地址接收: CPU执行单元将虚拟地址(VA)发送给MMU。
- TLB查找: MMU根据VA的页号(VPN)在TLB中并行查找。若命中,直接获取物理页框号(PFN)并拼接偏移量,跳至步骤5。
- 页表遍历: TLB未命中时,页表遍历器从控制寄存器读取页表基址,逐级读取页表条目(PTE),检查权限位(读/写/执行)和存在位(Present)。
- TLB填充: 遍历成功后,将新映射条目写入TLB(硬件自动完成或触发微码填充),并更新访问位/脏位。
- 物理地址生成: 将物理页框号与页内偏移拼接,生成最终物理地址(PA),发送给内存控制器。
- 权限检查: 若页表条目权限不足(如写只读页),MMU触发权限异常(Page Fault),由操作系统处理。
3.3 MMU与CPU的交互
MMU并非独立工作,它与CPU流水线、Cache、中断控制器紧密耦合。以下是关键的交互机制:
3.3.1 流水线集成
在CPU流水线中,MMU位于取指阶段(IF)和访存阶段(MEM)之间。具体交互如下:
- 取指阶段: 指令指针(PC)输出的虚拟地址先经过MMU转换为物理地址,再发送给L1指令缓存(L1 I-Cache)。
- 访存阶段: 加载/存储指令的虚拟地址同样经过MMU转换,然后访问L1数据缓存(L1 D-Cache)。
- 乱序执行: 现代CPU支持推测执行,MMU必须处理推测性地址转换,并在推测错误时回滚TLB状态。
3.3.2 异常与中断交互
当MMU检测到异常情况时,通过CPU的异常处理机制通知操作系统:
| MMU异常类型 | 触发条件 | CPU响应 | Linux处理 |
|---|---|---|---|
| 缺页异常 | 页表条目Present位为0 | 保存上下文,跳转到缺页异常向量 | do_page_fault()分配物理页 |
| 权限异常 | 写只读页/执行不可执行页 | 触发保护异常(#GP/Segfault) | 发送SIGSEGV信号 |
| TLB冲突异常 | TLB中同VPN多条目(罕见) | 硬件自动清除冲突条目 | 无需软件干预 |
| 访问位更新 | PTE的Accessed位为0 | MMU设置Accessed位,可能触发写回 | 页面回收算法使用 |
3.3.3 TLB刷新与上下文切换
进程切换时,新进程的页表基址写入控制寄存器,但TLB中可能残留旧进程的映射。MMU与CPU通过以下机制保证正确性:
- ASID(地址空间标识符): 每个TLB条目携带ASID标签,不同进程使用不同ASID。切换时仅需更新控制寄存器中的ASID,无需刷TLB。
- TLB刷指令: 当页表修改(如解除映射)时,内核执行特权指令(x86:
INVLPG,ARM:TLBI VAE1IS)使特定条目失效。 - 全局页(Global Page): 内核空间映射通常标记为Global,TLB条目不携带ASID,切换进程时保留,减少TLB缺失。
flush_tlb_mm_range()等函数管理此过程。
3.4 多级页表与MMU硬件适配
MMU的页表遍历器设计为支持固定级数的页表结构。以x86-64的4级页表和ARMv8-A的3级/4级页表为例,MMU硬件直接硬编码了各级索引的位域划分:
| 架构 | 页表级数 | 索引位域 | 页大小 | 最大地址空间 |
|---|---|---|---|---|
| x86-64 | 4级(PML4→PDPT→PD→PT) | 9+9+9+9+12 | 4KB | 48位(256TB) |
| ARMv8-A(4KB页) | 4级(PGD→PUD→PMD→PTE) | 9+9+9+9+12 | 4KB | 48位(256TB) |
| ARMv8-A(64KB页) | 3级(PGD→PMD→PTE) | 13+13+13+16 | 64KB | 48位(256TB) |
| RISC-V Sv39 | 3级 | 9+9+9+12 | 4KB | 39位(512GB) |
3.5 关键硬件特性总结
MMU硬件设计对操作系统内存管理有深远影响。以下是需要重点记忆的硬件特性:
- TLB是性能关键: TLB命中率直接影响内存访问延迟。大页(Huge Pages)通过减少页表级数和TLB条目占用,显著提升TLB命中率。
- 页表遍历器固定格式: 硬件遍历器要求页表格式严格遵循架构规范。Linux通过
pgtable_t类型和pte_t等抽象层适配不同架构。 - 控制寄存器切换开销: 写控制寄存器(如CR3)通常需要数十周期,且可能触发TLB刷全。Linux通过PCID/ASID技术减少切换开销。
- 硬件一致性: 多核系统中,MMU必须保证页表修改的全局可见性。通常需要内存屏障指令(如
sfence/dsb)配合TLB刷新。
arch/x86/mm/和arch/arm64/mm/目录下的文件。其中tlb.c处理TLB刷新,fault.c处理缺页异常,pgtable.c管理页表分配。结合本章的硬件知识,可以更深入理解内核为何如此设计。