3. TLB缺失处理流程:硬件自动遍历页表、软件处理与异常机制

TLB缺失,说白了就是CPU要访问的地址在TLB里没命中。这时候系统怎么办?是硬件自己搞定,还是把活儿丢给软件?我做了这么多年底层验证,发现很多工程师对这块的理解停留在“缺了就查页表”这个层面。但实际硬件走的是哪条路径、软件handler怎么介入、异常怎么触发——这里面的门道,值得好好捋一捋。

3.1 硬件自动遍历页表(Hardware Page Table Walk)

现代SMMU大多支持硬件自动遍历页表。TLB没命中,硬件直接接管,自己去内存里把页表翻出来。这个过程叫Page Table Walk(PTW)。

我习惯把PTW分成三个阶段:

  1. 定位根节点:从SMMU的TTBR(Translation Table Base Register)拿到页表基地址。
  2. 逐级解析:根据输入地址的VA[47:39]、VA[38:30]等位段,一级一级往下查。
  3. 获取物理地址:最后一级页表项(PTE)里拿到PA,同时把属性(权限、缓存策略)一并带回。

关键点:硬件PTW是阻塞式的。在页表遍历完成之前,发起访问的master(比如CPU、GPU)会被stall住。这个stall时间直接决定了系统延迟。

我在项目中遇到过一个问题:某次性能测试,TLB缺失率只有2%,但整体带宽上不去。后来抓波形发现,硬件PTW每次都要走4级页表,而页表本身在DDR里,访问延迟高得吓人。嗯,这就是典型的“TLB缺失惩罚”被低估了。

3.2 多级页表的硬件遍历路径

以ARMv8的4级页表为例,硬件遍历路径是这样的:

级别 位段 页表类型 粒度
L0 VA[47:39] PGD(Page Global Directory) 512GB块
L1 VA[38:30] PUD(Page Upper Directory) 1GB块
L2 VA[29:21] PMD(Page Middle Directory) 2MB块
L3 VA[20:12] PTE(Page Table Entry) 4KB页

硬件每查一级,都要读一次内存。如果页表不在cache里,那就是一次完整的DDR访问。4级页表就是4次DDR读——你想想看,这延迟得多大。

我的建议:如果系统里TLB缺失率超过5%,优先检查页表是否被换出了cache。我曾经在调试一个视频编解码场景时,发现页表频繁被DMA踩踏,导致PTW每次都走DDR。后来把页表锁在L2 cache里,性能直接提升了30%。

3.3 软件TLB Miss Handler

有些场景下,硬件PTW搞不定。比如:

  • 页表被换出到交换分区(swap)
  • 需要做权限检查或地址重映射
  • 硬件PTW不支持某些页表格式

这时候,硬件会触发一个TLB缺失异常,把控制权交给软件handler。软件handler的工作流程大致如下:

// 伪代码:TLB Miss Handler
void tlb_miss_handler(uint64_t fault_va) {
    // 1. 保存现场
    save_context();

    // 2. 查找页表(可能涉及文件系统IO)
    pte_t *pte = walk_page_table(fault_va);
    if (pte == NULL) {
        // 页表不存在,触发缺页异常
        handle_page_fault(fault_va);
    }

    // 3. 填充TLB
    tlb_insert(fault_va, pte->pa, pte->attr);

    // 4. 恢复现场并返回
    restore_context();
    eret();  // 异常返回
}

这里有个坑:软件handler执行期间,中断是关闭的。如果handler里做了耗时操作(比如读磁盘),整个系统的实时性就崩了。我曾经在一个车载项目里遇到过,TLB miss handler里做了文件系统查找,结果导致CAN总线报文超时——嗯,那真是血泪教训。

避坑指南:软件TLB miss handler里绝对不要做IO操作。如果页表不在内存里,应该先返回一个“可恢复异常”,让操作系统去处理缺页,而不是在handler里硬等。

3.4 异常处理机制

TLB缺失相关的异常,在ARM架构里主要分两类:

异常类型 触发条件 处理方式
TLB缺失异常 TLB未命中且硬件PTW失败 软件handler填充TLB
缺页异常 页表项无效(PTE.Valid=0) 操作系统分配物理页并更新页表
权限异常 访问权限不匹配(读/写/执行) 检查权限位,必要时触发SIGSEGV

异常处理的核心是异常向量表。SMMU会把异常路由到对应的异常级别(EL1/EL2/EL3)。我习惯在验证阶段把异常向量表打印出来,确认每个异常入口都绑对了handler。否则,一个TLB缺失异常可能直接跳转到未定义指令handler——系统就挂了。

3.5 硬件PTW vs 软件Handler:怎么选?

你可能会问:既然硬件PTW快,为什么还要软件handler?

原因很简单:硬件PTW只能处理“页表在内存里”的情况。一旦页表被换出,或者需要做复杂的权限审计,硬件就无能为力了。实际项目中,通常是混合模式:

  • 硬件PTW:处理绝大多数TLB缺失(页表在内存)
  • 软件handler:处理硬件PTW失败的“漏网之鱼”

我在调试一个虚拟化平台时,发现guest OS的TLB缺失几乎全部走硬件PTW,但hypervisor的stage-2页表缺失却频繁触发软件handler。原因?stage-2页表被hypervisor动态调整,硬件PTW无法感知——说白了,硬件只认页表结构,不认页表内容的变化。

总结一下:TLB缺失处理不是非黑即白的选择。硬件PTW负责快,软件handler负责兜底。两者配合,才能既保证性能,又保证灵活性。

3.6 核心流程:一张图看懂

下面这张图展示了TLB缺失处理的完整流程,从硬件PTW到软件handler再到异常处理:

TLB缺失处理流程 TLB缺失处理流程 TLB未命中 硬件PTW 成功? 填充TLB并返回 触发TLB缺失异常 软件TLB Miss Handler 页表有效? 填充TLB并返回 触发缺页异常 说明: 硬件PTW处理大多数情况 软件handler处理异常场景 缺页异常由OS内核处理

这张图把整个流程串起来了。从TLB未命中开始,硬件PTW先试一把。成功了直接填充TLB,失败了就交给软件handler。软件handler再判断页表是否有效——有效就填充TLB,无效就触发缺页异常。每一步都有明确的出口,不会死循环。

3.7 实战中的调试要点

最后,分享几个我在调试TLB缺失问题时常用的检查点:

  • 检查TLB缺失率:用性能计数器监控TLB缺失次数,如果超过5%,优先优化页表布局。
  • 检查PTW延迟:硬件PTW的延迟可以通过SMMU的PMU(Performance Monitor Unit)测量。如果单次PTW超过100ns,说明页表可能不在cache里。
  • 检查异常向量表:确认TLB缺失异常、缺页异常、权限异常都绑定了正确的handler。我见过最离谱的bug是异常向量表被DMA踩坏了,导致TLB缺失异常直接跳到了reset handler——系统反复重启。
  • 检查页表一致性:如果软件和硬件同时修改页表,一定要加锁或使用原子操作。否则,硬件PTW读到一半的页表,可能是个“半成品”。

一个小技巧:在验证阶段,可以故意把页表放在DDR的冷区域(比如刚初始化但没访问过的地址),然后触发TLB缺失。这样能快速验证硬件PTW是否正确地走完了所有页表级别。我每次做bringup都会跑一遍这个用例——省得后面出问题再回头查。