4. 转换表查找过程:多级页表遍历算法、TLB缓存机制与失效策略

ARMv8-A 64位架构下,虚拟地址空间高达2⁶⁴字节,若采用单级页表,页表项数量将不可接受。因此,硬件采用多级页表(Translation Table Walk)机制,将线性查找转化为树形遍历,配合TLB(Translation Lookaside Buffer)缓存最近使用的转换结果,实现虚拟地址到物理地址的高效转换。

4.1 多级页表层级结构与遍历算法

ARMv8-A 使用 4级页表(Stage 1),分别为:L0、L1、L2、L3。每一级页表包含若干描述符(Descriptor),描述符指向下一级页表的基地址或直接指向物理页框。

页表级别 粒度(块/页大小) 覆盖地址范围 典型用途
L0 — 512 GiB 顶级页表,仅包含指向L1的指针
L1 1 GiB 块 1 GiB 大块内存映射(设备寄存器、大缓冲区)
L2 2 MiB 块 2 MiB 中等粒度映射(内核堆、大页)
L3 4 KiB 页 4 KiB 标准页粒度(进程堆栈、代码段)

遍历算法核心步骤:

  1. 提取索引:从虚拟地址中提取 L0、L1、L2、L3 索引位。
  2. 读取TTBR0_EL1:获取L0页表的基地址(Translation Table Base Register)。
  3. 逐级查找:
      • L0索引 → 读取L0描述符 → 若为表描述符,则获取L1基地址;
      • L1索引 → 读取L1描述符 → 若为块描述符,则直接得到物理基址(1 GiB块);
      • 否则继续到L2、L3。
  4. 终止条件:遇到块描述符或页描述符,提取物理地址;遇到无效描述符触发缺页异常。
🔍 关键点: 多级页表并非每次遍历都走满4级。当L1或L2描述符为“块”类型时,遍历提前终止,大幅减少内存访问次数。设备驱动映射大块寄存器区域时,常使用1 GiB或2 MiB块映射,既节省页表空间,又提升TLB覆盖范围。
ARMv8-A 4级页表遍历流程 ARMv8-A 4级页表遍历流程 虚拟地址 [63:0] L0索引[47:39] L1索引[38:30] L2索引[29:21] L3索引[20:12] 页内偏移[11:0] 基址 TTBR0_EL1 L0页表 表描述符 L1页表 块描述符 表描述符 1 GiB 块 L2页表 表描述符 L3页表 4 KiB 页 说明:遍历在遇到块描述符(1GiB/2MiB)时提前终止,无需走完L2/L3。设备驱动映射寄存器区域时优先使用大块映射。

4.2 TLB缓存机制

TLB 是 MMU 内部的高速缓存,存储最近使用的虚拟地址到物理地址的转换结果。TLB 命中时,MMU 直接获取物理地址,无需遍历页表,延迟从数十纳秒降至1-2个CPU周期。

4.2.1 TLB结构

  • 全相联TLB:所有条目可同时比较,命中率高,但功耗大,通常用于L1 TLB。
  • 组相联TLB:分多组,每组内全相联,平衡速度与功耗,常用于L2 TLB。
  • 指令TLB(ITLB):缓存指令地址转换。
  • 数据TLB(DTLB):缓存数据地址转换。
TLB层级 典型容量 相联度 延迟(周期) 覆盖范围(4KiB页)
L1 ITLB 32-64 条目 全相联 1 128-256 KiB
L1 DTLB 32-64 条目 全相联 1 128-256 KiB
L2 TLB(统一) 512-2048 条目 4-8路组相联 2-5 2-8 MiB
💡 驱动开发提示: 使用 大页(HugePage) 映射(如2 MiB或1 GiB)可显著提升TLB覆盖范围。例如,一个L1 TLB条目覆盖2 MiB,64条目即可覆盖128 MiB,而4 KiB页仅覆盖256 KiB。对于设备DMA缓冲区,优先使用大页映射。
TLB缓存层次结构 TLB缓存层次结构 CPU核心 L1 ITLB L1 DTLB L2 TLB(统一) 页表遍历器 TLB命中路径:CPU → L1 TLB(1周期)→ 物理地址。TLB未命中路径:CPU → L1 TLB → L2 TLB → 页表遍历器(内存访问,数十周期)。 驱动应尽量保证关键代码段和数据段在TLB覆盖范围内,避免频繁的页表遍历。

4.3 TLB失效策略

当页表内容被修改(如解除映射、更改权限、替换页框)后,TLB中缓存的旧转换结果必须失效(Invalidate),否则MMU可能使用过时的映射,导致数据不一致或安全漏洞。

4.3.1 失效指令

指令 作用范围 典型使用场景
TLBI VMALLE1 所有TLB条目(EL1) 进程切换、全局映射变更
TLBI VAE1, Xn 指定虚拟地址的TLB条目 单页解除映射
TLBI ASIDE1, Xn 指定ASID的所有TLB条目 进程退出、ASID回收
TLBI VAAE1, Xn 指定虚拟地址和ASID的条目 精确失效,最小化TLB抖动

4.3.2 失效策略选择

  • 全局失效(VMALLE1):简单粗暴,但会清空所有TLB条目,后续访问将大量未命中,性能损失大。适用于进程切换或全局映射变更。
  • 按地址失效(VAE1):仅失效特定虚拟地址的条目,粒度细,对性能影响小。适用于驱动动态映射/解除映射单个缓冲区。
  • 按ASID失效(ASIDE1):失效特定地址空间的所有条目,适用于进程退出时清理。
⚠️ 驱动常见错误: 在修改页表后忘记执行TLB失效,导致硬件仍使用旧映射访问已释放的内存,造成use-after-free或访问错误物理地址。在SMP系统中,还需通过跨核TLB失效(TLB shootdown)确保所有核的TLB一致。

4.3.3 TLB失效流程示例(Linux内核)

/* 解除映射并失效TLB */
void unmap_and_flush(struct mm_struct *mm, unsigned long vaddr)
{
    /* 1. 清除页表项 */
    pte_clear(mm, vaddr, pte_offset_map(...));
    
    /* 2. 刷新TLB(按地址失效) */
    flush_tlb_page(vma, vaddr);
    
    /* 3. 在SMP系统中,发送IPI触发其他核的TLB失效 */
    smp_call_function(remote_tlb_flush, NULL, 1);
}
TLB失效策略对比 TLB失效策略对比 全局失效 TLBI VMALLE1 清空所有TLB条目 按地址失效 TLBI VAE1, Xn 仅失效指定虚拟地址 按ASID失效 TLBI ASIDE1, Xn 失效指定ASID所有条目 性能影响:全局失效 > 按ASID失效 > 按地址失效。驱动应优先使用按地址失效,仅在进程切换或全局映射变更时使用全局失效。 ✅ 推荐驱动使用:TLBI VAE1(按地址失效)

4.4 多级页表与TLB协同工作流程

MMU在地址转换时,首先查询TLB:

  1. TLB命中:直接返回物理地址,零页表遍历。
  2. TLB未命中:硬件自动启动页表遍历器(Table Walk Unit),执行4级页表查找。
  3. 遍历成功:将转换结果填入TLB,并返回物理地址。
  4. 遍历失败:触发缺页异常(Page Fault),由操作系统处理。
🔧 驱动适配要点:
• 设备寄存器映射:使用 ioremap 时,内核自动分配页表并设置缓存属性(Device-nGnRE)。
• DMA缓冲区:使用 dma_alloc_coherent 分配非缓存一致性内存,页表属性为 Normal Non-cacheable。
• 用户态驱动:通过 mmap 映射设备内存时,需在VMA中设置 pgprot_noncached 或 pgprot_writecombine。
• TLB失效时机:在 unmap、remap、mprotect 后必须执行TLB失效,否则硬件可能访问错误物理地址。
MMU地址转换完整流程 MMU地址转换完整流程 虚拟地址 TLB查询 命中 未命中 物理地址 页表遍历器 多级页表 成功 → 填入TLB 失败 → 缺页异常 缺页异常处理 TLB命中延迟:1-2周期;TLB未命中+页表遍历:10-100周期;缺页异常:数千周期(涉及磁盘I/O)。 驱动应通过大页映射、TLB预取(Prefetch)等方式最大化TLB命中率。

4.5 本章小结

  • 多级页表通过树形遍历将大地址空间映射到物理内存,支持1 GiB/2 MiB/4 KiB多种粒度。
  • TLB缓存最近使用的转换结果,是MMU性能的关键。L1 TLB命中仅需1周期,未命中则需遍历页表。
  • TLB失效策略需根据场景选择:全局失效用于进程切换,按地址失效用于驱动动态映射管理。
  • 驱动开发应善用大页映射、合理选择缓存属性、及时执行TLB失效,避免性能陷阱和安全漏洞。