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
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    lintcode:最大子正方形
    lintcode 中等题:k Sum ii k数和 II
    lintcode 中等题:A + B Problem A + B 问题
    Protege汉字不能正常显示问题
    Protege A DOT error has occurred错误
    lintcode :reverse integer 颠倒整数
    Reported time is too far out of sync with master. Time difference of 52692ms > max allowed of 30000ms
    Please add or free up more resources then turn off safe mode manually.
    Permission denied: user=root, access=WRITE, inode="/":hadoopuser:supergroup:drwxr-xr-x
    Hadoop重新格式化HDFS的方法
  • 原文地址:https://www.cnblogs.com/Ph-one/p/9922015.html
Copyright © 2011-2022 走看看