5.1 GRO(Generic Receive Offload)

GRO(Generic Receive Offload)在网卡驱动收包时通过napi_gro_receive函数合并数据包,提高效率。当支持分散-聚集IO,合并发生在skb的frag page数组;否则,发生在frag_list。GRO队列中的包由napi_gro_flush送入协议栈,避免长时间滞留。经过GRO处理的skb在协议栈中,通过pskb_may_pull整合数据到线性空间,确保TCP处理不受影响。最终,应用进程通过tcp_recvmsg函数接收数据。
摘要由CSDN通过智能技术生成
  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_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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值