第1章:CXL协议基础——三大子协议与分层架构
大家好,我是你们的老朋友。今天咱们正式开讲CXL协议。
说实话,我第一次接触CXL是在2019年,当时还在做一款分布式存储系统。内存带宽不够用,CPU插槽又有限,愁得我整宿睡不着。后来看到CXL的spec,眼前一亮——这不就是我一直想要的东西吗?
好,废话不多说。咱们先搞清楚CXL到底是个什么玩意儿。
1.1 CXL的诞生背景
CXL,全称Compute Express Link。说白了,就是一条高速通道,让CPU和加速器、内存扩展设备之间能高效通信。
为什么需要它?你想想看,传统DDR内存通道就那么几条,插满也就几百GB。但AI训练、大数据分析这些场景,动不动就要TB级内存。怎么办?
我当年在项目中就遇到过这个瓶颈。一台服务器配了1TB内存,但跑一个推荐模型训练,内存直接爆了。加内存?没槽位了。换机器?成本太高。后来用CXL内存扩展,直接把内存池化,问题迎刃而解。
核心思想:CXL让内存不再局限于CPU本地,而是可以像网络资源一样按需分配。
1.2 三大子协议概览
CXL协议栈分为三个子协议:CXL.io、CXL.cache、CXL.mem。它们各司其职,配合默契。
我习惯用一个比喻来理解:
- CXL.io —— 相当于快递员,负责设备发现、配置、中断这些“杂活”
- CXL.cache —— 相当于共享笔记本,让加速器能访问CPU的缓存
- CXL.mem —— 相当于内存仓库,让CPU能访问加速器挂载的内存
嗯,这里要注意:这三个子协议不是独立运行的,它们共享同一个物理链路。就像一条高速公路,有快车道、慢车道、应急车道,各走各的,互不干扰。
1.2.1 CXL.io
CXL.io基于PCIe协议改造而来。说白了,它就是PCIe的“换皮版”。
为什么这么说?因为CXL.io的事务层、数据链路层、物理层,几乎和PCIe一模一样。我刚开始学的时候,直接拿PCIe的spec对照着看,省了不少功夫。
CXL.io主要负责:
- 设备枚举和配置(比如告诉系统“我是一块CXL内存扩展卡”)
- 中断处理(MSI/MSI-X)
- DMA传输(大块数据搬运)
- 错误报告和恢复
避坑指南:我曾经在调试CXL设备时,发现设备死活枚举不上。查了半天,原来是CXL.io的配置空间里有一个保留位没置对。记住,CXL.io虽然像PCIe,但有些寄存器定义是CXL特有的,别想当然。
1.2.2 CXL.cache
CXL.cache这个子协议,是CXL的精髓之一。它允许加速器(比如GPU、FPGA)直接访问CPU的缓存一致性域。
什么意思?传统做法是:加速器要数据,得先通过DMA搬到自己的显存里,然后CPU再通知它“数据到了”。这中间有延迟,而且数据可能不一致。
CXL.cache解决了这个问题。加速器可以直接读取CPU的L3缓存,甚至能监听缓存行失效事件。我做过一个测试,用CXL.cache做数据共享,延迟比传统DMA降低了40%以上。
CXL.cache支持两种模式:
- Device-side caching:加速器有自己的缓存,但需要和CPU保持一致性
- Host-side caching:加速器没有缓存,直接访问CPU的内存
我个人更常用Device-side caching模式,因为加速器通常有自己的SRAM,用起来更灵活。
1.2.3 CXL.mem
CXL.mem是内存扩展的核心。它让CPU可以直接访问加速器挂载的内存,就像访问本地DDR一样。
你想想看,传统内存扩展卡(比如AEP内存)用的是DDR-T协议,延迟高、带宽低。CXL.mem直接走PCIe物理层,带宽翻了好几倍。
CXL.mem支持两种访问模式:
- Memory Mode:纯内存扩展,CPU把CXL内存当作普通内存用
- Bias Mode:偏向模式,可以指定某段内存由CPU还是加速器优先访问
我在项目中主要用Memory Mode,简单粗暴。但如果你做的是异构计算,Bias Mode更合适,可以减少数据搬移的开销。
1.3 链路层与事务层
聊完三大子协议,咱们看看CXL的分层架构。CXL协议栈分为三层:事务层、链路层、物理层。
物理层就是PCIe物理层,没什么好说的。咱们重点看链路层和事务层。
1.3.1 事务层
事务层是CXL协议的大脑。它负责生成和处理各种事务请求。
CXL的事务分为三类:
- 请求事务:比如读内存、写内存、缓存行失效
- 响应事务:比如读完成、写完成、错误响应
- 数据事务:携带实际数据的事务
我举个例子:CPU要读CXL内存地址0x1000。事务层会生成一个“读请求”事务,包含地址、长度、标签等信息。然后交给链路层去传输。
这里有个细节:CXL的事务层支持乱序完成。也就是说,你先发的请求可能后收到响应。这在高并发场景下很有用,但调试起来比较头疼。我曾经为了追一个乱序导致的bug,花了整整两天。
1.3.2 链路层
链路层负责可靠传输。它把事务层的数据包封装成FLIT(Flow Control Unit),然后通过物理层发送出去。
FLIT是CXL链路层的基本传输单位。一个FLIT通常是68字节(64字节数据+4字节CRC)。
链路层的主要功能:
- 流量控制:防止发送方太快,接收方来不及处理
- 错误检测:CRC校验,发现错误就重传
- 链路初始化:建立连接、协商参数
我记得第一次看CXL链路层的spec,被一堆状态机搞得头晕。后来画了个流程图,才理清楚。
这张图我画了好几次才满意。你看,从上到下依次是:应用层、事务层、链路层、物理层。每一层都依赖下层提供的服务。
1.4 三大子协议如何协同工作
说了这么多,你可能想问:这三个子协议到底怎么配合?
我举个实际场景:
- 系统启动时,CXL.io负责枚举设备,告诉CPU“我是一块CXL内存扩展卡,有256GB内存”
- 应用程序开始跑,需要读写CXL内存。CXL.mem接手,把读写请求转换成FLIT,通过链路层发送
- 如果加速器需要访问CPU的缓存数据,CXL.cache就上场了,发送缓存行失效请求
这三个子协议共享同一个物理链路,但通过不同的虚拟通道(VC)来区分。CXL.io走VC0,CXL.cache和CXL.mem走VC1。这样即使CXL.io有大量配置流量,也不会影响CXL.mem的延迟。
注意:CXL.cache和CXL.mem不能同时使用。也就是说,一个CXL设备要么是缓存设备(支持CXL.cache),要么是内存设备(支持CXL.mem),不能两者都支持。这是CXL 1.0/2.0的限制,CXL 3.0有所放宽。
1.5 实战经验分享
最后,分享一个我踩过的坑。
有一次,我在调试CXL内存扩展卡时,发现读写性能远低于预期。用逻辑分析仪抓波形,发现链路层一直在重传FLIT。
查了半天,原来是物理层的信号完整性出了问题。CXL跑在PCIe Gen5的速率(32GT/s),对PCB走线要求极高。我那块板子的走线长度超标了,导致信号衰减严重。
解决办法?重新布局布线,缩短走线长度,增加retimer芯片。从那以后,我设计CXL板卡时,都会先做信号完整性仿真。
嗯,这就是经验。书本上不会告诉你这些,只有亲手做过才知道。
核心要点回顾:
- CXL三大子协议:CXL.io(配置/控制)、CXL.cache(缓存一致性)、CXL.mem(内存扩展)
- 协议分层:事务层→链路层→物理层,每层职责清晰
- FLIT是链路层基本单位,68字节/个
- CXL.cache和CXL.mem互斥,一个设备只能选其一
公众号:蓝海资料掘金营,微信deep3321