1. DOCA DPU网络开发与卸载工程导论
在传统服务器里,网络功能几乎全部由 CPU 承担:数据包到达网卡后,通过 PCIe 写入主机内存,再由内核网络栈逐层解析、转发或交给应用。随着 25G、100G 甚至 400G 网卡普及,以及虚拟交换、容器网络、加密隧道、存储协议叠加,CPU 花在“搬包、查表、加解密、封装解封装”上的时间越来越多,真正留给业务逻辑的算力被不断挤压。DPU(Data Processing Unit,数据处理单元)正是为缓解这一矛盾而出现的专用处理器,它把网络、存储、安全等基础设施任务从主机 CPU 卸载到独立的片上系统上执行。
理解 DPU 的定位,关键是把它放回“主机—DPU—网络”这条链路中看。DPU 通常以 PCIe 卡或板载芯片形式存在,一侧通过 PCIe 与主机 CPU、内存相连,另一侧通过以太网 MAC/PHY 连接外部网络。它内部集成了多核 ARM 处理器、可编程数据包处理引擎、硬件加速器(加解密、正则匹配、压缩、DMA 等)以及高速网络接口。主机看到的是一个或若干 PCIe 功能设备,而 DPU 自身可以运行独立的操作系统与网络协议栈。这样,主机 CPU 可以把“转发、过滤、加密、限速、遥测采集”等任务交给 DPU,自己专注于应用。
DOCA(Data Center Infrastructure-on-a-Chip Architecture)是 NVIDIA 为 BlueField 系列 DPU 提供的软件开发框架。它并不是单一库,而是一套分层软件栈:最底层是 DPU 的固件与驱动,向上提供 DOCA Core(设备发现、内存管理、任务调度、进度引擎等公共能力);再向上是各类 DOCA 库,例如 DOCA Flow 用于可编程报文转发流水线,DOCA Eth 用于网络接口管理,DOCA RDMA 用于远程直接内存访问,DOCA Crypto 用于加解密卸载,DOCA Telemetry 用于遥测数据采集,DOCA App Shield 用于主机侧安全监控等。开发者既可以用 C/C++ 调用这些库,也可以通过 DOCA 提供的服务容器、gRPC 接口或与 Kubernetes、OVS、DPDK 等生态集成来使用。
从工程价值看,网络卸载带来的收益主要体现在三个维度。第一是性能:把报文转发、加解密、封装解封装放到 DPU 的硬件流水线上,可以显著降低主机 CPU 占用,同时获得更稳定的低时延与高吞吐。第二是隔离:DPU 上运行的基础设施任务与主机业务相互独立,即使主机侧出现异常,网络与安全策略仍可继续执行。第三是可编程性:DOCA Flow 允许开发者用表项和动作描述转发逻辑,无需修改硬件即可适配新的协议或策略,兼顾了性能与灵活性。
典型应用场景可以归纳为几类。在云原生环境中,DPU 常被用来卸载容器网络的 VXLAN/Geneve 封装、服务负载均衡和网络策略执行,使节点上的 CPU 可以更多地分配给业务容器。在分布式存储中,DPU 承担 NVMe-oF 的发起端或目标端处理、RDMA 传输和校验计算,降低存储访问对主机 CPU 的依赖。在安全领域,DPU 可以卸载 IPsec、TLS、防火墙规则匹配和 DDoS 防护,把安全策略下沉到网卡侧执行。在边缘与 5G 场景中,DPU 可用于 UPF 用户面转发、时间同步和流量整形。这些场景的共同点是:任务规则明确、数据量大、对时延敏感,且适合用硬件或专用核并行处理。
下面给出一个最小可运行的 DOCA 程序骨架,用于观察 DPU 设备是否被正确识别。示例假设开发环境已安装 DOCA SDK,并且主机上存在一块 BlueField DPU。
#include <doca_error.h>
#include <doca_common.h>
#include <stdio.h>
int main(void) {
doca_error_t rc = doca_init();
if (rc != DOCA_SUCCESS) {
printf("doca_init failed: %s\n", doca_error_get_descr(rc));
return 1;
}
/* 枚举系统中的 DOCA 设备 */
struct doca_devinfo **list = NULL;
uint32_t count = 0;
rc = doca_devinfo_list_create(&list, &count);
if (rc != DOCA_SUCCESS) {
printf("list_create failed: %s\n", doca_error_get_descr(rc));
doca_shutdown();
return 1;
}
printf("found %u DOCA device(s)\n", count);
for (uint32_t i = 0; i < count; i++) {
const char *name = NULL;
doca_devinfo_get_name(list[i], &name);
printf(" [%u] %s\n", i, name ? name : "(unknown)");
}
doca_devinfo_list_destroy(list);
doca_shutdown();
return 0;
}
编译时通常需要链接 DOCA 公共库,例如使用 gcc demo.c -o demo -ldoca_common。运行后若输出类似 found 1 DOCA device(s) 以及设备名称,说明 DOCA 运行时已经能够发现 DPU。若返回 DOCA_ERROR_NOT_FOUND,则应检查驱动是否加载、PCIe 设备是否被主机识别、容器是否透传了设备节点。这个例子虽然简单,却覆盖了后续所有 DOCA 程序的基本模式:初始化、枚举设备、打开设备、创建上下文、提交任务、销毁资源。
lspci | grep -i mellanox 或 lspci | grep -i bluefield),再运行上面的枚举程序。只有设备发现正常,后续的 Flow、RDMA、Crypto 示例才有意义。需要强调的是,DPU 并不是要取代 CPU,而是与 CPU 形成分工。主机 CPU 仍然负责业务逻辑、控制面和复杂状态管理;DPU 负责高吞吐、规则化、可并行的基础设施数据面任务。DOCA 的价值在于把这种分工变成可编程、可移植的工程接口,使开发者不必直接操作底层固件或寄存器,就能把网络功能卸载到 DPU 上。理解这条分工边界,是后续学习 DOCA Flow、DOCA RDMA、DOCA Crypto 等具体模块的前提。