flush_tlb_kernel_range是 x86 架构中用于刷新内核地址空间一段范围 TLB 条目的顶层函数。它负责根据刷新范围的大小和硬件能力,选择最高效的刷新策略,确保内核页表修改对所有 CPU 立即可见。
核心决策:全量刷新还是范围刷新?
根据 2026 年 Chuyi Zhou 提交的补丁,该函数当前的实现逻辑已被简化为直接基于地址范围做决策,不再复用flush_tlb_info结构体:
void flush_tlb_kernel_range(unsigned long start, unsigned long end) { guard(preempt)(); if (end == TLB_FLUSH_ALL || tlb_range_exceeds_ceiling(start, end, PAGE_SHIFT)) kernel_tlb_flush_all(); else kernel_tlb_flush_range(start, end); }决策逻辑:
全量刷新路径:如果请求的是全量刷新(
end == TLB_FLUSH_ALL),或者刷新范围超过共享的阈值判断(tlb_range_exceeds_ceiling),则调用kernel_tlb_flush_all()。后者会根据 CPU 是否支持 INVLPGB 选择硬件广播或 IPI 广播。范围刷新路径:对于较小的范围,调用
kernel_tlb_flush_range(start, end),同样根据 INVLPGB 支持情况选择硬件广播或 IPI 逐页刷新。
历史演进:从简单到精细
这个函数的实现经历了多次优化,反映了内核对 TLB 刷新开销的持续关注:
早期实现(简单但粗暴):在 2012 年的补丁之前,x86 的flush_tlb_kernel_range直接调用flush_tlb_all(),即无论范围多小都执行全量刷新。这在内核频繁建立/拆除 vmalloc 映射时造成了显著的性能浪费。
2012 年优化:引入基于范围大小的决策,当范围较小时使用逐页INVLPG刷新,只有大范围才退化为全量刷新。这使内核执行该函数时,用户程序的内存访问性能提升了 30% 以上。
2026 年解耦:Chuyi Zhou 的补丁将flush_tlb_kernel_range与flush_tlb_info解耦,直接传递start/end参数,消除了内核刷新路径对mm状态、TLB 世代和 CPU 固定的不必要依赖,并允许在等待 IPI 期间启用抢占,改善了调度延迟。
架构差异
不同架构对flush_tlb_kernel_range的处理方式存在显著差异,这源于 TLB 硬件设计和缓存一致性模型的不同:
x86:内核映射修改通常只需刷新单个 CPU(如
clear_fixmap路径使用flush_tlb_one_kernel),因为内核映射是全局的且 x86 的 TLB 一致性由硬件维护。ARM64:即使只修改单个内核页的映射,也必须广播到所有 CPU。注释指出这是为了防止其他 CPU 因推测执行而残留“垃圾条目”(junk entries),且该函数可能在中断上下文中被调用(如
ghes_iounmap_irq),因此必须确保不依赖 IPI 完成刷新。RISC-V:2023 年的补丁将其从简单的
flush_tlb_all()改进为基于范围的刷新,但受限于无法得知底层映射的页面大小,只能以PAGE_SIZE为步长进行。
这个函数是内核内存管理中“变更页表后必须通知 CPU”这一基本原则的具体体现,其演进方向始终是在正确性(确保所有 CPU 看到最新映射)与性能(避免不必要的全量刷新和 IPI)之间寻找最佳平衡点。