DOCA Core
介绍
DOCA Core库只支持beta版。
DOCA Core对象为应用程序开发人员提供了与各种DOCA库交互的统一的接口。DOCA Core API和对象为应用程序提供了一个标准化的流程和构建,同时隐藏了软硬件内部实现细节。DOCA Core旨在保持性能的同时提供适当的抽象。
DOCA Core对于DPU和CPU安装具有相同的API(头文件),但是如果该CPU没有实现该API,则该API调用可能会返回DOCA_ERROR_NOT_SUPPORTED。但,Windows和Linux的除外,因为DOCA Core在Windows和Linux安装之间确实存在API差异。
DOCA Core向应用程序编写者公开了C语言API,用户必须根据其应用程序所需的DOCA Core组件包含要使用的正确头文件。
DOCA Core可分为以下几个软件模块:
DOCA Core模块 |
说明 |
General |
l DOCA Core枚举和基本的数据结构 l 头文件:DOCA_error.h、DOCA_types.h |
Device操作 |
l 查询设备信息(主机端和DPU)和Device capability(例如,设备的PCIe BDF地址) n 在DPU上 u Get local DPU devices u Get representors list (表示host local devices) n 在host上 u Get local devices u Query device capabilities and library capabilities u Open and use the selected device representor u Relevant entities – DOCA_devinfo, DOCA_devinfo_rep, DOCA_dev, DOCA_dev_rep n 头文件:DOCA_dev.h |
内存管理 |
l 处理应用程序使用的优化过的内存池,并支持在DOCA库之间共享资源(同时隐藏与硬件相关的技术) l Data buffer服务(例如,支持SGL) l 将主机内存映射到DPU以便直接访问 l 相关实体:DOCA_buff, doca_mmap, DOCA_buf_inventory, DOCA_buf_array, DOCA_bufpool l 头文件:doc_buf_h, doc_buf_inventory .h, doc_mmap .h, doc_buf_array .h, doc_bufpool .h |
处理引擎与任务执行 |
l 支持向DOCA库提交任务并跟踪任务进度(支持轮询模式和事件驱动模式) l 相关实体:doc_ctx, doc_task, doc_event, doc_event_handle_t, doc_pe l 头文件:doc_ctx .h |
Sync event |
l 用于同步不同的处理器(例如:DPU和主机) l 头文件: DOCA_dpa_sync_event.h, DOCA_sync_event.h |
前提条件
在DPU target和主机侧支持DOCA Core对象,两者必须满足以下前提条件:
1. DOCA version 2.0.2或更高
2. NVIDIA BlueField软件版本4.0.2或更高
3. NVIDIA BlueField-3固件版本32.37.1000或更高
4. NVIDIA BlueField-2固件版本24.37.1000或更高
架构
以下章节将介绍DOCA Core各个软件模块的架构。
General
所有的Core对象都遵循相同的流程,在之后的快路径中不用再分配,流程如下:
1. 创建对象实例(如:doca_mmap_create)
2. 配置实例(如:doca_mmap_set_memory_range)
3. 启动实例(如:doca_mmap_start)
在实例启动后,可以在数据路径中安全的使用,不用再分配,当实例完成后,必须停止和destroy掉(doca_mmap_stop, doca_mmap_destroy)
Core对象可以被重新配置和重新启动(如:create->configure->start->stop->configure->start),对象是否支持该功能请阅读该对象相关的头文件。
DOCA_error_t
所有DOCA API都会返回类型为DOCA_error_t的状态码。
typedef enum DOCA_error {
DOCA_SUCCESS,
DOCA_ERROR_UNKNOWN,
DOCA_ERROR_NOT_PERMITTED, /**< Operation not permitted */
DOCA_ERROR_IN_USE, /**< Resource already in use */
DOCA_ERROR_NOT_SUPPORTED, /**< Operation not supported */
DOCA_ERROR_AGAIN, /**< Resource temporarily unavailable, try again */
DOCA_ERROR_INVALID_VALUE, /**< Invalid input */
DOCA_ERROR_NO_MEMORY, /**< Memory allocation failure */
DOCA_ERROR_INITIALIZATION, /**< Resource initialization failure */
DOCA_ERROR_TIME_OUT, /**< Timer expired waiting for resource */
DOCA_ERROR_SHUTDOWN, /**< Shut down in process or completed */
DOCA_ERROR_CONNECTION_RESET, /**< Connection reset by peer */
DOCA_ERROR_CONNECTION_ABORTED, /**< Connection aborted */
DOCA_ERROR_CONNECTION_INPROGRESS, /**< Connection in progress */
DOCA_ERROR_NOT_CONNECTED, /**< Not Connected */
DOCA_ERROR_NO_LOCK, /**< Unable to acquire required lock */
DOCA_ERROR_NOT_FOUND, /**< Resource Not Found */
DOCA_ERROR_IO_FAILED, /**< Input/Output Operation Failed */
DOCA_ERROR_BAD_STATE, /**< Bad State */
DOCA_ERROR_UNSUPPORTED_VERSION, /**< Unsupported version */
DOCA_ERROR_OPERATING_SYSTEM, /**< Operating system call failure */
DOCA_ERROR_DRIVER, /**< DOCA Driver call failure */
DOCA_ERROR_UNEXPECTED, /**< An unexpected scenario was detected */
DOCA_ERROR_ALREADY_EXIST, /**< Resource already exist */
DOCA_ERROR_FULL, /**< No more space in resource */
DOCA_ERROR_EMPTY, /**< No entry is available in resource */
DOCA_ERROR_IN_PROGRESS, /**< Operation is in progress */
DOCA_ERROR_TOO_BIG, /**< Requested operation too big to be contained */
} DOCA_error_t;
更多详细信息请参考DOCA_error.h。
通用数据结构和枚举
以下数据结构对于所有的DOCA API都适用。
union DOCA_data {
void *ptr;
uint64_t u64;
};
enum DOCA_access_flags {
DOCA_ACCESS_LOCAL_READ_ONLY = 0,
DOCA_ACCESS_LOCAL_READ_WRITE = (1 << 0),
DOCA_ACCESS_RDMA_READ = (1 << 1),
DOCA_ACCESS_RDMA_WRITE = (1 << 2),
DOCA_ACCESS_RDMA_ATOMIC = (1 << 3),
DOCA_ACCESS_DPU_READ_ONLY = (1 << 4),
DOCA_ACCESS_DPU_READ_WRITE = (1 << 5),
};
enum DOCA_pci_func_type {
DOCA_PCI_FUNC_PF = 0, /* physical function */
DOCA_PCI_FUNC_VF, /* virtual function */
DOCA_PCI_FUNC_SF, /* sub function */
};
更多信息请参考DOCA_types.h。
DOCA Device
Local Device and Representor
先决条件
对于representors模型,BlueField必须工作在DPU模式下。
拓扑结构
DOCA device代表一个由软件或硬件实现的处理单元。DOCA device公开了其属性以便于应用程序选择正确的设备。DOCA Core支持两种类型的设备:
Local device:在本地系统(DPU或host)中公开的真实的设备,可以执行DOCA库的处理任务。
Representor device: local device的代表,local device通常在host上(SF除外),而representor device总是在DPU端(主机端设备在DPU上的代理)。
上图表示一个DPU(图右侧)连接到一个host(图左侧)。Host有两个PF,PF0和PF1。PF0有两个VF,VF0和VF1。PF1只有一个VF,VF1。通过DOCA SDK API,用户可以获取到这五个设备,这五个设备是host上的local device。
在DPU端,对于每一个host function(PF或VF)都有一个一对一的representor device(例如:hpf0是host device pf0的representor device)。每个SF也有一个一对一的representor device,SF及其representor device都位于DPU中。
如果用户在DPU端查询local device,会获取到两个设备,即:pf0和pf1,它们是以下设备的父设备:
l 7个representor device
n 5个前缀为hpf的representor device,host到DPU的5个箭头指向的设备,即:hpf0、hpf0vf0、hpf0vf1、hpf1、hpf1vf0;
n 2个SF device的representor device,即:pf0sf0、pf1sf0;
l 2个local SF device,即:p0s0和p1s0
也就是说,pf0是hpf0、hpf0vf0、hpf0vf1、p0s0、pf0sf0的父设备,pf1是hpf1、hpf1vf0、p1s0、pf1sf0的父设备。
如上图所示,虚线风格成了两部分,上半部分表示DPU设备p0,下半部分表示DPU设备p1,p0和p1负责创建所有其它local device,host PF(pf0、pf1),host VF(pf0vf0、pf0vf1、pf1vf0),DPU SF(p0s0、p1s0)。所以,p0和p1是所有设备的父设备,也可以访问每个function的representor(通过DOCA_devinfo_rep_list_create)。
Local device和representor device的映射关系
host侧的local device |
DPU侧的representor device |
DPU侧的device |
pf0 |
hpf0 |
p0 |
pf0vf0 |
hpf0vf0 |
|
pf0vf1 |
hpf0vf1 |
|
pf1 |
hpf1 |
p1 |
pf1vf0 |
hpf1vf0 |
使用doca_mmap_export_dpu()选择host侧的local device,使用doca_mmap_create_from_export()选择DPU侧的device。
SF与VF的区别
VF是SR-IOV标准中定义的,VF共享PF的资源,由PF创建和分配。SF(scalable function或sub-function)是Mellanox DPU系列产品提供的一种轻量级的function,具有独立的function和资源。VF和SF都有一个parent function,即PF。SF可以单个进行部署,而VF需要整体一起使能。SF因为没有全部实现PCI配置空间、寄存器和复位,所以比较轻量级。多个SF可以共享MSI-X向量,减少了硬件和中断控制器的中断数量。SF主要用于容器,当创建容器的时候,就可以SF对应的netdevice分配给容器使用。SF可以从DPU以云原生方式部署,这避免了在representor设置中的跨系统同步。换句话说,在将SF热插到host之前,管理员可以完全设置SF、它的representor、QoS、SF所需的参数。
device discovery
为了使用DOCA库和DOCA Core对象,应用程序必须打开DPU或host上的device。如上图所示,通常有多个设备可用。应用程序可以根据device capabilities和DOCA Core API来选择打开哪个设备。流程如下:
1. 获取Device列表;
2. 根据某些capability和属性来选择一个DOCA_devinfo,上图根据PCI地址来选择;
3. 一旦找到符合要求的DOCA_devinfo,就打开DOCA_dev;
4. 在打开了DOCA_dev后,就可以关闭DOCA_devinfo,DOCA_dev可以继续使用;
5. 最后关闭DOCA_dev。
representor device discovery
为了和DOCA 库和DOCA Core对象一起工作,一些应用程序必须打开并使用DPU上的representor device。在打开并使用representor device之前,需要工具来选择representor device。DOCA Core API提供了丰富的Device capability以便应用程序选择正确的Device对(Device及其representor device)。
1. 在DPU端,应用程序获取某个local Device的representor Device列表;
2. 根据某些属性来选择一个DOCA_devinfo_rep,上图根据PCI地址来选择;
3. 一旦找到符合要求的DOCA_devinfo_rep,就打开DOCA_dev_rep;
4. 在打开了DOCA_dev_rep后,就可以关闭DOCA_devinfo_rep,DOCA_dev_rep可以继续使用;
5. 最后关闭DOCA_dev_rep。
需要注意的是,representor device的属性存在缓存,因此当representor device的任何属性动态改变后,需要重新获取representor device列表才能感知新的改动。
DOCA Memory Subsystem
DOCA内存子系统旨在优化性能,同时保持最小的内存占用作为主要设计目标。DOCA内存有如下两个主要组件:
l DOCA_buf:data buffer的描述符,不是实际的data buffer,只是拥有data buffer的metadata;
l doca_mmap:DOCA_buf指向的data buffer pool,应用程序将内存作为单个内存区域提供,并为某些设备提供访问它的权限。
doca_mmap作为data buffer的pool,也有个实体叫DOCA_buf_inventory,作为DOCA_buf的pool。与所有DOCA实体一样,DOCA内存子系统对象是不透明的,只能由DOCA SDK实例化。
下图所示,显示了DOCA内存子系统的各个模块:
在图中,有两个DOCA_buf_inventory。每个DOCA_buf都指向一个memory buffer,而memory buffer又是doca_mmap的一部分,DOCAo_mmap是一段连续的内存,并且被映射给两个dev。
要求和注意事项
l DOCA内存子系统要求使用池,而不是动态分配:
n DOCA_buf_inventory是DOCA_buf的pool
n doca_mmap是data memory的pool
l doca_mmap中的内存可以映射给一个或多个dev;
l dev访问内存有权限限制;
l DOCA_buf不是实际的data buffer,包含的是描述的data buffer相关信息,即:metadata;
l 内存映射和dev的使用(如:mr)对应用程序是隐藏了实现细节的。
doca_mmap
doca_mmap不仅仅是一个数据缓冲区,因为它向应用程序开发人员隐藏了许多细节(例如,RDMA技术,设备处理等),为软件提供了适当的抽象。doca_mmap是在主机和DPU之间共享内存的最佳方式,这样DPU就可以直接访问主机内存,主机也可以直接访问DPU内存。后续,doca_mmap映射的内存简称mmap。
local mmap
这是doca_mmap的基本类型,它将本地内存映射到本地设备。
1. 应用程序创建doca_mmap;
2. 应用程序使用doc_mmap_set_memrange设置mmap的内存范围;
3. 应用程序添加dev,授予dev访问内存区域的权限;
4. 应用程序可以使用doc_mmap_set_permissions指定设备对该内存的访问权限;
5. 如果mmap仅在本地使用,则必须指定DOCA_ACCESS_LOCAL_*;
6. 如果mmap是在主机上创建,但是要与DPU共享,那么必须指定DOCA_ACCESS_PCI_*;
7. 如果mmap是在DPU上创建,但是要与主机共享,那么必须指定DOCA_ACCESS_PCI_*;
8. 如果mmap与远端RDMA target共享,则必须指定DOCA_ACCESS_RDMA_*;
9. 应用程序启动mmap,此后不能再对mmap做修改;
10. 调用doca_mmap_export_pci和主机或DPU共享mmap,调用doca_mmap_export_rdma和RDMA target共享mmap,成功返回一个blob,如果权限不对,export会失败;
11. 如果与远端RDMA target共享mmap,可以使用套接字在带外交换上一步生成的blob;如与DPU共享,建议使用DOCA Comm Channel。
mmap from export
这用于访问主机内存(从DPU)或远端RDMA target的内存。
1. 应用程序接收来自对端的blob;
2. 应用程序调用doca_mmap_create_from_export导入对端定义的mmap。
现在应用程序可以创建DOCA_buf指向这个导入的mmap来直接访问对端的内存。
注意:DPU可以访问同一台机器上的主机导出的内存;如果内存是RDMA导出的,不管内存位于同一台主机还是远程主机还是远程DPU上,DPU都可以访问。但主机只能访问通过RDMA导出的内存,不管这些内存位于远程主机上还是远程DPU上还是同一机器的DPU上。
doca_buf
doca_buf是可由DPU硬件访问的内存,且可以跨不同的DPU加速器使用。doca_buf可以CPU内存、GPU内存、主机内存甚至RDMA内存,创建后的使用方式都是类似的。doca_buf有一个地址和长度来描述一段内存区域。每个buffer还可以指向区域内的某段内存。于是可以将doca_buf分为三个部分:headroom、dataroom和tailroom。如下图所示:
doca_buf的注意事项
l doca_buf有多种创建方式,一旦创建,表现出的行为都一致;
l doca_buf可以是CPU不访问的内存,例如:RDMA内存;
l doca_buf是线程非安全的;
l doca_buf可以是非连续的内存区域(scatter/gather list);
l doca_buf不拥有也不管理它所引用的数据,释放buffer不会影响它引用的内存。
Headroom
用户空间,用户可以用来保存一些私有信息,所有DOCA库API都会忽略这部分空间。
Dataroom
保存数据或者结果。
Tailroom
可以自由写入的空间。
Buffer as Source
当doca_buf作为源时,源数据就是dataroom。
Buffer as Destination
当doca_buf作为目的时,数据以追加的方式被写到tailroom,数据长度相应的进行增加。
Scatter/Gather List
要在非连续内存区域上执行操作,可以创建一个缓冲区链表,用单个doc_buf指向链表的头部。要创建一个缓冲区链表,用户必须首先单独分配每个缓冲区,然后将它们链接起来。
l doca_buf_chain_list将第二个链表附加到第一个链表尾部形成一个链表;
l doca_buf_unchain_list将指定元素从链表中摘除;
l 一旦创建了列表,就可以使用doc_buf_get_next_in_list()遍历它。一旦到达最后一个元素就返回NULL。
传递一个链表和传递单个buffer是一样的。支持这个特性的DOCA库可以将一个链表组成的内存区域看做一个连续的内存区域。
当使用一个buffer链表作为源时,收集每个buffer中的数据(在dataroom中)聚合成一段连续的数据再做处理。
当使用一个buffer链表作为目的时,数据会被分散写到buffer的tailroom中直到全部写入为止。
Buffer Use Cases
除非另有说明,否则对缓冲区数据进行的操作不是原子性的。一旦缓冲区作为任务的一部分被传递给库,缓冲区的所有权就会转移到库中,直到任务完成。当使用doca_buf作为某个库(例如,doca_dma)的输入时,在处理完成之前,doca_buf必须保持有效且不可修改。写入正在in-flight缓冲区可能会导致异常行为。同样,从in-flight缓冲区读取数据时也不能保证数据的有效性。
Inventory
inventory负责分配doca_buf。inventory有多种类型。最基本的inventory分配doca_buf时无需申请任何系统内存。其它类型的inventory要求buffer地址不能重叠。
l 所有类型的inventory启动后都零分配;
l 申请一个doca buffer需要一个data source和一个inventory;
n data source定义了数据存放的位置,谁可以访问以及访问的权限;
n data source必须由应用程序创建;
l inventory描述了buffer的申请模式,例如:随机访问或pool、可变大小或固定大小、连续或非连续内存;
l 有些inventory需要在分配缓冲区时提供data source和doca_mmap,有些则需要在创建库存时提供;
l 所有类型的inventory都是线程不安全的。
Inventory type
Inventory type |
特点 |
使用场景 |
doca_buf_inventory |
多个mmap,灵活的地址,灵活的buffer大小 |
多个大小或mmap |
doca_buf_array |
单个mmap,固定buffer大小;用户接收一个指向DOCA buf的数组;在DPA场景下,mmap和buffer size不能被用户配置,只能稍后从DPA设置 |
用于在GPU或DPA上创建DOCA buffer |
doca_bufpool |
单个mmap,固定buffer大小,地址不受用户控制 |
当buffer地址不重要时用作具有相同特征的buffer pool |
举例
以下步骤展示了如何将主机mmap导出到DPU以供DOCA用于直接访问主机内存(例如,用于DMA):
1 在主机上创建mmap;添加单个doca_dev到mmap并导出,以便DPU或RDMA端可以访问;
2 导出到DPU或RDMA端(使用mmap描述符输出参数作为doc_mmap_create_from_export的输入);
DOCA Execution Model
执行模型是基于数据和应用线程的硬件处理的。DOCA不创建内部线程(操作系统的线程)。工作负载由task和event组成。有些任务用于数据传输。最基本的数据传输就是DMA操作,把数据从一个内存位置移动到另外一个内存位置。其它操作允许接收来自网络的数据包,或者源数据的SHA值并将其写到目的地。
举例来说,一份负责数据传输的工作负载分以下三部:
1. 读源数据;
2. 对被读的数据启动操作(由专用的硬件加速器完成);
3. 写操作的结果到目的地。
每个这样的操作就被叫做任务(doca_task)。任务描述了应用程序想要提交给DOCA(硬件或DPU)的操作。为此,应用程序需要一种与硬件或DPU通信的方法。这就是doca_pe发挥作用的地方。Process engine(PE)是一个单线程对象,用于将任务排队以卸载到DOCA,并最终接收它们的compeletion状态。
Doca_pe引入了三个主要操作:
1. 提交任务;
2. 检查提交任务的进度或状态;
3. 接收任务完成的通知(以回调的形式)。
一个工作负载可以被分成许多不同的任务,这些任务可以在不同的线程上执行;每个线程由不同的PE表示。每个任务必须与某个context相关联,其中context定义要执行的任务类型。
context可以从DOCA SDK中的一些库中获得。例如,要提交DMA任务,可以从doca_dma.h获取DMA context,而可以使用doca_sha.h获取SHA context。每个这样的context允许提交多种任务类型。
任务被认为是异步的,因为一旦应用程序提交任务,DOCA执行引擎(硬件或DPU)将开始处理它,应用程序可以继续执行其他处理,直到硬件完成。为了跟踪哪个任务已经完成,有两种操作模式:轮询模式和事件驱动模式。
要求和注意事项
l 任务提交/执行/API针对性能(延迟)进行了优化;
l DOCA不管理内部线程(操作系统的线程),进度是通过轮询或者事件驱动的方式来获取;
l 执行任务基本的对象是doc_task,每个任务都是从特定的DOCA context分配的;
l doca_pe表示一个逻辑上的线程,执行提交给PE的应用程序和任务;
l PE不是线程安全的,每个PE由单个应用程序线程管理;
l 与执行相关的元素(例如,doca_pe, doca_ctx, doca_task)是不透明的,应用程序在使用这些元素之前执行最小的初始化或配置;
l 提交给PE的任务可能会执行失败(即使在提交成功之后);在某些情况下,可以从错误中恢复;某些情况下,只能重新初始化相关对象;
l PE不保证顺序(例如,按一定顺序提交的任务可能会乱序完成);保序由应用程序自己负责(如:一个任务完成后再提交另外一个任务);
l PE可以在轮询模式或事件驱动模式下工作,但不能同时在这两种模式下工作;
l 所有DOCA context都支持轮询模式。
DOCA context
DOCA Context (struct doc_ctx)定义并实现了task/event的处理。Context有多种类型。
l Context至少利用了一种DOCA Device功能或硬件加速能力;
l 对于每种task类型能支持它的有且仅有一种context;
l 一个context包含每种task类型支持的inventory;
l Context包含了每个任务类型执行的所有参数(例如:库存大小、加速处理设备);
l 每个context都需要一个PE的实例来运行其任务,也就是说context必须和PE关联才能执行任务。
下图显示了DOCA Core实体之间的关系:
1. Doca_task与执行任务的相关doc_ctx相关联(在相关doc_dev的帮助下);
2. Doca_task初始化后,提交给doca_pe执行;
3. Doca_ctxs关联到doca_pe,一旦doc_task排队到doc_pe,它就由与相关联的doc_ctx执行。
下图描述了context的初始化顺序:
配置阶段
在尝试使用doc_ctx_start()启动DOCAcontext之前,必须先配置它。有些配置是强制性的(例如,提供doc_dev)。
l 配置用于启用某些任务或事件、启用默认禁用的feature以及优化性能;
l 配置使用setter函数提供,有关强制性和可选配置及其相应api的列表,请参阅context相关文档;
l 配置在创建context之后和启动之前完成,context一旦启动,就不能再配置它,除非再次停止它。
执行阶段
一旦context配置完成,就可以使用context执行任务。context通过将工作负载卸载给硬件来执行任务,而任务是异步的,因此软件需要轮询任务,直到它们完成。
应用程序采用以下模式中的一种来轮询任务:
l Poll mode
l Notification-driven mode
在此阶段,context和所有DOCA Core对象通过内存池执行零分配。建议应用程序也使用相同的方法。
State Machine
State |
说明 |
dile |
没有任务执行; 初始化阶段(就在doca_<T>_create(ctx)之后):所有配置api都有效; 重新配置(从stop状态转换过来):部分配置api有效; |
starting |
对context是强制性的; |
Running |
允许申请和提交任务,且只有此状态允许; 所有配置api被禁止; |
Stopping |
任务停止前的准备; 清楚所有未完成的任务; |
下图描述了DOCA context状态的转换:
Internal error
DOCA Context在任何时候都可能遇到一些内部错误。如果状态正处于starting或running状态,则可能导致状态转换到stopping;
停止后,状态可能变为idle状态。但是,如果存在配置问题或错误事件阻止正确转换到starting或running,doc_ctx_start()可能会失败。
DOCA task
任务是可卸载到硬件的(功能或处理)工作负载单元。大多数任务使用NVIDIA BlueField和NVIDIA ConnectX硬件来加速处理任务定义的工作负载。任务是异步的(例如,通过非阻塞doca_task_submit() API提交处理的任务)。
任务完成后,在context中执行预设的completion callback。completion callback是任务的基本属性,类似于用户数据。大多数任务都是由NVIDIA设备硬件执行或加速的IO操作。
Task Properties
任务熟悉分通用属性和特定属性。由于任务的结构是不透明的(即,它的内容不向用户公开),所以对任务属性的访问由set/get api提供。
以下是通用任务属性:
l Setting completion callback-成功和失败是两个单独的callback;
l Getting/setting user data-用于completion callback;
l Getting task status-在失败完成时获取错误代码。
对于每个任务,只有一个所有者,就是context对象,可以通过doc_task_get_ctx () API来获取通用context对象。
以下是通用的任务api:
l 从CTX(内部/虚拟)inventory中分配和释放;
l 通过setter(或init API)进行配置;
l 提交任务(即,doca_task_submit(task));
l 完成后,有一组getter来访问任务执行的结果。
Task Lifecycle
每个DOCA任务对象的生命周期:
l DOCA context进入running状态即开始,一旦进入运行状态,应用程序就可以通过调用doc_ <CTX名称>_task_<任务名称>_alloc_init(CTX,…)从CTX获得任务;
l DOCA context进入stopping状态时结束,一旦相关的DOCA context离开running状态,应用程序就不能再分配任务;
从应用程序的角度来看,DOCA context提供了一个虚拟的任务清单。
DOCA Progress Engine
Progress engine(PE)支持在单线程执行环境中异步处理不同类型的多个任务和事件。它是所有基于context的DOCA库的事件循环,其中I/O completion是最常见的事件类型。
PE被设计为线程不安全的(即,它一次只能在一个线程中使用),但单个操作系统线程可以使用多个PE。用户可以通过将不同的context添加到不同的PE中,并相应地调整每个PE的轮询频率,从而为不同的context分配不同的优先级。另外一种角度看,PE是一个计划执行的工作负载队列。
没有专门的api来添加或在PE上调度工作负载,但可以通过以下方式添加工作负载:
l 向PE添加DOCA context;
l 注册一个DOCA event到probe,在主动probe时执行相关的处理程序。
PE负责调度工作负载(即选择下一个要执行的工作负载)。工作负载执行的顺序与任务提交顺序、事件注册顺序或关联context的顺序无关。多个任务的completion callback可能以不同于任务提交的顺序执行。
PE的初始化流程如下图所示:
在PE被创建并关联到context之后,它可以开始处理提交给context的任务。请参阅context相关文档以查找详细信息,例如可以使用context提交哪些任务。
注意:PE可以关联到多个context。这些context可以是相同类型的,也可以是不同类型的。这允许将不同的任务类型提交到相同的PE,并等待它们从相同的位置/线程完成。
初始化PE后,应用程序可以使用以下模式之一定义事件循环:
l Poll mode
l Notification-driven mode
PE as Event Loop Mode of Operation
任务和事件的所有completion handler都在doca_pe_progress()的context中执行。doca_pe_progress()会循环执行的每个工作负载:
运行选定的工作负载单元。以下情况:
l 任务完成后,执行相关处理程序并中断循环并返回状态;
l 主动探测事件,执行相关处理程序,打破循环并返回状态;
l 后续任务处理完成,打破循环并返回状态。否则,循环结束并返回状态no progress。
Polling mode
在这种模式下,应用程序提交一个任务,然后执行忙等待,以确定任务何时完成。执行顺序如下图所示:
1. 应用程序提交所有任务(一个或多个)并跟踪任务完成的数量,以了解是否完成了所有任务;
2. 应用程序通过不断轮询doc_pe_progress()等待任务完成;
3. 如果doca_pe_progress()返回1,则表示某些任务或事件处理完成;
4. 每次任务或事件处理完成时,都会相应地执行其预设的回调函数;
5. 如果任务执行出错,则执行错误回调函数。
6. 应用程序可以向完成回调函数中添加代码,以跟踪已完成和待处理工作负载的数量。在这种模式下,应用程序总是占用CPU,即使它什么都不做(忙等)。
阻塞模式- Notification Driven
在这种模式下,应用程序提交任务,然后等待接收通知,最后再查询状态。执行顺序如下:
1. 应用程序从doca_pe中获得一个通知句柄,该句柄是一个Linux文件描述符,用于通知应用程序某些工作已经完成;
2. 应用程序用doc_pe_request_notification()告知PE为事件通知模式,之后不允许调用doc_pe_progress();
3. 应用程序提交任务;
4. 应用程序等待在pe-fd上接收通知(例如,Linux epoll/select);
5. 应用程序清除接收到的通知,通知PE已接收到信号,并允许PE执行通知处理;
6. 应用程序尝试通过(多次)调用doca_pe_progress()来处理接收到的通知;
请注意:不能保证对doca_pe_progress()的调用将执行任何任务完成/事件处理程序,但是PE可以继续操作。
7. 应用程序处理任务完成或事件处理程序引起的内部状态更改;
8. 重复步骤2-7,直到所有任务完成,所有预期事件都得到处理。
Progress Engine vs Epoll
Linux中的epoll机制和DOCA PE处理在事件驱动架构中的高并发性。这两个系统在等待事件或任务完成时都能有效地管理资源。
DOCA Event
要注册一个事件,用户必须调用doca_<event_type>_reg(pe,…)函数,事件处理程序必须与PE关联。注册事件后,将由doc_pe_progress()函数定期检查该事件,该函数与PE在相同的执行context中运行。如果满足事件条件,则调用处理程序函数。事件不是线程安全的,只能由它们绑定的PE访问。
Error Handling
在一个任务被成功提交后,后续对doca_pe_progress()的调用可能会失败。一旦任务失败,上下文可能会转换到stopping状态,在这种状态下,应用程序必须在销毁或重新启动上下文之前执行完所有in-flight的任务。
下图显示了应用程序如何处理来自doca_pe_progress()的错误:
1. 应用程序轮询doca_pe_progress;
2. 可能发生下列情况:
l 任务失败,调用fail completion callback;
n 由错误的参数或其他致命错误引起;
n 处理程序释放任务和所有相关资源;
l context转换到stopping状态,并调用context state changed handler;
n 由任务失败或其他致命错误引起;
n 在这种状态下,所有in-flight任务都肯定会失败;
n 释放非in-flight的任务;
l context转换到idle状态,并调用context state changed handler;
n 遇到错误并且context没有任何必须由应用程序释放的资源;
n 应用程序可通过再次调用start来恢复context,或者销毁context并退出应用程序。
DOCA Graph Execution
DOCA Graph可以以特定的顺序和依赖关系运行一组操作(任务、用户回调)。DOCA Graph运行在DOCA PE上。DOCA Graph创建的图形实例通过doc_graph_instance_submit提交给PE。
Nodes
DOCA Graph由context、user、sub-graph nodes组成,这些节点可以位于网络中的位置如下:
l 根节点:根节点没有父节点,图可以有一个或多个根节点。当提交图形实例时,所有根节点开始运行;
l 边缘节点:没有子节点的节点,叶子节点;当所有边缘节点完成时,图实例就执行完成;
l 中间节点:有父节点和子节点的节点。
Context Node
Context node运行特定的任务并使用特点的DOCA context(doca_ctx),context必须在图启动之前关联到PE。任务的生命周期必须大于或等于图实例的生命周期。
User Node
User node运行用户的回调函数,以便在图实例运行期间执行某些操作(例如,调整下一个节点任务数据,比较结果等)。
Sub-graph Node
sub-graph node运行着一个graph的实例。
Using DOCA Graph
1. 使用doc_graph_create创建图;
2. 创建图形节点(例如,doca_graph_node_create_from_ctx);
3. 使用doc_graph_add_dependency定义依赖项;
l 注意:DOCA graph不支持循环依赖(例如,A => B => A);
4. 使用doca_graph_start启动图;
5. 使用doc_graph_instance_create创建图形实例;
6. 设置节点数据(例如,doca_graph_instance_set_ctx_node_data);
7. 使用doc_graph_instance_submit将图实例提交给PE;
8. 调用doca_pe_progress,直到图形回调被调用;
l 进度引擎可以同时运行图形实例和独立的任务。
DOCA Graph Limitations
l 不支持循环依赖;
l 必须至少包含一个context node。
Alternative Data Path
除了DOCA PE将数据路径从CPU卸载到硬件外,另外一些库还支持备选数据路径,可以卸载DPA或GPU。
注意事项:
l 并非所有上下文都支持备选数据路径;
l 配置阶段总是在CPU上完成;
l 数据路径操作总是卸载到硬件上,卸载的对象可以是CPU/DPA/GPU;
l 默认数据路径模式是CPU;
l 每种模式都会引入一组不同的API,以在执行路径中使用;所使用的API对于特定的context实例是互斥的。
DPA
用户必须首先检查DPA上的数据路径是否支持。
要将数据路径模式设置为DPA,需要获取一个DOCA DPA实例,然后使用doc_ctx_set_datapath_on_dpa () 。
使用这种模式启动context之后,就可以使用相关的API获取DPA句柄(例如,doc_rdma_get_dpa_handle())。这个句柄可以用来访问DPA代码中的DPA数据路径API。
GPU
用户必须首先检查是否支持GPU上的数据路径。
如果需要将数据路径模式设置为GPU,需要获取一个DOCA GPU实例,然后使用doc_ctx_set_datapath_on_gpu ()。
context以这种模式启动之后,可以使用相关的API获得GPU句柄(例如,doca_eth_rxq_get_gpu_handle())。这个句柄可以用来访问GPU代码中的GPU数据路径API。
Task and Event批处理
DOCA批处理可以将多个DOCA任务或相同类型的DOCA事件组成一组并将其作为单个单元来处理。批处理的completion需要等待所有项目的completion执行完成才会执行。
批处理的completion以批处理中所有项目的completion为基础,并作为单个completion进行处理。这允许在单个API调用中进行多个DOCA任务初始化/提交和多个DOCA任务/事件completion处理。
Object Life Cycle
大多数DOCA Core对象都有着相同的处理模型:
1. 该对象由DOCA分配,因此对应用程序来说是不透明的(例如,doca_buf_inventory_create, doca_mmap_create);
2. 应用程序初始化对象并设置所需的属性(例如,doca_mmap_set_memrange);
3. 对象已启动,不允许更改配置或属性(例如,doca_buf_inventory_start, doca_mmap_start);
4. 对象被使用;对象被停止和删除(例如,doca_buf_inventory_stop→doca_buf_inventory_destroy, doca_mmap_stop→doca_mmap_destroy)。
下面的过程描述了两台机器(远程机器或主机-DPU)之间的mmap导出机制:
1. Machine1分配内存;
2. Machine1创建mmap;
3. mmap导出到Machine2并ping住内存;
4. Machine2创建导入的mmap,并hold对Machine1内存的引用;
5. Machine2可以使用导入的mmap来分配buffer;
6. Machine2销毁导入的mmap;
7. Machine1销毁导出的mmap;
8. 销毁释放内存。
RDMA bridge
DOCA Core库提供了应用程序使用的模块,同时抽象了依赖于RDMA驱动程序的许多细节。这减少了复杂性,增加了灵活性,特别是对于已经基于rdma-core的应用程序。RDMA bridge允许DOCA SDK和RDMA -core之间的互相转换,可以将基于DOCA的对象转换为基于RDMA -core的对象。
要求和注意事项
对于已经使用rdma-core的应用程序,DOCA Core库可以方便其移植或K对其进行扩展。Bridge允许将DOCA Core对象转换为等价的rdma-core对象。
DOCA Core对象到rdma-core对象的映射