3. Cache 工作原理:从 Line 到 Way,再到读写与替换策略

大家好,我是你们的嵌入式系统讲师。今天我们来聊聊 Cache 的工作原理。说实话,Cache 这个概念,很多工程师做了三五年项目,可能还是一知半解。但你要做多核调试、搞缓存一致性,这块必须吃透。

我个人习惯把 Cache 比作「工程师的临时工作台」。你想想看,CPU 从主存拿数据,就像你去仓库找零件——来回跑一趟太慢了。Cache 就是放在你手边的工具盒,常用的东西随手就能拿到。

3.1 Cache 的基本概念:Line、Set、Way

先看三个最基础的概念。搞懂了它们,Cache 的骨架你就搭起来了。

术语 英文 一句话解释
Line Cache Line Cache 的最小存储单元,通常 32 或 64 字节
Set Set 一组 Cache Line 的集合,按地址索引分组
Way Way 每个 Set 中包含的 Line 数量,决定相联度

Cache Line 是搬运数据的最小单位。CPU 从内存读一个字节,实际上会把整条 Line 都搬进 Cache。我在 STM32H7 上调试时遇到过一个问题:两个变量刚好落在同一条 Cache Line 里,一个被修改,另一个也跟着「遭殃」——这就是伪共享(False Sharing)的典型场景。

Set 和 Way 决定了 Cache 的组织方式。举个例子:一个 4-Way Set-Associative Cache,每个 Set 里有 4 个 Line 位置。地址映射时,先算 Set 索引,再在 4 个 Way 里找空闲或可替换的 Line。

核心公式:

Cache 总大小 = Set 数量 × Way 数量 × Line 大小

比如:256 Sets × 4 Ways × 64 Bytes = 64 KB

我画了一张图,帮你把这三个概念串起来:

Cache 组织结构:Set × Way × Line Set 0 Set 1 Set 2 Set 3 …… 更多 Set …… Way 0 Way 1 Way 2 Way 3 Line (64B) Line (64B) Line (64B) Line (64B) 每个 Set 包含 4 个 Way,每个 Way 存放 1 条 Cache Line(64 字节)

3.2 Cache 的读写策略

读写策略决定了 Cache 和主存之间怎么「同步账本」。两种主流策略:Write-Through 和 Write-Back。我当年在项目里选错策略,导致系统性能直接腰斩——嗯,这里要注意。

3.2.1 Write-Through(写通)

写操作同时更新 Cache 和主存。优点是数据永远一致,缺点是每次写都要访问慢速的主存。

  • 写命中: 同时写入 Cache Line 和主存
  • 写未命中: 直接写入主存(有些实现会先加载到 Cache)

我曾经踩过的坑: 在 STM32H743 上用 Write-Through 策略做高速数据采集,结果发现写入带宽被主存延迟卡死了。后来换成 Write-Back,性能直接翻倍。

3.2.2 Write-Back(写回)

写操作只更新 Cache,标记该 Line 为「脏」(Dirty)。只有该 Line 被替换时,才写回主存。

  • 写命中: 更新 Cache,标记 Dirty
  • 写未命中: 先加载到 Cache,再更新,标记 Dirty
  • 替换时: 检查 Dirty 位,脏则写回主存
对比项 Write-Through Write-Back
写性能 慢(每次写主存) 快(只写 Cache)
数据一致性 天然一致 需要软件维护
总线流量 高 低
典型场景 共享数据、外设寄存器 大块数据、性能敏感

我的建议: STM32 的 MPU(内存保护单元)可以按区域配置 Cache 策略。对性能要求高的数据区用 Write-Back,对外设寄存器或共享内存用 Write-Through 或 Non-Cacheable。

3.3 Cache 的替换策略

当 Cache 满了,新数据要进来,就得把旧数据踢出去。怎么踢?这就是替换策略的事。

3.3.1 常见替换算法

  1. LRU(最近最少使用):替换最久没被访问的 Line。命中率最高,但硬件开销大。
  2. FIFO(先进先出):替换最早进入的 Line。实现简单,但可能踢掉热点数据。
  3. Random(随机):随机选一个 Line 替换。硬件最简单,性能不稳定。
  4. PLRU(伪 LRU):用二叉树近似 LRU,硬件复杂度适中。ARM Cortex-A 系列常用。

STM32 的 Cortex-M7 和 M33 内核,Cache 替换策略通常是硬件固定的——你没法改。但了解它,能帮你理解为什么某些访问模式会频繁 Miss。

避坑指南: 如果你在循环里反复访问超过 Cache 大小的数组,LRU 策略会导致「Cache Thrashing」——数据刚加载就被替换,性能还不如不用 Cache。我曾经在图像处理项目里遇到这个问题,后来通过分块处理(Tiling)解决了。

3.3.2 替换策略对比

策略 命中率 硬件开销 适用场景
LRU 高 大 通用高性能
FIFO 中 小 顺序访问
Random 中低 极小 低功耗、简单场景
PLRU 接近 LRU 中 ARM 多核处理器

最后说一句:Cache 不是万能的。你写代码时,数据布局、访问模式、对齐方式,都会影响 Cache 效率。多核环境下,Cache 一致性更是个大坑——我们后面几章会重点讲。