1. DOCA数据面开发总览
DOCA(Data Center Infrastructure on a Chip Architecture)是NVIDIA为BlueField系列DPU提供的软件开发框架。要理解DOCA数据面开发,首先要建立一个基本认知:BlueField DPU不是一块普通的网卡,而是一台"嵌入在PCIe卡上的小型服务器"。它内部集成了Arm多核CPU、专用网络加速硬件、可编程数据包处理引擎以及独立的内存子系统。DOCA的价值,就是把这套异构硬件的能力抽象成统一的编程接口,让开发者既能用Arm核跑控制逻辑,又能把高吞吐的数据处理卸载到硬件加速器上。
数据面与控制面的分工
在BlueField DPU上,数据面(Data Plane)和控制面(Control Plane)运行在不同的执行环境中,这是DOCA开发最核心的架构前提。
控制面运行在DPU的Arm CPU上,通常以Linux用户态进程或内核模块的形式存在。它负责策略下发、连接管理、路由决策、配置更新等"低频但需要灵活性"的工作。控制面代码可以用标准C/C++编写,链接DOCA的库,通过DOCA提供的API去配置硬件加速资源。
数据面则运行在硬件加速引擎上,包括网络流水线、正则表达式引擎、压缩/解压引擎、加解密引擎、DMA引擎等。数据面处理的是每一个到达的数据包或每一段需要搬运的数据,要求极低延迟和极高吞吐。开发者通过DOCA的编程模型,把数据面的处理逻辑"描述"给硬件,而不是在CPU上逐包处理。
这种分工带来的直接好处是:控制面可以慢、可以复杂、可以频繁修改;数据面必须快、必须简单、必须稳定。DOCA的API设计正是围绕这一分工展开的——控制面API负责创建和配置数据面对象,数据面API负责在硬件上执行实际的数据处理。
DOCA SDK的组成
DOCA SDK并不是一个单一库,而是一组按功能划分的库和工具的集合。从数据面开发的角度,可以把它分成三个层次来理解。
最底层是DOCA Core,它提供设备抽象、内存管理、事件通知、进度引擎(Progress Engine)等基础设施。所有其他DOCA库都构建在Core之上。开发者需要理解Core中的几个关键概念:doca_dev代表一个DPU设备,doca_mmap代表一段可以被硬件直接访问的内存区域,doca_pe是事件轮询引擎,doca_ctx是各种加速功能的执行上下文。
中间层是各类功能库,按数据面处理类型划分:DOCA Flow用于可编程网络流水线,支持在硬件中匹配和转发数据包;DOCA Compress和DOCA Crypto分别提供压缩和加解密卸载;DOCA DMA用于主机与DPU之间的高效内存搬运;DOCA RDMA提供远程直接内存访问能力;DOCA Regex用于正则表达式匹配加速。每个库都遵循"创建上下文→配置参数→提交任务→轮询完成"的异步编程模式。
最上层是应用与工具,包括DOCA提供的参考应用、命令行工具(如doca_caps用于查询设备能力)以及容器化部署支持。DOCA应用通常以容器形式部署在DPU的Arm侧,通过doca_container工具管理生命周期。
开发环境搭建
DOCA开发环境有两种典型搭建方式:直接在BlueField DPU的Arm侧编译运行,或在x86主机上交叉编译后部署到DPU。对于初学者,建议先在DPU的Arm侧直接开发,减少工具链配置的复杂度。
在DPU上,首先确认DOCA已安装:
# 查看DOCA版本
doca_version
# 查询DPU设备能力
doca_caps --list-devs
# 典型输出示例(示例值,实际以设备为准)
# Device: mlx5_0
# DOCA Flow: supported
# DOCA Compress: supported
# DOCA DMA: supported
编译一个DOCA程序需要链接对应的库。以DOCA Flow为例,编译命令通常形如:
gcc -o my_flow_app my_flow_app.c \
-I/opt/mellanox/doca/include \
-L/opt/mellanox/doca/lib \
-ldoca_flow -ldoca_common -ldoca_core
运行前需要确保DPU的固件版本与DOCA版本匹配,并且相关内核模块已加载。可以用doca_info查看当前环境状态。
第一个DOCA程序:查询设备并创建上下文
下面是一个最小化的DOCA程序示例,它完成三件事:打开DPU设备、查询设备能力、创建一个DOCA Core上下文。这个程序不涉及具体的数据面处理,但它是所有DOCA应用的基础骨架。
#include <doca_common.h>
#include <stdio.h>
int main(void) {
struct doca_devinfo *devinfo;
struct doca_dev *dev = NULL;
struct doca_ctx *ctx = NULL;
doca_error_t rc;
/* 1. 获取第一个可用的DPU设备信息 */
devinfo = doca_devinfo_get_first();
if (devinfo == NULL) {
printf("未找到DOCA设备\n");
return 1;
}
/* 2. 打开设备 */
rc = doca_dev_open(devinfo, &dev);
if (rc != DOCA_SUCCESS) {
printf("打开设备失败: %s\n", doca_error_get_descr(rc));
return 1;
}
printf("设备打开成功\n");
/* 3. 查询设备能力(示例:检查是否支持Flow) */
if (doca_devinfo_is_cap_supported(devinfo, DOCA_DEV_CAP_FLOW)) {
printf("该设备支持 DOCA Flow\n");
} else {
printf("该设备不支持 DOCA Flow\n");
}
/* 4. 清理 */
doca_dev_close(dev);
return 0;
}
编译并运行这个程序,如果输出"设备打开成功"和"该设备支持 DOCA Flow",说明开发环境基本可用。如果打开设备失败,常见原因包括:没有以root权限运行、DPU固件未正确加载、或DOCA驱动未安装。可以用dmesg | grep mlx5查看内核日志中的驱动加载信息。
实践建议:在开始编写数据面逻辑之前,先用doca_caps确认目标DPU支持哪些加速功能。不同型号的BlueField(如BF2、BF3)支持的DOCA库集合不同,提前确认可以避免在编译或运行阶段才发现功能不可用。
从控制面到数据面的典型调用链
一个完整的DOCA数据面应用通常遵循这样的调用链:Arm侧的控制面程序先通过DOCA Core打开设备、创建doca_ctx;然后调用具体功能库(如DOCA Flow)的API,把匹配规则和转发动作下发到硬件流水线;硬件流水线开始对经过DPU的网络流量执行匹配和转发;控制面程序通过进度引擎轮询完成事件,并根据需要更新规则或销毁上下文。
这个调用链的关键在于:控制面的每一次API调用,最终都转化为对硬件寄存器的配置或对硬件队列的提交。开发者不需要直接操作寄存器,但需要理解"API调用是异步的"——提交任务后,必须通过进度引擎等待完成事件,不能假设任务立即生效。