根据上篇博文的介绍,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;
/*遍历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层进行处理,这里就不进行GRO的处理了。*/
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);
}
我们从上面分析看到,链路层处理完链路层上GRO的处理后,会再调用网络层上对应得GRO处理。每一层协议自己负责自己的GRO处理。上次处理完后把处理结果返给下一层。最终由链路层来根据处理结果来把报文送给协议栈 。
下文我们分析一下IP层对GRO的处理实现。
阅读(2393) | 评论(0) | 转发(0) |