4. Marvell DPU与SmartNIC架构
Marvell 的 DPU(Data Processing Unit)与 SmartNIC 并不是“把网卡做快一点”的产物,而是把一颗可编程的片上系统放到服务器与网络之间,让原本由主机 CPU 承担的报文解析、连接跟踪、加密、存储协议处理等任务下沉到网卡侧完成。理解其架构的关键,不是记住某个型号的规格,而是看清三条主线如何咬合:数据平面开发套件(DPDK/DPU SDK)提供软件运行环境,可编程流水线决定报文如何被分类与改写,存储与网络加速引擎负责把高频、定型的重活从通用核上卸载。
一、从主机视角看:DPU 是“带流水线的第二颗 CPU”
在传统 SmartNIC 中,报文路径基本固定:MAC 收包、DMA 到主机内存、由主机协议栈处理。Marvell DPU 的差异在于,报文进入网卡后先经过一条可编程流水线,由它决定这个包是直接硬件转发、交给片上 ARM 核处理、还是 DMA 给主机。主机看到的不再只是一张网卡,而是一个可以通过 PCIe 暴露块设备、网络设备、virtio 设备的复合端点。
因此,DPU 上通常存在三类执行资源:固定功能加速器(加解密、正则匹配、压缩、RAID/纠删码)、可编程流水线(P4/微码驱动的解析—匹配—动作)、以及通用 ARM 核集群(跑控制面、异常路径和 DPDK 应用)。三者不是并列关系,而是按“越靠前越便宜、越靠后越灵活”的成本梯度排列。
二、可编程流水线:报文在网卡内的“第一道分拣”
可编程流水线的核心是“解析—匹配—动作”三段式。以一条典型的存储前端流量为例:报文进入后,解析器按预定义或 P4 描述的头部序列提取以太网、IP、TCP、NVMe-oF 各层字段;匹配阶段用这些字段查流表,命中则执行对应动作,未命中则上送 ARM 核或主机。
这里有一个容易被忽略的工程细节:流表不是一张,而是分层的。 通常第一层做粗粒度分流(比如按目的 MAC 或 VNI 区分租户),第二层做五元组连接跟踪,第三层才做应用层识别。分层的好处是每层表项规模可控,且高优先级流可以绕过后续查表。在 Marvell 的实现中,这类流水线常以微码或 P4 编译后的表项形式加载,运行时通过 DPDK 的 rte_flow API 动态增删。
下面是一段示意性的 rte_flow 规则,用于把目的端口 4420(NVMe-oF)的 TCP 流量定向到存储加速引擎队列,而不是默认的主机队列:
struct rte_flow_attr attr = { .ingress = 1, .group = 2 };
struct rte_flow_item pattern[] = {
{ .type = RTE_FLOW_ITEM_TYPE_ETH },
{ .type = RTE_FLOW_ITEM_TYPE_IPV4 },
{ .type = RTE_FLOW_ITEM_TYPE_TCP,
.spec = &(struct rte_flow_item_tcp){
.hdr.dst_port = rte_cpu_to_be_16(4420) },
.mask = &(struct rte_flow_item_tcp){
.hdr.dst_port = 0xffff } },
{ .type = RTE_FLOW_ITEM_TYPE_END }
};
struct rte_flow_action actions[] = {
{ .type = RTE_FLOW_ACTION_TYPE_COUNT },
{ .type = RTE_FLOW_ACTION_TYPE_QUEUE,
.conf = &(struct rte_flow_action_queue){ .index = 3 } },
{ .type = RTE_FLOW_ACTION_TYPE_END }
};
rte_flow_create(port_id, &attr, pattern, actions, &error);
这段代码的含义是:在 group 2 的入向流水线上,匹配目的端口 4420 的 TCP 报文,计数后送入队列 3。队列 3 在 DPU 上被绑定给存储加速引擎的轮询线程,而不是主机 virtio 队列。实际部署时,group 编号、队列映射和掩码需要与固件加载的流水线布局一致,否则规则会创建失败并返回 EINVAL。
三、存储与网络加速引擎如何“共用一份状态”
如果网络加速和存储加速各自维护一套连接表,就会出现状态不一致:网络侧认为某条 TCP 连接已建立,存储侧却查不到对应的 NVMe-oF 会话。Marvell DPU 的做法是让两类引擎共享同一份连接状态表和报文缓冲区池,网络引擎完成隧道解封装后,把报文描述符直接传给存储引擎,后者在同一缓冲区上继续做 NVMe 命令解析,避免二次拷贝。
这种协同在参数上体现为几个可观测点:队列深度、缓冲区池大小、流表老化时间。例如,当 NVMe-oF 前端并发连接数上升时,若流表老化时间设得过短(如 30 秒),已建立的会话可能被提前回收,表现为间歇性 I/O 超时;若缓冲区池过小,则在高吞吐下出现 mbuf 分配失败,DPDK 会打印 “Failed to allocate mbuf” 并丢包。合理的起点是:流表老化时间不低于 300 秒,缓冲区池按“最大并发连接数 × 每连接在途报文数 × 1.5”估算。
四、开发套件在其中的位置
DPDK 在 DPU 上不是“可选项”,而是把上述硬件能力暴露给软件的统一接口。它提供三样关键东西:轮询模式驱动(避免中断开销)、内存池与无锁环(让 ARM 核与加速引擎安全共享缓冲区)、以及 rte_flow(让软件能动态编排流水线)。没有 DPDK,可编程流水线就只能靠固件静态配置,无法随业务变化调整。
实际开发时,典型的启动顺序是:先由固件加载基础流水线,再由 DPDK 应用通过 rte_flow 下发业务规则,最后启动轮询线程处理异常路径。这个顺序不能颠倒,因为规则依赖流水线中已存在的表和 group。若在流水线未就绪时下发规则,会得到 “No such group” 或 “Table not found” 类错误。
把这三层放在一起看,Marvell DPU 的架构逻辑就清晰了:可编程流水线负责“快速判断这个包该谁处理”,存储与网络加速引擎负责“把定型的重活干完”,DPDK 负责“让软件能指挥前两者并处理剩下的灵活部分”。三者协同的目标只有一个——让主机 CPU 只处理真正需要应用逻辑的流量。