TSO/GSO/LRO/GRO

(1)TSO (TCP Segmentation Offload)

  TSO (TCP Segmentation Offload) 是一种利用网卡分割大数据包,减小 CPU 负荷的一种技术,也被叫做 LSO (Large segment offload) ,如果数据包的类型只能是 TCP,则被称之为 TSO,如果硬件支持 TSO 功能的话,也需要同时支持硬件的 TCP 校验计算和分散 - 聚集 (Scatter Gather) 功能。

  可以看到 TSO 的实现,需要一些基本条件,而这些其实是由软件和硬件结合起来完成的,对于硬件,具体说来,硬件能够对大的数据包进行分片,分片之后,还要能够对每个分片附着相关的头部。TSO 的支持主要有需要以下几步:

  如果网路适配器支持 TSO 功能,需要声明网卡的能力支持 TSO,这是通过以 NETIF_F_TSO 标志设置 net_device structure 的 features 字段来表明,

例如,在 benet(drivers/net/benet/be_main.c) 网卡的驱动程序中,设置 NETIF_F_TSO 的代码如下:

  benet 网卡驱动声明支持 TSO 功能

[cpp] view plaincopy

  1.   static void be_netdev_init(struct net_device *netdev)
  2. {
  3. struct be_adapter *adapter = netdev_priv(netdev);
  4. netdev->features |= NETIF_F_SG | NETIF_F_HW_VLAN_RX | NETIF_F_TSO | NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_FILTER | NETIF_F_HW_CSUM | NETIF_F_GRO | NETIF_F_TSO6;
  5. netdev->vlan_features |= NETIF_F_SG | NETIF_F_TSO | NETIF_F_HW_CSUM; netdev->flags |= IFF_MULTICAST;
  6. adapter->rx_csum = true; /* Default settings for Rx and Tx flow control */
  7. adapter->rx_fc = true;
  8. adapter->tx_fc = true;
  9. netif_set_gso_max_size(netdev, 65535);
  10. BE_SET_NETDEV_OPS(netdev, &be_netdev_ops);
  11. SET_ETHTOOL_OPS(netdev, &be_ethtool_ops);
  12. netif_napi_add(netdev, &adapter->rx_eq.napi, be_poll_rx, BE_NAPI_WEIGHT);
  13. netif_napi_add(netdev, &adapter->tx_eq.napi, be_poll_tx_mcc, BE_NAPI_WEIGHT);
  14. netif_carrier_off(netdev);
  15. netif_stop_queue(netdev);
  16. }

  在代码中,同时也用 netif_set_gso_max_size 函数设置了 net_device 的 gso_max_size 字段。该字段表明网络接口一次能处理的最大 buffer 大小,一般该值为 64Kb,这意味着只要 TCP 的数据大小不超过 64Kb,就不用在内核中分片,而只需一次性的推送到网络接口,由网络接口去执行分片功能。

  当一个 TCP 的 socket 被创建,其中一个职责是设置该连接的能力,在网络层的 socket 的表示是 struck sock,其中有一个字段 sk_route_caps 标示该连接的能力,在 TCP 的三路握手完成之后,将基于网络接口的能力和连接来设置该字段。

. 网路层对 TSO 功能支持的设定

[cpp] view plaincopy

  1.  /* This will initiate an outgoing connection. */
  2. nt tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
  3. { …… /* OK, now commit destination to socket. */
  4. sk->sk_gso_type = SKB_GSO_TCPV4;
  5. sk_setup_caps(sk, &rt->dst); ……

  代码中的 sk_setup_caps() 函数则设置了上面所说的 sk_route_caps 字段,同时也检查了硬件是否支持分散 - 聚集功能和硬件校验计算功能。需要这 2 个功能的原因是:Buffer 可能不在一个内存页面上,所以需要分散 - 聚集功能,而分片后的每个分段需要重新计算 checksum,因此需要硬件支持校验计算。

  现在,一切的准备工作都已经做好了,当实际的数据需要传输时,需要使用我们设置好的 gso_max_size,我们知道,TCP 向 IP 层发送数据会考虑 mss,使得发送的 IP 包在 MTU 内,不用分片。而 TSO 设置的 gso_max_size 就影响该过程,这主要是在计算 mss_now 字段时使用。如果内核不支持 TSO 功能,mss_now 的最大值为“MTU – HLENS”,而在支持 TSO 的情况下,mss_now 的最大值为“gso_max_size -HLENS”,这样,从网络层带驱动的路径就被打通了。

(2)GSO (Generic Segmentation Offload)

  TSO 是使得网络协议栈能够将大块 buffer 推送至网卡,然后网卡执行分片工作,这样减轻了 CPU 的负荷,但 TSO 需要硬件来实现分片功能;而性能上的提高,主要是因为延缓分片而减轻了 CPU 的负载,因此,可以考虑将 TSO 技术一般化,因为其本质实际是延缓分片,这种技术,在 Linux 中被叫做 GSO(Generic Segmentation Offload),它比 TSO 更通用,原因在于它不需要硬件的支持分片就可使用,对于支持 TSO 功能的硬件,则先经过 GSO 功能,然后使用网卡的硬件分片能力执行分片;而对于不支持 TSO 功能的网卡,将分片的执行,放在了将数据推送的网卡的前一刻,也就是在调用驱动的 xmit 函数前。

  我们再来看看内核中数据包的分片都有可能在哪些时刻:

  在传输协议中,当构造 skb 用于排队的时候

  在传输协议中,但是使用了 NETIF_F_GSO 功能,当即将传递个网卡驱动的时候

[cpp] view plaincopy

  1.   int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq)
  2. {
  3. …… if (netif_needs_gso(dev, skb))
  4. if (unlikely(dev_gso_segment(skb)))
  5. goto out_kfree_skb;
  6. if (skb->next) goto gso; }
  7. else { …… }
  8. …… }

  在驱动程序里,此时驱动支持 TSO 功能 ( 设置了 NETIF_F_TSO 标志 )

  对于支持 GSO 的情况,主要使用了情况 2 或者是情况 2.、3,其中情况二是在硬件不支持 TSO 的情况下,而情况 2、3 则是在硬件支持 TSO 的情况下。

  代码中是在 dev_hard_start_xmit 函数里调用 dev_gso_segment 执行分片,这样尽量推迟分片的时间以提高性能:

  清单 8. GSO 中的分片

(3)接收路径上的优化 LRO (Large Receive Offload)

  Linux 在 2.6.24 中加入了支持 IPv4 TCP 协议的 LRO (Large Receive Offload) ,它通过将多个 TCP 数据聚合在一个 skb 结构,在稍后的某个时刻作为一个大数据包交付给上层的网络协议栈,以减少上层协议栈处理 skb 的开销,提高系统接收 TCP 数据包的能力。

  当然,这一切都需要网卡驱动程序支持。理解 LRO 的工作原理,需要理解 sk_buff 结构体对于负载的存储方式,在内核中,sk_buff 可以有三种方式保存真实的负载:

  数据被保存在 skb->data 指向的由 kmalloc 申请的内存缓冲区中,这个数据区通常被称为线性数据区,数据区长度由函数 skb_headlen 给出

  数据被保存在紧随 skb 线性数据区尾部的共享结构体 skb_shared_info 中的成员 frags 所表示的内存页面中,skb_frag_t 的数目由 nr_frags 给出,skb_frags_t 中有数据在内存页面中的偏移量和数据区的大小

  数据被保存于 skb_shared_info 中的成员 frag_list 所表示的 skb 分片队列中

  合并了多个 skb 的超级 skb,能够一次性通过网络协议栈,而不是多次,这对 CPU 负荷的减轻是显然的。

  LRO 的核心结构体如下:

  . LRO 的核心结构体

[cpp] view plaincopy

  1. /* * Large Receive Offload (LRO) Manager * * Fields must be set by driver */
  2. struct net_lro_mgr {
  3. struct net_device *dev;
  4. struct net_lro_stats stats; /* LRO features */
  5. unsigned long features;
  6. #define LRO_F_NAPI 1 /* Pass packets to stack via NAPI */
  7. #define LRO_F_EXTRACT_VLAN_ID 2 /* Set flag if VLAN IDs are extracted from received packets and eth protocol is still ETH_P_8021Q */
  8. /* * Set for generated SKBs that are not added to * the frag list in fragmented mode */
  9. u32 ip_summed;
  10. u32 ip_summed_aggr; /* Set in aggregated SKBs: CHECKSUM_UNNECESSARY * or CHECKSUM_NONE */
  11. int max_desc; /* Max number of LRO descriptors */
  12. int max_aggr; /* Max number of LRO packets to be aggregated */
  13. int frag_align_pad; /* Padding required to properly align layer 3 * headers in generated skb when using frags */
  14. struct net_lro_desc *lro_arr; /* Array of LRO descriptors */
  15. /* * Optimized driver functions * * get_skb_header: returns tcp and ip header for packet in SKB */
  16. int (*get_skb_header)(struct sk_buff *skb, void **ip_hdr, void **tcpudp_hdr, u64 *hdr_flags, void *priv); /* hdr_flags: */ #define LRO_IPV4 1
  17. /* ip_hdr is IPv4 header */
  18. #define LRO_TCP 2 /* tcpudp_hdr is TCP header */ 
  19. /* * get_frag_header: returns mac, tcp and ip header for packet in SKB * * @hdr_flags: Indicate what kind of LRO has to be done * (IPv4/IPv6/TCP/UDP) */
  20. int (*get_frag_header)(struct skb_frag_struct *frag, void **mac_hdr, void **ip_hdr, void **tcpudp_hdr, u64 *hdr_flags, void *priv); };

  在该结构体中:

  dev:指向支持 LRO 功能的网络设备

  stats:包含一些统计信息,用于查看 LRO 功能的运行情况

  features:控制 LRO 如何将包送给网络协议栈,其中的 LRO_F_NAPI 表明驱动是 NAPI 兼容的,应该使用 netif_receive_skb() 函数,而 LRO_F_EXTRACT_VLAN_ID 表明驱动支持 VLAN

  ip_summed:表明是否需要网络协议栈支持 checksum 校验

  ip_summed_aggr:表明聚集起来的大数据包是否需要网络协议栈去支持 checksum 校验

  max_desc:表明最大数目的 LRO 描述符,注意,每个 LRO 的描述符描述了一路 TCP 流,所以该值表明了做多同时能处理的 TCP 流的数量

  max_aggr:是最大数目的包将被聚集成一个超级数据包

  lro_arr:是描述符数组,需要驱动自己提供足够的内存或者在内存不足时处理异常

  get_skb_header()/get_frag_header():用于快速定位 IP 或者 TCP 的头,一般驱动只提供其中的一个实现

  一般在驱动中收包,使用的函数是 netif_rx 或者 netif_receive_skb,但在支持 LRO 的驱动中,需要使用下面的函数,这两个函数将进来的数据包根据 LRO 描述符进行分类,如果可以进行聚集,则聚集为一个超级数据包,否者直接传递给内核,走正常途径。需要 lro_receive_frags 函数的原因是某些驱动直接将数据包放入了内存页,之后去构造 sk_buff,对于这样的驱动,应该使用下面的接口:

  LRO 收包函数

  void lro_receive_skb(struct net_lro_mgr *lro_mgr, struct sk_buff *skb, void *priv);

void lro_receive_frags(struct net_lro_mgr *lro_mgr, struct skb_frag_struct *frags, int len, int true_size, void *priv, __wsum sum);

  因为 LRO 需要聚集到 max_aggr 数目的数据包,但有些情况下可能导致延迟比较大,这种情况下,可以在聚集了部分包之后,直接传递给网络协议栈处理,这时可以使用下面的函数,也可以在收到某个特殊的包之后,不经过 LRO,直接传递个网络协议栈:

  . LRO flush 函数

  void lro_flush_all(struct net_lro_mgr *lro_mgr);

void lro_flush_pkt(struct net_lro_mgr *lro_mgr, struct iphdr *iph, struct tcphdr *tcph);

(4) GRO (Generic Receive Offload)

  前面的 LRO 的核心在于:在接收路径上,将多个数据包聚合成一个大的数据包,然后传递给网络协议栈处理,但 LRO 的实现中存在一些瑕疵:

  数据包合并可能会破坏一些状态

  数据包合并条件过于宽泛,导致某些情况下本来需要区分的数据包也被合并了,这对于路由器是不可接收的

  在虚拟化条件下,需要使用桥接功能,但 LRO 使得桥接功能无法使用

  实现中,只支持 IPv4 的 TCP 协议

  而解决这些问题的办法就是新提出的 GRO(Generic Receive Offload),首先,GRO 的合并条件更加的严格和灵活,并且在设计时,就考虑支持所有的传输协议,因此,后续的驱动,都应该使用 GRO 的接口,而不是 LRO,内核可能在所有先有驱动迁移到 GRO 接口之后将 LRO 从内核中移除。而 Linux 网络子系统的维护者 David S. Miller 就明确指出,现在的网卡驱动,有 2 个功能需要使用,一是使用 NAPI 接口以使得中断缓和 (interrupt mitigation) ,以及简单的互斥,二是使用 GRO 的 NAPI 接口去传递数据包给网路协议栈。

  在 NAPI 实例中,有一个 GRO 的包的列表 gro_list,用堆积收到的包,GRO 层用它来将聚集的包分发到网络协议层,而每个支持 GRO 功能的网络协议层,则需要实现 gro_receive 和 gro_complete 方法。

  协议层支持 GRO/GSO 的接口

[cpp] view plaincopy

  1.  struct packet_type {
  2. __be16 type; /* This is really htons(ether_type)。 */
  3. struct net_device *dev; /* NULL is wildcarded here */
  4. int (*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *);
  5. struct sk_buff *(*gso_segment)(struct sk_buff *skb, int features);
  6. int (*gso_send_check)(struct sk_buff *skb);
  7. struct sk_buff **(*gro_receive)(struct sk_buff **head, struct sk_buff *skb);
  8. int (*gro_complete)(struct sk_buff *skb);
  9. void *af_packet_priv; struct list_head list;
  10. };

  其中,gro_receive 用于尝试匹配进来的数据包到已经排队的 gro_list 列表,而 IP 和 TCP 的头部则在匹配之后被丢弃;而一旦我们需要向上层协议提交数据包,则调用 gro_complete 方法,将 gro_list 的包合并成一个大包,同时 checksum 也被更新。在实现中,并没要求 GRO 长时间的去实现聚合,而是在每次 NAPI 轮询操作中,强制传递 GRO 包列表跑到上层协议。GRO 和 LRO 的最大区别在于,GRO 保留了每个接收到的数据包的熵信息,这对于像路由器这样的应用至关重要,并且实现了对各种协议的支持。以 IPv4 的 TCP 为例,匹配的条件有:

  源 / 目的地址匹配

  TOS/ 协议字段匹配

  源 / 目的端口匹配

  而很多其它事件将导致 GRO 列表向上层协议传递聚合的数据包,例如 TCP 的 ACK 不匹配或者 TCP 的序列号没有按序等等。

  GRO 提供的接口和 LRO 提供的接口非常的类似,但更加的简洁,对于驱动,明确可见的只有 GRO 的收包函数了 , 因为大部分的工作实际是在协议层做掉了:

   GRO 收包接口

  gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) gro_result_t napi_gro_frags(struct napi_struct *napi)

TSO/GSO/LRO/GRO相关推荐

  1. 网络协议栈TSO/UFO/GSO/LRO/GRO/RSS特性

    作者 QQ群:852283276 微信:arm80x86 微信公众号:青儿创客基地 B站:主页 https://space.bilibili.com/208826118 参考 网卡多队列技术与RSS功 ...

  2. TCP TSO/GSO初步探索

    参考:https://blog.csdn.net/quqi99/article/details/51066800            https://www.ibm.com/developerwor ...

  3. linux 网卡gso,linux内核网络协议栈学习笔记:关于GRO/GSO/LRO/TSO等patch的分析和测试...

    TSO,全称是TCP Segmentation Offload,我们知道通常以太网的MTU是1500,除去TCP/IP的包头,TCP的MSS (Max Segment Size)大小是1460,通常情 ...

  4. TSO/GSO GRO/LRO 从入门到精通

    目录 概念介绍 功能与用途 使用场景 在协议栈各个层次如何实现 参考资料 一.概念介绍: TSO/GSO TSO 是(TCP segmentation offload )的缩写,主要把TCP分段这个o ...

  5. linux tso gso关系,1.3.1 TSO/GSO

    1.3.1  TSO/GSO TSO是通过网络设备进行TCP段的分割,从而来提高网络性能的一种技术.较大的数据包(超过标准1518B的帧)可以使用该技术,使操作系统减少必须处理的数据数量以提高性能.通 ...

  6. linux内核协议栈 TCP层数据发送之TSO/GSO

    目录 1 基本概念 2 TCP延迟分段判定 2.1 客户端初始化 2.2 服务器端初始化 2.3 sk_setup_caps() 3 整体结构 4. TCP发送路径TSO处理 4.1 tcp_send ...

  7. TCP数据发送之TSO/GSO

    TSO相关的内容充斥着TCP的整个发送过程,弄明白其机制对理解TCP的发送过程至关重要,这篇笔记就来看看TSO相关内容. 1. 基本概念 我们知道,网络设备一次能够传输的最大数据量就是MTU,即IP传 ...

  8. TCP 的演化史-byte stream 和 packet

    不想写太多代码,我想直接抄一个 TCP sack 实现,参考了 lwIP TCP,很遗憾:TCP: Implement handling received SACKs 无奈不得不自己实现 sack o ...

  9. 理解 Linux 网络栈(2):非虚拟化Linux 环境中的 Segmentation Offloading 技术

    本系列文章总结 Linux 网络栈,包括: (1)Linux 网络协议栈总结 (2)非虚拟化Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO (3)QEMU/KVM + Vx ...

  10. 《深入浅出DPDK》读书笔记(十五):DPDK应用篇(Open vSwitch(OVS)中的DPDK性能加速)

    Table of Contents Open vSwitch(OVS)中的DPDK性能加速 174.虚拟交换机简介 175.OVS简介 176.DPDK加速的OVS 177.OVS的数据通路 178. ...

最新文章

  1. Transformer也能生成图像
  2. python处理大量excel数据-使用python将大量数据导出到Excel中的小技巧分享
  3. Myeclipse开发内存溢出问题
  4. 基于Linux的集群系统(八)--转
  5. 图像传感器与信号处理——光学系统
  6. java 处理byte_java - 文件到Java中的byte [] - 堆栈内存溢出
  7. linux下搭建lua开发环境
  8. C#中配置文件的使用
  9. mse均方误差计算公式_PCA的两种解读:方差最大与均方误差最小的推导
  10. 协同进化遗传算法 代码_遗传算法在组卷中的应用
  11. Serverless 实战 —— 前端也可以快速开发一个 Puppeteer 网页截图服务
  12. (77)FPGA时序违例及解决办法-面试必问(一)(第16天)
  13. 原生JS事件中,return false 和 preventDefault() 的区别
  14. 关于DNF的多媒体包NPK文件的那些事儿(2)
  15. macbook pro配置maven环境变量
  16. 获取当前屏幕各种高度
  17. 老司机带你检测相似图片【转】
  18. 全国电话区号->地址映射表
  19. 一个Word中的样式导入另一个Word
  20. [解决]IDEA每次启动都会打开Licenses激活弹窗、IDEA打不开

热门文章

  1. 表单checkbook获取已选择的值
  2. python精通 epub_精通Python自然语言处理 pdf epub mobi txt 下载
  3. 固态硬盘(SSD)——NAND闪存芯片(颗粒)QLC、SLC、MLC、TLC
  4. 触发器(SR锁存器、SR触发器、JK触发器、D触发器、T触发器)
  5. sqlite3 error: database is locked
  6. C语言发展史的点点滴滴
  7. 设计灵感|App登录注册页面设计方式
  8. 设备管理 设备控制方式
  9. 什么叫工作波长,截止波长和波导波长
  10. BLEU——机器翻译评测