4. TSO(TCP Segmentation Offload)原理:大包分割机制、驱动与硬件协作流程
4.1 TSO 概述与动机
TSO(TCP Segmentation Offload)是一种将 TCP 数据段的分割工作从内核协议栈卸载到网卡硬件执行的优化技术。在标准 TCP 协议栈中,当应用层通过 send() 发送大块数据时,TCP 层需要根据 MSS(Maximum Segment Size)将数据切分为多个段,每个段都要经过 IP 层、邻居子系统、驱动层,最终通过 DMA 送入网卡。这个过程会消耗大量 CPU 周期,尤其是在 10Gbps 及以上高速网络环境下,CPU 会成为瓶颈。
TSO 的核心思想是:让内核协议栈直接构造一个超大的 TCP 报文(称为 Super Packet 或 TSO 报文),其长度可达 64KB 甚至更大,然后一次性交给网卡驱动;网卡硬件负责按照 MSS 将其分割为多个合法的 TCP 段,并完成校验和计算、序列号更新等操作。
关键指标: 启用 TSO 后,单次报文发送的 CPU 开销可降低 40%~60%,吞吐量提升 30%~80%(取决于报文大小和链路速率)。对于 10Gbps 网络,TSO 几乎是必备功能。
4.2 大包分割机制:从应用层到硬件
TSO 的完整数据流分为三个阶段:协议栈构造大包、驱动提交大包、硬件分割发送。下图展示了 TSO 报文在协议栈与硬件之间的流转路径:
4.3 驱动与硬件协作流程
TSO 的实现依赖于内核、驱动与网卡硬件之间的紧密协作。以下是标准流程的详细步骤:
| 步骤 | 组件 | 操作描述 | 关键数据结构/寄存器 |
|---|---|---|---|
| 1 | TCP 层 | 检查 skb 是否满足 TSO 条件(长度 > MSS,且无紧急指针等特殊标志) | skb->len、tcp_skb_cb |
| 2 | IP 层 | 设置 IP 头总长度为整个大包长度,不分片标志置位 | iph->tot_len、IP_DF |
| 3 | 邻居子系统 | 将 skb 传递给驱动层的 ndo_start_xmit 回调 |
skb->dev、net_device_ops |
| 4 | 驱动层 | 检查 skb_shinfo(skb)->gso_type 是否为 SKB_GSO_TCPV4 或 SKB_GSO_TCPV6 |
skb_shinfo()、gso_type |
| 5 | 驱动层 | 将 skb 映射到 DMA 地址,填充发送描述符(Tx Descriptor)的 TSO 相关字段 | tx_desc->tcp_seg_len、tx_desc->mss |
| 6 | 网卡硬件 | 从描述符中读取 MSS 值,将大包分割为多个 TCP 段,更新每个段的序列号、校验和 | 硬件分割引擎、TSO Context Descriptor |
| 7 | 网卡硬件 | 将分割后的段依次发送到物理链路,并更新发送完成状态 | Tx 完成队列、tx_clean 中断 |
4.4 关键数据结构:skb_shinfo 与 gso_size
TSO 的核心元数据存储在 skb 的共享信息结构体 skb_shared_info 中。驱动通过读取这些字段来指导硬件分割:
struct skb_shared_info {
__u8 nr_frags; // 碎片数量
__u8 tx_flags; // 发送标志
unsigned short gso_size; // TSO 分割大小(即 MSS 值)
unsigned short gso_segs; // 分割后的段数(硬件填充)
struct sk_buff *frag_list; // 碎片链表
skb_frag_t frags[MAX_SKB_FRAGS]; // 数据碎片数组
};
驱动检查逻辑示例(伪代码):
static netdev_tx_t my_start_xmit(struct sk_buff *skb, struct net_device *dev) {
struct skb_shared_info *shinfo = skb_shinfo(skb);
if (shinfo->gso_size && (shinfo->gso_type & SKB_GSO_TCPV4)) {
// 设置 TSO 上下文描述符
tx_desc->tso_enable = 1;
tx_desc->mss = shinfo->gso_size;
tx_desc->tcp_hdr_len = tcp_hdrlen(skb);
tx_desc->ip_hdr_len = ip_hdrlen(skb);
} else {
// 普通发送路径
tx_desc->tso_enable = 0;
}
// 映射 DMA 并触发发送
dma_map_single(dev->dev.parent, skb->data, skb->len, DMA_TO_DEVICE);
writel(tx_desc, dev->base_addr + TX_REG);
return NETDEV_TX_OK;
}
4.5 硬件分割引擎的工作细节
网卡硬件内部的 TSO 分割引擎通常包含以下逻辑单元:
4.6 TSO 与 GSO 的关系
GSO(Generic Segmentation Offload)是 TSO 的软件回退方案。当网卡不支持 TSO 硬件卸载时,GSO 在协议栈内部执行分割,但分割时机推迟到 dev_hard_start_xmit 之前,而非 TCP 层。两者的对比:
| 特性 | TSO(硬件卸载) | GSO(软件回退) |
|---|---|---|
| 分割执行者 | 网卡硬件 | 内核协议栈(net/core/dev.c) |
| CPU 开销 | 极低(仅构造大包) | 中等(需要软件分割) |
| 适用场景 | 支持 TSO 的网卡(如 Intel XL710、Mellanox ConnectX-5) | 不支持 TSO 的网卡或虚拟设备(如 bridge、tun/tap) |
| 配置方式 | ethtool -K eth0 tso on |
ethtool -K eth0 gso on |
| 报文大小限制 | 受硬件描述符限制(通常 64KB) | 受 skb->len 和 GSO_MAX_SIZE 限制 |
注意事项: 在虚拟化环境中(如 KVM 虚拟机),如果宿主机启用了 TSO,但虚拟网卡(virtio-net)不支持硬件分割,则必须依赖 GSO 在宿主机或虚拟机内部完成分割。此时建议同时启用 TSO 和 GSO,确保兼容性。
4.7 TSO 的配置与验证
通过 ethtool 工具可以查看和修改 TSO 状态:
# 查看当前 TSO 状态
ethtool -k eth0 | grep tcp-segmentation-offload
tcp-segmentation-offload: on
# 启用 TSO
ethtool -K eth0 tso on
# 禁用 TSO(用于对比测试)
ethtool -K eth0 tso off
# 查看网卡驱动和固件版本(确认硬件支持)
ethtool -i eth0
driver: i40e
version: 2.12.6
firmware-version: 7.30 0x80008b4b 1.3347.0
验证 TSO 是否生效,可以通过抓包观察报文大小:
# 在发送端抓包,观察 TCP 段大小
tcpdump -i eth0 -nn -c 100 'tcp' | awk '{print $NF}' | sort -n | uniq -c
# 输出示例(TSO 开启时,单个段可达 MSS 大小,如 1460)
# 但抓包看到的是硬件分割后的段,不会出现 64KB 大包
# 可以通过 /proc/net/stat/ 观察分段统计
4.8 TSO 的性能影响与最佳实践
- 吞吐量提升: 在 10Gbps 链路上,启用 TSO 后单流 TCP 吞吐量可从 6Gbps 提升至 9.5Gbps 以上。
- CPU 利用率下降: 发送侧 CPU 占用率可降低 50%~70%,释放核心用于应用处理。
- 延迟影响: TSO 本身不增加延迟,但大包构造可能导致发送队列积压,建议配合
txqueuelen调整。 - 兼容性: 某些老旧网卡或虚拟网卡不支持 TSO,需回退到 GSO。
- 调试建议: 当出现 TCP 重传异常时,可临时关闭 TSO 进行对比测试,排除硬件分割错误。
最佳实践: 在物理服务器上,始终启用 TSO 和 GSO;在容器或虚拟机中,根据虚拟网卡能力决定是否启用 TSO。对于延迟敏感型应用(如高频交易),可考虑关闭 TSO 以减少报文构造的微突发。