第4章 MESI协议详解
说到缓存一致性,MESI协议是绕不开的基础。它定义了四种状态,用来跟踪每个缓存行在多个核之间的“身份”。我刚开始接触时觉得这四状态挺简单,但真正调起DMA来才发现——状态搞错一步,数据就全乱套了。
咱们今天就把MESI拆开揉碎了讲。重点是状态转换,以及在DMA场景下怎么用它来保证数据没错。
4.1 四种状态——一个缓存行的四种活法
每个缓存行可以处于M、E、S、I中的一种。看名字有点抽象,我习惯用“脏”“独享”“分享”“无效”去记。
| 状态 | 全称 | 缓存行有效? | 与主存一致? | 其他核是否有副本? |
|---|---|---|---|---|
| M | Modified(已修改) | 有效 | 不一致(脏) | 无 |
| E | Exclusive(独占) | 有效 | 一致(干净) | 无 |
| S | Shared(共享) | 有效 | 一致(干净) | 可能有 |
| I | Invalid(无效) | 无效 | — | — |
4.2 状态转换——核心流程图
状态切换是由本地CPU操作(读/写)和总线嗅探事件(其他核的读/写请求)触发的。举个例子:一个缓存行在E状态,本地CPU写它,它会变成M;如果另一个核发起读请求,总线嗅探到后,E会变成S。
下面这张图把四种状态和主要的转换路径都画出来了。你仔细看箭头上的标签,就能理解每个动作会触发什么变化。
4.3 总线事务与嗅探行为
MESI的状态切换不是凭空发生的。每当一个核(或DMA)想读/写内存,它会先发个总线事务到共享总线上。其他核的缓存控制器“监听”到这个事务,就叫缓存嗅探(snooping)。
常见事务类型:
- BusRd — 总线读事务。其他核看到后,如果自己的缓存行处于M状态,必须先写回主存再共享为S;如果处于E/S状态,直接共享为S。
- BusWr — 总线写事务。其他核看到后必须将自己的缓存行置为I。
- BusRdX — 读并独占,通常配合写失效。用于准备写一个当前不在缓存中的行。
我建议你记住这个逻辑:谁写的,谁负责失效别人。如果你用硬件DMA直接写内存,它必须发送总线写失效,不然其他核的缓存就是脏数据。
4.4 MESI在DMA中的实际应用
DMA控制器本质上是一个“远程处理器”。它读写内存时不经过CPU缓存,也就不会自动更新缓存状态。所以进入DMA传输前,你必须手动处理缓存一致性:
- DMA写内存(CPU读):CPU在读之前必须将对那块内存的缓存行全部失效(Invalidate)。不然CPU会读到老缓存里的内容。
- CPU写内存(DMA读):CPU在写完后,必须先把缓存行刷回主存(Flush / Clean),再启动DMA。否则DMA读到的可能是过期物理内存。
4.5 避坑指南
另外要注意伪共享(False Sharing)。两个核心各自操作不同变量,但变量落在同一个缓存行里。如果其中一个核做DMA更新,那整个缓存行都会被失效,导致另一个核性能骤降。所以设计缓冲区时,最好按缓存行大小(通常64字节)对齐,变量之间用padding隔开。
MESI协议本身不复杂,但一跟DMA结合,就容易出各种时序和状态错乱。抓住核心——谁修改了数据,谁就要通知别人失效——很多问题就能提前规避。