第3章 缓存基础知识:Cache line、写策略与MESI协议
做DMA和缓存一致性设计,不懂Cache的基本原理,那就是在裸奔。说实话,我见过不少工程师一上来就调DMA描述符,结果数据老是乱掉,折腾好几天才发现是Cache没处理好。
这一章,我们把Cache的核心概念讲透。你搞明白了这些东西,后面再讲DMA一致性方案,就顺理成章了。
3.1 什么是Cache line?
Cache本身不是按字节来管理的,而是按“行”来管理的。这个“行”就是Cache line。为什么这么搞?
你可以想想看,如果CPU每次从内存读一个字节,都要去查Cache里的每个字节,那Cache的查找逻辑得多复杂?所以我把它切成一小块一小块,每一块叫做一个Cache line。每次读写,都是以line为单位。
Tip: 典型的Cache line大小是64字节。ARM Cortex-A系列、x86都是这样。我在一个视频编解码项目里见过一次诡异的DMA问题,正是因为加速器写的buffer没对齐到64字节边界,导致Cache line被部分覆盖——那个bug查了我整整两天。
一个Cache line里通常包含这些信息:
- 有效位(Valid bit):这一行是不是有效数据
- 标签(Tag):保存内存地址的高位部分,用来判断命中
- 数据块(Data block):真正存的那64字节内容
举个例子。假设Cache有64个line,每个line64字节。内存地址0x1000到0x103F,正好落在同一个line里。CPU读0x1000时,Cache会把整个0x1000~0x103F都搬进来。这就是所谓的时间局部性和空间局部性。
明白了Cache line,我们再来看一个更关键的问题——写的时候怎么办?
3.2 Cache写策略:写直达 vs. 写回
CPU写完数据,是不是马上写回内存?这个问题,不同芯片有不同的选择。
| 写策略 | 操作方式 | 优缺点 | 典型应用场景 |
|---|---|---|---|
| 写直达(Write-Through) | 数据同时写入Cache和下一级存储(内存) | 实现简单,一致性容易维护;但每次写操作都很慢 | 早期CPU,对性能要求不高的嵌入式系统 |
| 写回(Write-Back) | 数据只写入Cache,标记为dirty;当该line被替换时再写回内存 | 写操作很快,减少内存带宽占用;但一致性复杂 | 现代高性能处理器(ARM A系列、RISC-V高性能核) |
我个人的习惯是,在做DMA设计之前,先搞清楚处理器用的是哪种写策略。你可以在芯片手册里找到L1 Cache的配置寄存器,通常会有CTR_EL0这样的系统寄存器,里面就写着Cache line大小和写策略。
写回策略带来的一个直接后果是:CPU改了数据,可能很久都不写回内存。这时候DMA从内存拿数据,拿到的还是老版本,数据就错了。反过来也一样——DMA把新数据写进内存,但Cache里还躺着旧数据,CPU一读,读到的又是旧数据。
这就是缓存一致性问题的根源。
警告: 使用写回Cache的系统中,DMA操作前后必须做Cache维护操作(Clean/Invalidate)。不做的话,数据损坏几乎是必然的,只是早晚问题。我曾经在一个SoC项目中吃过这个大亏,样片回来后DMA传输的图像总是有条纹——就是因为我们忘了做Cache刷新。
3.3 Cache一致性协议:MESI 简介
上面说的是单核CPU与DMA之间的问题。那多个CPU核呢?每个核有自己的L1 Cache,它们各自写自己的数据,怎么保证最终看到同一个值?
嗯,这就是Cache一致性协议要干的事。MESI是最经典的一种,名字来源于四种状态。
- M(Modified,已修改):这一行数据被当前CPU核改过了,和其他Cache里的副本不一样。而且它还没写回内存。别的核想看?不行,得先写回或者转移所有权。
- E(Exclusive,独占):只有当前核有这个数据,和内存内容一致。没有其他核有副本。别人要来读?可以,变成S。
- S(Shared,共享):多个核都有这个数据的副本,都和内存一致。谁要写?必须先发Invalidate消息,把其他核的副本都干掉。
- I(Invalid,无效):这一行数据是废的,别人更新过了,我不再信任它。
状态之间怎么切换的?我画了一张图,你看一眼就明白了。
这张图看着复杂,其实核心逻辑就几个字:谁改了数据,别人就不能碰那个line,等它写回去再通知所有人。
关键认知: MESI只是协议族里的一个基础版本。现代芯片用的更多是MOESI(增加了Owned状态)或MESIF(Intel的F状态,用于优化多播)。但万变不离其宗——核心思想没变,只是状态多了,性能更好了。
我建议你记住一点:在做DMA设计时,虽然DMA自己不参与MESI握手,但Cache控制器必须知道DMA的存在。所以很多SoC会在总线层面做“snoop filter”或者“DMA coherency port”——让DMA的访问能够触发Cache的监听逻辑,自动刷掉或者更新对应的行。
后面几章,我们会看到这些机制的具体硬件实现和软件编码。
Tip: 学习MESI最好的办法,不是背状态表,而是自己画一遍状态机。我在给团队做培训时,要求每个工程师用白板画三次——第一次对着笔记,第二次闭眼画,第三次讲给别人听。能讲清楚,才是真懂了。