3. TLB缺失处理流程:硬件自动遍历页表、软件处理与异常机制
TLB缺失,说白了就是CPU要访问的地址在TLB里没命中。这时候系统怎么办?是硬件自己搞定,还是把活儿丢给软件?我做了这么多年底层验证,发现很多工程师对这块的理解停留在“缺了就查页表”这个层面。但实际硬件走的是哪条路径、软件handler怎么介入、异常怎么触发——这里面的门道,值得好好捋一捋。
3.1 硬件自动遍历页表(Hardware Page Table Walk)
现代SMMU大多支持硬件自动遍历页表。TLB没命中,硬件直接接管,自己去内存里把页表翻出来。这个过程叫Page Table Walk(PTW)。
我习惯把PTW分成三个阶段:
- 定位根节点:从SMMU的TTBR(Translation Table Base Register)拿到页表基地址。
- 逐级解析:根据输入地址的VA[47:39]、VA[38:30]等位段,一级一级往下查。
- 获取物理地址:最后一级页表项(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未命中开始,硬件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都会跑一遍这个用例——省得后面出问题再回头查。