承接上一篇blog--sk_buff整理笔记(二、操作函数),这篇是要来讲解下sk_buff结构的内存申请和释放函数。因为sk_buff结构是比较复杂的(并不是其本身结构复杂,而是其所指的数据区以及分片结构等,合在一起就变复杂了),所以在内存申请和释放时,就要搞清楚什么函数对应的申请分配或释放什么结构内存。这里不提倡自己用kmalloc()和kfree()函数来为sk_buff相关结构体申请内存及销毁,而要用内核提供好的一些函数去为这些结构体申请内存。内核开发的原则:尽量用内核定义好的数据和函数以及操作宏,不到迫不得已不要自行定义任何东西。这样做是为了接口统一,移植方便,容易维护。

第一、sk_buff结构的内存申请:

static inline struct sk_buff *alloc_skb(unsigned int size,
                    gfp_t priority)
{
    return __alloc_skb(size, priority, 0, -1);
}
 
static inline struct sk_buff *alloc_skb_fclone(unsigned int size,
                           gfp_t priority)
{
    return __alloc_skb(size, priority, 1, -1);
}
struct sk_buff *dev_alloc_skb(unsigned int length)
{
    /*
     * There is more code here than it seems:
     * __dev_alloc_skb is an inline
     */
    return __dev_alloc_skb(length, GFP_ATOMIC);
}
static inline struct sk_buff *__dev_alloc_skb(unsigned int length,
                          gfp_t gfp_mask)
{
    struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);
    if (likely(skb))
        skb_reserve(skb, NET_SKB_PAD);
    return skb;
}
        这几个函数都是在linux-2.6.32.63\include\linux\sk_buff.h文件中的,其实就函数也可以看得出这是内联函数。不记得在前面讲过没,对于简洁常用的函数一般都定义为内联函数,这样做是为了提高CPU工作效率和内存利用率。一般的函数调用要保存现场(在堆栈中保存调用函数时现场状态,包括地址,执行状态等);当调用函数完后,又要恢复现场状态(把开始保存的状态数据从堆栈中读取出来)。在调用函数和返回时,浪费了CPU很多时间,再个在存储时也会出现些碎片。所以再使用简洁函数时,一般定义为内联函数,在函数前面加个inline关键字。

讲了内联函数细节后,来看下上面这几个内存申请函数。发现这几个函数其实本质上都是封装了 __alloc_skb();函数,那么首先就要来分析下__alloc_skb()函数,然后再去分析下这几个函数的异同点,以方便在申请sk_buff结构时,该用哪个函数去申请。

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 = fclone ? skbuff_fclone_cache : skbuff_head_cache;
 
    /* Get the HEAD */
    
    skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
    if (!skb)
        goto out;
   
    size = SKB_DATA_ALIGN(size);
 
    data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),
            gfp_mask, node);
    if (!data)
        goto nodata;
 
    /*
     * 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 sk_buff!
     */
    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);
    atomic_set(&shinfo->dataref, 1);
    shinfo->nr_frags  = 0;
    shinfo->gso_size = 0;
    shinfo->gso_segs = 0;
    shinfo->gso_type = 0;
    shinfo->ip6_frag_id = 0;
    shinfo->tx_flags.flags = 0;
    skb_frag_list_init(skb);
    memset(&shinfo->hwtstamps, 0, sizeof(shinfo->hwtstamps));
 
    if (fclone) {
        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;
}
        首先来看下函数参数:第一个参数 unsigned int size,数据区的大小;第二个参数 gfp_t gfp_mask,有些blog说这是优先级,个人觉得是不对的。我们都知道内核动态分配函数kmalloc()里面要有两个参数,第一个是要分配的空间大小,第二个是内存分配方式(这里我们一般用GFP_KERNEL)。所以这里的gfp_t gfp_mask应该是内核动态分配方式的一个掩码(就是各种申请方式的集合);第三个参数 int fclone,表示在哪块分配器上分配;第四个参数 int node,用来表示用哪种区域去分配空间。
        第9行代码:cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;由传入的参数来决定用哪个缓冲区中的内存来分配(一般是用skbuff_head_cache缓存池来分配)。说到这里就插入的讲下缓存池的概念。

内核对于sk_buff结构的内存分配不是和一般的结构动态内存申请一样:只分配指定大小的内存空间。而是在开始的时候,在初始化函数skb_init()中就分配了两段内存(skbuff_head_cache和skbuff_fclone_cache )来供sk_buff后期申请时用,所以后期要为sk_buff结构动态申请内存时,都会从这两段内存中来申请(其实这不叫申请了,因为这两段内存开始就申请好了的,只是根据你要的内存大小从某个你选定的内存段中还回个指针给你罢了)。如果在这个内存段中申请失败,则再用内核中用最低层,最基本的kmalloc()来申请内存了(这才是真正的申请)。释放时也一样,并不会真正的释放,只是把数据清零,然后放回内存段中,以供下次sk_buff结构的申请。这是内核动态申请的一种策略,专门为那些经常要申请和释放的结构设计的,这种策略不仅可以提高申请和释放时的效率,而且还可以减少内存碎片的。(注意:上面提到的内存段中的段不是指内存管理中的段、页的段,而是表示块,就是两块比较大的内存)

第13行代码:skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);从指定段中为skb分配内存,分配的方式是去除在DMA内存中分配,因为DMA内存比较小,且有特定的作用,一般不用来分配skb。

第17行代码是调整sk_buff结构指向的数据区的大小。

第19行代码:data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),  gfp_mask, node);这也是关键性代码之一(这里和上面分配skb的分配方式不一样,这里允许有些数据可以用DMA内存来分配)。这也是从特殊的缓存池中分配内存的,如果看函数里面的参数不难发现,要分配的空间大小为:size + sizeof(struct skb_shared_info),前面的size是指skb结构体指向的数据区大小,而sizeof(struct skb_shared_info)则是为分片数据分配空间。因为分片结构就是在skb结构指向的数据区的下面,就是end指针的下一个字节,所以就一起分配。

第29行到42行代码则是为sk_buff的数据区初始化,第44行到第53行则是为分片结构进行初始化。

第55行开始就是另外个知识点了,和第9行代码有点关系。skbuff_fclone_cache和skbuff_head_cache两个块内存缓存池是不一样的。我们一般是在skbuff_head_cache这块缓存池中来申请内存的,但是如果你开始就知道这个skb将很有可能被克隆(至于克隆和复制将在下一篇bolg讲),那么你最好还是选择在skbuff_fclone_cache缓存池中申请。因为在这块缓存池中申请的话,将会返回2个skb的内存空间,第二个skb则是用来作为克隆时使用。(其实从函数名字就应该知道是为克隆准备的,fclone嘛)虽然是分配了两个sk_buff结构内存,但是数据区却是只有一个的,所以是两个sk_buff结构中的指针都是指向这一个数据区的。也正因为如此,所以分配sk_buff结构时也顺便分配了个引用计数器,就是来表示有多少个sk_buff结构在指向数据区(引用同一个数据区),这是为了防止还有sk_buff结构在指向数据区时,而销毁掉这个数据区。有了这个引用计数,一般在销毁时,先查看这个引用计数是否为0,如果不是为0,就让引用计数将去1;如果是0,才真正销毁掉这个数据区。

第56行代码就好理解了:用child结构体变量来指向第二sk_buff结构体内存地址。第57行代码就是获取到引用计数器,因为引用计数器是在第二个sk_buff结构体内存下一个字节空间开始的,所以用(child + 1)来获取到引用计数器的开始地址。后面的代码就比较好理解了,无非就是些设置性参数了。

好了,基本函数__alloc_skb()已经分析过了,那么现在来看下开始的那几个函数的异同点吧。

alloc_skb():是用来分配单纯的sk_buff结构内存的,一般都是使用这个;alloc_skb_fclone():这是用来分配克隆sk_buff结构的,因为这个分配函数会分配一个子skb用来后期克隆使用,所以如果能预见要克隆skb_buff结构,则使用这种方法会方便些。dev_alloc_skb():其实这个函数实质上是调用了alloc_skb()函数单纯分配了一个sk_buff内存。但是通常来说这是在驱动程序中申请sk_buff结构时,用到的申请函数,和一般的申请内存函数有点不一样,它是用GFP_ATOMIC的内存分配方式来申请的(一般我们用GFP_KERNEL),这是个原子操作,表示申请时不能被中断。其实还有个申请函数:netdev_alloc_skb(),这个没怎么研究,估计是专门为网络设备中使用sk_buff时,用来内存分配的吧。

第二、sk_buff结构的内存释放:

void kfree_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;
    if (likely(atomic_read(&skb->users) == 1))
        smp_rmb();
    else if (likely(!atomic_dec_and_test(&skb->users)))
        return;
    trace_kfree_skb(skb, __builtin_return_address(0));
    __kfree_skb(skb);
}
void __kfree_skb(struct sk_buff *skb)
{
    skb_release_all(skb);
    kfree_skbmem(skb);
}
static void skb_release_all(struct sk_buff *skb)
{
    skb_release_head_state(skb);
    skb_release_data(skb);
}
        首先还是来说下dev_kfree_skb()函数吧,这个函数和dev_alloc_skb()相对应的,一般也是在驱动程序中使用,其实现也是对kfree_skb()进行封装的,所以重点还是kfree_skb()函数。 
        kfree_skb()函数首先是获取到skb->users成员字段,这是个引用计数器,当只有skb->users == 1是才能真正释放空间内存(也不是释放,而是放回到缓存池中)。如果不为1的话,那么kfree_skb()函数只是简单的skb->users减去个1而已。skb->users表示有多少个人正在引用这个结构体,如果不为1表示还有其他人在引用他,不能释放掉这

个结构体,否则会让引用者出现野指针、非法操作内存等错误。这种情况下只需要skb->users减去个1即可,表明我不再引用这个结构体了。如果skb->users == 1,则表明是最后一个引用该结构体的,所以可以调用_kfree_skb()函数直接释放掉了。当skb释放掉后,dst_release同样会被调用以减小相关dst_entry数据结构的引用计数。如果destructor(skb的析构函数)被初始化过,相应的函数会在此时被调用。还有分片结构体(skb_shared_info)也会相应的被释放掉,然后把所有内存空间全部返还到skbuff_head_cache缓存池中,这些操作都是由kfree_skbmem()函数来完成的。这里分片的释放涉及到了克隆问题:如果skb没有被克隆,数据区也没有其他skb引用,则直接释放即可;如果是克隆了skb结构,则当克隆数计数为1时,才能释放skb结构体;如果分片结构被克隆了,那么也要等到分片克隆计数为1时,才能释放掉分片数据结构。如果skb是从skbuff_fclone_cache缓存池中申请的内存时,则要仔细销毁过程了,因为从这个缓存池中申请的内存,会返还2个skb结构体和一个引用计数器。所以销毁时不仅要考虑克隆问题还要考虑2个skb的释放顺序。销毁过程见下图(原图来自《深入理解linux网络技术内幕》):

--------------------- 
作者:庾志辉 
来源:CSDN 
原文:https://blog.csdn.net/yuzhihui_no1/article/details/38737615 
版权声明:本文为博主原创文章,转载请附上博文链接!

sk_buff整理笔记(三、内存申请和释放)相关推荐

  1. fork练习、从进程角度考虑堆区内存申请与释放的有关问题

    1.fork练习 1.1代码1; int main( int argc, char* argv[], char* envp[]) {int i = 0;for( ; i < 2; i++ ){f ...

  2. C/C++动态内存申请与释放

    20.1 理解指针的两种"改变" 普通变量(非指针,简单类 型变量)只能改变值:   1) int a = 100; 2) ... 3) a = 200;   第 1 行代码,声明 ...

  3. C/C++内存申请和释放(一)

    这一篇主要介绍一下C中的malloc和free(当然在C++中它们也可以使用),下一篇将主要介绍一下C++中的new和delete 如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢 0. mallo ...

  4. 内存申请与释放(转)

    释放内存?那要看你怎么申请的了 new->delete;malloc->free;GlobalAlloc->GlobalFree;VirtualAlloc(Ex)->Virtu ...

  5. malloc的内存申请和释放

    一.malloc malloc是个库函数,使用时要包含<stdlib.h>这个头文件  malloc向内存申请空间时需要我们指定所需内存的大小,并且申请成功时,返回指向所申请的内存空间的指 ...

  6. 结构体变量内存申请与释放

    目录 1.前言 2.常见结构类型 3.Demo 4.结束 1.前言 结构体是C.C++开发中不可或缺的数据结构,往往涉及到函数的入参以及出参等,也必然涉及到参数的初始化.对于字符串往往是需要在堆上开辟 ...

  7. C++之内存管理:申请与释放

    目录 前言 1.C/C++内存分布 1.1虚拟内存分段 1.2理解一些概念 1.2.1栈帧向下增长 1.2.2堆向上生长 1.2.3栈和堆会碰撞吗? 1.2.4关于const的说明 2.C语言中动态内 ...

  8. 【JVM学习笔记】内存回收与内存回收算法 就哪些地方需要回收、什么时候回收、如何回收三个问题进行分析和说明

    目录 一.相关名词解释 垃圾收集常用名词 二.哪些地方需要回收 本地方法栈.虚拟机栈.程序计数器 方法区 Java堆 三.什么时候回收 1. 内存能否被回收 内存中的引用类型 引用计数算法 可达性分析 ...

  9. linux内存管理笔记(三十九)----kswapd内存回收

    在linux操作系统中,当内存充足的时候,内核会尽量使用内存作为文件缓存(page cache),从而提高系统的性能.例如page cache缓冲硬盘中的内容,dcache.icache缓存文件系统的 ...

最新文章

  1. 【干货分享】流程DEMO-外出申请
  2. Android学习--------实现增删改查数据库操作以及实现相似微信好友对话管理操作...
  3. UNICODE与多字节字符集等字符问题
  4. Linux操作系统下Oracle主要监控工具介绍
  5. 让 gRPC 提供 REST 服务
  6. python正则匹配找到所有的浮点数_Python随笔17:Python正则表达式基础(4):贪婪匹配和最小匹配...
  7. MSDN Visual系列:利用关联来过滤MOSS中的BDC数据
  8. unity天空盒渐变_在Unity3D中使用天空盒
  9. 再多的非标电气设计也不怕了
  10. GO GOPROXY代理设置
  11. 百旺红字发票信息表显示服务器返回为空,红字发票信息表状态详细说明
  12. Java生成与解析二维码
  13. 1^2+2^2+…+n^2求和公式推导
  14. 物联网 | HASS+MQTT+树莓派室内监测小型物联网系统
  15. ES版cpu如何购买和判断?
  16. 摘《阿里巴巴JAVA开发手册》易错题目
  17. 终端(terminal)打印彩色文字
  18. Railway:怎么通过github来部署vue项目
  19. tl494c封装区别_tl494详解(特性、封装、内部电路方框图)
  20. 【C语言】结构体、共用体、位域

热门文章

  1. 哪款物业管理软件又便宜又好用?
  2. 行车记录仪SD卡删除视频的数据恢复技术
  3. HTML5智慧渔业WebGL可视化云平台
  4. ASCII,GBK,Unicode(UTF-32/UTF-8),乱码,ANSI详解
  5. fabric systemd journalctl
  6. 新品周刊 | 小猪佩奇发布周边年货大礼;斐乐推出情人节“锁锁鞋”
  7. HelloGitHub我感兴趣的python项目
  8. android中进程和线程的关系与区别
  9. 小米快传html,手机中的小米快传怎么用?小米快传的详细使用教程
  10. 【行研报告】2021年度中国汽车保值率研究报告—附下载