9. DPDK:硬件加速与功能卸载

140 阅读21分钟

1. 硬件卸载简介

1.1 网络系统为什么需要硬件卸载?

过去多年中,网络栈的处理主要依赖 CPU: 解析协议、计算校验和、分片、封装隧道、加解密……
当网卡还是 10Mbps、100Mbps 时,CPU 轻松应付这些处理任务。

但时代变了:

  • 网卡从 10Mbps → 1Gbps → 10Gbps → 25Gbps → 100Gbps
  • 数据中心流量呈指数级上涨
  • 每台服务器承担的业务复杂度不断提升

结果就是: 如果所有包处理都依靠 CPU,CPU 会被耗死。

随着半导体技术发展,网卡本身也发生了质变: 它不再是一个“简单的帧收发器”,而是逐渐具备类似独立处理器的能力。

今天的高端网卡已经能做到:

  • 线速 checksum offload
  • TSO/GSO 分片
  • VXLAN/GRE/Geneve 封装/解封
  • 加解密 offload
  • 流表匹配(Flow Director)
  • 大规模多队列 RSS
  • 隧道解析和报文重组
  • FPGA/ASIC/SmartNIC 程序化 pipeline

换句话说:

网卡已经从“网线接口”变成了“小型硬件数据面”。


1.2 硬件卸载发展的脉络:从“能算 checksum”到“小型可编程交换机”

要理解硬件卸载的发展,非常建议看 Intel 几代网卡的能力演进。发展趋势呈现两个特点:

① 由简单到复杂

最早一代的卸载能力很单一,比如:

  • L3/L4 checksum offload
  • VLAN tag 插入/去除
  • 基础的 LRO/TSO

后来开始支持:

  • 多队列 + RSS
  • Flow Director
  • 隧道 offload
  • 内置 crypto 引擎
  • Representor port(虚拟交换机能力)

今天的新 NIC 甚至开始支持:

  • P4 级别的可编程 match-action pipeline
  • 动态流表(类似 switch ASIC)
  • 完整的数据平面 offload

硬件越来越强,软件卸载的空间越来越小。


1.3 专有网络设备早就实现了软硬分离:数据面 vs 控制面

虽然数据中心服务器网卡是在最近 10 年才飞速增强,但在传统网络设备(路由器、防火墙、交换机)领域,“硬件卸载”是老祖宗级别的技术。

在路由器、交换机里:

  • 控制面(Control Plane)
    跑 BGP、OSPF、策略调整、复杂指令,依赖 CPU。
  • 数据面(Data Plane)
    做查表、修改头部、队列调度、统计计数,这些功能全由 ASIC/NP 芯片以硬件执行。

数据面硬件的经典任务:

  • 查路由表 / ACL / QoS 队列
  • 计算校验和
  • 修改 Ethernet/IP header
  • 隧道封装/解封
  • 加解密
  • 高速 buffer/队列调度

这些功能在服务器领域也越来越需要,因此 NIC 承担了类似角色:

服务器网卡 =“微型网络 ASIC”化的趋势越来越明显。


1.4 为什么 DPDK 特别强调硬件卸载?

DPDK 提供高性能用户态网卡驱动,它的最大卖点就是:

  • 绕开内核
  • 深度利用 NIC 硬件能力

其核心思想不是让 CPU 工作更快,而是让:

  • 能给硬件做的,通通交给硬件
  • CPU 只负责价值最高的那部分逻辑

DPDK API 底层有一大部分就是在“启用/控制硬件 offload 功能”, 没用好硬件 offload 的 DPDK 程序,性能上不去完全是正常的。


1.5 硬件加速有什么限制?

硬件的能力强,但它也有天然缺陷:

① 功能固化

芯片一旦 tape-out,就无法轻易修改;
你能用的能力完全依赖芯片厂商当初设计了什么。

② 更新速度慢

软件可以一天一更新;
硬件可能 2~3 年才迭代一代。

③ 资源有限

硬件流表、加解密单元、队列数量都有限,不可能无限扩展。

④ 调试能力弱

硬件 pipeline 很难像软件一样用 log、debug 调试。

硬件适合做高吞吐、低延迟、重复性、结构清晰的逻辑;
软件适合做复杂、多变、策略性强的逻辑。

这就是为什么 DPDK 大量 API 都在“展示硬件能做什么”,而不是让你用 CPU 全部完成。

2 网卡硬件卸载功能

DPDK 所支持的网卡型号已经非常丰富,本节不会对所有型号逐一列举,而是选择几种在业界具有代表性的产品,从而勾勒网卡硬件卸载能力的演进路线。Intel 的 i350、82599、X550、XL710 构成了一条传统以太网卡从 1Gb 到 40Gb 的典型发展轴线,而近年来在高性能计算(HPC)、云数据中心及算力集群中广泛使用的 NVIDIA Mellanox ConnectX 系列,则代表了更先进的可编程数据面方向。本节将加入目前主流的 ConnectX-7(简称 CX7) ,以形成更完整的对比视图。

选取的五款网卡覆盖了从千兆到 400Gb 的多个代际,功能也从基础的校验和卸载逐步扩展到可编程流表、虚拟化加速以及 RDMA 等更高级特性。通过对比这些硬件,读者可以整体理解网卡卸载功能如何随着硬件升级而不断丰富,也能够看到不同市场定位的网卡,其功能侧重点如何不同。

本节选取的几款典型网卡如下:

  • i350:Intel 面向传统企业市场的 1Gb 网卡,硬件卸载能力较为基础。
  • 82599:最经典的 10Gb Intel 网卡,DPDK 社区广泛使用,支持较完整的 L3/L4 卸载能力与多队列接收。
  • X550:82599 的增强版,仍为 10Gb,但在虚拟化与隧道卸载方面有所提升。
  • XL710:Intel 40Gb 网卡,支持更丰富的流分类、隧道卸载与大规模队列。
  • ConnectX-7(CX7) :NVIDIA/Mellanox 面向 HPC、云和 AI 集群的 100/200/400Gb 以太网卡,支持可编程流表、RDMA、隧道加速及 DOCA Flow,是当前最具代表性的智能网卡架构之一。
功能类别i35082599X550XL710CX7
基本 L3/L4 校验和卸载✓(更完整 Offload Pipeline)
TSO/LRO/GROTSOTSOTSOTSO/GROTSO、LRO、GRO(硬件+软件联合)
多队列 RSS基本完整增强8 字节哈希大规模可编程 RSS
隧道协议卸载(VXLAN/GRE/GENEVE)基础✓(成熟)✓(支持多层隧道)
隧道内 TSO/校验和
硬件流分类(Flow Director)有限✓(丰富)可编程流表(RQT/FT)
SR-IOV/虚拟化有限完整完整完整增强 SR-IOV,支持 vDPA/virtio 加速
RDMA(RoCE)✓(RoCEv2)
加密卸载(IPsec/TLS)有限✓(inline crypto)
DPDK 驱动支持i40e/e1000ixgbeixgbei40emlx5(DPDK 最佳性能路径)
数据速率1Gb10Gb10Gb10/40Gb100/200/400Gb

通过表可以看到,硬件卸载功能在不同代际之间具有明显的继承关系:传统功能如校验和、TSO/RSS 会在不同型号间延续,而新的功能则会随着硬件架构的演进不断引入。例如,从 XL710 开始,Intel 网卡在隧道卸载、流分类方面变得更加成熟;而从 ConnectX-7 所代表的 Mellanox 系列开始,网卡功能已经不局限于“卸载”,而是进一步走向“可编程数据面加速”,包括可编程流表、RDMA、虚拟化加速、加密卸载以及适配 DOCA 的可编程网络框架等。

传统 Intel 网卡展示了卸载功能从无到有、从小到大的演进;而 Mellanox CX7 则展示了当代高性能网络设备的方向:面向更高带宽、更强可编程性和更多应用层加速场景。

3. DPDK 软件接口:搞懂 ol_flags,就能搞懂硬件卸载

端口 offload 决定网卡“能不能做”,ol_flags 决定网卡“做了没 / 要不要做”。

DPDK 的硬件卸载看似一堆 flag,其实背后逻辑非常清晰,只要把握 两层含义 + 两类 flag,就能在自己的程序里非常精准地控制网卡做哪些事情、跳过哪些事情。

这一节我们就把这一套机制讲清楚,以后看到 ol_flags 不再头大。


3.1 DPDK 里,硬件卸载只有 2 类:端口级 VS 包级

DPDK 把硬件卸载拆成两层:

① 端口级卸载(port-level offload)

初始化网口时告诉网卡:“这些能力你要开启”。

例如:

port_conf.rxmode.offloads |= RTE_ETH_RX_OFFLOAD_RSS_HASH;

像是在设置系统能力开关


② 包级卸载(mbuf-level offload)

操作 mbuf->ol_flags,告诉网卡某个包需要什么特殊处理。

例如:

mbuf->ol_flags |= RTE_MBUF_F_TX_TCP_CKSUM;

像是在给当前这个包贴任务标签。


📌 核心理解:端口 offload 是“启用功能”,mbuf ol_flags 是“使用功能”。

非常关键。


3.2 接收侧(RX):ol_flags 告诉你“网卡做了什么”

当你收到一个包,驱动会在 mbuf->ol_flags 填写状态。


RX 常用卸载标志

标志位含义你能拿来干什么
RTE_MBUF_F_RX_IP_CKSUM_GOODIP 校验 OK可以放心用
RTE_MBUF_F_RX_IP_CKSUM_BADIP 校验错误直接丢
RTE_MBUF_F_RX_L4_CKSUM_GOODTCP/UDP 校验 OK可加速处理
RTE_MBUF_F_RX_L4_CKSUM_BADL4 校验错误丢包
RTE_MBUF_F_RX_VLANVLAN tag 已自动 stripVLAN 信息在 vlan_tci
RTE_MBUF_F_RX_RSS_HASHRSS hash 可用多队列调度关键字段

实际代码示例:

if (m->ol_flags & RTE_MBUF_F_RX_IP_CKSUM_BAD) {
    rte_pktmbuf_free(m);
    continue;
}

if (m->ol_flags & RTE_MBUF_F_RX_RSS_HASH) {
    uint32_t hash = m->hash.rss;
}

3.3 发送侧(TX):ol_flags 是给网卡的“工作单”

相比接收,发送更主动,需要你告诉网卡“这个包需要你帮我处理”。


常用卸载标志

标志位功能你需要提前设置
RTE_MBUF_F_TX_IP_CKSUMIPv4 校验和填 l2_len/l3_len
RTE_MBUF_F_TX_TCP_CKSUMTCP 校验和填 l4_len
RTE_MBUF_F_TX_UDP_CKSUMUDP 校验同上
RTE_MBUF_F_TX_VLANVLAN 插入填 vlan_tci
RTE_MBUF_F_TX_TCP_SEGTSO(大包分段)填 tso_segsz 和长度

最常用的 TSO 示例:

m->l2_len = sizeof(struct rte_ether_hdr);
m->l3_len = sizeof(struct rte_ipv4_hdr);
m->l4_len = sizeof(struct rte_tcp_hdr);

m->tso_segsz = 1460;

m->ol_flags |= RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_TCP_CKSUM;

你把头部长度告诉网卡,它把大包自动拆成小包,校验和也顺带帮你算好了。

已思考 7s

4. 硬件与软件功能实现

硬件卸载(offload)是把原本由软件处理的某些网络功能交给网卡硬件去做——这能把 CPU 从重复、可并行化但开销大的工作中解放出来,从而提高包处理吞吐和降低延迟。

要使用硬件卸载,需要网卡驱动提供接口;这些接口由两类硬件机制支撑:全局寄存器(Register)包级描述符(Descriptor) 。寄存器是“全局开关/参数”,按端口或队列生效;描述符是“每包属性”,随每个数据包传到硬件,用来指示该包需要硬件做哪些额外工作。

寄存器像路由器上的配置开关(“总开关”),描述符像随信封贴的便签(“这个包需要做 X、Y”)。两者配合,网卡就知道“先整体允许,再按包决定执行哪项卸载”。


4.1 为什么分“寄存器 + 描述符”两层粒度

  • 寄存器(Register) :设置功能是否可用、性能/参数的全局阈值、端口级别的默认行为(例如:是否开启 TCP seg-offload/TCP segmentation offload,或默认 LRO)。
  • 描述符(Descriptor / mbuf.ol_flags) :每个包能携带专属指示,告诉硬件“这一个包需要校验和卸载 / TSO / VLAN 插入 / 分片处理”等。DPDK 用 rte_mbufol_flagsl2_len/l3_len/l4_len等字段来传递这些信息。

两层结合的好处是:驱动在端口层面声明能力与默认行为(寄存器),应用程序或上层库在包级别通过描述符开启/关闭单包逻辑,从而获得灵活性与性能兼顾。


4.2 常见的硬件卸载分类

  1. 计算及更新功能(Checksums、L4/L3 header update、timestamp 等)
  2. 分片功能(TSO/Segmentation、IP fragment offload)
  3. 组包/聚合功能(LRO、GRO、集中转发/合包等)

书中以 Intel 网卡为主,但多数厂商(Mellanox/Broadcom/Cavium 等)也有类似功能,只是能力集和行为细节不同。所以本节以“通用性”为主,必要时说明如何用 DPDK API 探测能力并兼容不同网卡。


4.3 DPDK 中的实现与使用要点

1) 先查能力

在使用任何卸载前必须查询网卡是否支持对应能力,否则可能出现未定义行为或性能下降:

struct rte_eth_dev_info dev_info;
rte_eth_dev_info_get(port_id, &dev_info);

/* 示例:检查 tx checksum offload 能力 */
if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_IPV4_CKSUM) {
    printf("支持 IPv4 校验和卸载\n");
}
if (dev_info.tx_offload_capa & DEV_TX_OFFLOAD_TCP_CKSUM) {
    printf("支持 TCP 校验和卸载\n");
}

dev_info 里常用字段:

  • dev_info.tx_offload_capa / dev_info.rx_offload_capa(端口级能力)
  • dev_info.flow_type_rss_offloads(RSS 支持类型)
  • dev_info.min_rx_bufsize / max_mtu 等(硬件限制)

2) 在端口层开启/配置(寄存器级)

通过 rte_eth_dev_configure()rte_eth_tx_queue_setup() 等 API 在端口配置阶段打开端口级 offload 能力。例如启用 TX checksum offload / TSO:

struct rte_eth_conf port_conf = {0};
port_conf.rxmode.mq_mode = ETH_MQ_RX_NONE;

/* 开启端口级的 TX offloads(示例) */
port_conf.txmode.offloads =
    DEV_TX_OFFLOAD_IPV4_CKSUM |
    DEV_TX_OFFLOAD_TCP_CKSUM |
    DEV_TX_OFFLOAD_UDP_CKSUM |
    DEV_TX_OFFLOAD_TCP_TSO;

rte_eth_dev_configure(port_id, nb_rx_queues, nb_tx_queues, &port_conf);

注意:不是所有驱动都需要在 txmode.offloads 写全部位;有的驱动以 dev_info 为准,实际行为以驱动/硬件为最终决定。


3) 在包级(描述符)指示(rte_mbuf)

当端口支持且已开启后,应用需要在 rte_mbuf 上设置 ol_flagsl2/l3/l4 长度来让驱动生成正确描述符:

struct rte_mbuf *m = /* 从 mempool 拿到并构造报文 */;
m->packet_type = RTE_PTYPE_L3_IPV4 | RTE_PTYPE_L4_TCP;
m->l2_len = sizeof(struct ether_hdr);
m->l3_len = sizeof(struct ipv4_hdr);
m->l4_len = sizeof(struct tcp_hdr);

/* 告诉硬件进行 ipv4 + tcp 校验和计算(TX) */
m->ol_flags |= PKT_TX_IPV4 | PKT_TX_IP_CKSUM | PKT_TX_TCP_CKSUM;

/* 如果做 TSO,需要设置 MSS */
m->tso_segsz = 1460;
m->ol_flags |= PKT_TX_TCP_SEG;

关键点:

  • l2_len/l3_len/l4_len 的值必须精确(硬件需要定位 L4 header)。
  • ol_flags 的位是 driver/DPDK 公约(例如 PKT_TX_TCP_CKSUM),驱动会把这些映射到硬件描述符位。

5. 硬件卸载功能解析

现代网卡的硬件卸载能力非常强,能够把原来由 CPU 负责的许多「重复 + 机械」的操作交给硬件完成,从而大幅减少延迟和 CPU 占用。本节我们从 VLAN、IEEE1588、Checksum、Tunnel 卸载 四类常用功能切入,结合 DPDK 的实现细节,拆解这些卸载能力是如何在真实系统中发挥价值的。


5.1 VLAN 相关卸载能力

VLAN 是最常用的二层网络属性,因此几乎所有现代网卡都会针对 VLAN 提供硬件加速,包含:

  • VLAN 过滤
  • VLAN 剥离(RX)
  • VLAN 插入(TX)
  • 多层 VLAN(QinQ)支持

下面逐条说明。

① VLAN 过滤(Rx Filtering)

由于网卡可能会根据 VLAN ID 做过滤,程序员经常会遇到「为什么这个包收不到?」的问题。原因往往就在于过滤机制被打开了。

Testpmd 提供了相关命令,可以帮助排查:

vlan set filter on|off port_id

注意:不同网卡支持的过滤规则不一样,具体实现要看硬件手册。


② 收包方向 VLAN Tag 剥离(RX VLAN Strip)

网卡可以自动把接收到的数据包的 VLAN Tag(4 字节)剥离掉。

开启方式(端口级别或队列级别):

vlan set strip on|off port_id
vlan set stripq on|off port_id queue_id

剥离后的 VLAN 信息不会丢失,而是写入 rx 描述符,并由驱动填入 rte_mbuf

struct rte_mbuf {
    uint16_t vlan_tci;      // 被剥离的 VLAN TCI
    // ol_flags 中包含 PKT_RX_VLAN_PKT 标志
};

上层应用只需要检查 ol_flagsvlan_tci 即可,不需要自己解析 VLAN Tag。


③ 发包方向 VLAN Tag 插入(TX VLAN Insert)

如果应用需要构造带 VLAN 的报文,可以完全交给硬件处理,只需要提前在 mbuf 中标记:

mbuf->ol_flags |= PKT_TX_VLAN_PKT;
mbuf->vlan_tci = vlan_id;

Testpmd 中的设置命令为:

tx_vlan set port_id vlan_id [vlan_outer]

示例:

  • 单层 VLAN:

    tx_vlan set 0 5
    
  • 双层 VLAN:

    tx_vlan set 1 2 3
    

④ 多层 VLAN(QinQ)支持

随着数据中心规模扩大,QinQ (802.1ad) 已经成为常见需求。

不同网卡支持不同程度:

  • Intel 82599/X550
    → 仅对 内层 VLAN 做过滤和剥离。
  • Intel XL710
    → 可以同时剥离 内层 + 外层,并写入 mbuf

Testpmd 可启用:

vlan set qinq on|off port_id

5.2 IEEE1588(PTP)硬件时间戳卸载

PTP 的关键价值在于「精确时间同步」。而时间戳必须在非常靠近物理层的位置记录,否则软件延迟会破坏精度。

因此,所有 PTP 实现几乎都依赖硬件卸载。

硬件卸载实现的能力:

  1. 识别 PTP 报文
  2. 给报文打时间戳(RX + TX)
  3. 将时间戳存放到寄存器
  4. 驱动通过 API 读出时间戳

DPDK 开启 1588 功能:

编译配置:

CONFIG_RTE_LIBRTE_IEEE1588=y

启用硬件时间戳:

rte_eth_timesync_enable(port_id);

读取 RX 时间戳:

rte_eth_timesync_read_rx_timestamp(port_id, &ts, flags);

读取 TX 时间戳:

rte_eth_timesync_read_tx_timestamp(port_id, &ts);

注意:
DPDK 仅提供时间戳机制,不包含 PTP 协议栈。你仍然需要自己管理 Sync、Follow_up、Delay_req/resp 等流程。


5.3 IP/TCP/UDP/SCTP Checksum 卸载

Checksum 本质属于「固定格式的机械计算」,非常适合硬件加速。

分为两个方向:


5.3.1 接收方向(RX 校验)

硬件自动校验 checksum,如果错误则写入 mbuf 标志位:

PKT_RX_IP_CKSUM_BAD   // IP checksum 错误
PKT_RX_L4_CKSUM_BAD   // TCP/UDP/SCTP checksum 错误

应用只需检查 ol_flags


5.3.2 发送方向(TX 计算)

发送方向稍复杂,需要软件告诉硬件「这个包的协议头长度」等信息,便于硬件计算 checksum。

关键字段在 mbuf 的 tx_offload 中:

l2_len
l3_len
l4_len

以及需要清空 checksum 字段,例如:

IPv4
ipv4_hdr->hdr_checksum = 0;
mbuf->ol_flags |= PKT_TX_IP_CKSUM;
UDP/TCP
udp_hdr->dgram_cksum = 0;
mbuf->ol_flags |= PKT_TX_UDP_CKSUM;
udp_hdr->dgram_cksum = get_psd_sum(...); // 伪头部部分
SCTP
sctp_hdr->hdr_checksum = 0;
mbuf->ol_flags |= PKT_TX_SCTP_CKSUM;

Checksum 是逐包计算的,如果没有硬件卸载会明显拖慢 CPU。


5.4 Tunnel(VxLAN/NVGRE)卸载

VxLAN / NVGRE 属于 Overlay 网络,封装形式复杂,因此硬件卸载会带来极大收益。

不同厂商支持程度不一致,但典型能力包括:

  1. 识别 VxLAN/NVGRE 报文
  2. 流重定向(Flow Director)
  3. 外层头部插入/剥离(部分高端网卡支持)

6. 分片功能卸载:TSO(TCP Segment Offload)

当应用层提交一个大块数据给 TCP 协议栈时,TCP 不能直接整块发送出去。原因很简单:下面的网络层会受到 MTU(通常 1500 字节)的限制。
于是软件需要把大块数据拆成一个个小的 TCP Segment,再发送出去。

问题是——分片其实是“重复性机械劳动”: 复制 TCP 头、更新 seq/len、重算 checksum…… CPU 很烦但硬件很擅长。

所以现代网卡直接把这活接过去了,这就是 TSO 硬件卸载


6.1 TSO 做了什么

应用一次发送大包,网卡把它自动切成多个小 TCP 包,并填好所有头部字段。

软件只需要准备好一个“巨型 TCP 包”(巨帧),剩下的交给硬件搞定。

表达的就是这个意思:

  • 原始 TCP Segment 很大
  • 网卡把它拆成多个连续的小 Segment
  • 每个 Segment 都有独立的 TCP/IP/MAC 头
  • seq、length、checksum 全部由硬件重写

你只需要告诉网卡 每块多大 就行了(tso_segsz)。


6.2 testpmd 怎么玩

DPDK 已经在 testpmd 中把 TSO 的能力封装好,可以直接用来验证。

tso set 14000

设置分片目标大小为 14000 字节。

tso show 0

查看端口 0 当前生效的 TSO 配置。


6.3 应用自己如何用?

就像 checksum 硬件卸载一样,TSO 也需要你在 mbuf 中提供必要的信息,网卡才能替你完成后续动作。

关键字段如下:

/* fields to support TX offloads */
uint64_t tx_offload;

struct {
    uint64_t l2_len:7;     /* MAC 头长度 */
    uint64_t l3_len:9;     /* IP 头长度 */
    uint64_t l4_len:8;     /* TCP 头长度 */
    uint64_t tso_segsz:16; /* 单个 TCP segment 的大小(MSS) */
};

你需要做的就是:

① 告诉网卡“这是一个要做 TSO 的包”

mbuf->ol_flags |= PKT_TX_TCP_SEG;

② 设置分片大小(一般等于 MSS,例如 1460)

mbuf->tso_segsz = mss;

③ 提供 L2/L3/L4 的头部长度

这些信息让网卡知道“从哪里开始复制 TCP 头”。


6.4 TSO 的价值

如果你不用 TSO,那么一个 64KB 的应用发送动作会被软件拆成:

-几十个 TCP segment
-几十次 checksum
-几十次头部修改
-几十次 PCIe DMA 下发

而 TSO 可以把这些变成:

-1 次 DMA
-硬件自动拆包
-CPU 几乎零开销

在高吞吐场景(如大文件传输、虚拟化、隧道外层 TCP)中效果非常明显。

7. 组包功能卸载:RSC(Receive Side Coalescing)

在前一节我们讲了 TSO(发送方向拆包) ,它属于“把大包拆成小包”的硬件卸载。

RSC(Receive Side Coalescing) 则正好反过来:

RSC = 把多个小的 TCP segment,在硬件接收路径中重新组装成一个大包。
最终交给软件的是一个“大包”,而不是几十个“小包”。

这相当于是 TSO 的接收端逆操作。


7.1 为什么需要 RSC?

正常情况下,TCP 把数据拆成多个 segment,网卡收到后会把每个 segment 单独交给 DPDK 应用:

  • 应用要处理多个 TCP 头
  • 要做包序检查
  • 要做 reassembly(例如 TCP 流式处理时)

这对软件是很大的负担。

而 RSC 直接把这些工作交给硬件 pipeline:

  • 多个连续 TCP segment
  • 序号连续
  • 属于同一条 TCP 流

硬件缓存起来,排序,自动拼成一个大 Segment 再给应用层

软件看到的,就是一个“大包”。
这大大减少了 CPU 端的 per-packet overhead。


7.2 RSC 如何工作?

RSC 的处理流程可以总结为 3 步

① 网卡识别这些 TCP segment 属于同一个流

条件一般包括:

  • 相同的五元组
  • seq 号连续
  • TCP 非乱序、非重传

符合条件的 segment 会进入同一个“RSC 缓冲区”。


② 缓存多个 segment,根据 seq 号排序

硬件会保证 segment 顺序正确。

这一步相当于替软件做了: tcp_reassembly() 的核心部分。


③ 组包完成后,作为一个大包交给 DPDK

最终软件接收到的是:

[ MAC | IP | TCP | Payload(payload1 + payload2 + payload3...) ]

这样软件只需要处理一次协议栈头部,大幅减轻加载压力。


7.3 RSC 与 LRO 的关系

你可能在 DPDK 文档看到的不是 RSC,而是 LRO(Large Receive Offload)

它们的关系如下:

RSC = Intel 的术语
LRO = 社区通用术语(DPDK/内核通用)

DPDK 内部使用的字段名称是 enable_lro


7.4 如何在 DPDK 中启用 RSC/LRO?

关键结构就是:

struct rte_eth_rxmode {
    ...
    uint16_t enable_lro : 1; /**< Enable LRO (aka RSC). */
};

只要你把 enable_lro = 1,DPDK 驱动就会在初始化时自动配置硬件 RSC。


驱动内部调用流程

以 Intel ixgbe(X550)为例:

rte_eth_dev_configure()
    ↓
ixgbe_dev_rx_init()
    ↓
ixgbe_set_rsc()     <-- 判断 enable_lro
    ↓
设置 X550 中对应的 RSC 寄存器

也就是说:

你只需要设置 enable_lro,剩下驱动自动帮你搞定。


7.5 如何知道你的网卡支不支持?

不是所有 NIC 都支持 RSC/LRO。

判断方法:

  • 看 PMD 文档
  • testpmd 中跑:
show port 0 rx_offloads

如果看到 LRO,则说明可以用。

你也可以在代码里打印:

dev_info.rx_offload_capa

7.6 RSC 的优势

一句话:

减少包数量,降低 PCIe 压力,减少 CPU 解析成本,提高 TCP 流吞吐。

适合应用场景:

  • TCP 大流传输(比如大文件下载)
  • DPDK L7 处理(例如用户态负载均衡)
  • VNF、虚拟化场景

7.7 RSC 使用时的注意事项

  • RSC 会改变分片粒度,对某些需要逐包处理的应用不适合
  • 乱序包会导致 RSC 中断(fallback 到普通模式)
  • 某些 NIC 只支持 IPv4 TCP,不支持 IPv6
  • VXLAN/NVGRE 外层封装一般不能用 RSC

这部分可以提一下,不需要非常复杂,掘金读者看到就会觉得写得很专业。