icache invalid 场景:什么时候需要刷缓存

原始问题:icache invalid 这个业务场景是啥,什么时候需要刷啊。还有其他需要OS操作 cache 的的操作吗,是什么情况下需要操作啊。cache 不是对os透明吗 · 2026-08-04

ICache 失效与 Cache 维护全景:什么时候 OS 必须”动手”刷 Cache

一、先回答”Cache 不是对 OS 透明吗?”——半透明

对普通数据读写,Cache 对 OS 完全透明:CPU 硬件自动保证同一地址的多核一致性(ARM 的 MESI 协议、D-cache 的 PIPT/VIPT 属性)。但有两类场景硬件不自动维护,必须由软件显式介入:

不透明场景 原因 典型架构差异
指令路径与数据路径分离 ARM 的 I-cache 与 D-cache 是两套独立存储,CPU 写指令走 D-cache,取指走 I-cache,二者不自动同步 x86 有 self-snooping 硬件保证(写指令时硬件自动探测 I-cache),无需软件刷 icache;ARM 必须软件维护
非 CPU 代理访问内存(DMA) 设备绕过 Cache 直接读写内存,看不到 Cache 里的脏数据 所有架构都需要软件维护(DMA API)
生命周期事件(复位/下电) Reset 后 Cache RAM 内容不可靠、断电丢内容 所有架构的电源管理路径需要软件 clean/invalidate
Errata/硬件缺陷 部分 CPU 的 Cache 行为有 bug 如 ARM64 的 ARM64_WORKAROUND_CLEAN_CACHE(见 arch/arm64/kernel/cpu_errata.c:654)

二、ICache Invalidate:业务场景与触发时机

核心概念:PoU(Point of Unification)

刷 icache 必须针对 PoU(指令/数据统一点)操作,标准序列是:

store 新指令 → [1] clean D-cache to PoU   (把新指令从 D-cache 写到统一点)
            → [2] invalidate I-cache to PoU(丢掉可能残留的旧指令)
            → [3] DSB(ish)                  (确保维护操作对所有核完成广播)
            → [4] ISB                       (本核丢弃流水线中已取出的旧指令)

ARM64 的封装见 arch/arm64/include/asm/cacheflush.h:84-106,其中 kick_all_cpus_sync() 就是用 IPI 让其他核也执行一次上下文同步(ISB)。

触发条件:“写入新指令”事件,不是周期性操作

# 业务场景 代码路径 说明
1 内核模块加载 kernel/module/main.c:2903 flush_icache_range() 模块代码写入内存后必须刷,否则执行到旧内容
2 动态插桩(kprobes/ftrace/uprobes) arch/arm64/kernel/probes/uprobes.c:32 sync_icache_aliases() 把目标函数首指令改写为 BRK/跳转指令
3 Livepatch 热补丁 kernel/livepatch 路径 函数体整体替换
4 内核自修改(alternatives/static key/BPF JIT) arch/arm64/kernel/alternative.c、kernel/bpf 启动期/运行时指令替换
5 用户态 JIT(JVM、V8、LuaJIT) cacheflush() 系统调用(ARM64 __ARM64_NR_cacheflush) 生成机器码后用户态主动刷
6 execve / 新可执行映射(ARM64 特有路径) arch/arm64/include/asm/pgtable.h:435-439 __sync_cache_and_tags() → __sync_icache_dcache() PTE 从不可执行变为可执行时,若 D-cache 有脏指令(PG_dcache_clean 未置位),必须同步刷 icache
7 调试器断点(KGDB/GDB) kernel/debug/debug_core.c:286、kernel/debug/gdbstub.c:383 写入 BRK 指令
8 休眠恢复(hibernation) kernel/power/swap.c:255 恢复镜像后统一刷新

判断口诀:只要”内存里的字节被 CPU 以数据方式写过、之后又要以指令方式取指”,就必须刷 icache。x86 靠硬件免了这一步,ARM 必须软件做。

三、OS 需要操作 Cache 的完整清单(不只有 icache)

以 ARM64 的维护 API 为纲(arch/arm64/include/asm/cacheflush.h:72-82 + arch/arm64/mm/cache.S),按维护点(Point of Coherency / Unification / Persistence)分类:

API 维护目标 触发场景 是否高频
caches_clean_inval_pou / icache_inval_pou PoU 第二节所有 icache 场景的前置操作 低频(事件驱动)
dcache_clean_poc / dcache_clean_inval_poc PoC DMA 写方向:设备要读内存前,把 D-cache 脏数据写回 高(每笔 IO)
dcache_inval_poc PoC DMA 读方向:设备写完内存后,本地 D-cache 失效,防止读到旧数据 高(每笔 IO)
dcache_clean_pop / arch_wb_cache_pmem PoP(持久点) 持久内存/DAX:写屏障必须刷到掉电不丢的层次(arch/arm64/mm/flush.c:89) 中(pmem 写路径)
arch_invalidate_pmem PoP pmem 读前失效 中
flush_dcache_folio/page PoC 驱动直接往页/folio 写入数据后,需让设备可见(arch/arm64/mm/flush.c:70) 中
sync_icache_aliases PoU uprobes、新 exec 映射 低频
by Set/Way 全 Cache 操作(dcache_clean_inval_poc 全范围) 整个 Cache CPU hotplug 下电 / suspend-resume / 核心电源关闭:断电前必须把脏数据 clean 出去,下电时 Cache SRAM 内容不可靠(对应你贴的 ARM 手册 4.3.1) 低频但关键
MTE 标签同步 PoU+标签 __sync_cache_and_tags():可执行映射同时同步内存标签(arch/arm64/include/asm/pgtable.h:432) 低频
属性变更前失效 PoC 页面 Cacheable 属性变更(Normal↔Device)前必须 inval,否则 stale 数据风险(pgattr_change_is_safe 检查) 低频

DMA 是最高频的 Cache 维护路径

dma_map_*/dma_unmap_* 内部就是上述 PoC 操作的调用者:

四、与你前面 TLBI 问题的关联

你提到的”TLBI 只在虚拟机范围内核操作”是另一个独立的维护域:

维度 TLBI(页表缓存) ICache/DCache 维护
维护对象 TLB 中的地址翻译 Cache 中的指令/数据
广播域 按 Shareability(Inner/Outer)广播;KVM 场景可由软件限定范围(VM 内 VMID 隔离) 按 Shareability 广播(dsb(ish) 同步)
触发 页表变更(unmap、mprotect、KVM 内存属性变更) 代码修改、DMA、电源事件
相同点 都依赖 dsb 确保广播完成;错误操作都会造成跨故障域的连带影响(你上一个问题讨论的核心) 同左

在虚拟化场景,D-cache 维护(如 DMA 直通时的 PoC 操作)是全局广播的,一个核的 cache 维护指令会因为 Inner Shareable 域而要求其他核参与完成,这正是”一个核 hung 会拖垮其他核”的机制之一——与 TLBI 一样,本质都是共享一致性域的同步点。

五、结论

  1. icache invalid 的业务场景 = 一切”数据写入 + 指令执行”混合路径:模块加载、kprobes/uprobes/ftrace/livepatch、BPF JIT、用户态 JIT、exec 新映射、调试断点、休眠恢复。
  2. 其他 Cache 维护 = DMA 方向维护(最高频)、CPU 下电/挂起(by Set/Way)、持久内存刷写(PoP)、属性变更、MTE、Errata 规避。
  3. 为什么”透明”但仍要刷:Cache 对”CPU 单视角数据访问”透明,但对”指令/数据双视角”和”非 CPU 代理”不透明。这是体系结构(I/D 分离、DMA 绕过)决定的,不是软件可选的。

    参考来源

⚠️ 免责声明

本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。