Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1801476
  • 博文数量: 306
  • 博客积分: 3133
  • 博客等级: 中校
  • 技术积分: 3932
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-19 16:50
文章分类

全部博文(306)

文章存档

2018年(7)

2017年(18)

2016年(39)

2015年(35)

2014年(52)

2013年(39)

2012年(22)

2011年(29)

2010年(53)

2009年(12)

分类: LINUX

2016-07-19 19:14:17

根据上篇博文的介绍,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的处理实现。
阅读(2415) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~