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,是当前最具代表性的智能网卡架构之一。
| 功能类别 | i350 | 82599 | X550 | XL710 | CX7 |
|---|---|---|---|---|---|
| 基本 L3/L4 校验和卸载 | ✓ | ✓ | ✓ | ✓ | ✓(更完整 Offload Pipeline) |
| TSO/LRO/GRO | TSO | TSO | TSO | TSO/GRO | TSO、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/e1000 | ixgbe | ixgbe | i40e | mlx5(DPDK 最佳性能路径) |
| 数据速率 | 1Gb | 10Gb | 10Gb | 10/40Gb | 100/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_GOOD | IP 校验 OK | 可以放心用 |
RTE_MBUF_F_RX_IP_CKSUM_BAD | IP 校验错误 | 直接丢 |
RTE_MBUF_F_RX_L4_CKSUM_GOOD | TCP/UDP 校验 OK | 可加速处理 |
RTE_MBUF_F_RX_L4_CKSUM_BAD | L4 校验错误 | 丢包 |
RTE_MBUF_F_RX_VLAN | VLAN tag 已自动 strip | VLAN 信息在 vlan_tci |
RTE_MBUF_F_RX_RSS_HASH | RSS 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_CKSUM | IPv4 校验和 | 填 l2_len/l3_len |
RTE_MBUF_F_TX_TCP_CKSUM | TCP 校验和 | 填 l4_len |
RTE_MBUF_F_TX_UDP_CKSUM | UDP 校验 | 同上 |
RTE_MBUF_F_TX_VLAN | VLAN 插入 | 填 vlan_tci |
RTE_MBUF_F_TX_TCP_SEG | TSO(大包分段) | 填 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_mbuf的ol_flags、l2_len/l3_len/l4_len等字段来传递这些信息。
两层结合的好处是:驱动在端口层面声明能力与默认行为(寄存器),应用程序或上层库在包级别通过描述符开启/关闭单包逻辑,从而获得灵活性与性能兼顾。
4.2 常见的硬件卸载分类
- 计算及更新功能(Checksums、L4/L3 header update、timestamp 等)
- 分片功能(TSO/Segmentation、IP fragment offload)
- 组包/聚合功能(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_flags 和 l2/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_flags 和 vlan_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 实现几乎都依赖硬件卸载。
硬件卸载实现的能力:
- 识别 PTP 报文
- 给报文打时间戳(RX + TX)
- 将时间戳存放到寄存器
- 驱动通过 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 网络,封装形式复杂,因此硬件卸载会带来极大收益。
不同厂商支持程度不一致,但典型能力包括:
- 识别 VxLAN/NVGRE 报文
- 流重定向(Flow Director)
- 外层头部插入/剥离(部分高端网卡支持)
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
这部分可以提一下,不需要非常复杂,掘金读者看到就会觉得写得很专业。