根据上篇博文的介绍,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) { int grow = skb_gro_offset(skb) - skb_headlen(skb); BUG_ON(skb->end - skb->tail 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的处理实现。