根據上篇博文的介紹,GRO需要支援GRO的每種協定都要實作自己的封包比對合并函數和合并完成函數。這裡我們先來看看鍊路層上
實作的自己的GRO函數。
鍊路層的接收比對函數 __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;
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;
if (!(skb->dev->features & NETIF_F_GRO))
{
goto normal;
}
if (skb_is_gso(skb) || skb_has_frags(skb))
{
goto normal;
}
rcu_read_lock();
list_for_each_entry_rcu(ptype, head, list)
{
if (ptype->type != type || ptype->dev || !ptype->gro_receive)
continue;
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;
pp = ptype->gro_receive(&napi->gro_list, skb);
break;
}
rcu_read_unlock();
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;
if (pp)
{
struct sk_buff *nskb = *pp;
*pp = nskb->next;
nskb->next = NULL;
napi_gro_complete(nskb);
napi->gro_count--;
}
if (same_flow)
{
goto ok;
}
if (NAPI_GRO_CB(skb)->flush ||
napi->gro_count >= MAX_GRO_SKBS)
{
goto normal;
}
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:
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;
}
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);
}
我們從上面分析看到,鍊路層處理完鍊路層上GRO的處理後,會再調用網絡層上對應得GRO處理。每一層協定自己負責自己的GRO處理。上次處理完後把處理結果返給下一層。最終由鍊路層來根據處理結果來把封包送給協定棧 。
下文我們分析一下IP層對GRO的處理實作。