linux核心網路通訊協定棧學習筆記:關於GRO/GSO/LRO/TSO等patch的分析和測試

來源:互聯網
上載者:User

TSO,全稱是TCP Segmentation Offload,我們知道通常乙太網路的MTU是1500,除去TCP/IP的包頭,TCP的MSS (Max Segment Size)大小是1460,通常情況下協議棧會對超過1460的TCP payload進行segmentation,保證產生的IP包不超過MTU的大小,但是對於支援TSO/GSO的網卡而言,就沒這個必要了,我們可以把最多64K大小的TCP payload直接往下傳給協議棧,此時IP層也不會進行segmentation,一直會傳給網卡驅動,支援TSO/GSO的網卡會自己產生TCP/IP包頭和幀頭,這樣可以offload很多協議棧上的記憶體操作,checksum計算等原本靠CPU來做的工作都移給了網卡

GSO是TSO的增強 http://lwn.net/Articles/188489/ ,GSO不只針對TCP,而是對任意協議,儘可能把segmentation推後到交給網卡那一刻,此時會判斷下網卡是否支援SG和GSO,如果不支援則在協議棧裡做segmentation;如果支援則把payload直接發給網卡

ethtool -k lo
Offload parameters for lo:
rx-checksumming: on
tx-checksumming: on
scatter-gather: on
tcp segmentation offload: on
udp fragmentation offload: off
generic segmentation offload: on
generic-receive-offload: on

目前很多網卡都支援tso,但很少有支援ufo的,而gso/gro和網卡無關,只是核心的特性。gso用來delay segmentation,一直到 dev_hard_start_xmit 函數

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
            struct netdev_queue *txq)
{
    const struct net_device_ops *ops = dev->netdev_ops;
    int rc;
    unsigned int skb_len;

    if (likely(!skb->next)) {
        if (!list_empty(&ptype_all))
            dev_queue_xmit_nit(skb, dev);

        if (netif_needs_gso(dev, skb)) {
            if (unlikely(dev_gso_segment(skb)))
                goto out_kfree_skb;
            if (skb->next)
                goto gso;
        }

......

gso:
    do {
        struct sk_buff *nskb = skb->next;

        skb->next = nskb->next;
        nskb->next = NULL;
        skb_len = nskb->len;
        rc = ops->ndo_start_xmit(nskb, dev);
        trace_net_dev_xmit(nskb, rc, dev, skb_len);
        if (unlikely(rc != NETDEV_TX_OK)) {
            nskb->next = skb->next;
            skb->next = nskb;
            return rc;
        }
        txq_trans_update(txq);
        if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
            return NETDEV_TX_BUSY;
    } while (skb->next);

    skb->destructor = DEV_GSO_CB(skb)->destructor;

out_kfree_skb:
    kfree_skb(skb);
    return NETDEV_TX_OK;
}

dev_hard_start_xmit 裡判斷 netif_needs_gso 判斷網卡是否支援gso,如果不支援則調用 dev_gso_segment 裡面又調用 skb_gso_segment 把報文分區,對於ipv4而言,實際調用了 tcp_tso_segment,最後返回多個sk_buff 組成的鏈表,頭指標存在 skb->next 裡;如果網卡本身支援的話,直接把大塊的skb交給網卡:調用netdev_ops->ndo_start_xmit 發送出去

可以看到,在判斷netif_need_gso時,是要檢查網卡的netdev->features值的,我們可以在include/linux/netdevice.h中看到這些值:

#define NETIF_F_SG      1   /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM     2   /* Can checksum TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM     4   /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM     8   /* Can checksum all the packets. */

#define NETIF_F_FRAGLIST    64  /* Scatter/gather IO. */

#define NETIF_F_GSO     2048    /* Enable software GSO. */

#define NETIF_F_GSO_SHIFT   16
#define NETIF_F_GSO_MASK    0x00ff0000
#define NETIF_F_TSO     (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO     (SKB_GSO_UDP << NETIF_F_GSO_SHIFT)

對於要支援TSO的網卡而言,需要有 NETIF_F_SG | NETIF_F_TSO | NETIF_F_IP_CSUM,相應如果要支援UFO,應該就需要 NETIF_F_SG | NETIF_F_UFO | NETIF_F_IP_CSUM

下面做個測試來考量下tso, gso對效能的影響,本人手頭的測試機不支援ufo,所以只好拿tcp來測試了

scatter-gather: on
tcp segmentation offload: on
udp fragmentation offload: off
generic segmentation offload: on
generic-receive-offload: on

Recv   Send    Send                          

Socket Socket  Message  Elapsed              
Size   Size    Size     Time     Throughput  
bytes  bytes   bytes    secs.    10^6bits/sec  

87380  65536  65536    10.00    26864.51  

關閉了tso, gso之後

Recv   Send    Send                          
Socket Socket  Message  Elapsed              
Size   Size    Size     Time     Throughput  
bytes  bytes   bytes    secs.    10^6bits/sec  

87380  65536  65536    10.00    18626.44   

對於如果只是關閉gso而言,throughput不太穩定,但平均下來還是比gso開啟有點降低的

 順便說下,tso, gso效果隨著MTU增大越來越不明顯,

#ifconfig lo mtu 65535

之後 netperf -t TCP_STREAM 測下來,tso開或者關已經差別不大了,10%左右吧

GSO的commit 在這裡, http://marc.info/?l=git-commits-head&m=115107854915578&w=2 這個patch很老了。。新核心已經改了很多

主要增加了 dev_gso_segment,skb_gso_segment 函數,修改了dev_hard_start_xmit ,dev_queue_xmit 函數,這些之前已經提過了

------------------------------ 華麗的分割線 ------------------------------------

LRO(Large Receive Offload)是針對TCP的機制,GRO(Generic Receive Offload) 是LRO的增強版,對skb merge的限制更多,同時不限於tcp/ip,本文主要講GRO,因為很多網卡驅動貌似已經不支援LRO,只支援GRO了

如果驅動開啟了gro特性,會調用napi_gro_receive來收包,而不是通常的netif_receive_skb或者netif_rx,可以看到gro是和napi_struct緊密綁在一起的,我們這裡回到之前研究過很多遍的napi_struct結構上來

struct napi_struct {
    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_struct 包含了 gro_list 一個skb的鏈表,鏈表中的每一個skb都代表了一個flow,gro_count代表了flow的個數

napi_gro_receive 會調用 __napi_gro_receive_gr,之後又會調用 __napi_gro_receive, __napi_gro_receive 會遍曆 napi_struct->gro_list,通過比較skb->dev,和skb的mac_header來確定是否屬於同一個flow,並存在napi_gro_cb->flow中。這裡要提下 struct napi_gro_cb 結構,對於通過gro處理的每一個skb,都在skb->cb儲存一個私人資料結構的指標,就是這個napi_gro_cb。
注意這裡skb的私人資料結構只是個void*,和skb_shared_info不要搞混了,後者是在sk_buff後面的一塊線性記憶體

struct napi_gro_cb {
    /* Virtual address of skb_shinfo(skb)->frags[0].page + offset. */
    void *frag0;
    
    /* Length of frag0. */
    unsigned int frag0_len;
    
    /* This indicates where we are processing relative to skb->data. */
    int data_offset;
            
    /* This is non-zero if the packet may be of the same flow. */
    int same_flow;
    
    /* This is non-zero if the packet cannot be merged with the new skb. */
    int flush;
        
    /* Number of segments aggregated. */
    int count;
    
    /* Free the skb? */
    int free;
};

然後 __napi_gro_receive 會調用 dev_gro_receive ,dev_gro_receive會先調用ptype->gso_receive,一般而言就是ip協議對應的inet_gso_receive

inet_gro_receive 主要做如下事情:

首先拿到ip包頭,然後對包頭做check,如果check passed則開始遍曆napi_struct->gro_list,根據ip saddr, daddr, tos, protocol等來和那些之前在二層有可能是同一flow的skb進行判斷,如果不一致就把same_flow置0,當然光是slow_flow並不能就此開始merge,還要進行flush的判斷,任何flush判斷不過都會放棄merge而調用直接調用skb_gro_flush函數交到協議棧上層去處理

ip層結束之後,會繼續tcp層的gro_receive,調用tcp_gro_receive ,其核心也是遍曆napi_struct->gro_list,基於source addr判斷是否是same_flow,對是否需要flush做計算,這裡提一下關於ack一致的要求,ack一致說明是同一個tcp payload被tso/gso分段之後的結果,所以是必需條件

如果 tcp 也認為不需要flush,那麼會進到 skb_gro_receive 中,這個函數就是用來合并的,第一個參數是gro_list裡的那個skb,第二個是新來的skb,這裡不多說了,我推薦的部落格文章裡講的很清楚了。其實就分兩種情況,如果是scatter-gather的skb包,則把新skb裡的frags的資料放到gro_list的skb對應的frags資料後面;否則skb資料都在skb的線性地址中,這樣直接alloc一個新的skb,把新skb掛到frag_list裡面,最後放到原來gro_list的位置上;如果gro_list的skb已經有了frag_list,那麼就直接掛進去好了

現在返回到dev_gro_receive中了,這時如果需要flush或者same_flow為0,說明需要傳給上層協議棧了,此時調用napi_gro_complete

走到最後一種情況即這個skb是個新的flow,那麼就加到gro_list的鏈表中

最後提下,所謂flush是指把現有gro_list中的skb flush到上層協議,千萬別搞反了

更多關於gro詳細的說明請參考 http://marc.info/?l=git-commits-head&m=123050372721308&w=2

這篇部落格對GRO解釋的要清楚很多 http://www.pagefault.info/?p=159

實際測試下來,TSO在對效能的提升上非常明顯,但是GRO並不是太明顯,不知道在極限的效能測試下會是神馬情況

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.