zoukankan      html  css  js  c++  java
  • alloc_skb申请函数分析

    alloc_skb()用于分配缓冲区的函数。由于"数据缓冲区"和"缓冲区的描述结构"(sk_buff结构)是两种不同的实体,这就意味着,在分配一个缓冲区时,需要分配两块内存(一个是缓冲区,一个是缓冲区的描述结构sk_buff)。

    首先看alloc_skb
    static inline struct sk_buff *alloc_skb(unsigned int size,
                        gfp_t priority)
    {
        return __alloc_skb(size, priority, 0, -1);
    }
    这个函数比较简单,参数中的size不用解释,为skb数据段的大小,但是第二个参数priority名字比较奇怪。叫优先级,实际上则是GFP MASK宏,如GFP_KERNEL,GFP_ATOMIC等。
     __alloc_skb()调用kmem_cache_alloc()从缓存中获取一个sk_buff结构,并调用kmalloc_track_caller分配缓冲区

    接下来看__alloc_skb
    /*
    参数:
    size:skb的数据大小
    gfp_mask:不用解释
    fclone:表示从哪个cache中分配
           当fclone为1时,从skbuff_fclone_cache上分配
           当fclone为0时,从skbuff_head_cache上分配
    node: NUMA节点
    */
    struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
                 int fclone, int node)
    {
        struct kmem_cache *cache;
        struct skb_shared_info *shinfo;
        struct sk_buff *skb;
        u8 *data;
         /* 获得指定的cache */
        cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
         /* 从cache上分配, 如果cache上无法分配,则从内存中申请 */
        /* Get the HEAD */
        skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
        if (!skb)
            goto out;
        prefetchw(skb);

        size = SKB_DATA_ALIGN(size);
        data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),
                gfp_mask, node);
        if (!data)
            goto nodata;
        prefetchw(data + size);

        /*
         * Only clear those fields we need to clear, not those that we will
         * actually initialise below. Hence, don't put any more fields after
         * the tail pointer in struct 
         */
        memset(skb, 0, offsetof(struct sk_buff, tail));
        skb->truesize = size + sizeof(struct sk_buff);
        atomic_set(&skb->users, 1);
        skb->head = data;
        skb->data = data;
        skb_reset_tail_pointer(skb);
        skb->end = skb->tail + size;
        kmemcheck_annotate_bitfield(skb, flags1);
        kmemcheck_annotate_bitfield(skb, flags2);
    #ifdef NET_SKBUFF_DATA_USES_OFFSET
        skb->mac_header = ~0U;
    #endif

        /* make sure we initialize shinfo sequentially */
        shinfo = skb_shinfo(skb);
        memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
        atomic_set(&shinfo->dataref, 1);

        if (fclone) {
            /* 如果是fclone cache的话,那么skb的下一个buf,也将被分配*/
            struct sk_buff *child = skb + 1;
            atomic_t *fclone_ref = (atomic_t *) (child + 1);

            kmemcheck_annotate_bitfield(child, flags1);
            kmemcheck_annotate_bitfield(child, flags2);
            skb->fclone = SKB_FCLONE_ORIG;
            atomic_set(fclone_ref, 1);

            child->fclone = SKB_FCLONE_UNAVAILABLE;
        }
    out:
        return skb;
    nodata:
        kmem_cache_free(cache, skb);
        skb = NULL;
        goto out;
    }
    这里有两个cache,skbuff_fclone_cache和skbuff_head_cache。它们两个的区别是前者是每两个skb为一组。当从skbuff_fclone_cache分配skb时,会两个连续的skb一起分配,但是释放的时候可以分别释放。也就是说当调用者知道需要两个skb时,如后面的操作很可能使用skb_clone时,那么从skbuff_fclone_cache上分配skb会更高效一些。

    skb的分配细节
    1. 关于 SKB 的分配细节.

    LINUX 中 SKB 的分配最终是由函数 : struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,int fclone) 来完成.
    SKB 可以分为 SKB 描述符与 SKB 数据区两个部分,其中描述符必须从 CACHE 中来分配 : 或者从skbuff_fclone_cache 中分配,或者从 skbuff_head_cache 中来分配.
    如果从分配描述符失败,则直接反回 NULL,表示 SKB 分配失败.

    SKB 描述符分配成功后,即可分配数据区.
    在具体分配数据区之前首先要对数据区的长度进行 ALIGN 操作, 通过宏 SKB_DATA_ALIGN 来重新确定 size 大小. 然后调用 kmalloc 函数分配数据区 :
    data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
    需要注意的是数据区的大小是 SIZE 的大小加上 skb_shared_info 结构的大小.

    数据区分配成功后,便对 SKB 描述符进行与此数据区相关的赋值操作 :
    memset(skb, 0, offsetof(struct sk_buff, truesize));
    skb->truesize = size + sizeof(struct sk_buff);
    atomic_set(&skb->users, 1);
    skb->head = data;
    skb->data = data;
    skb->tail = data;
    skb->end = data + size;
    需要主意的是, SKB 的 truesize 的大小并不包含 skb_shared_info 结构的大小. 另外,skb 的 end 成员指针也就事 skb_shared_info 结构的起始指针,系统用
    一个宏 : skb_shinfo 来完成寻找 skb_shared_info 结构指针的操作.

    最后,系统初始化 skb_shared_info 结构的成员变量 :
    atomic_set(&(skb_shinfo(skb)->dataref), 1);
    skb_shinfo(skb)->nr_frags = 0;
    skb_shinfo(skb)->tso_size = 0;
    skb_shinfo(skb)->tso_segs = 0;
    skb_shinfo(skb)->frag_list = NULL;
    skb_shinfo(skb)->ufo_size = 0;
    skb_shinfo(skb)->ip6_frag_id = 0;

    最后,返回 SKB 的指针.

    2. SKB 的分配时机
    SKB 的分配时机主要有两种,最常见的一种是在网卡的中断中,有数据包到达的时,系统分配 SKB 包进行包处理; 第二种情况是主动分配 SKB 包用于各种调试或者其他处理环境.

    3. SKB 的 reserve 操作
    SKB 在分配的过程中使用了一个小技巧 : 即在数据区中预留了 128 个字节大小的空间作为协议头使用, 通过移动 SKB 的 data 与 tail 指针的位置来实现这个功能.

    4. SKB 的 put 操作
    put 操作是 SKB 中一个非常频繁也是非常重要的操作, 胆识, skb_put()函数其实什么也没做!
    它只是根据数据的长度移动了 tail 指针并改写了 skb->len 的值,其他的什么都没做,然后就返回了 skb->data 指针(就是 tail 指针在移动之前的位置). 看上去此函数仿佛要拷贝数据到 skb 的数据区中,其实这事儿是 insl 这个函数干的,跟 skb_put() 函数毫不相关,不过它仍然很重要.

    5. 中断环境下 SKB 的分配流程
    当数据到达网卡后,会触发网卡的中断,从而进入 ISR 中,系统会在 ISR 中计算出此次接收到的数据的字节数 : pkt_len, 然后调用 SKB 分配函数来分配 SKB :
    skb = dev_alloc_skb(pkt_len+5);
    我们可以看到, 实际上传入的数据区的长度还要比实际接收到的字节数多,这实际上是一种保护机制. 实际上,在 dev_alloc_skb 函数调用 __dev_alloc_skb 函数,而 __dev_alloc_skb 函数又调用 alloc_skb 函数 时,其数据区的大小又增加了 128 字节, 这 128 字节就事前面我们所说的 reserve 机制预留的 header 空间.


    本文参考资料:感谢作者的分享
    http://blog.chinaunix.net/uid-23629988-id-257902.html
    http://blog.chinaunix.net/uid-1728743-id-24312.html
    ---------------------
    作者:不会游泳的鱼star
    来源:CSDN
    原文:https://blog.csdn.net/star_xiong/article/details/17588629
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    cf C. Vasya and Robot
    zoj 3805 Machine
    cf B. Vasya and Public Transport
    cf D. Queue
    cf C. Find Maximum
    cf B. Two Heaps
    cf C. Jeff and Rounding
    cf B. Jeff and Periods
    cf A. Jeff and Digits
    I Think I Need a Houseboat
  • 原文地址:https://www.cnblogs.com/Ph-one/p/9922015.html
Copyright © 2011-2022 走看看