导读:本期聚焦于追梦人创作的《Large Receive Offload(LRO)如何合并接收段减少CPU负载?》,敬请观看详情。万兆网卡满载小包时,软中断和协议栈处理往往先于带宽成为瓶颈。LRO的核心价值在于让网卡硬件或驱动把多个TCP接收段合并成一个大的skb,使内核只处理一次协议头解析和校验,从而大幅降低每个数据包的平均CPU开销。本文将拆解LRO的合并条件、报文重组逻辑,对比GRO的实现差异,并给出ethtool启用、关闭与验证的完整命令。还会讨论LRO在路由器、网桥、虚拟机场景中可能破坏端到端语义的原因,以及为什么转发节点应该优先使用GRO。读者可以据此判断自己的负载是否适合开启LRO,避免单纯追求吞吐而引入难以排查的连接问题。

在高速网络接收路径上,每处理一个数据包都要经历网卡中断、NAPI轮询、skb分配、协议头解析、校验和验证、TCP状态机处理等多个步骤。当单个连接的数据段很短时,大量CPU时间被消耗在固定开销上,而不是真正搬运数据。Large Receive Offload(LRO)尝试在靠近网卡的层次把多个连续TCP段合并成一个更大的段,再一次性送入内核协议栈。这样内核只需要处理一次skb生命周期,协议栈也只针对合并后的段计算一次校验、查找一次socket。

Large Receive Offload(LRO)如何合并接收段减少CPU负载?

一、LRO如何把多个段合并成一个

LRO通常实现在网卡驱动或网卡硬件中。以Linux内核的NAPI收包流程为例,驱动在软中断里从接收环取出多个描述符,每个描述符对应一个到来的以太网帧。驱动先不急着把每个skb单独送给协议栈,而是检查相邻skb是否满足合并条件。合并条件包括:TCP连接相同,即源IP、目的IP、源端口、目的端口一致;序列号连续,下一个段的序列号正好等于当前段的序列号加上当前段有效载荷长度;PSH标志没有被置位;TCP选项一致,避免时间戳或SACK选项在合并后失效;IP标识、DF位等字段也能对齐。

满足条件后,驱动会修改聚合skb的线性区和分片区,把后续段的payload追加到同一个skb的数据区,同时更新skb的长度字段和聚合信息。合并后的skb通常被标记为GSO段,记录每个子段的MSS,以便后续发送或转发时能够重新切分。例如一个10GB网卡接收64KB的数据,如果由16个4KB段组成,LRO可以将其合并成一个64KB的skb,协议栈只需处理一次。对于受CPU限制而非带宽限制的服务器,这能显著提高有效吞吐。

#include <stdio.h>

struct lro_packet {
    unsigned int seq;
    unsigned int next_seq;
    unsigned int psh:1;
    unsigned int syn:1;
};

static int lro_can_merge(struct lro_packet *a, struct lro_packet *b)
{
    if (a->psh || a->syn || b->psh || b->syn)
        return 0;
    if (a->next_seq != b->seq)
        return 0;
    return 1;
}

int main(void)
{
    struct lro_packet p1 = {1000, 2000, 0, 0};
    struct lro_packet p2 = {2000, 3000, 0, 0};

    if (lro_can_merge(&p1, &p2))
        printf("can merge\n");
    return 0;
}

上面这段简化代码展示了合并前最基本的两个判断:是否带PSH或SYN标志,以及序列号是否刚好衔接。实际内核中的lro_mgr和lro_frame结构要复杂得多,还需要考虑IP分片、校验和卸载状态、VLAN标签等。但无论实现细节如何,核心思想都是把协议栈处理从“按包次数”转变为“按数据批次”,从而摊薄CPU开销。

二、LRO与GRO:硬件合并与软件合并的边界

LRO经常和Generic Receive Offload(GRO)放在一起比较。GRO是内核提供的通用接收合并机制,不依赖特定网卡,只要驱动在NAPI轮询时把skb送入协议栈,GRO层就能在软中断中执行合并。GRO的合并条件比LRO更严格,会保留更多原始包信息,因此可以在转发、桥接、Netfilter等路径使用。LRO则发生在驱动或硬件层,合并更早,理论上节省的中断和软中断开销更多,但也更容易破坏转发语义。

两者最重要的差异在于对IP转发和数据包观测的影响。LRO合并后的skb在转发节点上可能超过出接口MTU,如果网卡不支持分段卸载,内核必须重新分片,但重组后的IP分片可能丢失原始TCP段的边界。抓包工具在接收端看到的是巨型TCP段,实际发送方根本没有这么大的段,这会误导排障。GRO虽然也聚合,但它处于协议栈入口,内核可以在必要时重新分离成原始段,对转发和抓包更友好。

特性LROGRO
合并位置网卡硬件或驱动内核协议栈入口
硬件依赖需要网卡支持内核通用能力
转发节点适用性通常不推荐推荐
抓包观测可能显示异常大段可保留原始段信息
CPU节省效果更早合并,效果更明显稍弱,但更安全

因此,纯主机接收TCP流量的场景,LRO收益较高;而任何需要转发、NAT、隧道、防火墙、负载均衡的场景,都应该优先使用GRO,甚至显式关闭LRO。很多云厂商的虚拟网卡默认关闭LRO,正是为了避免虚拟化环境中的语义破坏。

三、在Linux中启用、关闭和验证LRO

Linux中管理网卡卸载功能最直接的工具是ethtool。下面先查看网卡当前的LRO状态。不同驱动对LRO的命名可能有差异,多数使用large-receive-offload,但某些老版本驱动可能使用rx-lro或lro。执行后如果显示large-receive-offload: on,说明LRO已经启用。

# 查看eth0的LRO状态
ethtool -k eth0 | grep large-receive-offload

# 启用LRO
ethtool -K eth0 lro on

# 关闭LRO
ethtool -K eth0 lro off

启用或关闭后,可以通过ethtool -S查看网卡统计计数器变化。有些驱动会提供lro_aggregated、lro_flushed等统计项,能直接看到合并发生的次数。如果计数器不增长,说明网卡或驱动并不支持LRO,或者流量模式不满足合并条件。还可以用perf top观察软中断和协议栈函数的热点变化,开通LRO前后对比,通常能发现tcp_ack、ip_rcv等函数占用下降。

对于使用systemd-networkd或NetworkManager的系统,可以将ethtool命令写入udev规则或网络配置文件的post-up脚本,确保重启后仍然生效。例如在/etc/network/interfaces中添加up ethtool -K eth0 lro on。但要注意,如果网卡驱动不支持LRO,ethtool会返回Operation not supported,此时不应强制开启。

四、LRO的副作用与场景取舍

LRO并非没有代价。最大的问题是它改变了TCP段边界,可能影响TCP时间戳、SACK块和拥塞窗口计算。合并后内核看到的TCP报文长度远大于实际发送的段,如果接收端需要基于每个原始段生成ACK,LRO会延迟确认,甚至改变RTT测量。在丢包重传时,SACK块可能无法精确描述丢失的段,导致发送端重传更多数据。TCP时间戳选项在合并时也可能出现不连续,影响RTTM算法。

另一个典型问题出现在虚拟化和容器网络。虚拟机之间通过vSwitch转发时,宿主机上的LRO会把虚拟机发出的多个TCP段合并成一个,导致VM的vNIC接收到的段大小异常,甚至超过MTU。如果虚拟网卡没有开启相应的分段卸载,数据包会被丢弃或分片。因此,VMware、KVM等虚拟化平台的最佳实践通常是在宿主机物理网卡上关闭LRO,改由虚拟机内的GRO完成聚合。

对于纯接收型的高吞吐服务,比如视频流媒体服务器、文件下载服务器、日志采集节点,LRO可以降低CPU使用率,提高单连接吞吐。但如果服务同时承担转发、代理或深度包检测,或者需要精确抓包排障,则建议关闭LRO。折中方案是在物理网卡上开启LRO,但在网络命名空间或veth接口上启用GRO,不过同一条路径上不要同时启用多个聚合层,否则可能产生二次合并,增加复杂性。

最后,配置变更后应使用iperf3或netperf进行对比测试,并抓取接收端报文长度统计。观察平均包长、CPU占用、吞吐和重传率四个指标。如果吞吐提升明显、CPU下降且重传率没有上升,说明LRO适合当前业务;如果出现TCP乱序或重传增加,应立即回退。性能优化没有全局最优,只有结合流量特征和协议栈行为的持续验证。

Large Receive Offload接收段合并CPU负载修改时间:2026-09-17 13:56:53

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。