3. CXL.io 与 CXL.cache 的区别归纳总结

说实话,刚接触 CXL 协议的人,最容易搞混的就是 .io 和 .cache 这两条“车道”。让我先给你一个直观画面:CXL.io 就像是你办公室的快递收发室——负责常规的、一次性的、带状态码的包裹传递;而 CXL.cache 则像是你工位旁边那个高速共享文件柜,能实时缓存、同步修改、甚至允许别人直接拿走你正在用的那份文档。

我在一个 FPGA 原型项目里踩过坑:验证团队把 CXL.io 的事务当 CXL.cache 去配,结果延迟死活降不下来,最后翻协议才发现根本走错了通道。所以这一章咱们彻底理清楚它俩到底有什么不同。

3.1 协议栈定位

CXL 协议栈分为三层:事物层、链路层、物理层。但在事物层内部,CXL.io 和 CXL.cache 是两套独立的事物层协议,共用同一套热插拔、链路训练、错误上报机制。你可以把 CXL.io 看作 PCIe 的兼容模式,而 CXL.cache 才是 CXL 真正想让加速器干活的“秘密武器”。

特征CXL.ioCXL.cache
基础协议基于 PCIe 5.0/6.0 事物层完全自定义的 CXL 事物层
主要用途配置、控制、DMA 传输、中断缓存一致性维护、主权请求、监听
数据模型非一致性(Non-Coherent)完全一致性(Coherent)
地址空间仅宿主物理地址(HPA)HPA + 设备物理地址(DPA)混合
延迟特征几百纳秒到微秒级几十纳秒级

个人习惯:我拿到一个新芯片的 RTL 仿真波形,第一眼先看事务类型头字段——前两位如果是 2'b00 那就是 .io 请求,如果是 2'b01 就是 .cache 请求。这比看地址范围快多了。

3.2 事务类型对比

CXL.io 的事务说白了就是 PCIe 的 MRd/MWr/CfgWr 那一套换了个马甲。但 CXL.cache 的事务类型就很不一样了,它围绕“共享主权”设计。核心事务分三类:请求类、代理类、监听类。

  • 请求类:设备发起读(RdShared/RdOwn)或写(WrLine/WrPartial),向主机申请缓存行。
  • 代理类:主机代理主动回应数据或完成写操作。
  • 监听类:主机代理监听设备缓存行状态变更。

我记得有个调试案例:某款加速器在大量写入时频繁超时,抓 trace 发现主机代理一直被 CXL.cache 的 WrLine 请求淹死——因为设计者误用了不带数据预览的写请求,导致主机必须每次回读旧数据。后来改成 WrPartial + 数据预览,吞吐直接翻倍。这就是没搞清楚事务语义的代价。

3.3 一致性模型差异

CXL.io 不维护任何一致性。你从设备 DMA 写了一段数据到主机内存,主机如果正好在 L1/L2 有这份数据的缓存副本,那你写的那个地址在 CPU 视角可能仍然是旧的。需要软件手动刷 cache 或通过 CXL.io 的 MemFlush 命令让硬件做。

CXL.cache 则内置了 MESI(更准确地说是 MESI 变体 —— CXL 使用的 Coherence Engine 通常叫 MESI-N+ 或自定义状态机)。设备内部的缓存行可以处于 Modified、Exclusive、Shared、Invalid 状态。主机会通过 Snoop(监听)请求实时拉回最新数据。

我曾经踩过一个大坑:一个加速器卡在 CXL.io 模式下做预测查表,结果 CPU 侧把表更新了,加速器还拿着旧值算了好几个毫秒。后来改成 CXL.cache 模式,把查表区域声明为 Shared 状态,主机一改就收到 Invalidate,虽然延迟多了一点点,但数据永远不会错。这就是一致性的代价和必要性。

3.4 调试工具差异

调试 CXL.io 你可以直接用 PCIe 分析仪,抓 TLP 包就行,几乎不需要额外知识。但 CXL.cache 就麻烦得多——你得用支持 CXL 协议解码的逻辑分析仪,并且要看懂 CXL.cache 事务的 Request/Response/Snoop 三类子通道。

  • CXL.io 调试要点:关注 Tag、Address、Length、Completion Status。
  • CXL.cache 调试要点:关注 SnoopID、CacheLine State、Data Phase、Snoop Filter Hit/Miss。

注意:很多便宜的 PCIe 分析仪声称支持 CXL,但只支持 .io 部分。买设备之前一定要确认固件版本是否包含 .cache 解码功能。我在合作伙伴那吃过一次亏,设备买回来只能抓 .io 流量,.cache 全显示为“未知事务类型”。

3.5 整体关系图

下面这张图帮你理清 CXL.io 和 CXL.cache 在系统中的地位和配合方式:

CXL.io 与 CXL.cache 关系图 主机 (Host) CPU + 内存控制器 一致性引擎 Snoop Filter PCIe 根端口 CXL.io 配置/控制/DMA 非一致性 CXL.cache 请求/响应/监听 完全一致性 设备 (Device) 加速器 / 内存池 设备缓存 CXL 控制器 一致性代理 解读: CXL.io 提供非一致的配置/控制通路,适合初始化、错误上报、大数据块搬运。 CXL.cache 提供一致的数据访问通路,设备可以缓存内存数据并参与一致性协议。

3.6 选择原则与避坑指南

很多团队在设计初期会问:我该用 .io 还是 .cache?其实没那么复杂。

  • 用 CXL.io:设备只需要做配置、控制、大块数据搬移(比如从 SSD 搬到主机内存),且不需要与 CPU 共享同一份数据的缓存副本。
  • 用 CXL.cache:设备需要频繁访问主机内存中的热点数据,每次访问延迟要低(几十纳秒),且数据必须实时与 CPU 保持一致。典型场景:加速卡内的查找表、推理模型的权重矩阵、共享队列状态。

我曾经见过一个设计:他们用 CXL.cache 去搬运视频帧——每个帧 4MB,一次搬运几十帧。结果缓存行反复命中失效,延迟比 CXL.io 还差。后来改成 CXL.io 做流式数据搬运,CXL.cache 只用于控制结构的同步,性能一下就正常了。记住:一致性不是免费的,每笔一致性操作都有协议开销。

还有一个常见误区:认为 CXL.cache 一定会比 CXL.io 慢,因为要跑一致性协议。恰恰相反——在正确的工作负载下,CXL.cache 因为省去了 DMA 的地址转换和中断处理,实际端到端延迟反而更低。

避坑指南:如果你决定用 CXL.cache,一定要确认设备端的一致性代理实现了完整的 Snoop Filter 或目录结构。否则,每次一致性请求都需要走主机端 Snoop Filter,一旦大量 Miss 就会出现性能雪崩。我们就在一个百万并发场景下遇到过,最后被迫降级到 CXL.io 模式才稳住。

3.7 小结(不是生硬的“综上所述”)

好了,CXL.io 和 CXL.cache 的区别说白了就三句话:

  1. CXL.io 继承 PCIe 的基因,是个“老实人”,做什么都告诉你状态码;
  2. CXL.cache 是 CXL 的亲儿子,专为缓存一致性而生,事务更短更快但协议更复杂;
  3. 调试的时候先看事务类型头,别拿 .io 的分析仪去解 .cache 的包——那会白白浪费你一个下午。