icache invalid 场景:什么时候需要刷缓存
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 操作的调用者:
- 写方向(设备读内存):
arch_sync_dma_for_device→ clean(确保设备看到最新数据) - 读方向(设备写内存):
arch_sync_dma_for_cpu→ invalidate(确保 CPU 不读 cache 里的旧数据) dma_alloc_coherent直接分配 Non-Cacheable 内存,从根上避免维护。
四、与你前面 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 一样,本质都是共享一致性域的同步点。
五、结论
- icache invalid 的业务场景 = 一切”数据写入 + 指令执行”混合路径:模块加载、kprobes/uprobes/ftrace/livepatch、BPF JIT、用户态 JIT、exec 新映射、调试断点、休眠恢复。
- 其他 Cache 维护 = DMA 方向维护(最高频)、CPU 下电/挂起(by Set/Way)、持久内存刷写(PoP)、属性变更、MTE、Errata 规避。
- 为什么”透明”但仍要刷:Cache 对”CPU 单视角数据访问”透明,但对”指令/数据双视角”和”非 CPU 代理”不透明。这是体系结构(I/D 分离、DMA 绕过)决定的,不是软件可选的。
参考来源
本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。