鏈路層的接收匹配函數__napi_gro_receive(napi, skb):
該函數對報文進行匹配,並不合併報文。
匹配規則(必須同時滿足以下兩個條件):
1、兩個報文的接收dev必須相同。
2、兩個報文的以太頭必須相同。
static int __napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) { struct sk_buff *p; /*遍歷napi 實例上的gro_list上掛的skb, 根據上面說的匹配規則設置鏈表上報文的same字段*/ for (p = napi->gro_list; p; p = p->next) { NAPI_GRO_CB(p)->same_flow = (p->dev == skb->dev) && !compare_ether_header(skb_mac_header(p), skb_gro_mac_header(skb)); NAPI_GRO_CB(p)->flush = 0; } return dev_gro_receive(napi, skb); }
int dev_gro_receive(struct napi_struct *napi, struct sk_buff *skb) { struct sk_buff **pp = NULL; struct packet_type *ptype; __be16 type = skb->protocol; struct list_head *head = &ptype_base[ntohs(type) & PTYPE_HASH_MASK]; int same_flow; int mac_len; int ret; /*如果接收網絡設備設置成不支持GRO功能,就不進行GRO合併處理*/ if (!(skb->dev->features & NETIF_F_GRO)) { goto normal; } /*如果是ip 分片報文,不進行GRO處理, * 因爲在ip層會對ip分片報文進行合併 */ if (skb_is_gso(skb) || skb_has_frags(skb)) { goto normal; } /*加RCU讀鎖對 ptype_base hahs 鏈表進行保護*/ rcu_read_lock(); /*遍歷鏈表,找到處理該類型報文的ptype, *並且該類型的ptype 實現了處理gro 的函數 */ list_for_each_entry_rcu(ptype, head, list) { if (ptype->type != type || ptype->dev || !ptype->gro_receive) continue; /*如果找到了,初始化報文頭指針, *並重置 skb中GRO使用的私有字段, *這些字段會在相應協議實現的GRO處理函數中進行設置 */ skb_set_network_header(skb, skb_gro_offset(skb)); mac_len = skb->network_header - skb->mac_header; skb->mac_len = mac_len; NAPI_GRO_CB(skb)->same_flow = 0; NAPI_GRO_CB(skb)->flush = 0; NAPI_GRO_CB(skb)->free = 0; /*調用該協議類型註冊的GRO處理函數對報文進行處理*/ pp = ptype->gro_receive(&napi->gro_list, skb); break; } rcu_read_unlock(); /*如果沒找到對該協議類型報文進行處理的GRO,不進行GRO操作*/ if (&ptype->list == head) { goto normal; } same_flow = NAPI_GRO_CB(skb)->same_flow; ret = NAPI_GRO_CB(skb)->free ? GRO_MERGED_FREE : GRO_MERGED; /*如果協議的GRO處理函數返回了合併後的報文, *就調用napi_gro_complete把報文送進協議棧進行處理 */ if (pp) { struct sk_buff *nskb = *pp; *pp = nskb->next; nskb->next = NULL; napi_gro_complete(nskb); napi->gro_count--; } /*如果same 被設置了,說明在鏈表上找到了相匹配的報文了, *已經合併過了,不再需要緩存了 */ if (same_flow) { goto ok; } /*如果沒找到相匹配的報文,需要緩存。 *緩存前需要判斷隊列是否已滿或該報文是否應該緩存 */ if (NAPI_GRO_CB(skb)->flush || napi->gro_count >= MAX_GRO_SKBS) { goto normal; } /*緩存沒有匹配的報文到gro_list,返回值爲GRO_HELD*/ napi->gro_count++; NAPI_GRO_CB(skb)->count = 1; skb_shinfo(skb)->gso_size = skb_gro_len(skb); skb->next = napi->gro_list; napi->gro_list = skb; ret = GRO_HELD; pull: /*經過這個協議棧的GRO receive的處理, *這時NAPI_GRO_CB(skb)->data_offset字段已經設置好了。 *如果GRO需要處理的數據不在skb的線性區, *把需要的數據copy到線性區,方便以後操作 */ if (skb_headlen(skb) < skb_gro_offset(skb)) { int grow = skb_gro_offset(skb) - skb_headlen(skb); BUG_ON(skb->end - skb->tail < grow); memcpy(skb_tail_pointer(skb), NAPI_GRO_CB(skb)->frag0, grow); skb->tail += grow; skb->data_len -= grow; skb_shinfo(skb)->frags[0].page_offset += grow; skb_shinfo(skb)->frags[0].size -= grow; /*如果把數據移入線性區後第一頁就空了, *釋放空頁並把後續頁依次前移 */ if (unlikely(!skb_shinfo(skb)->frags[0].size)) { put_page(skb_shinfo(skb)->frags[0].page); memmove(skb_shinfo(skb)->frags, skb_shinfo(skb)->frags + 1, (--skb_shinfo(skb)->nr_frags * sizeof(skb_frag_t))); } } ok: return ret; normal: ret = GRO_NORMAL; goto pull; }
鏈路層的GRO完成函數:
合併完成後的報文調用該函數來把報文送入協議棧。
static int napi_gro_complete(struct sk_buff *skb) { struct packet_type *ptype; __be16 type = skb->protocol; struct list_head *head = &ptype_base[ntohs(type) & PTYPE_HASH_MASK]; int err = -ENOENT; /*如果沒有和別的報文合併過, *就可以直接送協議棧進行處理了 */ if (NAPI_GRO_CB(skb)->count == 1) { skb_shinfo(skb)->gso_size = 0; goto out; } /*找到相關協議把報文送給協議的grp_complete函數處理*/ rcu_read_lock(); list_for_each_entry_rcu(ptype, head, list) { if (ptype->type != type || ptype->dev || !ptype->gro_complete ) continue; err = ptype->gro_complete(skb); break; } rcu_read_unlock(); if (err) { WARN_ON(&ptype->list == head); kfree_skb(skb); return NET_RX_SUCCESS; } /*各層協議處理完成後,送給協議棧進行處理*/ out: return netif_receive_skb(skb); }