GRO(Generic Receive Offload)的功能将多个 TCP 数据聚合在一个skb结构,然后作为一个大数据包交付给上层的网络协议栈,以减少上层协议栈处理skb的开销,提高系统接收TCP数据包的性能。这个功能需要网卡驱动程序的支持。合并了多个skb的超级 skb能够一次性通过网络协议栈,从而减轻CPU负载。
GRO是针对网络收包流程进行改进的,并且只有NAPI类型的驱动才支持此功能。因此如果要支持GRO,不仅要内核支持,驱动也必须调用相应的接口来开启此功能。用ethtool -K gro on来开启GRO,如果报错就说明网卡驱动本身就不支持GRO。
GRO与TSO类似,但TSO只支持发送数据包。支持GRO的驱动会在NAPI的回调poll方法中读取数据包,然后调用GRO的接口napi_gro_receive或者napi_gro_frags来将数据包送进协议栈。
GRO是针对网络收包流程进行改进的,并且只有NAPI类型的驱动才支持此功能。因此如果要支持GRO,不仅要内核支持,驱动也必须调用相应的接口来开启此功能。用ethtool -K gro on来开启GRO,如果报错就说明网卡驱动本身就不支持GRO。
GRO与TSO类似,但TSO只支持发送数据包。支持GRO的驱动会在NAPI的回调poll方法中读取数据包,然后调用GRO的接口napi_gro_receive或者napi_gro_frags来将数据包送进协议栈。
下面分析GRO处理数据包的流程。网卡驱动在收包时会调用napi_gro_receive函数接收数据:
3863 static void skb_gro_reset_offset(struct sk_buff *skb)
3864 {
3865 const struct skb_shared_info *pinfo = skb_shinfo(skb);
3866 const skb_frag_t *frag0 = &pinfo->frags[0];
3867
3868 NAPI_GRO_CB(skb)->data_offset = 0;
3869 NAPI_GRO_CB(skb)->frag0 = NULL;
3870 NAPI_GRO_CB(skb)->frag0_len = 0;
3871
3872 if (skb->mac_header == skb->tail &&
3873 pinfo->nr_frags &&
3874 !PageHighMem(skb_frag_page(frag0))) {//如果mac_header和skb->tail相等并且地址不在高端内存,则说明包头保存在skb_shinfo中
3875 NAPI_GRO_CB(skb)->frag0 = skb_frag_address(frag0);
3876 NAPI_GRO_CB(skb)->frag0_len = skb_frag_size(frag0);
3877 }
3878 }
3879
3880 gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
3881 {
3882 skb_gro_reset_offset(skb);
3883
3884 return napi_skb_finish(dev_gro_receive(napi, skb), skb);
3885 }
GRO将数据送进协议栈的点有两处,一个是在napi_skb_finish里,它会通过判断dev_gro_receive的返回值,来决定是否需要将数据包送入进协议栈;还有一个点是当napi的循环执行完毕执行napi_complete的时候。先来看napi_skb_finish
函数:
3836 static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
3837 {
3838 switch (ret) {
3839 case GRO_NORMAL://将数据包送进协议栈
3840 if (netif_receive_skb(skb))
3841 ret = GRO_DROP;
3842 break;
3843
3844 case GRO_DROP:
3845 kfree_skb(skb);
3846 break;
3847
3848 case GRO_MERGED_FREE://表示skb可以被free,因为GRO已经将skb合并并保存起来
3849 if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)
3850 kmem_cache_free(skbuff_head_cache, skb);
3851 else
3852 __kfree_skb(skb);
3853 break;
3854
3855 case GRO_HELD://这个表示当前数据已经被GRO保存起来,但是并没有进行合并,因此skb还需要保存。
3856 case GRO_MERGED:
3857 break;
3858 }
3859
3860 return ret;
3861 }
dev_gro_receive函数用于合并skb,并决定是否将合并后的大skb送入网络协议栈:
3743 static enum gro_result dev_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
3744 {
3745 struct sk_buff **pp = NULL;
3746 struct packet_offload *ptype;
3747 __be16 type = skb->protocol;
3748 struct list_head *head = &offload_base;
3749 int same_flow;
3750 enum gro_result ret;
3751
3752 if (!(skb->dev->features & NETIF_F_GRO) || netpoll_rx_on(skb))//不支持GRO
3753 goto normal;
3754
3755 if (skb_is_gso(skb) || skb_has_frag_list(skb))//skb是GSO或skb有非线性空间的数据;这个skb已经是聚合状态了,不需要再次聚合
3756 goto normal;
3757
3758 gro_list_prepare(napi, skb);//比较GRO队列中的skb与当前skb在链路层是否属于同一个流
3759
3760 rcu_read_lock();
3761 list_for_each_entry_rcu(ptype, head, list) {//遍历GRO处理函数注册队列
3762 if (ptype->type != type || !ptype->callbacks.gro_receive)//查找网络层GRO处理函数
3763 continue;
3764
3765 skb_set_network_header(skb, skb_gro_offset(skb));
3766 skb_reset_mac_len(skb);
3767 NAPI_GRO_CB(skb)->same_flow = 0;
3768 NAPI_GRO_CB(skb)->flush = 0;
3769 NAPI_GRO_CB(skb)->free = 0;
3770
3771 pp = ptype->callbacks.gro_receive(&napi->gro_list, skb);//调用inet_gro_receive或ipv6_gro_receive合并skb
3772 break;
3773 }
3774 rcu_read_unlock();
3775
3776 if (&ptype->list == head)//没有找到处理函数
3777 goto normal;
3778
3779 same_flow = NAPI_GRO_CB(skb)->same_flow;
3780 ret = NAPI_GRO_CB(skb)->free ? GRO_MERGED_FREE : GRO_MERGED;
3781
3782 if (pp) {
3783 struct sk_buff *nskb = *pp;
3784
3785 *pp = nskb->next;
3786 nskb->next = NULL;
3787 napi_gro_complete(nskb);//更新聚合后的skb信息,将包送入协议栈
3788 napi->gro_count--;
3789 }
3790
3791 if (same_flow)//找到与当前skb属与同一个流的skb,此时当前skb已经聚合到所属的流中
3792 goto ok;
3793
3794 if (NAPI_GRO_CB(skb)->flush || napi->gro_count >= MAX_GRO_SKBS)//skb不能聚合或GRO队列已满
3795 goto normal;
3796
3797 napi->gro_count++;
3798 NAPI_GRO_CB(skb)->count = 1;
3799 NAPI_GRO_CB(skb)->age = jiffies;
3800 skb_shinfo(skb)->gso_size = skb_gro_len(skb);
3801 skb->next = napi->gro_list;//将skb是一个新包,在gro_list中没有能与之合并的,需要加入到GRO队列中
3802 napi->gro_list = skb;
3803 ret = GRO_HELD; //存储当前包,不能释放
3804
3805 pull:
3806 if (skb_headlen(skb) < skb_gro_offset(skb)) {//如果头不全部在线性区,则需要将其copy到线性区
3807 int grow = skb_gro_offset(skb) - skb_headlen(skb);
3808
3809 BUG_ON(skb->end - skb->tail < grow);
3810
3811 memcpy(skb_tail_pointer(skb), NAPI_GRO_CB(skb)->frag0, grow);//将第一个页的内容转移到线性空间
3812
3813 skb->tail += grow;
3814 skb->data_len -= grow;
3815
3816 skb_shinfo(skb)->frags[0].page_offset += grow;
3817 skb_frag_size_sub(&skb_shinfo(skb)->frags[0], grow);
3818
3819 if (unlikely(!skb_frag_size(&skb_shinfo(skb)->frags[0]))) {//第一个页的内容已经全部转移到线性空间
3820 skb_frag_unref(skb, 0); //释放页
3821 memmove(skb_shinfo(skb)->frags,
3822 skb_shinfo(skb)->frags + 1,
3823 --skb_shinfo(skb)->nr_frags * sizeof(skb_frag_t));//数组内容向前移动
3824 }
3825 }
3826
3827 ok:
3828 return ret;
3829
3830 normal:
3831 ret = GRO_NORMAL;
3832 goto pull;
3833 }
inet_gro_receive
函数是网络层skb聚合处理函数:
1351 static struct sk_buff **inet_gro_receive(struct sk_buff **head,
1352 struct sk_buff *skb)
1353 {
1354 const struct net_offload *ops;
1355 struct sk_buff **pp = NULL;
1356 struct sk_buff *p;
1357 const struct iphdr *iph;
1358 unsigned int hlen;
1359 unsigned int off;
1360 unsigned int id;
1361 int flush = 1;
1362 int proto;
1363
1364 off = skb_gro_offset(skb);
1365 hlen = off + sizeof(*iph); //MAC + IP头长度
1366 iph = skb_gro_header_fast(skb, off);
1367 if (skb_gro_header_hard(skb, hlen)) {
1368 iph = skb_gro