DOCA DPA
在 Beta 级别受支持。
本章概述了 DOCA DPA API 及其配置说明。
DOCA DPA 库提供了一种编程模型,用于将以通信为中心的用户代码卸载到 NVIDIA® BlueField®-3 网络平台上的 DPA 处理器上运行。DOCA DPA 为 DPA 处理器提供了高级编程接口。
DOCA DPA 提供:
完全控制 DPA 线程 –
用户可以控制在 DPA 上运行的线程函数(内核)及其在 DPA EU 上的放置
用户可以将 DPA 线程与 DPA 完成上下文关联。当完成上下文收到通知时,将调度 DPA 线程。
允许 DPA 线程发出异步操作的抽象
从主机应用程序执行阻塞式一次性调用以从主机应用程序 (RPC) 在 DPA 上执行内核的抽象
内存服务抽象
远程通信原语的抽象(与远程事件信号集成)
完全控制 DPA 和主机/目标 BlueField 的执行顺序和通知/同步
一组调试 API,允许诊断和排除设备上的任何问题,以及从正在运行的应用程序访问实时信息
适用于应用程序开发人员的 C API
DPACC 用于编译和链接内核与 DOCA DPA 设备库,以获取可以从主机程序加载以在 DPA 上执行的 DPA 应用程序(类似于 CUDA 与 NVCC 的用法)。有关 DPACC 的更多信息,请参阅DOCA DPACC 编译器。
DOCA DPA 应用程序可以在主机或目标 BlueField 上运行。在主机上运行需要使用 dpaeumgmt
工具进行 EU 预配置。有关更多信息,请参阅NVIDIA DOCA DPA EU 管理工具。
2.9.0 版本中的更改
以下小节详细介绍了 2.9.0 版本中的 doca_dpa
库更新。
已添加
doca_dpa_dev.h
添加了
enum doca_dpa_dev_submit_flag
以在新参数uint32_t flags
中使用,用于 DPA RDMA 设备和 DPA memcpy API
已移除
doca_dpa_dev_rdma.h
void doca_dpa_dev_rdma_synchronize(doca_dpa_dev_rdma_t rdma);
void doca_dpa_dev_rdma_srq_post_receive(doca_dpa_dev_rdma_srq_t rdma_srq, uint32_t next_index);
void doca_dpa_dev_rdma_srq_receive_ack(doca_dpa_dev_rdma_srq_t rdma_srq, uint32_t num_acked);
已更改
doca_dpa_dev_rdma.h
向所有 DPA RDMA 设备 API 添加了
uint32_t connection_id
参数在所有 DPA RDMA API 中,将
uint32_t completion_requested
参数更改为uint32_t flags
doca_dpa_dev_buf.h
在所有 DPA memcpy API 中,将
uint32_t completion_requested
参数更改为uint32_t flags
DOCA 使开发人员能够使用 DOCA DPA 库和其他工具套件(主要是 DPACC)对 DPA 处理器进行编程。
以下是启动 DPA 卸载编程的主要步骤:
编写 DPA 设备代码或内核(
.c
文件),使用:使用 DPACC 构建 DPA 程序(即,包含嵌入式设备可执行文件的主机库)。DPACC 的输入是:
上一步中的内核
DOCA DPA 设备库
使用主机编译器构建主机可执行文件。主机编译器的输入是:
上一步中的 DPA 程序
用户主机应用程序源文件
DOCA DPA 主机库
DPACC 由 DOCA SDK 安装提供。有关更多信息,请参阅DOCA DPACC 编译器。
DPA 查询
在调用 DPA API 之前,请确保相关设备确实支持 DPA。检查设备是否支持 DPA 的 API 是:
doca_error_t doca_devinfo_get_is_dpa_supported(
const
struct doca_devinfo *devinfo)信息只有当此调用返回
DOCA_SUCCESS
时,用户才能在设备上调用 DOCA DPA API。要为 DPA 线程的 DPA EU 亲和性使用有效的 EU ID,请使用以下 API 查询 EU ID 和核心有效值:
doca_error_t doca_dpa_get_core_num(struct doca_dpa *dpa, unsigned
int
*num_cores) doca_error_t doca_dpa_get_num_eus_per_core(struct doca_dpa *dpa, unsignedint
*eus_per_core) doca_error_t doca_dpa_get_total_num_eus_available(struct doca_dpa *dpa, unsignedint
*total_num_eus)可以运行单个内核的最大 DPA 线程数存在限制。可以通过调用主机 API 检索此限制:
doca_error_t doca_dpa_get_max_threads_per_kernel(struct doca_dpa *dpa, unsigned
int
*value)每个启动到 DPA 中的内核都有最大运行时限制。可以通过调用主机 API 检索此限制:
doca_error_t doca_dpa_get_kernel_max_run_time(struct doca_dpa *dpa, unsigned
long
long
*value)注意如果 DPA 上的内核执行时间超过此最大运行时限制,则可能会终止并导致致命错误。要恢复,应用程序必须销毁 DPA 上下文并创建一个新的上下文。
初始化
DPA 上下文封装了 DPA 设备和 DPA 进程(程序)。在此上下文中,应用程序创建各种 DPA SDK 对象并对其进行控制。在验证所选设备是否支持 DPA 后,将创建 DPA 上下文。
使用以下主机端 API 创建/设置 DPA 上下文,并且它应是第一个编程步骤:
创建/销毁 DPA 上下文
doca_error_t doca_dpa_create(struct doca_dev *dev, struct doca_dpa **dpa) doca_error_t doca_dpa_destroy(struct doca_dpa *dpa)
注意请注意,用于创建“基本”DPA 上下文的
dev
必须是 PF DOCA 设备。启动/停止 DPA 上下文
doca_error_t doca_dpa_start(struct doca_dpa *dpa) doca_error_t doca_dpa_stop(struct doca_dpa *dpa)
DPACC 接口
DPA 应用程序
要将 DPA 程序 (app) 与 DPA 上下文关联,请使用以下主机端 API:
doca_error_t doca_dpa_set_app(struct doca_dpa *dpa, struct doca_dpa_app *app)
doca_error_t doca_dpa_get_app(struct doca_dpa *dpa, struct doca_dpa_app **app)
doca_error_t doca_dpa_app_get_name(struct doca_dpa_app *app, char
*app_name, size_t *app_name_len)
在 doca_dpa_set_app()
API 中使用的 app
变量名称必须是传递给 DPACC --app-name
参数的令牌。
示例(伪代码)
例如,当使用以下 dpacc
命令行时:
dpacc \
kernels.c \
-o dpa_program.a \
-hostcc=gcc \
-hostcc-options="..."
\
--devicecc-options="..."
\
-device-libs="-L/opt/mellanox/doca/include -ldoca_dpa_dev -ldoca_dpa_dev_comm"
\
--app-name="dpa_example_app"
用户必须使用以下命令来设置 DPA 上下文的 app
:
extern struct doca_dpa_app *dpa_example_app;
doca_dpa_create(&dpa);
doca_dpa_set_app(dpa, dpa_example_app);
doca_dpa_start(dpa);
DOCA DPA 软件对象概述
术语 | 定义 |
DPA 上下文 | 主机进程的软件构造,封装了与 DPA 进程(在特定设备上)关联的状态。 DPA 上下文必须与 PF 设备关联。 |
扩展 DPA 上下文 | 与 VF/SF 设备关联的 DPA 上下文,用于 RDMA 实用程序。 这允许在 VF/SF 设备上创建 DPA 资源,例如 RDMA/DPA 完成/DPA 异步操作...上下文。 |
DPA 应用程序 | 与 DPACC 编译器接口,以生成 DPA 程序 (app),DPA 上下文获取该程序以开始在 DPA 上工作。 |
内核 | 要在 DPA 上执行的用户函数(及其参数)。 内核可以由一个或多个 DPA 线程执行。 |
DPA EU 亲和性 | 用于控制哪个 EU 用于 DPA 线程的对象。 |
DPA 线程 | DOCA DPA 提供 API 来创建/管理运行给定内核的 DPA 线程。 |
DPA 完成上下文 | 用于接收/处理完成通知的对象。 用户可以将 DPA 线程与完成上下文关联。当完成上下文收到通知时,将调度 DPA 线程。 |
DPA 线程通知 | 一个 DPA 线程通知另一个 DPA 线程的机制。 |
DPA 异步操作 | 用于允许 DPA 线程发出异步操作(如 memcpy 或 post_wait 操作)的对象。 |
DPA RPC | 从主机应用程序到在 DPA 上执行内核的阻塞式一次性调用。RPC 主要用于控制路径。RPC 的返回值将报告回主机应用程序。 |
DPA 内存 | DOCA DPA 提供 API 来分配/管理 DPA 内存,以及处理已导出到 DPA 的主机/目标 BlueField 内存。 |
同步事件 | CPU、目标 BlueField、GPU 或 DPA 堆中的数据结构。事件包含可以更新和等待的计数器。 |
RDMA | 围绕网络传输对象的抽象。允许执行各种 RDMA 操作。 |
DPA 哈希表 | DOCA DPA 提供 API 以在 DPA 上创建哈希表。此数据结构在 DPA 上使用相关的设备 API 进行管理。 |
DPA 记录器/跟踪器 | DOCA DPA 提供一组调试 API,允许用户诊断和排除设备上的任何问题,以及从正在运行的应用程序访问实时信息。 |
部署视图
DOCA DPA 包括两个主要组件,它们是 DOCA SDK 安装包的一部分:
DOCA DPA SDK 不使用任何多线程同步原语。所有 DOCA DPA 对象都不是线程安全的。开发人员应确保用户程序和内核的编写避免竞争条件。
DOCA DPA 主机库
主机库和头文件

主机库提供了一个接口,用于管理以下组件:
亲和性
用户可以使用 DPA EU 亲和性对象来控制将哪个 EU 分配给 DPA 线程。
DPA EU 亲和性对象可以配置为一次只针对一个 EU ID。
使用以下主机端 API 进行管理:
创建/销毁 DPA EU 亲和性对象
doca_error_t doca_dpa_eu_affinity_create(struct doca_dpa *dpa, struct doca_dpa_eu_affinity **affinity) doca_error_t doca_dpa_eu_affinity_destroy(struct doca_dpa_eu_affinity *affinity)
在 DPA EU 亲和性对象中设置/清除 EU ID
doca_error_t doca_dpa_eu_affinity_set(struct doca_dpa_eu_affinity *affinity, unsigned
int
eu_id) doca_error_t doca_dpa_eu_affinity_clear(struct doca_dpa_eu_affinity *affinity)获取 DPA EU 亲和性对象的 EU ID
doca_error_t doca_dpa_eu_affinity_get(struct doca_dpa_eu_affinity *affinity, unsigned
int
*eu_id)
DPA 线程
DOCA DPA 线程用于在 DPA 上运行用户函数“DPA 内核”。
用户可以通过将 DPA EU 亲和性对象附加到线程来控制在哪个 EU 上运行 DPA 内核。
可以使用两种方法在 DPA 上触发线程:
DPA 线程通知 - 从一个 DPA 线程通知另一个 DPA 线程。
DPA 完成上下文 - 完成到达附加到线程的 DPA 完成上下文。
创建/销毁 DPA 线程
doca_error_t doca_dpa_thread_create(struct doca_dpa *dpa, struct doca_dpa_thread **dpa_thread) doca_error_t doca_dpa_thread_destroy(struct doca_dpa_thread *dpa_thread)
设置/获取线程用户函数及其参数
doca_error_t doca_dpa_thread_set_func_arg(struct doca_dpa_thread *thread, doca_dpa_func_t *func, uint64_t arg) doca_error_t doca_dpa_thread_get_func_arg(struct doca_dpa_thread *dpa_thread, doca_dpa_func_t **func, uint64_t *arg)
设置/获取 DPA EU 亲和性
doca_error_t doca_dpa_thread_set_affinity(struct doca_dpa_thread *thread, struct doca_dpa_eu_thread_affinity *eu_affinity) doca_error_t doca_dpa_thread_get_affinity(struct doca_dpa_thread *dpa_thread,
const
struct doca_dpa_eu_affinity **affinity)线程本地存储 (TLS)
用户可以使用以下 API 请求在主机端应用程序中为 DPA 线程存储一个不透明值:
doca_error_t doca_dpa_thread_set_local_storage(struct doca_dpa_thread *dpa_thread, doca_dpa_dev_uintptr_t dev_ptr) doca_error_t doca_dpa_thread_get_local_storage(struct doca_dpa_thread *dpa_thread, doca_dpa_dev_uintptr_t *dev_ptr)
dev_ptr
是预分配的 DPA 内存。在内核中,用户可以使用相关的设备 API 检索存储的不透明值(请参阅下面的 API)。
此不透明值使用线程本地存储 (TLS) 机制存储/检索。
启动/停止 DPA 线程
doca_error_t doca_dpa_thread_start(struct doca_dpa_thread *thread) doca_error_t doca_dpa_thread_stop(struct doca_dpa_thread *dpa_thread)
运行 DPA 线程
doca_error_t doca_dpa_thread_run(struct doca_dpa_thread *dpa_thread)
此 API 将线程设置为运行状态。
必须在 DPA 线程执行以下操作后调用此函数:
已创建、设置和启动。
如果 DPA 线程附加到 DPA 完成上下文,则必须先启动完成上下文。
示例(主机端伪代码)
extern doca_dpa_func_t hello_kernel;
// create DPA thread
doca_dpa_thread_create(&dpa_thread);
// set thread kernel
doca_dpa_thread_set_func_arg(dpa_thread, &hello_kernel, func_arg);
// set thread affinity
doca_dpa_eu_affinity_create(&eu_affinity);
doca_dpa_eu_affinity_set(eu_affinity, 10
/* EU ID */
);
doca_dpa_thread_set_affinity(dpa_thread, eu_affinity);
// set thread local storage
doca_dpa_mem_alloc(&tls_dev_ptr);
doca_dpa_thread_set_local_storage(dpa_thread, tls_dev_ptr);
// start thread
doca_dpa_thread_start(dpa_thread);
// create and initialize DPA Completion Context
doca_dpa_completion_create(&dpa_comp);
doca_dpa_completion_set_thread(dpa_comp, dpa_thread);
doca_dpa_completion_start(dpa_comp);
// run thread only after both thread is started and the attached completion context is started
doca_dpa_thread_run(dpa_thread);
完成上下文
为了将用户应用程序与事件驱动调度/计算的 DPA 本机模型紧密结合,我们引入了 DPA 完成上下文。
用户将 DPA 线程与完成上下文关联。当完成上下文收到通知时,将触发 DPA 线程。
用户可以选择不将其与 DPA 线程关联,而是手动轮询它。
用户可以选择继续接收新通知或忽略它们。
DOCA DPA 提供了一个通用完成上下文,可以为消息队列、RDMA、以太网以及 DPA 异步操作共享。
创建/销毁 DPA 完成上下文
doca_error_t doca_dpa_completion_create(struct doca_dpa *dpa, unsigned
int
queue_size, struct doca_dpa_completion **dpa_comp) doca_error_t doca_dpa_completion_destroy(struct doca_dpa_completion *dpa_comp)获取队列大小
doca_error_t doca_dpa_completion_get_queue_size(struct doca_dpa_completion *dpa_comp, unsigned
int
*size)附加到 DPA 线程
doca_error_t doca_dpa_completion_set_thread(struct doca_dpa_completion *dpa_comp, struct doca_dpa_thread *thread) doca_error_t doca_dpa_completion_get_thread(struct doca_dpa_completion *dpa_comp, struct doca_dpa_thread **thread)
仅当用户希望在完成到达完成上下文时触发线程时,才需要附加到线程。
启动/停止 DPA 完成上下文
doca_error_t doca_dpa_completion_start(struct doca_dpa_completion *dpa_comp) doca_error_t doca_dpa_completion_stop(struct doca_dpa_completion *dpa_comp)
获取 DPA 句柄
doca_error_t doca_dpa_completion_get_dpa_handle(struct doca_dpa_completion *dpa_comp, doca_dpa_dev_completion_t *handle)
对于可在线程内核中使用的以下设备 API,请使用输出参数
handle
。
线程通知
线程激活是一种机制,用于一个 DPA 线程触发另一个 DPA 线程。
线程激活是在未收到附加线程的完成的情况下完成的。因此,预期此线程激活方法的用户以另一种方式(例如共享内存)传递消息。
可以使用 DPA 通知完成对象来实现线程激活。
创建/销毁 DPA 通知完成
doca_error_t doca_dpa_notification_completion_create(struct doca_dpa *dpa, struct doca_dpa_thread *dpa_thread, struct doca_dpa_notification_completion **notify_comp) doca_error_t doca_dpa_notification_completion_destroy(struct doca_dpa_notification_completion *notify_comp)
使用给定的参数
dpa_thread
将 DPA 通知完成附加到 DPA 线程。获取附加的 DPA 线程
doca_error_t doca_dpa_notification_completion_get_thread(struct doca_dpa_notification_completion *notify_comp, struct doca_dpa_thread **dpa_thread)
启动/停止 DPA 通知完成
doca_error_t doca_dpa_notification_completion_start(struct doca_dpa_notification_completion *notify_comp) doca_error_t doca_dpa_notification_completion_stop(struct doca_dpa_notification_completion *notify_comp)
获取 DPA 句柄
doca_error_t doca_dpa_notify_completion_get_dpa_handle(struct doca_dpa_notification_completion *notify_comp, doca_dpa_dev_notification_completion_t *comp_handle)
对于可在线程内核中使用的以下设备 API,请使用输出参数
comp_handle
。
示例(主机端伪代码)
extern doca_dpa_func_t hello_kernel;
// create DPA thread
doca_dpa_thread_create(&dpa_thread);
// set thread kernel
doca_dpa_thread_set_func_arg(dpa_thread, &hello_kernel, func_arg);
// start thread
doca_dpa_thread_start(dpa_thread);
// create and start DPA notification completion
doca_dpa_notification_completion_create(dpa, dpa_thread, ¬ify_comp);
doca_dpa_notification_completion_start(notify_comp);
// get its DPA handle
doca_dpa_notification_completion_get_dpa_handle(notify_comp, ¬ify_comp_handle);
// run thread only after both thread is started and attached notification completion is started
doca_dpa_thread_run(dpa_thread);
异步操作
DPA 异步操作允许 DPA 线程发出异步操作,如 memcpy 或 post_wait。
此功能要求用户创建“异步操作”上下文并附加到完成上下文。
用户应在发布操作时遵守设备上的 `queue_size` 限制。
如果完成上下文附加到 DPA 线程,则可以引发激活。
用户还可以选择通过手动轮询来推进完成上下文。
用户可以提供 DPA 异步操作 `user_data`,并使用相关的设备 API 在设备中检索此元数据。
创建/销毁 DPA 异步操作
doca_error_t doca_dpa_async_ops_create(struct doca_dpa *dpa, unsigned
int
queue_size, uint64_t user_data, struct doca_dpa_async_ops **async_ops) doca_error_t doca_dpa_async_ops_destroy(struct doca_dpa_async_ops *async_ops)请使用以下定义作为有效的 user_data 值:
#define DOCA_DPA_COMPLETION_LOG_MAX_USER_DATA (
24
)获取队列大小/user_data
doca_error_t doca_dpa_async_ops_get_queue_size(struct doca_dpa_async_ops *async_ops, unsigned
int
*queue_size) doca_error_t doca_dpa_async_ops_get_user_data(struct doca_dpa_async_ops *async_ops, uint64_t *user_data)附加到 DPA 完成上下文
doca_error_t doca_dpa_async_ops_attach(struct doca_dpa_async_ops *async_ops, struct doca_dpa_completion *dpa_comp)
启动/停止 DPA 异步操作
doca_error_t doca_dpa_async_ops_start(struct doca_dpa_async_ops *async_ops) doca_error_t doca_dpa_async_ops_stop(struct doca_dpa_async_ops *async_ops)
获取 DPA 句柄
doca_error_t doca_dpa_async_ops_get_dpa_handle(struct doca_dpa_async_ops *async_ops, doca_dpa_dev_async_ops_t *handle)
对于可在线程内核中使用的以下设备 API,请使用输出参数
handle
。
示例(主机端伪代码)
doca_dpa_thread_create(&dpa_thread);
doca_dpa_thread_set_func_arg(dpa_thread);
doca_dpa_thread_start(dpa_thread);
doca_dpa_completion_create(&dpa_comp);
doca_dpa_completion_set_thread(dpa_comp, dpa_thread);
doca_dpa_completion_start(dpa_comp);
doca_dpa_thread_run(dpa_thread);
doca_dpa_async_ops_create(&async_ops);
doca_dpa_async_ops_attach(async_ops, dpa_comp);
doca_dpa_async_ops_start(async_ops);
doca_dpa_async_ops_get_dpa_handle(async_ops, &handle); // use this handle in relevant Async Ops device APIs
线程组
线程组用于将单个 DPA 线程聚合到单个组中。
创建/销毁 DPA 线程组
doca_error_t doca_dpa_thread_group_create(struct doca_dpa *dpa, unsigned
int
num_threads, struct doca_dpa_tg **tg) doca_error_t doca_dpa_thread_group_destroy(struct doca_dpa_tg *tg)获取线程数
doca_error_t doca_dpa_thread_group_get_num_threads(struct doca_dpa_tg *tg, unsigned
int
*num_threads);在 DPA 线程组的“rank”处设置 DPA 线程
doca_error_t doca_dpa_thread_group_set_thread(struct doca_dpa_tg *tg, struct doca_dpa_thread *thread, unsigned
int
rank)线程 rank 是组内线程的索引(介于 0 和(num_threads - 1)之间)。
启动/停止 DPA 线程组
doca_error_t doca_dpa_thread_group_start(struct doca_dpa_tg *tg) doca_error_t doca_dpa_thread_group_stop(struct doca_dpa_tg *tg)
内存子系统
用户可以(从主机 API)分配和(从主机和设备 API)访问多个内存位置,使用相关的 DOCA DPA API。
DOCA DPA 支持从主机/目标 BlueField 访问 DPA 堆内存,并允许设备访问主机内存(例如,内核写入主机内存)。
正常的内存使用流程将是:
分配内存(主机/目标 BlueField/DPA)。
注册内存。
获取已注册内存的 DPA 句柄,以便 DPA 内核可以访问它。
从内核访问/使用内存(请参阅相关的设备端 API)。
分配 DPA 堆内存
doca_dpa_mem_alloc(doca_dpa_t dpa, size_t size, doca_dpa_dev_uintptr_t *dev_ptr)
释放先前分配的 DPA 内存
doca_dpa_mem_free(doca_dpa_dev_uintptr_t dev_ptr)
将先前分配的内存从主机指针复制到 DPA 堆设备指针
doca_dpa_h2d_memcpy(doca_dpa_t dpa, doca_dpa_dev_uintptr_t src_ptr,
void
*dst_ptr, size_t size)将先前分配的内存从 DOCA 缓冲区复制到 DPA 堆设备指针
doca_error_t doca_dpa_h2d_buf_memcpy(struct doca_dpa *dpa, doca_dpa_dev_uintptr_t dst_ptr, struct doca_buf *buf, size_t size)
将先前分配的内存从 DPA 堆设备指针复制到主机指针
doca_dpa_d2h_memcpy(doca_dpa_t dpa,
void
*dst_ptr, doca_dpa_dev_uintptr_t src_ptr, size_t size)将先前分配的内存从 DPA 堆设备指针复制到 DOCA 缓冲区
doca_error_t doca_dpa_d2h_buf_memcpy(struct doca_dpa *dpa, struct doca_buf *buf, doca_dpa_dev_uintptr_t src_ptr, size_t size)
设置内存
doca_dpa_memset(doca_dpa_t dpa, doca_dpa_dev_uintptr_t dev_ptr,
int
value, size_t size)要获取在内核中使用的 DPA 句柄,用户必须按以下方式使用 DOCA 核心内存清单对象(请参阅“DOCA 内存子系统”):
当用户想要将设备 API 与 DOCA 缓冲区一起使用时,请使用以下伪代码:
doca_buf_arr_create(&buf_arr); doca_buf_arr_set_target_dpa(buf_arr, doca_dpa); doca_buf_arr_start(buf_arr); doca_buf_arr_get_dpa_handle(buf_arr, &handle);
在线程内核中的相关设备 API 中使用输出参数
handle
。当用户想要将设备 API 与 DOCA Mmap 一起使用时,请使用以下伪代码:
doca_mmap_create(&mmap); doca_mmap_set_dpa_memrange(mmap, doca_dpa, dev_ptr, dev_ptr_len);
// dev-ptr is a pre-allocated DPA memory
doca_mmap_start(mmap); doca_mmap_dev_get_dpa_handle(mmap, doca_dev, &handle);在线程内核中的相关设备 API 中使用输出参数
handle
。
扩展 DOCA DPA 上下文
基本 DOCA DPA 上下文是在 PF DOCA 设备上使用以下命令创建的上下文:
doca_dpa_create(pf_doca_dev, &base_dpa_ctx);
为了能够在 VF/SF DOCA 设备上创建 DPA 资源(例如,RDMA/DPA 完成/DPA 异步操作上下文),需要扩展 DOCA DPA 上下文。
DOCA DPA 提供了以下主机 API,用于将基本 DOCA DPA 上下文(在 PF DOCA 设备上创建)扩展到 SF/VF DOCA 设备:
doca_error_t doca_dpa_device_extend(struct doca_dpa *dpa, struct doca_dev *other_dev, struct doca_dpa **extended_dpa)
扩展的 DPA 上下文稍后可用于在另一个 DOCA 设备 (SF/VF) 上创建 DPA 资源(例如,作为 RDMA/DPA 完成/DPA 异步操作上下文)。
请注意:
扩展的 DPA 上下文已启动。
扩展的 DPA 上下文稍后可用于所有 DOCA DPA API(例如,创建 DPA 内存、DPA 完成上下文)或内核启动流程中。
当从 DPU 运行时,必须在 SF DOCA 设备上创建 DOCA RDMA 上下文(在 DPA 数据路径上)。因此,必须使用扩展的 DOCA DPA 上下文(在同一 SF DOCA 设备上创建)来创建它。
要获取 DOCA DPA 上下文(基本上下文或扩展上下文)的 DPA 句柄:
doca_error_t doca_dpa_get_dpa_handle(struct doca_dpa *dpa, doca_dpa_dev_t *handle)
在线程内核中的相关设备 API 中使用输出参数 handle
。
当使用扩展的 DOCA DPA 上下文创建 DOCA RDMA 上下文,并且该 RDMA 上下文附加到 DPA 完成上下文和 DPA 线程时,所有 DOCA RDMA、DPA 完成上下文和 DPA 线程都必须在同一扩展的 DOCA DPA 上下文中创建。
示例(主机端伪代码)
// create a base DPA context
doca_dpa_create(pf_doca_dev, &base_dpa_ctx);
// call base_dpa_ctx setters
doca_dpa_start(base_dpa_ctx);
// create an extended DPA context
doca_dpa_device_extend(base_dpa_ctx, sf_doca_dev, &extended_dpa_ctx);
doca_dpa_get_dpa_handle(extended_dpa_ctx, &extended_dpa_ctx_handle);
// create DPA thread on extended DPA context
doca_dpa_thread_create(extended_dpa_ctx, &dpa_thread);
// call dpa_thread setters
doca_dpa_thread_start(dpa_thread);
// create DPA completion context on extended DPA context
doca_dpa_completion_create(extended_dpa_ctx, &dpa_completion);
doca_dpa_completion_set_thread(dpa_completion, dpa_thread);
doca_dpa_completion_start(dpa_completion);
// create DOCA RDMA context on SF DOCA device and extended DPA context
doca_rdma_create(sf_doca_dev, &rdma);
doca_ctx_set_datapath_on_dpa(rdma_as_ctx, extended_dpa_ctx);
doca_rdma_dpa_completion_attach(rdma, dpa_completion);
// other rdma setters
doca_ctx_start(rdma_as_ctx);
doca_rdma_get_dpa_handle(rdma, rdma_dpa_handle);
数据结构
哈希表
DOCA DPA 提供 API 以在 DPA 上创建哈希表。此数据结构在 DPA 上使用相关的设备 API 进行管理。
在 DPA 上创建哈希表
doca_error_t doca_dpa_hash_table_create(struct doca_dpa *dpa, unsigned
int
num_entries, struct doca_dpa_hash_table **ht)销毁哈希表
doca_error_t doca_dpa_hash_table_destroy(struct doca_dpa_hash_table *ht)
获取 DPA 句柄
doca_error_t doca_dpa_hash_table_get_dpa_handle(struct doca_dpa_hash_table *ht, doca_dpa_dev_hash_table_t *handle)
在线程内核中的相关设备 API 中使用输出参数
handle
。
RPC
从主机应用程序到在 DPA 上执行内核的阻塞式一次性调用。
RPC 主要用于控制路径。
RPC 的返回值将报告回主机应用程序。
doca_error_t doca_dpa_rpc(struct doca_dpa *dpa, doca_dpa_func_t *func, uint64_t *retval, … /* func arguments */
)
示例
设备端 – DPA 设备
func
必须使用__dpa_rpc__
注释进行注释,例如:__dpa_rpc__ uint64_t hello_rpc(
int
arg) { ... }主机端:
extern doca_dpa_func_t hello_rpc; uint64_t retval; doca_dpa_rpc(dpa, &hello_rpc, &retval,
10
);
内核启动
DOCA DPA 提供了一个 API,该 API 能够完全控制内核的启动和监视。
由于 DOCA DPA 库不是线程安全的,因此程序员有责任确保内核的编写允许其在多线程环境中运行。例如,要编程一个使用 RDMA 和 16 个并发线程的内核,用户应将 16 个 RDMA 的数组传递给内核,以便每个线程都可以使用其 rank (doca_dpa_dev_thread_rank()
) 作为数组的索引来访问其 RDMA。
doca_dpa_kernel_launch_update_<add|set>(struct doca_dpa *dpa, struct doca_sync_event *wait_event, uint64_t wait_threshold, struct doca_sync_event *comp_event, uint64_t comp_count, unsigned int
num_threads, doca_dpa_func_t *func, ... /* args */
)
此函数要求 DOCA DPA 通过
num_threads
在 DPA 中运行func
,并为其提供提供的参数列表(可变参数列表)。此函数是异步的,因此当它返回时,并不意味着
func
已开始/结束其执行。为了向这些异步内核添加控制或流程/排序,内核启动提供了两个可选参数:
wait_event
– 内核在其事件被信号通知之前不会开始执行(如果为 NULL,则内核在 DOCA DPA 有可用于运行的 EU 时立即启动),这意味着 DOCA DPA 在事件的计数器大于wait_threshold
之前不会运行内核。注意请注意,
wait_threshold
和wait_event
计数器的有效值范围为 [0-254]。超出此范围的值可能会导致异常行为。comp_event
– 一旦运行内核的最后一个线程完成,DOCA DPA 就会更新此事件(使用comp_count
设置或添加到其当前计数器值)。
DOCA DPA 负责打包(在主机/目标 BlueField 上)和解包(在 DPA 中)内核参数。
func
必须以__dpa_global__
宏为前缀,以便 DPACC 将其编译为内核(并将其添加到 DPA 可执行二进制文件中),而不是作为主机应用程序二进制文件的一部分。程序员还必须通过添加行
extern doca_dpa_func_t func
在其应用程序中声明func
。
以下 API 仅与 kernel_launch
API 中使用的内核相关。这些 API 与 doca_dpa_thread
内核无关。
检索 DPA 上给定内核的正在运行的线程的 rank。例如,如果内核启动以使用 16 个线程运行,则运行此内核的每个线程都会在此内核中分配一个从 0 到 15 的 rank。这有助于确保内核中的每个线程仅访问与其执行相关的数据,以避免数据竞争。
unsigned
int
doca_dpa_dev_thread_rank()返回运行当前内核的线程数
unsigned
int
doca_dpa_dev_num_threads()让运行内核的线程让步
void
doca_dpa_dev_yield(void
)
示例
线性执行示例
设备端伪代码
#include "doca_dpa_dev.h"
#include "doca_dpa_dev_sync_event.h"
__dpa_global__ void
linear_kernel(doca_dpa_dev_sync_event_t wait_ev, doca_dpa_dev_sync_event_t comp_ev)
{
if
(wait_ev)
doca_dpa_dev_sync_event_wait_gt(wait_ev, wait_th = 0
);
doca_dpa_dev_sync_event_update_add(comp_ev, comp_count = 1
);
}
主机端伪代码
#include <doca_dev.h>
#include <doca_error.h>
#include <doca_sync_event.h>
#include <doca_dpa.h>
int
main(int
argc, char
**argv)
{
/*
A
|
B
|
C
*/
/* Open DOCA device */
open_doca_dev(&doca_dev);
/* Create doca dpa conext */
doca_dpa_create(doca_dev, dpa_linear_app, &dpa_ctx, 0
);
/* Create event A - subscriber is DPA and publisher is CPU */
doca_sync_event_create(&ev_a);
doca_sync_event_add_publisher_location_cpu(ev_a, doca_dev);
doca_sync_event_add_subscriber_location_dpa(ev_a, dpa_ctx);
doca_sync_event_start(ev_a);
/* Create event B - subscriber and publisher are DPA */
doca_sync_event_create(&ev_b);
doca_sync_event_add_publisher_location_dpa(ev_b, dpa_ctx);
doca_sync_event_add_subscriber_location_dpa(ev_b, dpa_ctx);
doca_sync_event_start(ev_b);
/* Create event C - subscriber and publisher are DPA */
doca_sync_event_create(&ev_c);
doca_sync_event_add_publisher_location_dpa(ev_c, dpa_ctx);
doca_sync_event_add_subscriber_location_dpa(ev_c, dpa_ctx);
doca_sync_event_start(ev_c);
/* Create completion event for last kernel - subscriber is CPU and publisher is DPA */
doca_sync_event_create(&comp_ev);
doca_sync_event_add_publisher_location_dpa(comp_ev, dpa_ctx);
doca_sync_event_add_subscriber_location_cpu(comp_ev, doca_dev);
doca_sync_event_start(comp_ev);
/* Export kernel events and acquire their handles */
doca_sync_event_get_dpa_handle(ev_b, dpa_ctx, &ev_b_handle);
doca_sync_event_get_dpa_handle(ev_c, dpa_ctx, &ev_c_handle);
doca_sync_event_get_dpa_handle(comp_ev, dpa_ctx, &comp_ev_handle);
/* Launch kernels */
doca_dpa_kernel_launch_update_add(wait_ev = ev_a, wait_threshold = 1
, num_threads = 1
, &linear_kernel, kernel_args: NULL, ev_b_handle);
doca_dpa_kernel_launch_update_add(wait_ev = NULL, num_threads = 1
, &linear_kernel, kernel_args: ev_b_handle, ev_c_handle);
doca_dpa_kernel_launch_update_add(wait_ev = NULL, &linear_kernel, num_threads = 1
, kernel_args: ev_c_handle, comp_ev_handle);
/* Update host event to trigger kernels to start executing in a linear manner */
doca_sync_event_update_set(ev_a, 1
)
/* Wait for completion of last kernel */
doca_sync_event_wait_gt(comp_ev, 0
);
/* Tear Down... */
teardown_resources();
}
菱形执行示例
设备端伪代码
#include "doca_dpa_dev.h"
#include "doca_dpa_dev_sync_event.h"
__dpa_global__ void
diamond_kernel(doca_dpa_dev_sync_event_t wait_ev, uint64_t wait_th, doca_dpa_dev_sync_event_t comp_ev1, doca_dpa_dev_sync_event_t comp_ev2)
{
if
(wait_ev)
doca_dpa_dev_sync_event_wait_gt(wait_ev, wait_th);
doca_dpa_dev_sync_event_update_add(comp_ev1, comp_count = 1
);
if
(comp_ev2) // can be 0 (NULL)
doca_dpa_dev_sync_event_update_add(comp_ev2, comp_count = 1
);
}
主机端伪代码
#include <doca_dev.h>
#include <doca_error.h>
#include <doca_sync_event.h>
#include <doca_dpa.h>
int
main(int
argc, char
**argv)
{
/*
A
/ \
C B
/ /
D /
\ /
E
*/
/* Open DOCA device */
open_doca_dev(&doca_dev);
/* Create doca dpa conext */
doca_dpa_create(doca_dev, dpa_diamond_app, &dpa_ctx, 0
);
/* Create root event A that will signal from the host the rest to start */
doca_sync_event_create(&ev_a);
// set publisher to CPU, subscriber to DPA and start event
/* Create events B,C,D,E */
doca_sync_event_create(&ev_b);
doca_sync_event_create(&ev_c);
doca_sync_event_create(&ev_d);
doca_sync_event_create(&ev_e);
// for events B,C,D,E, set publisher & subscriber to DPA and start event
/* Create completion event for last kernel */
doca_sync_event_create(&comp_ev);
// set publisher to DPA, subscriber to CPU and start event
/* Export kernel events and acquire their handles */
doca_sync_event_get_dpa_handle(&ev_b_handle, &ev_c_handle, &ev_d_handle, &ev_e_handle, &comp_ev_handle);
/* wait threshold for each kernel is the number of parent nodes */
constexpr uint64_t wait_threshold_one_parent {1
};
constexpr uint64_t wait_threshold_two_parent {2
};
/* launch diamond kernels */
doca_dpa_kernel_launch_update_set(wait_ev = ev_a, wait_threshold = 1
, num_threads = 1
, &diamond_kernel, kernel_args: NULL, 0
, ev_b_handle, ev_c_handle);
doca_dpa_kernel_launch_update_set(wait_ev = NULL, num_threads = 1
, &diamond_kernel, kernel_args: ev_b_handle, wait_threshold_one_parent, ev_e_handle, NULL);
doca_dpa_kernel_launch_update_set(wait_ev = NULL, num_threads = 1
, &diamond_kernel, kernel_args: ev_c_handle, wait_threshold_one_parent, ev_d_handle, NULL);
doca_dpa_kernel_launch_update_set(wait_ev = NULL, num_threads = 1
, &diamond_kernel, kernel_args: ev_d_handle, wait_threshold_one_parent, ev_e_handle, NULL);
doca_dpa_kernel_launch_update_set(wait_ev = NULL, num_threads = 1
, &diamond_kernel, kernel_args: ev_e_handle, wait_threshold_two_parent, comp_ev_handle, NULL);
/* Update host event to trigger kernels to start executing in a diamond manner */
doca_sync_event_update_set(ev_a, 1
);
/* Wait for completion of last kernel */
doca_sync_event_wait_gt(comp_ev, 0
);
/* Tear Down... */
teardown_resources();
}
性能优化
当主机应用程序重复调用
doca_dpa_kernel_launch_update_<add|set>()
以使用相同数量的 DPA 线程执行时,从主机调用内核启动到内核在 DPA 上开始执行之间的时间间隔会得到显著优化。因此,如果应用程序调用doca_dpa_kernel_launch_update_<add|set>(..., num_threads = x)
,则下一次使用num_threads = x
的调用将具有更短的延迟(低至约 5-7 微秒),以便内核开始执行。使用等待事件(即,先前内核的完成事件)调用内核启动的应用程序在主机启动内核和内核在 DPA 上开始执行之间的时间也具有显著更低的延迟。因此,如果应用程序调用
doca_dpa_kernel_launch_update_<add|set>( ..., completion event = m_ev, ...)
,然后调用doca_dpa_kernel_launch_update_<add|set>( wait event = m_ev, ...)
,则后一个内核启动调用将具有更短的延迟(低至约 3 微秒),以便内核开始执行。
限制
内核启动的顺序很重要。如果应用程序启动 K1,然后启动 K2,则 K1 不得依赖于 K2 的完成(例如,等待 K2 应更新的其等待事件)。
不遵循此准则会导致应用程序出现不可预测的结果(在运行时),并可能需要重新启动 DOCA DPA 上下文(即,销毁、重新初始化和重新运行工作负载)。
DPA 线程是实际的硬件资源,因此数量限制为 256 个(包括内部分配和用户作为内核启动 API 的一部分显式请求的分配)
DOCA DPA 不检查这些限制。应用程序有责任遵守此数量并跨不同的 DPA 上下文跟踪线程分配。
每个
doca_dpa_dev_rdma_t
消耗一个线程。
DPA 具有内部看门狗定时器,以确保线程不会无限期地阻止。内核执行时间必须是有限的,并且不超过
doca_dpa_get_kernel_max_run_time
返回的时间。doca_dpa_kernel_launch
调用中的num_threads
参数不能超过doca_dpa_get_max_threads_per_kernel
返回的允许运行内核的最大线程数。
日志记录和跟踪
DOCA DPA 提供一组调试 API,以允许诊断和排除设备上的任何问题,以及从正在运行的应用程序访问实时信息。
数据路径中的日志记录对应用程序的性能有显著影响。虽然库提供的跟踪器是高频率的,旨在防止对应用程序性能产生显著影响。
因此,建议在以下情况下使用:
控制路径中的日志记录
数据路径中的跟踪
用户可以控制日志/跟踪文件路径和设备日志详细程度。
设置/获取跟踪文件路径
doca_error_t doca_dpa_trace_file_set_path(struct doca_dpa *dpa,
const
char
*file_path) doca_error_t doca_dpa_trace_file_get_path(struct doca_dpa *dpa,char
*file_path, uint32_t *file_path_len);设置/获取日志文件路径
doca_error_t doca_dpa_log_file_set_path(struct doca_dpa *dpa,
const
char
*file_path) doca_error_t doca_dpa_log_file_get_path(struct doca_dpa *dpa,char
*file_path, uint32_t *file_path_len)设置/获取设备日志详细程度
doca_error_t doca_dpa_set_log_level(struct doca_dpa *dpa, doca_dpa_dev_log_level_t log_level) doca_error_t doca_dpa_get_log_level(struct doca_dpa *dpa, doca_dpa_dev_log_level_t *log_level)
错误处理
DPA 上下文可能会进入设备流程导致的错误状态。应用程序可以通过调用以下主机 API 来检查此错误状态:
doca_error_t doca_dpa_peek_at_last_error(const
struct doca_dpa *dpa)
如果发生致命错误核心转储和崩溃,则数据将写入文件路径 /tmp/doca_dpa_fatal
或 API doca_dpa_log_file_set_path()
设置的文件路径,后缀分别为 .PID.core
和 .PID.crash
,其中 PID 是进程 ID。写入文件的数据将包括崩溃时的内存快照,其中将包含有助于查明崩溃原因的信息(例如,程序的状态、变量值和调用堆栈)。
创建核心转储文件可以在 DPA 应用程序崩溃后完成。
此调用不会重置错误状态。
如果发生错误,DPA 上下文将进入致命状态,并且必须由用户销毁。
DOCA DPA 设备库
DOCA DPA 设备库为常用实用程序提供接口,例如:
管理 DPA 线程
管理 DPA 完成上下文
管理 DPA 哈希表
日志和跟踪
管理 DPA 同步事件
管理 DPA DOCA buf 和 mmap
DPA 设备库和头文件

DPA 线程
线程重启 API
DPA 线程可以使用以下设备 API 之一结束其运行:
重新调度 API
void
doca_dpa_dev_thread_reschedule(void
)DPA 线程仍处于活动状态
DPA 线程资源返回到 RTOS
可以再次触发 DPA 线程
完成 API
void
doca_dpa_dev_thread_finish(void
)DPA 线程标记为已完成
DPA 线程资源返回到 RTOS
无法再次触发 DPA 线程
获取 TLS
doca_dpa_dev_uintptr_t doca_dpa_dev_thread_get_local_storage(
void
)此函数返回先前使用主机 API
doca_dpa_thread_set_local_storage()
设置的 DPA 线程本地存储。
DPA 线程设备 API 不能在用户编写的内核中使用,该内核在 DOCA DPA 内核启动 API 中使用。
完成上下文
内核获取 doca_dpa_dev_completion_t
句柄并调用以下 API:
获取完成元素
int
doca_dpa_dev_get_completion(doca_dpa_dev_completion_t dpa_comp_handle, doca_dpa_dev_completion_element_t *comp_element)使用返回的
comp_element
使用以下 API 检索完成信息。获取完成元素类型
typedef
enum
{ DOCA_DPA_DEV_COMP_SEND =0x0
,/**< Send completion */
DOCA_DPA_DEV_COMP_RECV_RDMA_WRITE_IMM =0x1
,/**< Receive RDMA Write with Immediate completion */
DOCA_DPA_DEV_COMP_RECV_SEND =0x2
,/**< Receive Send completion */
DOCA_DPA_DEV_COMP_RECV_SEND_IMM =0x3
,/**< Receive Send with Immediate completion */
DOCA_DPA_DEV_COMP_SEND_ERR =0xD
,/**< Send Error completion */
DOCA_DPA_DEV_COMP_RECV_ERR =0xE
/**< Receive Error completion */
} doca_dpa_dev_completion_type_t; doca_dpa_dev_completion_type_t doca_dpa_dev_get_completion_type(doca_dpa_dev_completion_element_t comp_element)获取完成元素用户数据
uint32_t doca_dpa_dev_get_completion_user_data(doca_dpa_dev_completion_element_t comp_element)
此 API 返回用户数据,该用户数据:
先前在主机 API
doca_dpa_async_ops_create(..., user_data, ...)
中设置当 DPA 完成上下文附加到 DPA 异步操作时。
等效于
doca_error_t doca_rdma_connection_get_id(const struct doca_rdma_connection *rdma_connection, uint32_t *connection_id)
中的connection_id
当 DPA 完成上下文附加到 DOCA RDMA 上下文时。
获取完成元素立即数据
uint32_t doca_dpa_dev_get_completion_immediate(doca_dpa_dev_completion_element_t comp_element)
此 API 返回类型为以下类型的完成元素的立即数据:
DOCA_DPA_DEV_COMP_RECV_RDMA_WRITE_IMM
DOCA_DPA_DEV_COMP_RECV_SEND_IMM
确认已在 DPA 完成上下文中读取完成
void
doca_dpa_dev_completion_ack(doca_dpa_dev_completion_t dpa_comp_handle, uint64_t num_comp)此 API 释放完成上下文中已确认完成元素的资源。此确认允许接收新的
num_comp
完成。请求 DPA 完成上下文上的通知
void
doca_dpa_dev_completion_request_notification(doca_dpa_dev_completion_t dpa_comp_handle)此 API 允许请求 DPA 完成上下文上的新通知。如果未调用此函数,则 DPA 完成上下文不会在新到达的完成元素上收到通知。因此,新完成不会在 DPA 完成上下文中填充。
示例(设备端伪代码)
__dpa_global__ void
hello_kernel(uint64_t arg)
{
// User is expected to pass in some way the attached completion context handle "dpa_comp_handle" to kernel such as func_arg or a shared memory.
DOCA_DPA_DEV_LOG_INFO("Hello from kernel\n"
);
doca_dpa_dev_completion_element_t comp_element;
found = doca_dpa_dev_get_completion(dpa_comp_handle, &comp_element);
if
(found) {
comp_type = doca_dpa_dev_get_completion_type(comp_element);
// process the completion according to completion type...
// ack on 1 completion
doca_dpa_dev_completion_ack(dpa_comp_handle, 1
);
// enable getting more completions and triggering the thread
doca_dpa_dev_completion_request_notification(dpa_comp_handle);
}
// reschedule thread
doca_dpa_dev_thread_reschedule();
}
线程通知
内核获取 doca_dpa_dev_notification_completion_t
句柄并调用以下 API:
void
doca_dpa_dev_thread_notify(doca_dpa_dev_notification_completion_t comp_handle)
调用此 API 将触发附加的 DPA 线程(在主机端 API doca_dpa_notification_completion_create()
的 dpa_thread
参数中指定的线程)。
异步操作
内核获取 doca_dpa_dev_async_ops_t
句柄并调用下面列出的 API。
用户可以使用以下枚举来控制工作请求提交配置:
/**
* @brief DPA submit flag type
*/
__dpa_global__ enum
doca_dpa_dev_submit_flag {
DOCA_DPA_DEV_SUBMIT_FLAG_NONE = 0U,
DOCA_DPA_DEV_SUBMIT_FLAG_FLUSH = (1U << 0
), /**
* Use flag to inform related DPA context
* (such as RDMA or DPA Async ops) to flush related
* operation and previous operations to HW,
* otherwise the context may aggregate the operation
* and not flush it immediately
*/
DOCA_DPA_DEV_SUBMIT_FLAG_OPTIMIZE_REPORTS = (1U << 1
), /**
* Use flag to inform related DPA context that it may
* defer completion of the operation to a later time. If
* flag is not provided then a completion will be raised
* as soon as the operation is finished, and any
* preceding completions that were deferred will also be
* raised. Use this flag to optimize the amount of
* completion notifications it receives from HW when
* submitting a batch of operations, by receiving a
* single completion notification on the entire batch.
*/
};
此枚举可以在以下设备 API 的 flags
参数中使用:
使用
doca_buf
发布 memcpy 操作void
doca_dpa_dev_post_buf_memcpy(doca_dpa_dev_async_ops_t async_ops_handle, doca_dpa_dev_buf_t dst_buf_handle, doca_dpa_dev_buf_t src_buf_handle, uint32_t flags)此 API 在两个 DOCA 缓冲区之间复制数据。由
dst_buf_handle
指定的目标缓冲区将在内存复制完成后包含复制的数据。这是一个非阻塞例程。使用
doca_mmap
和显式地址发布 memcpy 操作void
doca_dpa_dev_post_memcpy(doca_dpa_dev_async_ops_t async_ops_handle, doca_dpa_dev_mmap_t dst_mmap_handle, uint64_t dst_addr, doca_dpa_dev_mmap_t src_mmap_handle, uint64_t src_addr, size_t length, uint32_t flags)此 API 在两个 DOCA Mmap 之间复制数据。由
dst_mmap_handle
指定的目标 DOCA Mmap (dst_addr
) 将在内存复制完成后包含由src_mmap_handle
、src_addr
和length
指定的源 DOCA Mmap 中的复制数据。这是一个非阻塞例程。信息使用此 API 进行 memcpy,而不是使用
doca_buf memcpy
API,以获得更好的性能。在 DOCA 同步事件上发布 wait greater 操作
void
doca_dpa_dev_sync_event_post_wait_gt(doca_dpa_dev_async_ops_t async_ops_handle, doca_dpa_dev_sync_event_t wait_se_handle, uint64_t value)此函数使用 DPA 异步操作在 DOCA 同步事件上发布 wait 操作,以获取 DPA 线程激活。当 DOCA 同步事件的值大于给定值时,将激活附加的线程。这是一个非阻塞例程。
注意有效值必须在 [0, 254] 范围内,并且可以为值在 [0, 254] 范围内的事件调用。无效值会导致异常行为。
在 DOCA 同步事件上发布 wait not equal 操作
void
doca_dpa_dev_sync_event_post_wait_ne(doca_dpa_dev_async_ops_t async_ops_handle, doca_dpa_dev_sync_event_t wait_se_handle, uint64_t value)此函数使用 DPA 异步操作在 DOCA 同步事件上发布 wait 操作,以获取 DPA 线程激活。当 DOCA 同步事件的值不等于给定值时,将激活附加的线程。这是一个非阻塞例程。
内存子系统
DOCA DPA SDK 提供的内存 API 都是异步的(即,非阻塞的)。
用户可以获取以下任一项:
预配置的 DOCA 缓冲区(先前使用
doca_buf_arr_set_params
配置)。未配置的 DOCA 缓冲区,并使用以下设备 setter 配置它们。
设备端 API 操作:
从 buf 数组句柄获取单个缓冲区句柄
doca_dpa_dev_buf_t doca_dpa_dev_buf_array_get_buf(doca_dpa_dev_buf_arr_t buf_arr,
const
uint64_t buf_idx)设置/获取缓冲区句柄指向的地址
void
doca_dpa_dev_buf_set_addr(doca_dpa_dev_buf_t buf, uintptr_t addr) uintptr_t doca_dpa_dev_buf_get_addr(doca_dpa_dev_buf_t buf)设置/获取缓冲区长度
void
doca_dpa_dev_buf_set_len(doca_dpa_dev_buf_t buf, size_t len) uint64_t doca_dpa_dev_buf_get_len(doca_dpa_dev_buf_t buf)设置/获取与缓冲区关联的 DOCA Mmap
void
doca_dpa_dev_buf_set_mmap(doca_dpa_dev_buf_t buf, doca_dpa_dev_mmap_t mmap) doca_dpa_dev_mmap_t doca_dpa_dev_buf_get_mmap(doca_dpa_dev_buf_t buf)获取指向使用 DOCA 缓冲区在主机上注册的外部内存的指针
doca_dpa_dev_uintptr_t doca_dpa_dev_buf_get_external_ptr(doca_dpa_dev_buf_t buf)
信息调用此 API 后,用户可以从 DPA 内核读取/写入返回指针指示的内存。
获取指向使用显式地址和 DOCA Mmap 在主机上注册的外部内存的指针
doca_dpa_dev_uintptr_t doca_dpa_dev_mmap_get_external_ptr(doca_dpa_dev_mmap_t mmap_handle, uint64_t addr)
信息调用此 API 后,用户可以从 DPA 内核读取/写入返回指针指示的内存。
同步事件
同步事件履行以下角色
DOCA DPA 执行模型是异步的,同步事件用于控制系统中运行的各种线程(允许顺序和依赖性)
DOCA DPA 支持远程同步事件,因此程序员能够通过 DOCA 同步事件调用远程节点
对于主机端 API,请参阅“DOCA 同步事件”。
获取当前事件值
doca_dpa_dev_sync_event_get(doca_dpa_dev_sync_event_t event, uint64_t *value)
添加到/设置为当前事件值
doca_dpa_dev_sync_event_update_<add|set>(doca_dpa_dev_sync_event_t event, uint64_t value)
等待直到事件大于阈值
doca_dpa_dev_sync_event_wait_gt(doca_dpa_dev_sync_event_t event, uint64_t value, uint64_t mask)
使用掩码对 DOCA 同步事件值应用按位与,以便与等待阈值进行比较。
扩展 DOCA DPA 上下文
要使用在扩展 DOCA DPA 上下文上创建的 DPA 资源,DOCA DPA 提供了以下设备 API
void
doca_dpa_dev_device_set(doca_dpa_dev_t dpa_handle)
在调用在扩展 DOCA DPA 上下文上创建的 DPA 资源的任何相关设备 API(例如,DPA RDMA)之前,必须调用此函数。
注意
当在此应用程序中仅创建 DPA 基本上下文而不创建扩展 DPA 上下文时,无需调用此 API
当在基本和扩展 DPA 上下文上都创建 DPA 资源时,在调用相关的 DPA 资源 API 之前,必须设置正确的 DPA 设备(和 DPA 上下文)
示例(设备端伪代码)
__dpa_global__ void
kernel(uint64_t thread_arg)
{
// parse completion...
// set extended device before executing RDMA operation
doca_dpa_dev_device_set(thread_arg->extended_dpa_ctx_handle);
doca_dpa_dev_rdma_post_send(rdma_dpa_handle);
...
}
数据结构
哈希表
此数据结构在 DPA 上使用以下设备 API 进行管理
向哈希表添加新条目
void
doca_dpa_dev_hash_table_add(doca_dpa_dev_hash_table_t ht_handle, uint32_t key, uint64_t value)注意当哈希表已满时添加新键会导致异常行为。
从哈希表中删除条目
void
doca_dpa_dev_hash_table_remove(doca_dpa_dev_hash_table_t ht_handle, uint32_t key)返回哈希表中指定键映射到的值
int
doca_dpa_dev_hash_table_find(doca_dpa_dev_hash_table_t ht_handle, uint32_t key, uint64_t *value)
日志记录和跟踪
记录到主机
typedef
enum
doca_dpa_dev_log_level { DOCA_DPA_DEV_LOG_LEVEL_DISABLE =10
,/**< Disable log messages */
DOCA_DPA_DEV_LOG_LEVEL_CRIT =20
,/**< Critical log level */
DOCA_DPA_DEV_LOG_LEVEL_ERROR =30
,/**< Error log level */
DOCA_DPA_DEV_LOG_LEVEL_WARNING =40
,/**< Warning log level */
DOCA_DPA_DEV_LOG_LEVEL_INFO =50
,/**< Info log level */
DOCA_DPA_DEV_LOG_LEVEL_DEBUG =60
,/**< Debug log level */
} doca_dpa_dev_log_level_t;void
doca_dpa_dev_log(doca_dpa_dev_log_level_t log_level,const
char
*format, ...)日志宏
DOCA_DPA_DEV_LOG_CRIT(...) DOCA_DPA_DEV_LOG_ERR(...) DOCA_DPA_DEV_LOG_WARN(...) DOCA_DPA_DEV_LOG_INFO(...) DOCA_DPA_DEV_LOG_DBG(...)
创建带有参数的跟踪消息条目
void
doca_dpa_dev_trace(uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4, uint64_t arg5)将跟踪消息缓冲区刷新到主机
void
doca_dpa_dev_trace_flush(void
)
程序大纲
编写 DPA 设备代码(即,内核或
.c
文件)。使用 DPACC 构建 DPA 程序(即,包含嵌入式设备可执行文件的主机库)。DPACC 的输入
来自步骤 1 的内核。
DOCA DPA 设备库。
使用主机编译器构建主机可执行文件。主机编译器的输入
DPA 程序。
用户主机应用程序
.c
/.cpp
文件。
运行主机可执行文件。

程序步骤
以下代码片段显示了一个基本的 DPA 代码,最终将“Hello World”打印到 stdout
。
这是通过以下方式实现的
一个 DPA 线程,它打印字符串并发出 DOCA 同步事件信号,以向主机应用程序指示完成
一个 DPA RPC,用于通知 DPA 线程
以下小节详细阐述了这些步骤。
准备内核代码
#include "doca_dpa_dev.h"
#include "doca_dpa_dev_sync_event.h"
__dpa_global__ void
hello_world_thread_kernel(uint64_t arg)
{
DOCA_DPA_DEV_LOG_INFO("Hello World From DPA Thread!\n"
);
doca_dpa_dev_sync_event_update_set(arg, 1
);
doca_dpa_dev_thread_finish();
}
__dpa_rpc__ uint64_t hello_world_thread_notify_rpc(doca_dpa_dev_notification_completion_t comp_handle)
{
DOCA_DPA_DEV_LOG_INFO("Notifying DPA Thread From RPC\n"
);
doca_dpa_dev_thread_notify(comp_handle);
return
0
;
}
准备主机应用程序代码
#include <stdio.h>
#include <unistd.h>
#include <doca_dev.h>
#include <doca_error.h>
#include <doca_sync_event.h>
#include <doca_dpa.h>
/**
* A struct that includes all needed info on registered kernels and is initialized during linkage by DPACC.
* Variable name should be the token passed to DPACC with --app-name parameter.
*/
extern struct doca_dpa_app *dpa_hello_world_app;
/**
* kernel declaration that the user must declare for each kernel and DPACC is responsible to initialize.
* Only then, user can use this variable in relevant host APIs
*/
doca_dpa_func_t hello_world_thread_kernel;
doca_dpa_func_t hello_world_thread_notify_rpc;
int
main(int
argc, char
**argv)
{
struct doca_dev *doca_dev = NULL;
struct doca_dpa *dpa_ctx = NULL;
struct doca_sync_event *cpu_se = NULL;
doca_dpa_dev_sync_event_t cpu_se_handle = 0
;
struct doca_dpa_thread *dpa_thread = NULL;
struct doca_dpa_notification_completion *notify_comp = NULL;
doca_dpa_dev_notification_completion_t notify_comp_handle = 0
;
uint64_t retval = 0
;
printf("\n----> Open DOCA Device\n"
);
/* Open appropriate DOCA device doca_dev */
printf("\n----> Initialize DOCA DPA Context\n"
);
doca_dpa_create(doca_dev, &dpa_ctx);
doca_dpa_set_app(dpa_ctx, dpa_hello_world_app);
doca_dpa_start(dpa_ctx);
printf("\n----> Initialize DOCA Sync Event\n"
);
doca_sync_event_create(&cpu_se);
doca_sync_event_add_publisher_location_dpa(cpu_se, dpa_ctx);
doca_sync_event_add_subscriber_location_cpu(cpu_se, doca_dev);
doca_sync_event_start(cpu_se);
doca_sync_event_get_dpa_handle(cpu_se, dpa_ctx, &cpu_se_handle);
printf("\n----> Initialize DOCA DPA Thread\n"
);
doca_dpa_thread_create(dpa_ctx, &dpa_thread);
doca_dpa_thread_set_func_arg(dpa_thread, &hello_world_thread_kernel, cpu_se_handle);
doca_dpa_thread_start(dpa_thread);
printf("\n----> Initialize DOCA DPA Notification Completion\n"
);
doca_dpa_notification_completion_create(dpa_ctx, dpa_thread, ¬ify_comp);
doca_dpa_notification_completion_start(notify_comp);
doca_dpa_notification_completion_get_dpa_handle(notify_comp, ¬ify_comp_handle);
printf("\n----> Run DOCA DPA Thread\n"
);
doca_dpa_thread_run(dpa_thread);
printf("\n----> Trigger DPA RPC\n"
);
doca_dpa_rpc(dpa_ctx, &hello_world_thread_notify_rpc, &retval, notify_comp_handle);
printf("\n----> Waiting For hello_world_thread_kernel To Finish\n"
);
doca_sync_event_wait_gt(cpu_se, 0
, 0xFFFFFFFFFFFFFFFF
);
printf("\n----> Destroy DOCA DPA Notification Completion\n"
);
doca_dpa_notification_completion_destroy(notify_comp);
printf("\n----> Destroy DOCA DPA Thread\n"
);
doca_dpa_thread_destroy(dpa_thread);
printf("\n----> Destroy DOCA DPA event\n"
);
doca_sync_event_destroy(cpu_se);
printf("\n----> Destroy DOCA DPA context\n"
);
doca_dpa_destroy(dpa_ctx);
printf("\n----> Destroy DOCA device\n"
);
doca_dev_close(doca_dev);
printf("\n----> DONE!\n"
);
return
0
;
}
构建 DPA 程序
/opt/mellanox/doca/tools/dpacc \
kernel.c \
-o dpa_program.a \
-hostcc=gcc \
-hostcc-options="-Wno-deprecated-declarations -Werror -Wall -Wextra -W"
\
--devicecc-options="-D__linux__ -Wno-deprecated-declarations -Werror -Wall -Wextra -W"
\
--app-name="dpa_hello_world_app"
\
-ldpa \
-I/opt/mellanox/doca/include/
构建主机应用程序
gcc hello_world.c -o hello_world \
dpa_program.a \
-I/opt/mellanox/doca/include/ \
-DDOCA_ALLOW_EXPERIMENTAL_API \
-L/opt/mellanox/doca/lib/x86_64-linux-gnu/ -ldoca_dpa -ldoca_common \
-L/opt/mellanox/flexio/lib/ -lflexio \
-lstdc++ -libverbs -lmlx5
执行
$ ./hello_world
----> Open DOCA Device
----> Initialize DOCA DPA Context
----> Initialize DOCA Sync Event
----> Initialize DOCA DPA Thread
----> Initialize DOCA DPA Notification Completion
----> Run DOCA DPA Thread
----> Trigger DPA RPC
/ 10
/[DOCA][DPA DEVICE][INF] Notifying DPA Thread From RPC
/ 8
/[DOCA][DPA DEVICE][INF] Hello World From DPA Thread!
----> Waiting For hello_world_thread_kernel To Finish
----> Destroy DOCA DPA Notification Completion
----> Destroy DOCA DPA Thread
----> Destroy DOCA DPA event
----> Destroy DOCA DPA context
----> Destroy DOCA device
----> DONE!
本节提供了基于 BlueField-3 网络平台的 DPA 示例实现。
本节中描述的所有 DOCA 示例均受 BSD-3 软件许可协议管辖。
DPA 示例可以从主机或 DPU 运行。
从 DPU 运行时并使用 RDMA 实用程序时,需要提供 RDMA DOCA 设备(SF 设备)作为参数(如果未提供,则将选择随机 RDMA 设备)。
在这种情况下,DOCA DPA 上下文在 PF 设备上创建,扩展 DPA 上下文在 SF 设备上创建,DOCA RDMA 上下文在 SF 设备上创建。
所有与 SF RDMA 实例关联的 DPA 资源,例如 DPA 线程/DPA 完成上下文/... 必须 使用 扩展 DPA 上下文 创建。
要运行 DPA 示例
请参阅以下文档
Linux 版 DOCA 安装指南,了解如何安装 BlueField 相关软件的详细信息。
DOCA 故障排除,解决您在 DOCA 示例的安装、编译或执行过程中可能遇到的任何问题。
要构建给定的示例
cd /opt/mellanox/doca/samples/doca_dpa/<sample_name> meson /tmp/build ninja -C /tmp/build
信息二进制文件
doca_<sample_name>
在/tmp/build/
下创建。示例(例如,
dpa_initiator_target
)用法Usage: doca_dpa_initiator_target [DOCA Flags] [Program Flags] DOCA Flags: -h, --help Print a help synopsis -v, --version Print program version information -l, --log-level Set the (numeric) log level
for
the program <10
=DISABLE,20
=CRITICAL,30
=ERROR,40
=WARNING,50
=INFO,60
=DEBUG,70
=TRACE> --sdk-log-level Set the SDK (numeric) log levelfor
the program <10
=DISABLE,20
=CRITICAL,30
=ERROR,40
=WARNING,50
=INFO,60
=DEBUG,70
=TRACE> -j, --json <path> Parse all command flags from an input json file Program Flags: -pf_dev, --pf-device <PF DOCA device name> PF device name that supports DPA (optional). If not provided then a random device will be chosen -rdma_dev, --rdma-device <RDMA DOCA device name> device name that supports RDMA (optional). If not provided then a random device will be chosen有关每个示例的更多信息,请使用
-h
选项/tmp/build/doca_<sample_name> -h
基本发起者目标
此示例说明了如何使用连接到 DOCA RDMA 的 DPA 完成上下文来触发 DPA 线程。
此示例由发起者和目标端点组成。
在发起者端点中,DOCA RDMA 使用 DPA RPC 执行 RDMA post send 操作。
在目标端点中,连接到 DPA 完成上下文的 DOCA RDMA 使用 DPA RPC 执行 RDMA post receive 操作。
post receive 操作完成时,将触发 DPA 线程,该线程打印完成信息并设置 DOCA 同步事件以释放等待该事件的主机应用程序,然后再销毁所有资源并完成。
示例逻辑包括
为发起者和目标端点分配 DOCA DPA 和 DOCA RDMA 资源。
目标 – 将 DOCA RDMA 连接到 DPA 完成上下文,该上下文连接到 DPA 线程。
运行 DPA 线程。
目标 – DPA RPC 执行 RDMA post receive 操作。
发起者 – DPA RPC 执行 RDMA post send 操作。
post receive 操作完成时,将触发 DPA 线程。
等待从 DPA 线程设置完成事件。
销毁所有资源。

参考
/opt/mellanox/doca/samples/doca_dpa/dpa_basic_initiator_target/dpa_basic_initiator_target_main.c
/opt/mellanox/doca/samples/doca_dpa/dpa_basic_initiator_target/host/dpa_basic_initiator_target_sample.c
/opt/mellanox/doca/samples/doca_dpa/dpa_basic_initiator_target/device/dpa_basic_initiator_target_kernels_dev.c
/opt/mellanox/doca/samples/doca_dpa/dpa_basic_initiator_target/meson.build
/opt/mellanox/doca/samples/doca_dpa/dpa_common.h
/opt/mellanox/doca/samples/doca_dpa/dpa_common.c
/opt/mellanox/doca/samples/doca_dpa/build_dpacc_samples.sh
高级发起者目标
此示例说明了如何使用 DPA 通知完成和 DPA 完成上下文(连接到具有 2 个连接的 DOCA RDMA 上下文)来触发 DPA 线程。
此示例由发起者和目标端点组成。
在发起者端点中,两个 DOCA RDMA 连接按以下顺序使用 DPA RPC 执行 RDMA post send 操作
连接 #0 对值为 1 的缓冲区执行 RDMA post send 操作。
连接 #1 对值为 2 的缓冲区执行 RDMA post send 操作。
连接 #0 对值为 3 的缓冲区执行 RDMA post send 操作。
连接 #1 对值为 4 的缓冲区执行 RDMA post send 操作。
在目标端点中,具有两个连接的 DOCA RDMA 上下文连接到单个 DPA 完成上下文,该上下文连接到 DPA 线程 #1。
目标 RDMA 使用 DPA RPC 执行初始 RDMA post receive 操作。
post receive 操作完成时,将触发 DPA 线程 #1,该线程
打印完成信息,包括用户数据。
使用接收缓冲区值更新本地数据库。
重新发布 RDMA receive 操作。
确认、请求完成并重新调度。
一旦目标 DPA 线程 #1 接收到所有预期值“1、2、3、4”,它将通知 DPA 线程 #2 并完成。
一旦 DPA 线程 #2 被触发,它将设置 DOCA 同步事件以释放等待该事件的主机应用程序,然后再销毁所有资源并完成。
示例逻辑包括
为发起者和目标端点分配 DOCA DPA 和 DOCA RDMA 资源。
目标:将 DOCA RDMA 连接到 DPA 完成上下文,该上下文连接到 DPA 线程 #1。
目标:将 DPA 通知完成连接到 DPA 线程 #2。
运行 DPA 线程。
目标:DPA RPC 执行初始 RDMA post receive 操作。
发起者:DPA RPC 执行所有 RDMA post send 操作。
post receive 操作完成时(4 次完成),将触发 DPA 线程 #1。
一旦收到所有预期值,DPA 线程 #1 将通知 DPA 线程 #2 并完成。
等待从 DPA 线程 #2 设置完成事件。
销毁所有资源。

参考
/opt/mellanox/doca/samples/doca_dpa/dpa_initiator_target/dpa_initiator_target_main.c
/opt/mellanox/doca/samples/doca_dpa/dpa_initiator_target/host/dpa_initiator_target_sample.c
/opt/mellanox/doca/samples/doca_dpa/dpa_initiator_target/device/dpa_initiator_target_kernels_dev.c
/opt/mellanox/doca/samples/doca_dpa/dpa_initiator_target/meson.build
/opt/mellanox/doca/samples/doca_dpa/dpa_common.h
/opt/mellanox/doca/samples/doca_dpa/dpa_common.c
/opt/mellanox/doca/samples/doca_dpa/build_dpacc_samples.sh
Ping Pong
此示例说明了以下 DPA 对象的功能
DPA 线程
DPA 完成上下文
DOCA RDMA
此示例由 ping 和 pong 端点组成,它们运行 100 次迭代。在每次迭代中,DPA 线程(ping 和 pong)为值 [0-99] 的数据缓冲区发布 RDMA receive 和 send 操作。
一旦每个 DPA 线程接收到所有预期值,它将设置一个 DOCA 同步事件以释放等待该事件的主机应用程序,然后再销毁所有资源并完成。
为了触发 DPA 线程,该示例使用 DPA RPC。
示例逻辑包括
分配 DOCA DPA 和 DOCA RDMA 资源。
将 DOCA RDMA 连接到 DPA 完成上下文,该上下文连接到 DPA 线程。
运行 DPA 线程。
DPA RPC 以触发 DPA 线程。
100 次 RDMA post receive 和 send 操作的 ping pong 迭代。
等待从 DPA 线程设置完成事件。
销毁所有资源。

参考
/opt/mellanox/doca/samples/doca_dpa/dpa_ping_pong/dpa_ping_pong_main.c
/opt/mellanox/doca/samples/doca_dpa/dpa_ping_pong/host/dpa_ping_pong_sample.c
/opt/mellanox/doca/samples/doca_dpa/dpa_ping_pong/device/dpa_ping_pong_kernels_dev.c
/opt/mellanox/doca/samples/doca_dpa/dpa_ping_pong/meson.build
/opt/mellanox/doca/samples/doca_dpa/dpa_common.h
/opt/mellanox/doca/samples/doca_dpa/dpa_common.c
/opt/mellanox/doca/samples/doca_dpa/build_dpacc_samples.sh
内核启动
此示例说明了如何启动带有等待和完成 DOCA 同步事件的 DOCA DPA 内核。
示例逻辑包括
分配 DOCA DPA 资源。
初始化 DOCA DPA 内核的等待和完成 DOCA 同步事件。
运行等待等待事件的
hello_world
DOCA DPA 内核。运行一个单独的线程来触发等待事件。
hello_world
DOCA DPA 内核打印“Hello from kernel”。等待内核的完成事件。
销毁事件和资源。
参考
/opt/mellanox/doca/samples/doca_dpa/dpa_wait_kernel_launch/dpa_wait_kernel_launch_main.c
/opt/mellanox/doca/samples/doca_dpa/dpa_wait_kernel_launch/host/dpa_wait_kernel_launch_sample.c
/opt/mellanox/doca/samples/doca_dpa/dpa_wait_kernel_launch/device/dpa_wait_kernel_launch_kernels_dev.c
/opt/mellanox/doca/samples/doca_dpa/dpa_wait_kernel_launch/meson.build
/opt/mellanox/doca/samples/doca_dpa/dpa_common.h
/opt/mellanox/doca/samples/doca_dpa/dpa_common.c
/opt/mellanox/doca/samples/doca_dpa/build_dpacc_samples.sh