Linux 下网络性能优化方法简析

概述

对于网络的行为,可以简单划分为 3 条路径:1) 发送路径,2) 转发路径,3) 接收路径,而网络性能的优化则可基于这 3 条路径来考虑。由于数据包的转发一般是具备路由功能的设备所关注,在本文中没有叙述,读者如果有兴趣,可以自行学习(在 Linux 内核中,分别使用了基于哈希的路由查找和基于动态 Trie 的路由查找算法)。本文集中于发送路径和接收路径上的优化方法分析,其中的 NAPI 本质上是接收路径上的优化,但因为它在 Linux 的内核出现时间较早,而它也是后续出现的各种优化方法的基础,所以将其单独分析。

最为基本的 NAPI

NAPI

NAPI 的核心在于:在一个繁忙网络,每次有网络数据包到达时,不需要都引发中断,因为高频率的中断可能会影响系统的整体效率,假象一个场景,我们此时使用标准的 100M 网卡,可能实际达到的接收速率为 80Mbits/s,而此时数据包平均长度为 1500Bytes,则每秒产生的中断数目为:

80M bits/s / (8 Bits/Byte * 1500 Byte) = 6667 个中断 /s

每 秒 6667 个中断,对于系统是个很大的压力,此时其实可以转为使用轮询 (polling) 来处理,而不是中断;但轮询在网络流量较小的时没有效率,因此低流量时,基于中断的方式则比较合适,这就是 NAPI 出现的原因,在低流量时候使用中断接收数据包,而在高流量时候则使用基于轮询的方式接收。

现在内核中 NIC 基本上已经全部支持 NAPI 功能,由前面的叙述可知,NAPI 适合处理高速率数据包的处理,而带来的好处则是:

  • 中断缓和 (Interrupt mitigation),由上面的例子可以看到,在高流量下,网卡产生的中断可能达到每秒几千次,而如果每次中断都需要系统来处理,是一个很大的压力,而 NAPI 使用轮询时是禁止了网卡的接收中断的,这样会减小系统处理中断的压力
  • 数据包节流 (Packet throttling),NAPI 之前的 Linux NIC 驱动总在接收到数据包之后产生一个 IRQ,接着在中断服务例程里将这个 skb 加入本地的 softnet,然后触发本地 NET_RX_SOFTIRQ 软中断后续处理。如果包速过高,因为 IRQ 的优先级高于 SoftIRQ,导致系统的大部分资源都在响应中断,但 softnet 的队列大小有限,接收到的超额数据包也只能丢掉,所以这时这个模型是在用宝贵的系统资源做无用功。而 NAPI 则在这样的情况下,直接把包丢掉,不会继续将需要丢掉的数据包扔给内核去处理,这样,网卡将需要丢掉的数据包尽可能的早丢弃掉,内核将不可见需要丢掉的数 据包,这样也减少了内核的压力

对 NAPI 的使用,一般包括以下的几个步骤:

  1. 在中断处理函数中,先禁止接收中断,且告诉网络子系统,将以轮询方式快速收包,其中禁止接收中断完全由硬件功能决定,而告诉内核将以轮询方式处理包则是使 用函数 netif_rx_schedule(),也可以使用下面的方式,其中的 netif_rx_schedule_prep 是为了判定现在是否已经进入了轮询模式 ::

    清单 1. 将网卡预定为轮询模式
             void netif_rx_schedule(struct net_device *dev);
       或者
             if (netif_rx_schedule_prep(dev))
                     __netif_rx_schedule(dev);
  2. 在驱动中创建轮询函数,它的工作是从网卡获取数据包并将其送入到网络子系统,其原型是:
    清单 2. NAPI 的轮询方法
        int (*poll)(struct net_device *dev, int *budget);

    这里的轮询函数用于在将网卡切换为轮询模式之后,用 poll() 方法处理接收队列中的数据包,如队列为空,则重新切换为中断模式。切换回中断模式需要先关闭轮询模式,使用的是函数 netif_rx_complete (),接着开启网卡接收中断 .。

    清单 3. 退出轮询模式
             void netif_rx_complete(struct net_device *dev);
  3. 在驱动中创建轮询函数,需要和实际的网络设备 struct net_device 关联起来,这一般在网卡的初始化时候完成,示例代码如下:
    清单 4. 设置网卡支持轮询模式
       dev->poll = my_poll;
       dev->weight = 64;

    里面另外一个字段为权重 (weight),该值并没有一个非常严格的要求,实际上是个经验数据,一般 10Mb 的网卡,我们设置为 16,而更快的网卡,我们则设置为 64。

NAPI 的一些相关 Interface

下面是 NAPI 功能的一些接口,在前面都基本有涉及,我们简单看看:

netif_rx_schedule(dev)

在网卡的中断处理函数中调用,用于将网卡的接收模式切换为轮询

netif_rx_schedule_prep(dev)

在网卡是 Up 且运行状态时,将该网卡设置为准备将其加入到轮询列表的状态,可以将该函数看做是 netif_rx_schedule(dev) 的前半部分

__netif_rx_schedule(dev)

将设备加入轮询列表,前提是需要 netif_schedule_prep(dev) 函数已经返回了 1

__netif_rx_schedule_prep(dev)

与 netif_rx_schedule_prep(dev) 相似,但是没有判断网卡设备是否 Up 及运行,不建议使用

netif_rx_complete(dev)

用于将网卡接口从轮询列表中移除,一般在轮询函数完成之后调用该函数。

__netif_rx_complete(dev)

Newer newer NAPI

其实之前的 NAPI(New API) 这样的命名已经有点让人忍俊不禁了,可见 Linux 的内核极客们对名字的掌控,比对代码的掌控差太多,于是乎,连续的两次对 NAPI 的重构,被戏称为 Newer newer NAPI 了。

与 netif_rx_complete(dev) 类似,但是需要确保本地中断被禁止

Newer newer NAPI

在最初实现的 NAPI 中,有 2 个字段在结构体 net_device 中,分别为轮询函数 poll() 和权重 weight,而所谓的 Newer newer NAPI,是在 2.6.24 版内核之后,对原有的 NAPI 实现的几次重构,其核心是将 NAPI 相关功能和 net_device 分离,这样减少了耦合,代码更加的灵活,因为 NAPI 的相关信息已经从特定的网络设备剥离了,不再是以前的一对一的关系了。例如有些网络适配器,可能提供了多个 port,但所有的 port 却是共用同一个接受数据包的中断,这时候,分离的 NAPI 信息只用存一份,同时被所有的 port 来共享,这样,代码框架上更好地适应了真实的硬件能力。Newer newer NAPI 的中心结构体是napi_struct:

清单 5. NAPI 结构体
 /*
 * Structure for NAPI scheduling similar to tasklet but with weighting
 */
 struct napi_struct {
	 /* The poll_list must only be managed by the entity which
	 * changes the state of the NAPI_STATE_SCHED bit.  This means
	 * whoever atomically sets that bit can add this napi_struct
	 * to the per-cpu poll_list, and whoever clears that bit
	 * can remove from the list right before clearing the bit.
	 */
	 struct list_head 	 poll_list; 

	 unsigned long 		 state;
	 int 			 weight;
	 int 			 (*poll)(struct napi_struct *, int);
 #ifdef CONFIG_NETPOLL
	 spinlock_t 		 poll_lock;
	 int 			 poll_owner;
 #endif 

	 unsigned int 		 gro_count; 

	 struct net_device 	 *dev;
	 struct list_head 	 dev_list;
	 struct sk_buff 		 *gro_list;
	 struct sk_buff 		 *skb;
 };

熟悉老的 NAPI 接口实现的话,里面的字段 poll_list、state、weight、poll、dev、没什么好说的,gro_count 和 gro_list 会在后面讲述 GRO 时候会讲述。需要注意的是,与之前的 NAPI 实现的最大的区别是该结构体不再是 net_device 的一部分,事实上,现在希望网卡驱动自己单独分配与管理 napi 实例,通常将其放在了网卡驱动的私有信息,这样最主要的好处在于,如果驱动愿意,可以创建多个 napi_struct,因为现在越来越多的硬件已经开始支持多接收队列 (multiple receive queues),这样,多个 napi_struct 的实现使得多队列的使用也更加的有效。

与最初的 NAPI 相比较,轮询函数的注册有些变化,现在使用的新接口是:

 void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
		    int (*poll)(struct napi_struct *, int), int weight)

熟悉老的 NAPI 接口的话,这个函数也没什么好说的。

值得注意的是,前面的轮询 poll() 方法原型也开始需要一些小小的改变:

    int (*poll)(struct napi_struct *napi, int budget);

大部分 NAPI 相关的函数也需要改变之前的原型,下面是打开轮询功能的 API:

    void netif_rx_schedule(struct net_device *dev,
                           struct napi_struct *napi);
    /* ...or... */
    int netif_rx_schedule_prep(struct net_device *dev,
			       struct napi_struct *napi);
    void __netif_rx_schedule(struct net_device *dev,
		       	     struct napi_struct *napi);

轮询功能的关闭则需要使用 :

    void netif_rx_complete(struct net_device *dev,
			   struct napi_struct *napi);

因为可能存在多个 napi_struct 的实例,要求每个实例能够独立的使能或者禁止,因此,需要驱动作者保证在网卡接口关闭时,禁止所有的 napi_struct 的实例。

函数 netif_poll_enable() 和 netif_poll_disable() 不再需要,因为轮询管理不再和 net_device 直接管理,取而代之的是下面的两个函数:

    void napi_enable(struct napi *napi);
    void napi_disable(struct napi *napi);

发送路径上的优化

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 的代码如下:

    清单 6. 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,就不用在内核中分片,而只需一次性的推送到网络接口,由网络接口去执行分片功能。

  • 当一个 TCP 的 socket 被创建,其中一个职责是设置该连接的能力,在网络层的 socket 的表示是 struck sock,其中有一个字段 sk_route_caps 标示该连接的能力,在 TCP 的三路握手完成之后,将基于网络接口的能力和连接来设置该字段。
    清单 7. 网路层对 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,因此需要硬件支持校验计算。

  • 现在,一切的准备工作都已经做好了,当实际的数据需要传输时,需要使用我们设置好的 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 执行分片,这样尽量推迟分片的时间以提高性能:

清单 8. 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 的核心结构体如下:

清单 9. 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,对于这样的驱动,应该使用下面的接口:

清单 10. 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,直接传递个网络协议栈:

清单 11. 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);

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 方法。

清单 12. 协议层支持 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 为例,匹配的条件有:

  • 源 / 目的地址匹配
  • TOS/ 协议字段匹配
  • 源 / 目的端口匹配

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

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

清单 13. 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)

小结

从 上面的分析,可以看到,Linux 网络性能优化方法,就像一部进化史,但每步的演化,都让解决问题的办法更加的通用,更加的灵活;从 NAPI 到 Newer newer NAPI,从 TSO 到 GSO,从 LRO 到 GRO,都是一个从特例到一个更通用的解决办法的演化,正是这种渐进但连续的演化,让 Linux 保有了如此的活力。

http://www.ibm.com/developerworks/cn/linux/l-cn-network-pt/

时间: 2024-11-05 10:06:44

Linux 下网络性能优化方法简析的相关文章

Linux网络性能优化方法简析

Linux网络性能优化方法简析 2010-12-20 10:56 赵军 IBMDW 字号:T | T 性能问题永远是永恒的主题之一,而Linux在网络性能方面的优势则显而易见,这篇文章是对于Linux内核中提升网络性能的一些优化方法的简析,以让我们去后台看看魔术师表演用的盒子,同时也看看内核极客们是怎样灵活的,渐进的去解决这些实际的问题. AD:2014WOT全球软件技术峰会北京站 课程视频发布 对于网络的行为,可以简单划分为 3 条路径:1) 发送路径,2) 转发路径,3) 接收路径,而网络性

通过/proc/sys/net/ipv4/优化Linux下网络性能

通过/proc/sys/net/ipv4/优化Linux下网络性能 /proc/sys/net/ipv4/优化1)      /proc/sys/net/ipv4/ip_forward该文件表示是否打开IP转发.0,禁止1,转发 缺省设置:02)      /proc/sys/net/ipv4/ip_default_ttl   该文件表示一个数据报的生存周期(Time To Live),即最多经过多少路由器.   缺省设置:64 增加该值会降低系统性能. 3)      /proc/sys/ne

Oracle在Linux下的性能优化

Oracle数据库内存参数的优化 Ø       与oracle相关的系统内核参数 Ø       SGA.PGA参数设置   Oracle下磁盘存储性能优化 Ø       文件系统的选择(ext2/ext3.xfs.ocfs2) Ø       Oracle ASM存储  1.优化oracle性能参数之前要了解的情况 1)物理内存有多大 2)操作系统估计要使用多大内存 3)数据库是使用文件系统还是裸设备 4)有多少并发连接 5)应用是OLTP类型还是OLAP类型 2.oracle数据库内存参

redmine在linux上的mysql性能优化方法与问题排查方案

iredmine的linux服务器mysql性能优化方法与问题排查方案 问题定位: 客户端工具: 1. 浏览器inspect-tool的network timing工具分析 2. 浏览器查看 response header, 分析http server 与 web server.       服务器工具:   0. nmon 查看各类系统负载, rrdtool 查看网络状况.   1. uptime看cpu负载;    free看内存;  mem ; cat /proc/meminfo以及  i

Android性能优化方法(八)

Android SDK tools目录下提供一个观察布局的工具,层级观察器(Hierarchy Viewer).Hierarchy Viewer工具是一个非常好的布局优化工具,同时,你也可以通过它学习他人的布局.应该说是一个非常实用的工具. AD:WOT2014:用户标签系统与用户数据化运营培训专场 层级观察器(Hierarchy Viewer): Android SDK tools目录下提供一个观察布局的工具,层级观察器(Hierarchy Viewer).Hierarchy Viewer工具

GNU Linux高并发性能优化方案

/*********************************************************** * Author : Samson * Date : 07/14/2015 * Test platform: * gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2 * GNU bash, 4.3.11(1)-release (x86_64-pc-linux-gnu) * Nginx version: * Nginx 1.6.2 * Nginx 1.8.0

sqlserver2008 死锁解决方法及性能优化方法

sqlserver2008 死锁解决方法及性能优化方法 原文: http://blog.csdn.net/kuui_chiu/article/details/48621939 十步优化SQL Server中的数据访问 http://tech.it168.com/a2009/1125/814/000000814758_2.shtml 关于死锁: [sql] view plain copy sp_who active  --看看哪个引起的死锁, blk里面即阻塞的spid: dbcc inputbu

Android性能优化方法(六)

ContentProvider优化改进 1.索引简单的说,索引就像书本的目录,目录可以快速找到所在页数,数据库中索引可以帮助快速找到数据,而不用全表扫描,合适的索引可以大大提高数据库查询的效率.(1). 优点大大加快了数据库检索的速度,包括对单表查询.连表查询.分组查询.排序查询.经常是一到两个数量级的性能提升,且随着数据数量级增长. (2). 缺点索引的创建和维护存在消耗,索引会占用物理空间,且随着数据量的增加而增加.在对数据库进行增删改时需要维护索引,所以会对增删改的性能存在影响. (3).

Linux下网络流量实时监控工具

Linux下网络流量实时监控工具大全 在工作中发现,经常因为业务的原因,需要即时了解某台服务器网卡的流量,虽然公司也部署了cacti软件,但cacti是五分钟统计的,没有即时性,并且有时候打开监控页面不方便,个人喜欢随手在某台服务器上输入一个命令,查看网卡即时流量.百度了一下,发现有这么几种方法,现对此类软件进行了一个总结.一.iptraf软件   rhel的iso里有包含,我公司的系统,并没有默认安装,它功能强大,可以按照协议,网卡等进行分析.1.1 iptraf安装源码安装wget ftp: