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並不是太明顯,不知道在極限的效能測試下會是神馬情況