成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux網(wǎng)絡(luò)性能優(yōu)化方法簡(jiǎn)析

運(yùn)維 系統(tǒng)運(yùn)維
性能問(wèn)題永遠(yuǎn)是永恒的主題之一,而Linux在網(wǎng)絡(luò)性能方面的優(yōu)勢(shì)則顯而易見(jiàn),這篇文章是對(duì)于Linux內(nèi)核中提升網(wǎng)絡(luò)性能的一些優(yōu)化方法的簡(jiǎn)析,以讓我們?nèi)ズ笈_(tái)看看魔術(shù)師表演用的盒子,同時(shí)也看看內(nèi)核極客們是怎樣靈活的,漸進(jìn)的去解決這些實(shí)際的問(wèn)題。

對(duì)于網(wǎng)絡(luò)的行為,可以簡(jiǎn)單劃分為 3 條路徑:1) 發(fā)送路徑,2) 轉(zhuǎn)發(fā)路徑,3) 接收路徑,而網(wǎng)絡(luò)性能的優(yōu)化則可基于這 3 條路徑來(lái)考慮。由于數(shù)據(jù)包的轉(zhuǎn)發(fā)一般是具備路由功能的設(shè)備所關(guān)注,在本文中沒(méi)有敘述,讀者如果有興趣,可以自行學(xué)習(xí)(在 Linux 內(nèi)核中,分別使用了基于哈希的路由查找和基于動(dòng)態(tài) Trie 的路由查找算法)。本文集中于發(fā)送路徑和接收路徑上的優(yōu)化方法分析,其中的 NAPI 本質(zhì)上是接收路徑上的優(yōu)化,但因?yàn)樗?Linux 的內(nèi)核出現(xiàn)時(shí)間較早,而它也是后續(xù)出現(xiàn)的各種優(yōu)化方法的基礎(chǔ),所以將其單獨(dú)分析。

最為基本的 NAPI

NAPI

NAPI 的核心在于:在一個(gè)繁忙網(wǎng)絡(luò),每次有網(wǎng)絡(luò)數(shù)據(jù)包到達(dá)時(shí),不需要都引發(fā)中斷,因?yàn)楦哳l率的中斷可能會(huì)影響系統(tǒng)的整體效率,假象一個(gè)場(chǎng)景,我們此時(shí)使用標(biāo)準(zhǔn)的 100M 網(wǎng)卡,可能實(shí)際達(dá)到的接收速率為 80MBits/s,而此時(shí)數(shù)據(jù)包平均長(zhǎng)度為 1500Bytes,則每秒產(chǎn)生的中斷數(shù)目為:

80M bits/s / (8 Bits/Byte * 1500 Byte) = 6667 個(gè)中斷 /s

每秒 6667 個(gè)中斷,對(duì)于系統(tǒng)是個(gè)很大的壓力,此時(shí)其實(shí)可以轉(zhuǎn)為使用輪詢 (polling) 來(lái)處理,而不是中斷;但輪詢?cè)诰W(wǎng)絡(luò)流量較小的時(shí)沒(méi)有效率,因此低流量時(shí),基于中斷的方式則比較合適,這就是 NAPI 出現(xiàn)的原因,在低流量時(shí)候使用中斷接收數(shù)據(jù)包,而在高流量時(shí)候則使用基于輪詢的方式接收。

現(xiàn)在內(nèi)核中 NIC 基本上已經(jīng)全部支持 NAPI 功能,由前面的敘述可知,NAPI 適合處理高速率數(shù)據(jù)包的處理,而帶來(lái)的好處則是:

  • 中斷緩和 (Interrupt mitigation),由上面的例子可以看到,在高流量下,網(wǎng)卡產(chǎn)生的中斷可能達(dá)到每秒幾千次,而如果每次中斷都需要系統(tǒng)來(lái)處理,是一個(gè)很大的壓力,而 NAPI 使用輪詢時(shí)是禁止了網(wǎng)卡的接收中斷的,這樣會(huì)減小系統(tǒng)處理中斷的壓力
  • 數(shù)據(jù)包節(jié)流 (Packet throttling),NAPI 之前的 Linux NIC 驅(qū)動(dòng)總在接收到數(shù)據(jù)包之后產(chǎn)生一個(gè) IRQ,接著在中斷服務(wù)例程里將這個(gè) skb 加入本地的 softnet,然后觸發(fā)本地 NET_RX_SOFTIRQ 軟中斷后續(xù)處理。如果包速過(guò)高,因?yàn)?IRQ 的優(yōu)先級(jí)高于 SoftIRQ,導(dǎo)致系統(tǒng)的大部分資源都在響應(yīng)中斷,但 softnet 的隊(duì)列大小有限,接收到的超額數(shù)據(jù)包也只能丟掉,所以這時(shí)這個(gè)模型是在用寶貴的系統(tǒng)資源做無(wú)用功。而 NAPI 則在這樣的情況下,直接把包丟掉,不會(huì)繼續(xù)將需要丟掉的數(shù)據(jù)包扔給內(nèi)核去處理,這樣,網(wǎng)卡將需要丟掉的數(shù)據(jù)包盡可能的早丟棄掉,內(nèi)核將不可見(jiàn)需要丟掉的數(shù)據(jù)包,這樣也減少了內(nèi)核的壓力

對(duì) NAPI 的使用,一般包括以下的幾個(gè)步驟:

  1. 在中斷處理函數(shù)中,先禁止接收中斷,且告訴網(wǎng)絡(luò)子系統(tǒng),將以輪詢方式快速收包,其中禁止接收中斷完全由硬件功能決定,而告訴內(nèi)核將以輪詢方式處理包則是使用函數(shù) netif_rx_schedule(),也可以使用下面的方式,其中的 netif_rx_schedule_prep 是為了判定現(xiàn)在是否已經(jīng)進(jìn)入了輪詢模式 ::

    清單 1. 將網(wǎng)卡預(yù)定為輪詢模式

    				
             void netif_rx_schedule(struct net_device *dev); 
       或者
             if (netif_rx_schedule_prep(dev)) 
                     __netif_rx_schedule(dev); 
    

  2. 在驅(qū)動(dòng)中創(chuàng)建輪詢函數(shù),它的工作是從網(wǎng)卡獲取數(shù)據(jù)包并將其送入到網(wǎng)絡(luò)子系統(tǒng),其原型是:

    清單 2. NAPI 的輪詢方法

    				
        int (*poll)(struct net_device *dev, int *budget); 
    

    這里的輪詢函數(shù)用于在將網(wǎng)卡切換為輪詢模式之后,用 poll() 方法處理接收隊(duì)列中的數(shù)據(jù)包,如隊(duì)列為空,則重新切換為中斷模式。切換回中斷模式需要先關(guān)閉輪詢模式,使用的是函數(shù) netif_rx_complete (),接著開(kāi)啟網(wǎng)卡接收中斷 .。

    清單 3. 退出輪詢模式

    				
             void netif_rx_complete(struct net_device *dev); 
    

  3. 在驅(qū)動(dòng)中創(chuàng)建輪詢函數(shù),需要和實(shí)際的網(wǎng)絡(luò)設(shè)備 struct net_device 關(guān)聯(lián)起來(lái),這一般在網(wǎng)卡的初始化時(shí)候完成,示例代碼如下:

    清單 4. 設(shè)置網(wǎng)卡支持輪詢模式

    				
       dev->poll = my_poll; 
       dev->weight = 64; 
    

    里面另外一個(gè)字段為權(quán)重 (weight),該值并沒(méi)有一個(gè)非常嚴(yán)格的要求,實(shí)際上是個(gè)經(jīng)驗(yàn)數(shù)據(jù),一般 10Mb 的網(wǎng)卡,我們?cè)O(shè)置為 16,而更快的網(wǎng)卡,我們則設(shè)置為 64。

NAPI 的一些相關(guān) Interface

下面是 NAPI 功能的一些接口,在前面都基本有涉及,我們簡(jiǎn)單看看:

netif_rx_schedule(dev)

在網(wǎng)卡的中斷處理函數(shù)中調(diào)用,用于將網(wǎng)卡的接收模式切換為輪詢

netif_rx_schedule_prep(dev)

在網(wǎng)卡是 Up 且運(yùn)行狀態(tài)時(shí),將該網(wǎng)卡設(shè)置為準(zhǔn)備將其加入到輪詢列表的狀態(tài),可以將該函數(shù)看做是 netif_rx_schedule(dev) 的前半部分

__netif_rx_schedule(dev)

將設(shè)備加入輪詢列表,前提是需要 netif_schedule_prep(dev) 函數(shù)已經(jīng)返回了 1

__netif_rx_schedule_prep(dev)

與 netif_rx_schedule_prep(dev) 相似,但是沒(méi)有判斷網(wǎng)卡設(shè)備是否 Up 及運(yùn)行,不建議使用

netif_rx_complete(dev)

用于將網(wǎng)卡接口從輪詢列表中移除,一般在輪詢函數(shù)完成之后調(diào)用該函數(shù)。

__netif_rx_complete(dev)

Newer newer NAPI

其實(shí)之前的 NAPI(New API) 這樣的命名已經(jīng)有點(diǎn)讓人忍俊不禁了,可見(jiàn) Linux 的內(nèi)核極客們對(duì)名字的掌控,比對(duì)代碼的掌控差太多,于是乎,連續(xù)的兩次對(duì) NAPI 的重構(gòu),被戲稱為 Newer newer NAPI 了。

與 netif_rx_complete(dev) 類似,但是需要確保本地中斷被禁止

Newer newer NAPI

在最初實(shí)現(xiàn)的 NAPI 中,有 2 個(gè)字段在結(jié)構(gòu)體 net_device 中,分別為輪詢函數(shù) poll() 和權(quán)重 weight,而所謂的 Newer newer NAPI,是在 2.6.24 版內(nèi)核之后,對(duì)原有的 NAPI 實(shí)現(xiàn)的幾次重構(gòu),其核心是將 NAPI 相關(guān)功能和 net_device 分離,這樣減少了耦合,代碼更加的靈活,因?yàn)?NAPI 的相關(guān)信息已經(jīng)從特定的網(wǎng)絡(luò)設(shè)備剝離了,不再是以前的一對(duì)一的關(guān)系了。例如有些網(wǎng)絡(luò)適配器,可能提供了多個(gè) port,但所有的 port 卻是共用同一個(gè)接受數(shù)據(jù)包的中斷,這時(shí)候,分離的 NAPI 信息只用存一份,同時(shí)被所有的 port 來(lái)共享,這樣,代碼框架上更好地適應(yīng)了真實(shí)的硬件能力。Newer newer NAPI 的中心結(jié)構(gòu)體是napi_struct:

清單 5. NAPI 結(jié)構(gòu)體

				
 /* 
 * 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 接口實(shí)現(xiàn)的話,里面的字段 poll_list、state、weight、poll、dev、沒(méi)什么好說(shuō)的,gro_count 和 gro_list 會(huì)在后面講述 GRO 時(shí)候會(huì)講述。需要注意的是,與之前的 NAPI 實(shí)現(xiàn)的最大的區(qū)別是該結(jié)構(gòu)體不再是 net_device 的一部分,事實(shí)上,現(xiàn)在希望網(wǎng)卡驅(qū)動(dòng)自己?jiǎn)为?dú)分配與管理 napi 實(shí)例,通常將其放在了網(wǎng)卡驅(qū)動(dòng)的私有信息,這樣最主要的好處在于,如果驅(qū)動(dòng)愿意,可以創(chuàng)建多個(gè) napi_struct,因?yàn)楝F(xiàn)在越來(lái)越多的硬件已經(jīng)開(kāi)始支持多接收隊(duì)列 (multiple receive queues),這樣,多個(gè) napi_struct 的實(shí)現(xiàn)使得多隊(duì)列的使用也更加的有效。

與最初的 NAPI 相比較,輪詢函數(shù)的注冊(cè)有些變化,現(xiàn)在使用的新接口是:

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

熟悉老的 NAPI 接口的話,這個(gè)函數(shù)也沒(méi)什么好說(shuō)的。

值得注意的是,前面的輪詢 poll() 方法原型也開(kāi)始需要一些小小的改變:

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

大部分 NAPI 相關(guān)的函數(shù)也需要改變之前的原型,下面是打開(kāi)輪詢功能的 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); 

輪詢功能的關(guān)閉則需要使用 :

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

因?yàn)榭赡艽嬖诙鄠€(gè) napi_struct 的實(shí)例,要求每個(gè)實(shí)例能夠獨(dú)立的使能或者禁止,因此,需要驅(qū)動(dòng)作者保證在網(wǎng)卡接口關(guān)閉時(shí),禁止所有的 napi_struct 的實(shí)例。

函數(shù) netif_poll_enable() 和 netif_poll_disable() 不再需要,因?yàn)檩喸児芾聿辉俸?net_device 直接管理,取而代之的是下面的兩個(gè)函數(shù):

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

#p#

發(fā)送路徑上的優(yōu)化

TSO (TCP Segmentation Offload)

TSO (TCP Segmentation Offload) 是一種利用網(wǎng)卡分割大數(shù)據(jù)包,減小 CPU 負(fù)荷的一種技術(shù),也被叫做 LSO (Large segment offload) ,如果數(shù)據(jù)包的類型只能是 TCP,則被稱之為 TSO,如果硬件支持 TSO 功能的話,也需要同時(shí)支持硬件的 TCP 校驗(yàn)計(jì)算和分散 - 聚集 (Scatter Gather) 功能。

可以看到 TSO 的實(shí)現(xiàn),需要一些基本條件,而這些其實(shí)是由軟件和硬件結(jié)合起來(lái)完成的,對(duì)于硬件,具體說(shuō)來(lái),硬件能夠?qū)Υ蟮臄?shù)據(jù)包進(jìn)行分片,分片之后,還要能夠?qū)γ總€(gè)分片附著相關(guān)的頭部。TSO 的支持主要有需要以下幾步:

  • 如果網(wǎng)路適配器支持 TSO 功能,需要聲明網(wǎng)卡的能力支持 TSO,這是通過(guò)以 NETIF_F_TSO 標(biāo)志設(shè)置 net_device structure 的 features 字段來(lái)表明,例如,在 benet(drivers/net/benet/be_main.c) 網(wǎng)卡的驅(qū)動(dòng)程序中,設(shè)置 NETIF_F_TSO 的代碼如下:

    清單 6. benet 網(wǎng)卡驅(qū)動(dòng)聲明支持 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); 
     } 
    

    在代碼中,同時(shí)也用 netif_set_gso_max_size 函數(shù)設(shè)置了 net_device 的 gso_max_size 字段。該字段表明網(wǎng)絡(luò)接口一次能處理的最大 buffer 大小,一般該值為 64Kb,這意味著只要 TCP 的數(shù)據(jù)大小不超過(guò) 64Kb,就不用在內(nèi)核中分片,而只需一次性的推送到網(wǎng)絡(luò)接口,由網(wǎng)絡(luò)接口去執(zhí)行分片功能。

  • 當(dāng)一個(gè) TCP 的 socket 被創(chuàng)建,其中一個(gè)職責(zé)是設(shè)置該連接的能力,在網(wǎng)絡(luò)層的 socket 的表示是 struck sock,其中有一個(gè)字段 sk_route_caps 標(biāo)示該連接的能力,在 TCP 的三路握手完成之后,將基于網(wǎng)絡(luò)接口的能力和連接來(lái)設(shè)置該字段。

    清單 7. 網(wǎng)路層對(duì) TSO 功能支持的設(shè)定

    				
     /* 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() 函數(shù)則設(shè)置了上面所說(shuō)的 sk_route_caps 字段,同時(shí)也檢查了硬件是否支持分散 - 聚集功能和硬件校驗(yàn)計(jì)算功能。需要這 2 個(gè)功能的原因是:Buffer 可能不在一個(gè)內(nèi)存頁(yè)面上,所以需要分散 - 聚集功能,而分片后的每個(gè)分段需要重新計(jì)算 checksum,因此需要硬件支持校驗(yàn)計(jì)算。

  • 現(xiàn)在,一切的準(zhǔn)備工作都已經(jīng)做好了,當(dāng)實(shí)際的數(shù)據(jù)需要傳輸時(shí),需要使用我們?cè)O(shè)置好的 gso_max_size,我們知道,TCP 向 IP 層發(fā)送數(shù)據(jù)會(huì)考慮 mss,使得發(fā)送的 IP 包在 MTU 內(nèi),不用分片。而 TSO 設(shè)置的 gso_max_size 就影響該過(guò)程,這主要是在計(jì)算 mss_now 字段時(shí)使用。如果內(nèi)核不支持 TSO 功能,mss_now 的最大值為“MTU – HLENS”,而在支持 TSO 的情況下,mss_now 的最大值為“gso_max_size -HLENS”,這樣,從網(wǎng)絡(luò)層帶驅(qū)動(dòng)的路徑就被打通了。

GSO (Generic Segmentation Offload)

TSO 是使得網(wǎng)絡(luò)協(xié)議棧能夠?qū)⒋髩K buffer 推送至網(wǎng)卡,然后網(wǎng)卡執(zhí)行分片工作,這樣減輕了 CPU 的負(fù)荷,但 TSO 需要硬件來(lái)實(shí)現(xiàn)分片功能;而性能上的提高,主要是因?yàn)檠泳彿制鴾p輕了 CPU 的負(fù)載,因此,可以考慮將 TSO 技術(shù)一般化,因?yàn)槠浔举|(zhì)實(shí)際是延緩分片,這種技術(shù),在 Linux 中被叫做 GSO(Generic Segmentation Offload),它比 TSO 更通用,原因在于它不需要硬件的支持分片就可使用,對(duì)于支持 TSO 功能的硬件,則先經(jīng)過(guò) GSO 功能,然后使用網(wǎng)卡的硬件分片能力執(zhí)行分片;而對(duì)于不支持 TSO 功能的網(wǎng)卡,將分片的執(zhí)行,放在了將數(shù)據(jù)推送的網(wǎng)卡的前一刻,也就是在調(diào)用驅(qū)動(dòng)的 xmit 函數(shù)前。

我們?cè)賮?lái)看看內(nèi)核中數(shù)據(jù)包的分片都有可能在哪些時(shí)刻:

  1. 在傳輸協(xié)議中,當(dāng)構(gòu)造 skb 用于排隊(duì)的時(shí)候
  2. 在傳輸協(xié)議中,但是使用了 NETIF_F_GSO 功能,當(dāng)即將傳遞個(gè)網(wǎng)卡驅(qū)動(dòng)的時(shí)候
  3. 在驅(qū)動(dòng)程序里,此時(shí)驅(qū)動(dòng)支持 TSO 功能 ( 設(shè)置了 NETIF_F_TSO 標(biāo)志 )

對(duì)于支持 GSO 的情況,主要使用了情況 2 或者是情況 2.、3,其中情況二是在硬件不支持 TSO 的情況下,而情況 2、3 則是在硬件支持 TSO 的情況下。

代碼中是在 dev_hard_start_xmit 函數(shù)里調(diào)用 dev_gso_segment 執(zhí)行分片,這樣盡量推遲分片的時(shí)間以提高性能:

清單 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 { 
			……

		 } 

		……

 } 

#p#

接收路徑上的優(yōu)化

LRO (Large Receive Offload)

Linux 在 2.6.24 中加入了支持 IPv4 TCP 協(xié)議的 LRO (Large Receive Offload) ,它通過(guò)將多個(gè) TCP 數(shù)據(jù)聚合在一個(gè) skb 結(jié)構(gòu),在稍后的某個(gè)時(shí)刻作為一個(gè)大數(shù)據(jù)包交付給上層的網(wǎng)絡(luò)協(xié)議棧,以減少上層協(xié)議棧處理 skb 的開(kāi)銷,提高系統(tǒng)接收 TCP 數(shù)據(jù)包的能力。

當(dāng)然,這一切都需要網(wǎng)卡驅(qū)動(dòng)程序支持。理解 LRO 的工作原理,需要理解 sk_buff 結(jié)構(gòu)體對(duì)于負(fù)載的存儲(chǔ)方式,在內(nèi)核中,sk_buff 可以有三種方式保存真實(shí)的負(fù)載:

  1. 數(shù)據(jù)被保存在 skb->data 指向的由 kmalloc 申請(qǐng)的內(nèi)存緩沖區(qū)中,這個(gè)數(shù)據(jù)區(qū)通常被稱為線性數(shù)據(jù)區(qū),數(shù)據(jù)區(qū)長(zhǎng)度由函數(shù) skb_headlen 給出
  2. 數(shù)據(jù)被保存在緊隨 skb 線性數(shù)據(jù)區(qū)尾部的共享結(jié)構(gòu)體 skb_shared_info 中的成員 frags 所表示的內(nèi)存頁(yè)面中,skb_frag_t 的數(shù)目由 nr_frags 給出,skb_frags_t 中有數(shù)據(jù)在內(nèi)存頁(yè)面中的偏移量和數(shù)據(jù)區(qū)的大小
  3. 數(shù)據(jù)被保存于 skb_shared_info 中的成員 frag_list 所表示的 skb 分片隊(duì)列中

合并了多個(gè) skb 的超級(jí) skb,能夠一次性通過(guò)網(wǎng)絡(luò)協(xié)議棧,而不是多次,這對(duì) CPU 負(fù)荷的減輕是顯然的。

LRO 的核心結(jié)構(gòu)體如下:

清單 9. LRO 的核心結(jié)構(gòu)體

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

在該結(jié)構(gòu)體中:

dev:指向支持 LRO 功能的網(wǎng)絡(luò)設(shè)備

stats:包含一些統(tǒng)計(jì)信息,用于查看 LRO 功能的運(yùn)行情況

features:控制 LRO 如何將包送給網(wǎng)絡(luò)協(xié)議棧,其中的 LRO_F_NAPI 表明驅(qū)動(dòng)是 NAPI 兼容的,應(yīng)該使用 netif_receive_skb() 函數(shù),而 LRO_F_EXTRACT_VLAN_ID 表明驅(qū)動(dòng)支持 VLAN

ip_summed:表明是否需要網(wǎng)絡(luò)協(xié)議棧支持 checksum 校驗(yàn)

ip_summed_aggr:表明聚集起來(lái)的大數(shù)據(jù)包是否需要網(wǎng)絡(luò)協(xié)議棧去支持 checksum 校驗(yàn)

max_desc:表明最大數(shù)目的 LRO 描述符,注意,每個(gè) LRO 的描述符描述了一路 TCP 流,所以該值表明了做多同時(shí)能處理的 TCP 流的數(shù)量

max_aggr:是最大數(shù)目的包將被聚集成一個(gè)超級(jí)數(shù)據(jù)包

lro_arr:是描述符數(shù)組,需要驅(qū)動(dòng)自己提供足夠的內(nèi)存或者在內(nèi)存不足時(shí)處理異常

get_skb_header()/get_frag_header():用于快速定位 IP 或者 TCP 的頭,一般驅(qū)動(dòng)只提供其中的一個(gè)實(shí)現(xiàn)

一般在驅(qū)動(dòng)中收包,使用的函數(shù)是 netif_rx 或者 netif_receive_skb,但在支持 LRO 的驅(qū)動(dòng)中,需要使用下面的函數(shù),這兩個(gè)函數(shù)將進(jìn)來(lái)的數(shù)據(jù)包根據(jù) LRO 描述符進(jìn)行分類,如果可以進(jìn)行聚集,則聚集為一個(gè)超級(jí)數(shù)據(jù)包,否者直接傳遞給內(nèi)核,走正常途徑。需要 lro_receive_frags 函數(shù)的原因是某些驅(qū)動(dòng)直接將數(shù)據(jù)包放入了內(nèi)存頁(yè),之后去構(gòu)造 sk_buff,對(duì)于這樣的驅(qū)動(dòng),應(yīng)該使用下面的接口:

清單 10. LRO 收包函數(shù)

				
 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); 

因?yàn)?LRO 需要聚集到 max_aggr 數(shù)目的數(shù)據(jù)包,但有些情況下可能導(dǎo)致延遲比較大,這種情況下,可以在聚集了部分包之后,直接傳遞給網(wǎng)絡(luò)協(xié)議棧處理,這時(shí)可以使用下面的函數(shù),也可以在收到某個(gè)特殊的包之后,不經(jīng)過(guò) LRO,直接傳遞個(gè)網(wǎng)絡(luò)協(xié)議棧:

清單 11. LRO flush 函數(shù)

				
    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 的核心在于:在接收路徑上,將多個(gè)數(shù)據(jù)包聚合成一個(gè)大的數(shù)據(jù)包,然后傳遞給網(wǎng)絡(luò)協(xié)議棧處理,但 LRO 的實(shí)現(xiàn)中存在一些瑕疵:

  • 數(shù)據(jù)包合并可能會(huì)破壞一些狀態(tài)
  • 數(shù)據(jù)包合并條件過(guò)于寬泛,導(dǎo)致某些情況下本來(lái)需要區(qū)分的數(shù)據(jù)包也被合并了,這對(duì)于路由器是不可接收的
  • 在虛擬化條件下,需要使用橋接功能,但 LRO 使得橋接功能無(wú)法使用
  • 實(shí)現(xiàn)中,只支持 IPv4 的 TCP 協(xié)議

而解決這些問(wèn)題的辦法就是新提出的 GRO(Generic Receive Offload),首先,GRO 的合并條件更加的嚴(yán)格和靈活,并且在設(shè)計(jì)時(shí),就考慮支持所有的傳輸協(xié)議,因此,后續(xù)的驅(qū)動(dòng),都應(yīng)該使用 GRO 的接口,而不是 LRO,內(nèi)核可能在所有先有驅(qū)動(dòng)遷移到 GRO 接口之后將 LRO 從內(nèi)核中移除。而 Linux 網(wǎng)絡(luò)子系統(tǒng)的維護(hù)者 David S. Miller 就明確指出,現(xiàn)在的網(wǎng)卡驅(qū)動(dòng),有 2 個(gè)功能需要使用,一是使用 NAPI 接口以使得中斷緩和 (interrupt mitigation) ,以及簡(jiǎn)單的互斥,二是使用 GRO 的 NAPI 接口去傳遞數(shù)據(jù)包給網(wǎng)路協(xié)議棧。

在 NAPI 實(shí)例中,有一個(gè) GRO 的包的列表 gro_list,用堆積收到的包,GRO 層用它來(lái)將聚集的包分發(fā)到網(wǎng)絡(luò)協(xié)議層,而每個(gè)支持 GRO 功能的網(wǎng)絡(luò)協(xié)議層,則需要實(shí)現(xiàn) gro_receive 和 gro_complete 方法。

清單 12. 協(xié)議層支持 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 用于嘗試匹配進(jìn)來(lái)的數(shù)據(jù)包到已經(jīng)排隊(duì)的 gro_list 列表,而 IP 和 TCP 的頭部則在匹配之后被丟棄;而一旦我們需要向上層協(xié)議提交數(shù)據(jù)包,則調(diào)用 gro_complete 方法,將 gro_list 的包合并成一個(gè)大包,同時(shí) checksum 也被更新。在實(shí)現(xiàn)中,并沒(méi)要求 GRO 長(zhǎng)時(shí)間的去實(shí)現(xiàn)聚合,而是在每次 NAPI 輪詢操作中,強(qiáng)制傳遞 GRO 包列表跑到上層協(xié)議。GRO 和 LRO 的最大區(qū)別在于,GRO 保留了每個(gè)接收到的數(shù)據(jù)包的熵信息,這對(duì)于像路由器這樣的應(yīng)用至關(guān)重要,并且實(shí)現(xiàn)了對(duì)各種協(xié)議的支持。以 IPv4 的 TCP 為例,匹配的條件有:

  • 源 / 目的地址匹配
  • TOS/ 協(xié)議字段匹配
  • 源 / 目的端口匹配

而很多其它事件將導(dǎo)致 GRO 列表向上層協(xié)議傳遞聚合的數(shù)據(jù)包,例如 TCP 的 ACK 不匹配或者 TCP 的序列號(hào)沒(méi)有按序等等。

GRO 提供的接口和 LRO 提供的接口非常的類似,但更加的簡(jiǎn)潔,對(duì)于驅(qū)動(dòng),明確可見(jiàn)的只有 GRO 的收包函數(shù)了 , 因?yàn)榇蟛糠值墓ぷ鲗?shí)際是在協(xié)議層做掉了:

清單 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) 

小結(jié)

從上面的分析,可以看到,Linux 網(wǎng)絡(luò)性能優(yōu)化方法,就像一部進(jìn)化史,但每步的演化,都讓解決問(wèn)題的辦法更加的通用,更加的靈活;從 NAPI 到 Newer newer NAPI,從 TSO 到 GSO,從 LRO 到 GRO,都是一個(gè)從特例到一個(gè)更通用的解決辦法的演化,正是這種漸進(jìn)但連續(xù)的演化,讓 Linux 保有了如此的活力。

原文:http://www.ibm.com/developerworks/cn/linux/l-cn-network-pt/index.html?ca=drs-

【編輯推薦】

  1. 在VMware中對(duì)SUSE Linux的性能優(yōu)化手冊(cè)
  2. FreeBSD入門指南——安裝配置與系統(tǒng)優(yōu)化
  3. 優(yōu)化Linux生產(chǎn)服務(wù)器的經(jīng)驗(yàn)之談
責(zé)任編輯:yangsai 來(lái)源: IBMDW
相關(guān)推薦

2010-04-06 15:33:45

CDMA無(wú)線網(wǎng)絡(luò)優(yōu)化

2011-07-20 10:20:04

2010-06-21 14:05:08

Linux APM

2009-09-03 17:10:57

2019-12-10 08:10:35

LinuxCPU性能優(yōu)化

2009-11-24 13:08:52

SuSE Linux 390

2011-07-12 14:04:58

2023-11-01 11:59:13

2023-12-18 12:19:27

2010-09-28 10:34:06

無(wú)線網(wǎng)絡(luò)互聯(lián)

2009-09-03 14:20:21

C#日期格式化

2017-07-14 08:14:54

Python函數(shù)

2009-06-08 21:25:29

Java聲音技術(shù)

2010-09-08 16:25:39

SIP協(xié)議棧

2010-09-10 09:52:44

開(kāi)源協(xié)議棧

2010-09-13 16:58:13

2024-05-20 13:07:43

2023-12-13 18:31:47

2022-06-27 13:28:33

安全風(fēng)險(xiǎn)風(fēng)險(xiǎn)評(píng)估方法

2009-08-12 16:38:35

C#讀取XML節(jié)點(diǎn)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 欧美一级在线免费 | 日韩欧美一区二区三区四区 | 九九精品影院 | 欧美一级大片免费观看 | 久久99精品久久久久久秒播九色 | 五月婷婷在线播放 | 国产一区二区 | 美女视频久久 | 精久久久久 | 精品国产一区二区国模嫣然 | 福利视频日韩 | 国产成人精品网站 | 午夜99| 一级黄色日本片 | 一级黄色毛片子 | 免费黄色的网站 | 亚洲3级 | 日韩在线播放第一页 | 在线免费黄色小视频 | av一级久久 | 精品视频一区二区三区 | 成人国产精品免费观看 | 欧美狠狠操 | 人人九九精 | 日朝毛片 | 中文字幕亚洲精品 | 九一精品| 91久久 | 国产激情| 91精品国产乱码久久蜜臀 | 亚洲精精品 | 国产成人精品亚洲日本在线观看 | 一级片免费视频 | 国产精品自拍视频网站 | 在线观看视频91 | 国产精品亚洲成在人线 | 国产97碰免费视频 | 91成人在线 | 精品视频在线一区 | 精品国产乱码久久久久久果冻传媒 | 国产分类视频 |