当前位置: 首页 > news >正文

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

转载于:https://www.cnblogs.com/Ph-one/p/9922015.html

相关文章:

  • UML状态机图【图3】--☆
  • 构件图和部署图
  • UML建模类图【2】--☆☆
  • 活动图、泳道【图1】--☆☆
  • 组件图【图4】--☆
  • 部署图【图5】--☆
  • 顺序图【6】--☆☆
  • 用例图【图7】--☆
  • 四则运算表达式分解,前中后缀表达式(栈的应用)
  • 嵌入式 Linux 对内存的直接读写(devmem)
  • C语言三个结束符:EOF ‘\0’ '\n'
  • Ymodem协议(参考STM32)
  • android uboot中的mmc命令
  • stm32最简单的实现BootLoader
  • 变参标准函数的重新封装,如printf
  • CSS3 变换
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • go语言学习初探(一)
  • Javascript 原型链
  • nodejs:开发并发布一个nodejs包
  • Spring Cloud中负载均衡器概览
  • 不上全站https的网站你们就等着被恶心死吧
  • 从输入URL到页面加载发生了什么
  • 前端知识点整理(待续)
  • 如何解决微信端直接跳WAP端
  • 推荐一个React的管理后台框架
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 用Canvas画一棵二叉树
  • 由插件封装引出的一丢丢思考
  • 原生 js 实现移动端 Touch 滑动反弹
  • ​2020 年大前端技术趋势解读
  • ​第20课 在Android Native开发中加入新的C++类
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • (8)STL算法之替换
  • (done) NLP “bag-of-words“ 方法 (带有二元分类和多元分类两个例子)词袋模型、BoW
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (二)WCF的Binding模型
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (顺序)容器的好伴侣 --- 容器适配器
  • (一)SpringBoot3---尚硅谷总结
  • (转载)虚函数剖析
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • ***测试-HTTP方法
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET分布式缓存Memcached从入门到实战
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • ??eclipse的安装配置问题!??
  • @RequestParam,@RequestBody和@PathVariable 区别
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [20171102]视图v$session中process字段含义
  • [23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians