发送路径上的优化

  TSO (TCP Segmentation Offload)

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

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

  1、如果网路适配器支持 TSO 功能,需要声明网卡的能力支持 TSO,这是通过以 NETIF_F_TSO 标志设置 net_device structure 的 features 字段来表明,例如,在 benet(drivers/net/benet/be_main.c) 网卡的驱动程序中,设置 NETIF_F_TSO 的代码如下:

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

static void be_netdev_init(struct net_device *netdev) 
 { 
     struct be_adapter *adapter = netdev_priv(netdev);

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;

netdev->vlan_features |= NETIF_F_SG | NETIF_F_TSO | NETIF_F_HW_CSUM;

netdev->flags |= IFF_MULTICAST;

adapter->rx_csum = true;

/* Default settings for Rx and Tx flow control */ 
     adapter->rx_fc = true; 
     adapter->tx_fc = true;

netif_set_gso_max_size(netdev, 65535);

BE_SET_NETDEV_OPS(netdev, &be_netdev_ops);

SET_ETHTOOL_OPS(netdev, &be_ethtool_ops);

netif_napi_add(netdev, &adapter->rx_eq.napi, be_poll_rx, 
         BE_NAPI_WEIGHT); 
     netif_napi_add(netdev, &adapter->tx_eq.napi, be_poll_tx_mcc, 
         BE_NAPI_WEIGHT);

netif_carrier_off(netdev); 
     netif_stop_queue(netdev); 
 }

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

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

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

/* This will initiate an outgoing connection. */ 
 int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) 
 { 
         ……

/* OK, now commit destination to socket.  */ 
     sk->sk_gso_type = SKB_GSO_TCPV4; 
     sk_setup_caps(sk, &rt->dst);

……
 }

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

  3、现在,一切的准备工作都已经做好了,当实际的数据需要传输时,需要使用我们设置好的 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”,这样,从网络层带驱动的路径就被打通了。

  GSO (Generic Segmentation Offload)

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

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

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

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

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

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

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

 GSO 中的分片

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, 
             struct netdev_queue *txq) 
 { 
……
         if (netif_needs_gso(dev, skb)) { 
             if (unlikely(dev_gso_segment(skb))) 
                 goto out_kfree_skb; 
             if (skb->next) 
                goto gso; 
        } else { 
           ……

}

……

}

接收路径上的优化

  LRO (Large Receive Offload)

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

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

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

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

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

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

  LRO 的核心结构体如下:

  LRO 的核心结构体

/* 
 * Large Receive Offload (LRO) Manager 
 * 
 * Fields must be set by driver 
 */

struct net_lro_mgr { 
     struct net_device *dev; 
     struct net_lro_stats stats;

/* LRO features */ 
     unsigned long features; 
 #define LRO_F_NAPI            1  /* Pass packets to stack via NAPI */ 
 #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 */

/* 
     * Set for generated SKBs that are not added to 
     * the frag list in fragmented mode 
     */ 
     u32 ip_summed; 
     u32 ip_summed_aggr; /* Set in aggregated SKBs: CHECKSUM_UNNECESSARY 
                 * or CHECKSUM_NONE */

int max_desc; /* Max number of LRO descriptors  */ 
     int max_aggr; /* Max number of LRO packets to be aggregated */

int frag_align_pad; /* Padding required to properly align layer 3 
                 * headers in generated skb when using frags */

struct net_lro_desc *lro_arr; /* Array of LRO descriptors */

/* 
     * Optimized driver functions 
     * 
     * get_skb_header: returns tcp and ip header for packet in SKB 
     */ 
     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 /* ip_hdr is IPv4 header */ 
 #define LRO_TCP  2 /* tcpudp_hdr is TCP header */

/* 
     * 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) 
     */ 
     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_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);

  GRO (Generic Receive Offload)

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

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

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

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

  4、实现中,只支持 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 的接口

struct packet_type { 
     __be16              type;      /* This is really htons(ether_type). */ 
     struct net_device      *dev;      /* NULL is wildcarded here          */ 
     int              (*func) (struct sk_buff *, 
                     struct net_device *, 
                     struct packet_type *, 
                     struct net_device *); 
     struct sk_buff          *(*gso_segment)(struct sk_buff *skb, 
                         int features); 
     int              (*gso_send_check)(struct sk_buff *skb); 
     struct sk_buff          **(*gro_receive)(struct sk_buff **head, 
                           struct sk_buff *skb); 
     int              (*gro_complete)(struct sk_buff *skb); 
     void              *af_packet_priv; 
     struct list_head      list; 
 };

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

  1、源 / 目的地址匹配;

  2、TOS/ 协议字段匹配;

  3、源 / 目的端口匹配。

  而很多其它事件将导致 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)

网络性能优化(NAPI)相关推荐

  1. Linux内核网络性能优化

    Linux内核网络性能优化 1. 前言 2. Linux网络协议栈 3. DPDK 4. XDP 4.1 XDP主要的特性 4.2 XDP与DPDK的对比 4.3 应用场景 5. CPU负载均衡 5. ...

  2. 服务器性能优化之网络性能优化

    hi ,大家好,今天分享一篇后台服务器性能优化之网络性能优化,希望大家对Linux网络有更深的理解. 曾几何时,一切都是那么简单.网卡很慢,只有一个队列.当数据包到达时,网卡通过DMA复制数据包并发送 ...

  3. 44 | 套路篇:网络性能优化的几个思路(下)

    在优化网络的性能时,你可以结合 Linux 系统的网络协议栈和网络收发流程,然后从应用程序.套接字.传输层.网络层再到链路层等每个层次,进行逐层优化.上一期我们主要学习了应用程序和套接字的优化思路,比 ...

  4. 43 | 套路篇:网络性能优化的几个思路(上)

    上一节,我们了解了 NAT(网络地址转换)的原理,学会了如何排查 NAT 带来的性能问题,最后还总结了 NAT 性能优化的基本思路.我先带你简单回顾一下. NAT 基于 Linux 内核的连接跟踪机制 ...

  5. 性能优化系列(五)网络性能优化

    文章首发「Android波斯湾」公众号,更新地址:https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode 移动互联网时代,用户对网 ...

  6. 华为开发者大会主题演讲:抖音短视频网络性能优化实践

    内容来源:华为开发者大会2021 HMS Core 6 System技术论坛,主题演讲<抖音短视频网络性能优化实践>. 演讲嘉宾:卡涛,抖音Android架构师 大家好!我是来自字节跳动抖 ...

  7. 网络性能优化的几个思路

    本文是通过学习倪朋飞老师的<Linux性能优化实战> :网络性能优化的几个思路? 网络性能优化的几个思路 确定优化目标 网络性能工具 网络性能优化 应用程序 套接字 网络性能优化 传输层 ...

  8. Android 网络性能优化(4)弱网优化

    系列文章目录 1. Android 网络性能优化(1)概述 2. Android 网络性能优化(2)DNS优化 3. Android 网络性能优化(3)复用连接池 4. Android 网络性能优化( ...

  9. Android 网络性能优化(1)概述

    但是对于大型的App来说,仅仅是使用这些是不够的,它太机械,不能帮我们处理复杂多变的网络情况. 在我的上个公司,智能设备的网络连接是老大难问题,有时候设备连不上Wifi的情况下,开发人员去跟进,到最后 ...

  10. Android 网络性能优化-概述和DNS优化

    1. 移动App网络优化背景 对于Android来说,开发者可以轻松的打造一套 MVP + Retrofit + RxJava 的框架来处理所有的网络请求.因为 Retrofit下层封装的OkHttp ...

最新文章

  1. 第1关:求1000以内所有的水仙花数
  2. Python+pandas计算数据相关系数(person、Kendall、spearman)
  3. Kubernetes权威指南精彩语录
  4. 操作系统:ucore的部分Bug挑战练习
  5. Create Tables and Build inserts from Tables by using Mygeneration Templates(Sql Server)
  6. 校园表白墙APP使用体验
  7. mysql 线性表_数据结构-线性表之顺序表
  8. 每日一shell(八)nginx日志切割
  9. 带彩色字体的man pages(debian centos)
  10. 机器阅读(一)--整体概述
  11. 服务器里怎么更改网站图片大小,php实现在服务器端调整图片大小的方法
  12. Python接口自动化之yaml配置文件
  13. JavaSpring框架有哪些优势?
  14. 动态规划实战15 leetcode-256. Paint House
  15. 移动硬盘上安装Windows 10系统
  16. 第三章 教育法律法规
  17. 《缠中说禅108课》27: 盘整背驰与历史性底部
  18. Minio分布式文件系统学习笔记
  19. ohoTips - 最屌的消息弹窗工具!
  20. Amazon Alexa系列介绍(3)--Alexa Voice Service API介绍

热门文章

  1. 卷积法求解系统的零状态响应_动态系统的建模与分析
  2. 解决django需要手动调整数据库,避免manage.py各种报错
  3. c-free5.0运行程序错误_web前端之异常/错误监控
  4. 日志分析平台-ELK
  5. 判断数组是否有重复值
  6. Crazypony四轴飞行器代码框架
  7. 06_Jedis完成MySQL的条件查询案例
  8. ACM_基础知识(二)
  9. SPOJ GCDEX (数论)
  10. 一起学Windows Phone 7开发(九.Windows Phone Developer Tools Beta)