3. CXL.cache 消息类型:Request、Response、Snoop 详解
好,我们进入第三章。这章是 CXL.cache 协议的「骨架」——消息类型。我个人习惯把 CXL.cache 的消息分成三大类:Request、Response、Snoop。你想想看,任何缓存一致性协议,其实都是在做三件事:发请求、收回应、处理探测。CXL.cache 也不例外。
我之前刚接触 CXL Trace 时,看到一大堆 D2H、H2D 的消息头就头疼。后来发现,只要搞懂这三种消息类型,Trace 里 80% 的乱码都能读顺。咱们今天就把它彻底拆开。
3.1 消息类型的整体分类
CXL.cache 的 Device(比如加速器)和 Host(处理器)之间,通过两种物理通道通信:Request 和 Response。而 Snoop 是 Host 发往 Device 的特殊消息,用来查询或更新 Device 侧缓存的状态。
下面这张图是我经常在内部培训时用的分类框架,你可以先看个全貌:
3.2 Request 类消息(S2M 方向)
Request 是 Device 主动发给 Host 的消息。Device 要读数据、要写数据、或者想知道某个缓存行的最新状态,都得靠 Request 开口。在 Trace 里,你会看到方向标记为 S2M。
CXL.cache 定义了三种 Request:
| 消息名 | Opcode | 功能简述 | 常见场景 |
|---|---|---|---|
| Req_RdCurr | 0x00 | 读当前数据,不改变缓存状态 | Device 想做一次「只读窥探」,比如查表 |
| Req_RdOwn | 0x01 | 读数据并获取该行的所有权(写权限) | Device 要修改数据前,必须发这个 |
| Req_RdOnce | 0x02 | 读数据,但只请求一次有效副本 | 流式访问,数据只用一次,不需要后续缓存一致性维护 |
我记得第一次看 Trace 时,搞不清 RdCurr 和 RdOnce 有什么区别。后来在 FPGA 验证平台上跑了一个实际案例才明白:RdCurr 要求 Host 返回当前时刻的「有效数据」,哪怕这个数据是脏的;而 RdOnce 只要求 Host 返回一个「可用的副本」,Host 甚至可以返回一个过时的值(只要它保证一致性协议没问题)。所以 RdOnce 的延迟通常更低,但 Device 不能对它做写回。
3.3 Response 类消息(S2M 方向)
Response 是 Device 对 Host 此前 Snoop 探测的回复。注意,Response 和 Request 一样都是从 Device 到 Host,但 Response 的触发条件是被动的——Host 先发了一个 Snoop 问 Device「你那边的缓存行状态是什么?」,Device 才回一个 Response。
CXL.cache 定义了三类 Response:
| 消息名 | Opcode | 含义 | 附带数据? |
|---|---|---|---|
| RspData | 0x04 | Device 持有该行的有效副本,并将数据返回给 Host | 是(带 64B 数据体) |
| RspHit | 0x05 | Device 命中缓存行,但不需要返回数据(状态已足够) | 否 |
| RspI | 0x06 | Device 不持有该行(Miss),或状态无效(I 状态) | 否 |
很多人会把 RspData 和 RspHit 弄混。我教你一个记忆法:RspData 是「我不仅要告诉你我命中了,我还把数据直接塞给你」;RspHit 是「我命中了,但数据还在我手里,你别问我要」。前者多用于 Device 帮忙写回(比如 Device 有脏数据需要回写),后者用于状态确认。
3.4 Snoop 类消息(M2S 方向)
Snoop 是 Host 主动发给 Device 的探测消息。这是整个 CXL.cache 协议里真正体现「缓存一致性」的地方。Host 要维护自己的缓存视图,必须时不时地问 Device:「你那边那个缓存行现在是什么状态?是 M 还是 S 还是 I?」
CXL.cache 只定义了两种 Snoop:
| 消息名 | Opcode | 功能 | 典型触发条件 |
|---|---|---|---|
| SnpData | 0x07 | 要求 Device 返回该行的数据副本 | Host 需要读取该行,而 Device 可能是唯一拥有者 |
| SnpCurr | 0x08 | 仅查询状态,不要求返回数据 | Host 只想知道状态,比如做目录转移 |
我在调试过程中发现,很多人会因为不理解 Snoop 的方向而陷入困惑。你看,SnpData/SnpCurr 是 M2S,而它们对应的回复 RspData/RspHit/RspI 却是 S2M。这构成了一个典型的事务对:
Host → Device: SnpData (你想干嘛?)
Device → Host: RspData (我把数据给你,我转移所有权)
在 Trace 中找这种配对关系,是分析一致性协议是否正确的基础。
3.5 消息类型与 Opcode 编码表
为了方便你对照 Trace 日志,我整理了一份完整的 Opcode 编码表:
| Opcode (4-bit) | 方向 | 消息类型 | 是否带数据 |
|---|---|---|---|
| 0x00 | S2M | Req_RdCurr | 否(仅地址) |
| 0x01 | S2M | Req_RdOwn | 否 |
| 0x02 | S2M | Req_RdOnce | 否 |
| 0x04 | S2M | RspData | 是(64B) |
| 0x05 | S2M | RspHit | 否 |
| 0x06 | S2M | RspI | 否 |
| 0x07 | M2S | SnpData | 否 |
| 0x08 | M2S | SnpCurr | 否 |
注意,Opcode 0x03、0x09~0x0F 是保留的。如果你在 Trace 里看到这些值,要么是协议版本不匹配,要么是 Trace 工具误解析。我建议遇到这种情况直接抓原始 bit 流对照看。
3.6 如何从 Trace 中快速识别消息类型
给你几条实战经验。拿到一条 CXL.cache Trace 后,我一般按这个步骤扫:
- 看方向: S2M 是 Device→Host,只可能是 Req 或 Rsp;M2S 是 Host→Device,只可能是 Snp。
- 看 Opcode 高 4 位: 0x0~0x2 是 Req;0x4~0x6 是 Rsp;0x7~0x8 是 Snp。几乎一秒就能定位。
- 看是否带数据: 只有 RspData 在 Reply 通道带 64B 负载。Req 和 Snp 都不带数据。
- 配合事务 ID(TxnID)配对: 同一个事务的 Snp 和 Rsp 共享相同的 TxnID。这是硬件做的关联,永远不会错。
好了,这一章其实就讲一件事:CXL.cache 的 8 种消息,底层逻辑就是 Req/Rsp/Snp 三足鼎立。把这个框架刻进脑子里,后面的 Trace 分析就是在它上面加肉。