http://geek.csdn.net/news/detail/67260
摘要:第一篇文章总结了Linux 网络协议栈的概括和功能。本文总结非虚拟化环境中的各种 Segmentation Offloading 技术。
从第一篇文章的介绍中我们知道,Linux 内核传输层和网络层都要做大量的计算工作,具体见上图,这些计算都在服务器的主 CPU 中进行。这里有一些网络协议栈计算所需要的 CPU 资源的一些参考数据。大体上,发送或者接收 1 bit/s 的数据需要 1 赫兹的 CPU 处理能力,也就是说,5 Git/s (625 MB/s) 的网络流量大概需要 5 GHz 的 CPU 处理能力,相当于此时需要 2 个 2.5 Ghz 的多核处理器。因为以太网是单向的,发送和接收 10 Gbit/s (吞吐量就是 20 10 Gbit/s)时,大概需要 8 个 2.5 GHz 的 CPU 内核。
这些计算大概可以分为两类:(1)数据计算,比如校验和计算和验证、分包和组包等,这个和所处理的 packets 的数量有关(2)数据传输和上下文切换带来的 overhead,这个和传输和切换的次数有关。
为了解决问题,考虑到越来越多的物理网卡具有较强的处理能力,就出现了两个思路:
(1)如果网卡能够支持某些 Linux 内核协议栈所承担的计算任务,那么就可以将这些计算从协议栈 offload (卸载)到物理网卡。
(2)如果网卡不能支持这些计算,那么尽可能地将这些计算在 Linux 内核网络栈中延后(传输过程)和提前(接收过程)来减少 overhead。以 TCP 分组或者 IP 分片为例,延迟该过程,可以减少在网络栈中传输和处理的 packets 的数目,从而减少数据传输和上下文切换所需要的主 CPU 计算能力。
2.1 TSO (TCP Segmentation Offloading)
2.1.1 TCP Segmentation (TCP 分段)
MSS(Maxium Segment Size): MSS 是 TCP 数据段每次能够传输的最大数据分段的长度。为了达到最佳的传输效能,TCP 协议在建立连接的时候通常要协商双方的 MSS 值,这个值 TCP 协议在实现的时候往往用 MTU 值代替( MSS = MTU - IP 数据包包头大小20Bytes - TCP 数据段的包头大小20Bytes),所以在默认以太网 MTU 为 1500 bytes 时,MSS为 1460。
TCP 分段:当网络应用发给 TCP 的 message 的长度超过 MSS 时,TCP 会对它按照 MSS 的大小将其分为多个小的 packet,并且在每个 packet 上添加 TCP Header 成为一个 TCP 段(segement)。
2.1.2 TSO
TSO 是一种利用网卡分割大数据包,减小 CPU 负荷的一种技术,也被叫做 LSO (Large segment offload) ,如果数据包的类型只能是 TCP,则被称之为 TSO,如果硬件支持 TSO 功能的话,也需要同时支持硬件的 TCP 校验计算和分散 - 聚集 (Scatter Gather) 功能。可以看到 TSO 的实现,需要一些基本条件,而这些其实是由软件和硬件结合起来完成的,对于硬件,具体说来,硬件能够对大的数据包进行分片,分片之后,还要能够对每个分片附着相关的头部。
TSO 就是将由 TCP 协议栈所做的 TCP 分段交给具有这种能力的物理网卡去做,因此它需要如下支持:
使用 TSO 以后,应用发出的大的数据块在不超过 64k 的情况下,将会直接经过Linux 网络栈发到网卡的驱动的 driver queue,然后在网卡中根据 skb 中的预设分组数据(主要是 MSS)对它执行 TCP 分段。下图是使用 TSO 和不使用 TSO 的情形的对比:
2.2 UFO - UDP Fragmentation Offload
UDP 数据报,由于它不会自己进行分段,因此当长度超过了 MTU 时,会在网络层进行 IP 分片。同样,ICMP(在网络层中)同样会出现IP分片情况。
2.2.1 IP fragmentation (分片)
MTU 和 IP 分片:
IP 分片和 TCP 分段的区别:
IP 分片和 TCP 分段的关系:
2.2.2 UFO
UDP 协议层本身不对大的数据报进行分片,而是交给 IP 层去做。因此,UFO 就是将 IP 分片 offload 到网卡(NIC)中进行。其原理同 TSO。
“IPv4/IPv6: UFO (UDP Fragmentation Offload) Scatter-gather approach: UFO is a feature wherein the Linux kernel network stack will offload the IP fragmentation functionality of large UDP datagram to hardware. This will reduce the overhead of stack in fragmenting the large UDP datagram to MTU sized packets”
2.3 GSO - Generic Segemetation Offload
TSO 是使得网络协议栈能够将大块 buffer 推送至网卡,然后网卡执行分片工作,这样减轻了 CPU 的负荷,但 TSO 需要硬件来实现分片功能;而性能上的提高,主要是因为延缓分片而减轻了 CPU 的负载,因此,可以考虑将 TSO 技术一般化,因为其本质实际是延缓分片,这种技术,在 Linux 中被叫做 GSO(Generic Segmentation Offload)。它比 TSO 更通用,原因在于它不需要硬件的支持分片就可使用,对于支持 TSO 功能的硬件,则先经过 GSO 功能,然后使用网卡的硬件分片能力执行分片;而对于不支持 TSO 功能的网卡,将分片的执行,放在了将数据推送的网卡的前一刻,也就是在调用驱动的 xmit 函数前。
2.3.1 对于 UDP,在物理网卡不支持 UFO 时,使用和不使用 GSO 的情形
注意这两者中间的重要区别:
2.3.2 GSO for UDP 代码分析
GSO for UDP 代码在 http://www.mit.edu/afs.new/sipb/contrib/linux/net/ipv4/udp_offload.c:
static const struct net_offload udpv4_offload = { .callbacks = { .gso_segment = udp4_ufo_fragment, .gro_receive = udp4_gro_receive, .gro_complete = udp4_gro_complete, }, }
函数 udp4_ufo_fragment 最终调用 skb_segment 函数进行分片:
/** * skb_segment - Perform protocol segmentation on skb. * @head_skb: buffer to segment * @features: features for the output path (see dev->features) * * This function performs segmentation on the given skb. It returns * a pointer to the first in a list of new skbs for the segments. * In case of error it returns ERR_PTR(err). */ struct sk_buff *skb_segment(struct sk_buff *head_skb, netdev_features_t features)
2.3.3 对 TCP,在网卡不支持 TSO 时,使用和不使用 GSO 的情形
两者都是 TCP 分片,只是位置不同。
2.3.4 GSO for TCP 代码逻辑分析
(1)tcp_output 函数
1. Checks if GSO is enabled: sysctl net.inet.tcp.gso = 1 sysctl net.gso.”ifname”.enable_gso = 1 2. Checks if the packet length exceeds the MTU If 1 and 2 are true, sets GSO flag: m->m_pkthdr.csum_flags |= GSO_TO_CSUM(GSO_TCP4);
(2)ip_output 函数
If GSO is enabled and required, then avoids checksum (IP & TCP) and avoids IP Fragmentation
(3)ether_output 函数
If GSO is enabled and required: calls gso_dispatch() instead of ifp->transmit()
(4)gso_dispatch 函数
int gso_dispatch(struct ifnet *ifp, struct mbuf *m, u_int mac_hlen) { … gso_flags = CSUM_TO_GSO(m->m_pkthdr.csum_flags); … error = gso_functions[gso_flags](ifp, m, mac_hlen); return error; }
(5)gso_functions 函数
gso_functions[GSO_TCP4] gso_ip4_tcp(…) - GSO on TCP/IPv4 packet 1. m_seg(struct mbuf *m0, int hdr_len, int mss, …) returns the mbuf queue that contains the segments of the original packet (m0). hdr_len - first bytes of m0 that are copied in each new segments mss - maximum segment size 2. fixes TCP and IP headers in each new segments 3. sends new segments to the device driver [ifp->if_transmit()]
2.4 LRO (Large Receive Offload)
Linux 在 2.6.24 中加入了支持 IPv4 TCP 协议的 LRO (Large Receive Offload) ,它通过将多个 TCP 数据聚合在一个 skb 结构,在稍后的某个时刻作为一个大数据包交付给上层的网络协议栈,以减少上层协议栈处理 skb 的开销,提高系统接收 TCP 数据包的能力。当然,这一切都需要网卡驱动程序支持。理解 LRO 的工作原理,需要理解 sk_buff 结构体对于负载的存储方式,在内核中,sk_buff 可以有三种方式保存真实的负载:
合并了多个 skb 的超级 skb,能够一次性通过网络协议栈,而不是多次,这对 CPU 负荷的减轻是显然的。
2.5 GRO (Generic Receive Offloading)
前面的 LRO 的核心在于:在接收路径上,将多个数据包聚合成一个大的数据包,然后传递给网络协议栈处理,但 LRO 的实现中存在一些瑕疵:
而解决这些问题的办法就是新提出的 GRO。首先,GRO 的合并条件更加的严格和灵活,并且在设计时,就考虑支持所有的传输协议,因此,后续的驱动,都应该使用 GRO 的接口,而不是 LRO,内核可能在所有先有驱动迁移到 GRO 接口之后将 LRO 从内核中移除。GRO 和 LRO 的最大区别在于,GRO 保留了每个接收到的数据包的熵信息,这对于像路由器这样的应用至关重要,并且实现了对各种协议的支持。以 IPv4 的 TCP 为例,匹配的条件有:
源 / 目的端口匹配
这篇文章linux kernel 网络协议栈之GRO(Generic receive offload)详细分析了 GRO 代码。
2.5.1 在不支持 LRO 的情况下,对 TCP 使用和不使用 GRO 的情形
2.6 TCP/UDP Segementation Offload 小结
2.6.1 小结
Offload | 传输段还是接收端 | 针对的协议 | Offloading 的位置 | ethtool 命令输出中的项目 | ethtool 命令中的 option | 网卡/Linux 内核支持情况 |
TSO | 传输段 | TCP | NIC | tcp-segmentation-offload | tso | Linux 内核从 2.5.33 引入 (2002) 网卡普遍支持 |
UFO | 传输段 | UDP | NIC | udp-fragmentation-offload | ufo | linux 2.6.15 引入 (2006) 网卡普遍不支持 |
GSO | 传输段 | TCP/UDP | NIC 或者 离开 IP 协议栈进入网卡驱动之前 | generic-segmentation-offload | gso | GSO/TCP: Linux 2.6.18 中引入(2006) GSO/UDP: linux 3.16 (2014) |
LRO | 接收段 | TCP | NIC | large-receive-offload | lro | Linux 内核 2.6.24 引入(2008) 网卡普遍支持 |
GRO | 接收段 | TCP | NIC 或者离开网卡驱动进入 IP 协议栈之前 | generic-receive-offload | gro | Linux 内核 2.6.18 引入(2006) 网卡普遍支持 |
2.6.2 性能对比
[TSO/GSO for TCP/IPv4] [GSO for UDP/IPv4]从这图也可以看出:
2.6.3 Offloading 带来的潜在问题
分段offloading 可能会带来潜在的问题,比如网络传输的延迟 latency,因为 packets 的大小的增加,大大增加了 driver queue 的容量(capacity)。比如说,系统一方面在使用大的 packet size 传输大量的数据,同时在运行许多的交换式应用(interactive application)。因为交互式应用会定时发送许多小的packet,这时候可能会应为这些小的 packets 被淹没在大的 packets 之中,需要等待较长的时间才能被处理,这可能会带来不可接受的延迟。
在网络上也能看到一些建议,在使用这些 offloading 技术时如果发现莫名的网络问题,建议先将这些技术关闭后再看看情况有没有改变。
作者信息:刘世民(Sammy Liu),IBM 云架构师,十余年IT行业从业经历,在电信、企业软件、存储以及云计算等领域做过研发、管理和架构设计等工作。从 2012 年开始学习 OpenStack,对其核心模块有较深入的了解;带领过团队开发OpenStack模块。
责编:陈晨 欢迎投稿,chenchenjs#csdn.net