欢迎来到“SPDK 作为 IPU 固件”这个新系列的第一篇文章。
对于不熟悉术语Infrastructure Processing Unit (IPU, 基础设施处理器)的同学,IPU是PCIe形态的卡,连接到主机系统后可以卸载主机的“基础设施”工作。它通常是面向云服务商或者超融合服务提供商的。对于熟悉SPDK的开发人员来理解,这些卡通常具有一些能够向主机系统呈现看似物理NVMe或virtio-blk/scsi设备,但实际上IPU随后基本会通过网络将发送到该设备的任何I/O转发出去。
图1 From standard NIC to IPU
这类IPU通常作为某些硬件或基于FPGA的PCI Endpoint, 并与SoC结合来实现。它具有多种Physical Function或支持SR-IOV。其上的SoC通常运行Linux或至少一些类似于POSIX的操作系统。为了使这类卡具有好的灵活性,通常大多数决策类的逻辑是在该SoC上运行的软件(“固件”)中完成,同时仅让数据搬移由硬件处理。
图2 IntelIPU ES2000 ASIC
这意味着SoC上必须运行一些代码来实现这些硬件Endpoint的“后端”。这种代码需要能够处理从设备BAR空间来的对寄存器读写操作,并使设备展现为符合某种规范的物理设备。例如,如果PCI Endpoint声称是一个NVMe设备,那么主机驱动程序将建立一个管理队列,写入EN位,并等待RDY位翻转为 1。SoC上的软件必须能够执行所有这些仿真,此时卡上硬件通常只是在主机和SoC之间开辟寄存器访问的通道,但很少,甚至没有它自己的处理逻辑。
NVMe设备上线后,主机驱动会提交管理命令来创建I/O队列的命令。I/O队列位于主机内存中,对应的Doorbell是BAR中的寄存器。一些实现可能会让硬件自动处理Doorbell的写入硬件,并通过某种队列机制,传递传入的命令给SoC。但有些实现可能只是通知SoC,Doorbell被写入,需要软件去启动DMA来传输最新的命令记录。但无论如何,命令都会到达SoC的内存,SoC软件必须解析它并决定采取什么行动。
OK,可是SPDK从哪里登场呢?
一些IPU供应商决定从头开始编写自己的固件。但有人做到了一个关键的调研 - NVMe-oF Targets已经模拟了大多数的寄存器读写,以及基本上所有的管理和 I/O命令处理。通过做一些简单的调整,NVMe-oF Target除了现有的几种Fabric Transport,是否能够也将“PCIe” Transport也适配为一种前端机制。(“Transport”泛指一种传输机制,“Fabric Transport”指跨网络传输机制)。
随着NVMe 2.0规范的推出,围绕此的术语一切都变得更加清晰了,所以请让我稍微偏一下话题。当NVMe最初创建时,只有PCIe。然后随着NVMe-oF的加入,引入了 “Transport”的概念 – 作为PCIe外的可选方案 ,如TCP或RDMA。它们可以 在两个系统之间传递NVMe命令。但这些Fabric Transport确实表现的与PCIe Transport不同。为了解决这个问题,NVMe 2.0规范了两个Transport类别 - “基于内存”和“基于消息”。请参阅NVMe 2.0 Spec中的“Theory of Operation”一章, 来了解对此更清晰的描述。在某些情况下,这两个类别具有不同的语义,特别是围绕管理队列的建立和寄存器的读写,但至少一切都被正式化并记录下来。
图3 SPDKNVMe-oF Target for IPU
回到问题 - 现有的设计用于 “基于消息”Transport的NVMe-oF Target是否可以支持“基于内存”的Transport?事实证明,这个问题已经有了答案。SPDK添加了一个 “基于内存”的Transport,来模拟PCIe设备给虚拟机,它就是vfio-user。并且它已经完成了对现有NVMe-oF Target代码的所有适配工作来处理这两类Transport。这正是IPU供应商现在正在采用的方式——他们可以相对简单的编写一个基于内存的Transport,从IPU内部与其硬件设备交互,然后使用SPDK NVMe-oF Target处理所有寄存器模拟和命令处理。
棒极了!我们完活了吗?
设备供应商可以在SPDK上编写一个Transport插件,就能很快地拥有完整的 NVMe设备模拟工作。但现实是我们还远未完成。事实证明,SPDK还需要启用大量很酷的卸载功能 来高效地移动数据。
比如我们想从 主机系统的NVMe驱动程序做一次WRITE操作,数据从主机系统内存开始,在通过网络发送之前可能会以多种方式进行转换。任意类型的后端远程服务器都能真正处理它。这些转换包括加密和压缩等内容,还包括 DIF/DIX、校验和,甚至更多。通常硬件能够内联进行这些转换,当数据从主机移动到 SoC 内存时,或者当数据直接从主机通过线路发送走时。
这样看来,SPDK该如何形容和操作主机内存呢?现在,我们将了解这一系列帖子真正要讨论的内容。
一个例子
让我们深入研究一个向主机呈现NVMe设备的例子。这个例子里IPU需要对静态数据进行加密,并通过NVMe-oF/TCP转发给一个远端Target。假设IPU本身运行着Linux并且包含三个独立的PCI设备 - 一个是将NVMe设备呈现给主机系统的设备(我们称之为NVMe Endpoint),一个可以在主机和IPU的内存上执行复制和加密的设备(我们称之为DMA引擎),以及一张网卡。
图4
以下是基本数据流:
主机NVMe驱动程序要在NVMe queue pair上发出NVM WRITE命令, 则将其写入提交队列并按门铃(Doorbell)。
NVMe Endpoint硬件通知IPU软件一个NVMe命令已 到达。该命令是一个NVM WRITE,它描述了命令对应的数据在主机系统内存的位置。
IPU软件对DMA引擎进行编程,以将数据从主机传输到IPU-本地内存中。
然后,IPU软件再用DMA引擎对本地 内存中的数据进行加密操作。
最后,IPU软件通过NVMe-oF/TCP将加密数据发送到远端Target。
假如我们在现有SPDK框架内实现了此数据路径。同时假设NVMe Endpoint硬件相当“愚蠢”,仅在主机和IPU之间传递命令。如上所述,我们添加自定义Target来用硬件NVMe Endpoint向SPDK NVMe-oF Target收发命令,就设好了寄存器和命令的处理。
我们可以为DMA引擎硬件编写一个加速模块以适配进SPDK Accel框架用于加密和复制操作。SPDK将Bdev分层来 构建 它的数据处理流水线,并且已经有一个我们可以利用的加密Bdev,能将请求路由到Accel框架。
为了与卡上的NIC通信,假设网卡有Linux驱动程序,我们将使用Linux内核网络堆栈。前面我们已经构建了一个处理流水线:在前是带自定义Transport的NVMe-oF Target,命令给到加密Bdev,再交给NVMe Bdev去做NVMe-of/TCP。我们已经在IPU上加好了驱动,除此以外,我们还在使用普通的SPDK作为完整数据路径。我们要记住这个例子用以后续的讨论。
从主机传输数据
虽然将驱动程序插入现有SPDK做扩展十分直接,但尚不清楚我们应该如何指示 DMA引擎将数据从主机复制到IPU系统内存(上例中的第三步)。现有的 Accel框架API仅需要拿到源地址和目标地址,但是 在这种情况下,源地址却不是本地的。我们将其称为驻留在非本地内存域的内存 - 在本例中是在 “主机”域里。将这个信息穿透到Accel框架是必要的,去操作DMA引擎以从正确的源地址进行传输。
SPDK现在有一个用于声明和枚举内存域的框架,它定义引用这些域中内存的内存描述符。这个接口函数可以在include/spdk/dma.h中找到。我要感谢Alexey Marchuk和Konrad Sztyber为实现这一目标所做的一切努力。
在我们的示例中,将有一个主机内存域和本地系统内存域(它是始终存在的)。为了使整个示例流程正常工作,我们定制了NVMe-oF Transport来解析传入的NVM WRITE命令以提取主机内存地址,创建一个内存描述符,指出它位于另外一个内存域,并将其传递给accel框架进行数据复制操作。这样,我们就可以让整个示例流程正常工作了。
下一步
这使我们的基本数据流正常工作,并且我们可以处理读取和写入。下一篇文章我们将围绕加密功能讨论如何提升效率。然后在本系列第三篇文章中,我们将讨论更高阶的卸载,包括链式卸载,和框架的最终设计。
作者:Ben Walker;译:刘孝冬
注:Ben是SPDK maintainer与核心开发,参与了SPDK framework的完整设计与迭代过程,以及诸多SPDK功能的开发和审阅,从宏观到细节对SPDK有深刻的见解和洞察。SPDK As IPU Firmware系列文章层层深入,为读者讲述了SPDK运行到IPU中,面临的主要问题和解决方法。
转载须知
推荐阅读
从C10K到C1000M:HDSLB商业版23.04性能数据公布
点点“赞”和“在看”,给我充点儿电吧~