CXL.mem 协议可以支持连接在 CXL 链路上的 PMEM 设备,使得 CPU 可以高效地访问这些持久性内存, 换句话说 CXL TYPE 3 设备的存储介质可是持久内存Persistent Memory(PMEM). 通过 CXL.mem,系统可以将 PMEM 作为扩展内存的一部分使用,从而提供更大的内存容量和更高的可靠性. 同时,对于 PMEM 来说,DEVDAX 是持久内存的一种直接访问模式,因此可以将 PMEM 的 CXL TYPE 3 设备通过 DEVDAX 方式直接访问.
CXL Persistent Memory Device 可以直接插到 CXL Root Complex 的 RootPort 上,也可以插到 CXL SWITCH 的 DownStream Port 上. 对于 CXL Persisten Memory Device,命令接口(Command Interface)由系统固件、UEFI 和操作系统驱动程序使用,用于暴露额外的持久内存功能, 其由以下几部分组成:
- LSA(Label Storage Area): CXL 内存设备负责提供一个持久的标签存储区,UEFI 和操作系统驱动程序利用该区域读取和写入区域(交错集)配置信息和命名空间配置信息。这对于正确重组持久内存区域配置是必要的.
- 区域标签(Region Label): 持久配置信息,向 UEFI 和操作系统驱动程序描述持久内存区域,包括所有相关设备的 UUID、每个设备对区域贡献的持久容量以及每个设备在区域中的位置。在持久内存中,设备在区域中的顺序必须始终保持,以便正确重组区域中的数据.
- 命名空间标签(Namespace Label): 持久配置信息,向 UEFI 和操作系统驱动程序描述每个持久内存区域如何被细分为命名空间。命名空间有几种类型,包括基于 BTT(Block Translation Table)的块存储仿真
- Poison List: 设备需要维护一个持久的损坏列表,以便 UEFI 和操作系统驱动程序可以快速确定介质的哪些区域包含无效数据并且必须避免或纠正
上图展示了 CXL 内存设备在系统中的集成和操作流程, 图中主要涉及 CXL 持久内存设备(Type 3)与相关软件和硬件组件之间的关系:
- 固定 ACPI 描述表(FADT): 一个现有的 ACPI 表,用于在平台启动时报告平台的固定属性。对于 CXL,新添加的 PERSISTENT_CPU_CACHES 属性被平台用于报告 CPU 缓存是否被视为持久性缓存,并由操作系统用来设置应用程序的刷新策略.
- 持久区域(Persistent Region): 每个持久区域代表一个 HPA(主机物理地址)范围,该范围使用一组以特定顺序配置的 CXL 内存设备的持久容量. 每个区域的配置在存储于标签存储区(LSA)中的区域标签中进行描述,这些标签通过命令接口公开.
- PMEM/SCM 区域驱动程序: 每个区域(交错集 Interleave Set)的实例将由 PMEM/SCM(存储类内存)驱动程序的单独实例使用. 这可能是对现有操作系统内核 NVDIMM 组件的显著重用.
- 命名空间(Namespaces): 每个区域可以细分为称为命名空间的卷。每个命名空间的配置在存储于标签存储区(LSA)中的命名空间标签中进行描述,这些标签通过命令接口公开.
- 分区(Partitions): 每个命名空间通常由操作系统细分为分区
- 文件系统(File Systems): 现有的文件系统驱动程序将每个分区细分为一个或多个文件,并为用户提供标准的文件 API 和文件保护.
- 内存映射文件(Memory Mapped Files): 区域被细分为命名空间,命名空间被细分为分区,最后由文件系统细分为内存映射文件. 这是应用程序在具有文件安全性和便捷性的情况下直接访问持久内存的一种标准机制.
- 设备 DAX(Device DAX): 一种简化的直接通道,在应用程序和持久内存命名空间之间,不经过文件系统和内存映射文件使用.
- Libraries(PMDK): 大多数支持持久内存的应用程序使用如 PMDK 这样的 Ring3 库来简化持久内存编程模型。这些库通常使用内存映射文件或直接设备 DAX 来访问持久内存。这些库将增加新功能以支持新的 CXL 特性.
Intel 推出的傲腾 Persistent Memory 通过插入 DIMM 插槽被系统识别(上上图),而 CXL Type3 PMEM 则通过插入 PCIe 插槽被系统识别,虽然在硬件上访问两种 PMEM 设备的方式已经改变,但在系统协议栈上还是基于 NVDIMM 子系统,其提供了 FSDAX、DEVDAX 和 SYSTEM RAM 三种方式对 PMEM 进行访问. 其中 DEVDAX(Device DAX) 是一种简化的直接通道,在应用程序和持久内存命名空间之间,不经过文件系统和内存映射文件使用. 接下来基于 BiscuitOS 介绍如何通过 DEVDAX 方式访问 CXL Type3 PMEM 设备.
CXL TOPOLOGY ON BiscuitOS
- CXL TOPOLOGY A: BiscuitOS 支持的第一种 CXL Type3 PMEM 设备拓扑结构,其并没有带任何的 CXL Switch,而是将 CXL Type3 PMEM 设备直接介绍了 CXL Root Complex 的 RootPort(RP) 上, 基于该拓扑 BiscuitOS 提供了一个容量为 512MiB 的 CXL Type3 PMEM 设备.
- CXL TOPOLOGY B: BiscuitOS 支持的第二种 CXL Type3 PMEM 设备拓扑结构,其包含了一个 CXL Switch,并且该 Switch 直接连接到 CXL Root Complex 的 RootPort(RP) 上,而 CXLType3 PMEM 设备则连接到 CXL Switch 的下游端口上,基于该拓扑 BiscuitOS 提供了一个容量为 512MiB 的 CXL Type3 PMEM 设备.
- CXL TOPOLOGY C: BiscuitOS 支持的第三种 CXL Type3 PMEM 设备拓扑结构,其包含了三个 CXL Switch,其中一个 CXL Switch 直接连接到 CXL Root Complex 的 RootPort(RP) 上,另外两个 CXL Switch 分别连接到顶层 CXL Switch 的下游端口. 二级 CXL Switch 的下游端口各连接了 4 个 CXL Type3 Device,因此该拓扑一共 8 个 CXL Type3 Device. 基于该拓扑结构 BiscuitOS 提供了 8 个容量为 512MiB 的 CXL Type3 PMEM 设备,总容量达到 4GiB.
BiscuitOS 已经支持多种拓扑结构的 CXL Type3 PMEM Device,接下来通过对 CXL TOPOLOGY B 方式进行实践,以此研究 Linux 如何通过 DEVDAX 对 CXL Type3 Device 进行使用,开发者参考如下命令进行实践:
# 切换到 BiscuitOS 项目目录
cd BiscuitOS
# 选择开发环境,如果已经选择过可以跳过,这里与 linux 6.9 X86 为例
make linux-6.9-x86_64_defconfig
# 通过 Kbuild 选择需要部署的应用程序
make menuconfig
[*] DIY BiscuitOS/Broiler Hardware --->
[*] Intel Q35
[*] CXL: Compute Express Link
CXL Hardware Topology (CXL Switch With TYPE3: x1 Persistent Memory(PMEM)) --->
[*] Package --->
[*] HETEROGENEOUS MEMORY MANAGEMENT
[*] CXL: CXL.mem AS DEVDAX --->
# 配置完毕保存,然后进行部署
make
# 切换到实践案例所在目录
cd output/linux-6.9-x86_64/package/BiscuitOS-CXL-DEVDAX-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build
BiscuitOS 系统已经集成了 CXL 实践的全部工具链,默认提供 cxl、ndctl、daxctl、numactl 和 pci-utlis 工具链。当 BiscuitOS 运行之后,使用 ‘cxl list -v’ 命令可以看到 CXL Bus 的拓扑结构,如上图所示.
该字段描述了 CXL BUS 和 CXL Host Bridge 信息,BiscuitOS 提供了一个 CXL BUS,名为 ‘root0’, CXL BUS 包含了一个下游端口,该下游端口连接 CXL Host Bridge, CXL Host Bridge 的别名为 ‘ACPI0016:00’, CXL Host Bridge 包含一个下游端口,该下游端口连接 CXL Root Complex,可以看到 CXL Root Complex 的 BDF 为 ‘0000:0c’.
该字段描述了 CXL Root Complex 和 CXL Switch 信息,BiscuitOS 提供了一个 Root Complex,名为 ‘port1’, 其对应的 BDF 为 ‘0000:0c’,其包含一个 RootPort(RP),RootPort 的 BDF 是 ‘0000:0c:00.0’, 该端口连接 CXL Swtich. CXL Switch 名为 ‘port2’, 其对应的 BDF 为 ‘0000:0d:00.0’,可以看到 CXL Swith 的上游端口连接到 ‘0000:0c:00.0’, 即连接到 CXL Root Complex 的 RootPort, CXL Swtich 包含了一个下游端口,下游端口的 BDF 为 ‘0000:0e:00.0’.
该字段描述了 CXL Type3 Device 信息,BiscuitOS 提供了一个 CXL Type3 PMEM 设备,其名为 ‘endpoint3’, 设备映射到 ‘/dev/cxl/mem0’, 通过 ‘parent_dport’ 可以知道其连接到了 CXL Switch 的下游端口 ‘0000:0e:00.0’. 从 memdev 字段可以获得 PMEM 设备相关的信息,其中包含 PMEM 的容量为 512MiB, 并且设备的 BDF 号为 ‘0000:0f:00.0’
CXL PCIe TOPOLOGY
当 BiscuitOS 启动之后,使用 ‘lspci-common -vt’ 命令可以看到 CXL 在 PCIe BUS 上的拓扑结构. 从上图可以看到 CXL Root Complex 的 BDF 为 ‘0000:0c’, 由此可以知道 CXL BUS 和 CXL Host Bridge 并不以 PCIe 设备的形式呈现,其在 ACPI Tree 上呈现. CXL Root Complex 包含一个 RootProt,其 BDF 为 ‘0000:0c:00.0’, RootPort 直接连接到 CXL Switch 的上游端口,CXL Switch 的 BDF 为 ‘0000:0d:00.0’, 其包含一个下游端口,其 BDF 为 ‘0000:0e:00.0’, 并连接到 CXL Type3 PMEM Device. CXL Type3 PMEM Device 的 BDF 为 ‘0000:0f:00.0’
CXL ROOT Complex BDF.
CXL SWITCH BDF.
CXL SWITCH BDF.
CXL Type3 PMEM Device BDF.
DEVDAX FOR CXL
当 BiscuitOS 已经部署好 CXL 硬件拓扑之后,进入 BiscuitOS 系统,使用 ‘cxl list’ 命令可以看到 CXL Type3 Device 的信息,从信息可以看到 CXL Type3 Device 是一个 PMEM 设备,其容量为 512MiB,对应的 PCIe BDF 为 ‘0000:0f:00.0’. 并在 ‘/dev/cxl’ 目录下看到 mem0 设备,并在 ‘/sys/bus/cxl/device/’ 目录下看到 CXL Type3 Device mem0 和其他设备的信息. 接下来使用 cxl 和 ndctl 工具将 CXL Type3 PMEM Device 设置为 DEVDAX 模式,然后使用应用程序对 CXL Type3 PMEM Device 进行访问.
- cxl destroy-region -f all: 命令用于将 CXL BUS 所有的 Region 移除,使 CXL Bus 处于一个初始状态.
- cxl create-region -d decoder0.0 -m mem0 -s 512M -t pmem -w 1 -g 4096 -u: 用于创建一个 CXL Region,该 Region 的大小为 512MiB,大小必须与 PMEM 的大小一致,并且 Region 的类型需要使用 -t 选项指定为 pmem, 该 CXL Region 的 HDM Decorder 通过 -d 选项进行指定,这里使用 decoder0.0. 其余选项可选,用于设置 interleaving 相关的配置. 命令成功执行之后输出 CXL Region 的信息,可以看到 CXL Region 的名字、MMIO 地址和类型.
- cxl enable-region region0: 命令将 CXL Region 使能,以供 ndctl 工具使用.
- ndctl destroy-namespace –force all: 该命令用于强制删除系统的所有持久内存的 Namespace,这样所有持久内存上分配的逻辑区域都将被移除,所有存储在这些 Namespace 中的数据将被删除, 以此将 PMEM 设置为一个初始状态.
- ndctl enable-region region0: 该命令用于启用指定持久内存区域 ,这是对之前已经配置但未激活的区域进行激活,使其可以被系统正常使用.
- ndctl create-namespace –mode=dax -f: 该命令用于创建一个新的持久内存命名空间(Namespace), 并将其配置成 DEVDAX(Direct Access) 模式. 命令执行成功之后将输出新创建 Namespace 的信息,例如上图里新创建的 Namespace 名字为 ‘namespace0.0’, 模式为 DEVDAX,以及长度为 512MiB.
当以上的命令执行完毕之后,系统会在 /dev/ 目录下创建 dax0.0 设备,然后在系统物理地址空间里为 /dev/dax0.0 分配映射区域,此时这些区域并不是系统物理内存,而是 MMIO. 接下来应用程序可以通过 “/dev/dax0.0” 节点,采用 DAX 机制实现对 CXL Type3 Device 的访问,那么接下来分析应用程序的实现逻辑:
实践案例提供了如图的逻辑,对于 CXL Type3 Device 的访问,程序首先在 25 行调用 open 函数打开 “/dev/dax0.0” 节点,然后在 30 行调用 mmap 函数,通过文件映射的方式将分配的虚拟内存映射到打开的 “/dev/dax0.0” 文件描述符上,mmap 函数执行成功之后,那么 mem 指向的虚拟内存与 CXL Type3 Device 进行绑定. 由于 mmap 采用惰性分配,那么此时 mem 指向的虚拟内存只有虚拟内存部分,并没有真正映射到 CXL Type3 Device 的 PMEM 内存上. 程序接着在 39 行对虚拟内存发起写操作,由于此时页表并没有建立,因此 MMU 会触发缺页异常,在缺页异常处理函数里会将建立页表将虚拟内存映射到 CXL Type3 Device 的 PMEM 上,待缺页异常处理完毕之后,程序重新执行 39 行代码,那么此时可以将数据写入到 CXL Type3 Device 的 PMEM 上. 程序最后在 43 行对资源进行回收. 以上便是实践案例的处理逻辑,接下来可以参考下面命令进行实践:
# 切换到实践案例所在目录
cd output/linux-6.9-x86_64/package/BiscuitOS-CXL-DEVDAX-default
# 第一次部署采用运行准备依赖工具
make prepare
make download
# 编译实践案例
make build
BiscuitOS 运行之后,直接运行 RunBiscuitOS.sh 脚本,该脚本里包含了实践所需的所有命令,可以看到最后应用程序运行之后,从 CXL Type3 Device 里读到了刚写入的数据,以上实践便是 CXL Type3 PMEM Device 通过 DEVDAX 访问的全部逻辑.