flush_tlb_func是 x86 架构 TLB 刷新(TLB Shootdown)机制中的核心 IPI 处理函数。它运行在接收到刷新请求的远程 CPU 上,负责根据请求信息执行本地 TLB 失效操作。
核心作用:远程 TLB 失效执行者
当 CPU A 修改了页表后,需要通知所有正在使用该地址空间的 CPU 刷新 TLB。它通过smp_call_function_many发送 IPI,目标 CPU 的中断处理程序会调用flush_tlb_func,在本地执行实际的 TLB 失效操作。
核心执行逻辑
根据内核源码,其逻辑可以概括为以下几个关键阶段:
1. 快速检查与惰性 TLB 处理
if (f->mm && f->mm != this_cpu_read(cpu_tlbstate.active_mm)) return;
如果请求的mm与当前 CPU 活跃的mm不匹配,说明该 CPU 不需要刷新,直接返回。随后检查当前 CPU 是否处于惰性 TLB 模式:如果是,则调用leave_mm()退出惰性模式并切换回init_mm,而不是继续使用可能过期的页表。
2. TLB 世代(tlb_gen)检查
u64 mm_tlb_gen = atomic64_read(&loaded_mm->context.tlb_gen); u64 local_tlb_gen = this_cpu_read(cpu_tlbstate.ctxs[loaded_mm_asid].tlb_gen);
内核为每个mm维护一个全局递增的 TLB 世代计数器。每个 CPU 记录自己已“追赶”到的世代。如果请求的new_tlb_gen <= local_tlb_gen,说明该 CPU 的 TLB 已经是最新的,可以跳过刷新,避免不必要的开销。
3. 执行刷新
if (f->end == TLB_FLUSH_ALL) { local_flush_tlb(); } else { for (addr = f->start; addr < f->end; addr += PAGE_SIZE) __flush_tlb_single(addr); }根据请求范围决定刷新策略:
全量刷新(
end == TLB_FLUSH_ALL):调用local_flush_tlb(),通过重写 CR3 或切换 PGE 位来清空整个 TLB。范围刷新:逐页调用
__flush_tlb_single(addr)(即invlpg指令),精确失效指定地址的 TLB 条目。
4. 更新本地世代
刷新完成后,将本 CPU 记录的tlb_gen更新为mm_tlb_gen,表示已经追赶上了最新的页表状态。
调用关系与上下文
flush_tlb_mm_range() / flush_tlb_kernel_range() └── native_flush_tlb_others() / flush_tlb_multi() └── smp_call_function_many(cpumask, flush_tlb_func, info, 1) └── flush_tlb_func(info) // ← 远程 CPU 的 IPI 处理
flush_tlb_func作为smp_call_function_many的回调被调用,运行在中断上下文中,因此必须满足:中断已禁用、不可重入(代码中有VM_WARN_ON(!irqs_disabled())和重入检查)。
关键优化与演进
这个函数经历过多次优化:
延迟读取
mm_tlb_gen:2022 年的补丁将mm_tlb_gen的读取推迟到确认需要刷新之后,避免在刷新风暴中争抢mm结构体的缓存行,在 56 核 Skylake 上带来高达24%的性能提升。跳过无意义的跟踪:2022 年的补丁确保在“不执行刷新”的路径上不调用
trace_tlb_flush,保持跟踪行为一致。阈值决策集中化:2025 年的补丁将“部分刷新还是全量刷新”的决策集中到
get_flush_tlb_info中,减少代码重复。