一、为啥越来越多使用slub
       slab分配器的弊端,我们知道slab分配器中每个node结点(用slab结构体kmem_cache中的kmem_cache_node结构体表示node)有三个链表,分别是空闲slab链表(slabs_free),部分空slab链表(slabs_partial),已满slab链表(slabs_full),这三个链表中维护着对应的slab缓冲区。我们也知道slab缓冲区的内存是从伙伴系统中申请过来的,假设,如果没有内存回收机制的情况下,只要申请的slab缓冲区就会存入这三个链表中,并不会返回到伙伴系统里,如果这个类型的SLAB迎来了一个分配高峰期,将会从伙伴系统中获取很多页面去生成许多slab缓冲区,之后这些slab缓冲区并不会自动返回到伙伴系统中,而是会添加到node结点的这三个slab链表中去,这样就会有很多slab缓冲区是很少用到的。而slub分配器把node结点的这三个链表精简为了一个链表,只保留了部分空slab链表(partial),而SLUB中对于每个CPU来说已经不使用空闲对象链表,而是直接使用单个slab,并且每个CPU都维护有自己的一个部分空链表。在slub分配器中,对于每个node结点,也没有了所有CPU共享的空闲对象链表。
        发明SLUB分配器的主要目的就是减少slab缓冲区的个数,让更多的空闲内存得到使用。首先,SLUB和SLAB一样,都分为多种,同时也分为专用SLUB和普通SLUB。如TCP,UDP,dquot这些,它们都是专用SLAB,专属于它们自己的模块。而后面这张图,如kmalloc-8,kmalloc-16...还有dma-kmalloc-96,dma-kmalloc-192...在这方面与SLAB是一样的,同样地,也是使用一个struct kmem_cache结构来描述一个SLUB(与SLAB一样)。并且这个struct kmem_cache与SLAB的struct kmem_cache几乎是同一个,通过编译选项来具体区分,而且对于SLAB和SLUB,向外提供的接口是统一的(函数名、参数以及返回值一模一样),这样也就让驱动和其他模块在编写代码时无需操心系统使用的是SLAB还是SLUB。这是为了同一个内核可以通过编译选项使用SLAB或者SLUB。
下图是一个来自别人的一个slub分配器结构图:(参见https://blog.csdn.net/chenying126/article/details/78451344)

再来个简图(参考:https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/index.html),需要注意的是对于slub来说,cpu缓存中只有slabs_partial部分空一个链表了,默认没有full(Debug有)和empty;

此结构是slab分配器结构,每个SLAB缓存都有它自己的名字,例如kmalloc-8,kmalloc-16等。总的来说,kmem_cache结构用于描述一种SLAB,并且管理着这种SLAB中所有的对象。所有的kmem_cache结构会保存在以slab_caches作为头的链表中。在c可以通过kmem_cache_create自行创建一个kmem_cache用于管理属于自己模块的SLAB。

二、几个重要的结构体及其关系

下面来个图,这个图是真心的很棒,对于理解slub分配原理,各数据结构元素的意义的理解和代码流程的理解都非常重要。

这些数据结构之间的关系具体简单的例子,很形象。什么是slab缓存池呢?我的解释是使用struct kmem_cache结构描述的一段内存就称作一个slab缓存池。一个slab缓存池就像是一箱牛奶,一箱牛奶中有很多瓶牛奶,每瓶牛奶就是一个object。分配内存的时候,就相当于从牛奶箱中拿一瓶。总有拿完的一天。当箱子空的时候,你就需要去超市再买一箱回来。超市就相当于partial链表,超市存储着很多箱牛奶。如果超市也卖完了,自然就要从厂家进货,然后出售给你。厂家就相当于伙伴系统。

图来自:http://www-x-wowotech-x-net.img.abc188.com/content/uploadfile/201803/4a471520078976.png
       slub中主要的结构体有kmem_cache,kmem_cache_cpu,kmem_cache_node,还有个与页描述符复用的slab描述符page结构体,仔细想想,我们把kmem_cache叫做slub缓存(slub分配器),那么它里面存放的是什么呢?当然是slab描述符喽,由于slab是由伙伴系统分配的一段连续的物理页,所以用page描述符复用slab描述符,也解释的通。所以slub缓存的一系列操作其实都是基于slab描述符,也就是page描述符,因此每cpu缓存kmem_cache_cpu(当前slab描述符和使用了部分对象的slab描述符链表)和kmem_cache_node(其中三个链表保存的是这组页框的首页框的SLAB描述符)节点缓存的维护,都是维护的slab描述符(基于page描述符),这里就有利于理解上图。记下来细看各个数据结构。
1)kmem_cache结构

struct kmem_cache {struct kmem_cache_cpu __percpu *cpu_slab;/* 标志 */unsigned long flags;/* 每个node结点中部分空slab缓冲区数量不能低于这个值 */unsigned long min_partial;/* 分配给对象的内存大小(大于对象的实际大小,大小包括对象后边的下个空闲对象指针) */int size;    /* 对象的实际大小 */int object_size;  /* 存放空闲对象指针的偏移量 */int offset;  /* cpu的可用objects数量范围最大值 */int cpu_partial;   /* 保存slab缓冲区需要的页框数量的order值和objects数量的值,通过这个值可以计算出需要多少页框,这个是默认值,初始化时会根据经验计算这个值 */struct kmem_cache_order_objects oo;/* 保存slab缓冲区需要的页框数量的order值和objects数量的值,这个是最大值 */struct kmem_cache_order_objects max;/* 保存slab缓冲区需要的页框数量的order值和objects数量的值,这个是最小值,当默认值oo分配失败时,会尝试用最小值去分配连续页框 */struct kmem_cache_order_objects min;/* 每一次分配时所使用的标志 */gfp_t allocflags;   /* 重用计数器,当用户请求创建新的SLUB种类时,SLUB 分配器重用已创建的相似大小的SLUB,从而减少SLUB种类的个数。 */int refcount;  /* 创建slab时的构造函数 */void (*ctor)(void *);/* 元数据的偏移量 */int inuse;   /* 对齐 */int align;      int reserved;      /* 高速缓存名字 */const char *name;    /* 所有的 kmem_cache 结构都会链入这个链表,链表头是 slab_caches */struct list_head list;
#ifdef CONFIG_SYSFS/* 用于sysfs文件系统,在/sys中会有个slub的专用目录 */struct kobject kobj;
#endif
#ifdef CONFIG_MEMCG_KMEM/* 这两个主要用于memory cgroup的 */struct memcg_cache_params *memcg_params;int max_attr_size;
#ifdef CONFIG_SYSFSstruct kset *memcg_kset;
#endif
#endif#ifdef CONFIG_NUMA/* 用于NUMA架构,该值越小,越倾向于在本结点分配对象 */int remote_node_defrag_ratio;
#endif/*在这个结构中,最重要的可能就属struct kmem_cache_node * node[MAX_NUMNODES]这个指针数组了,指向的struct kmem_cache_node中保存着slab链表,
在NUMA架构中每个node对应数组中的一个元素,因为每个SLAB高速缓存都有可能在不同结点维护有自己的SLAB用于这个结点的分配*/struct kmem_cache_node *node[MAX_NUMNODES];
};

扫一下整个kmem_cache结构,知识点最重要的有4个:每CPU对应的cpu_slab结构,每个node结点对应的kmem_cache_node结构,slub重用以及struct kmem_cache_order_objects结构对应的oo,max,min这三个值。
  除去以上4个知识点,先简单说说kmem_cache中的一些成员变量:

1)     cpu_slab:一个per cpu变量,对于每个cpu来说,相当于一个本地内存缓存池。当分配内存的时候优先从本地cpu分配内存以保证cache的命中率。

2)     flags:object分配掩码,例如经常使用的SLAB_HWCACHE_ALIGN标志位,代表创建的kmem_cache管理的object按照硬件cache 对齐,一切都是为了速度。

3)     min_partial:限制struct kmem_cache_node中的partial链表slab的数量。虽说是mini_partial,但是代码的本意告诉我这个变量是kmem_cache_node中partial链表最大slab数量,如果大于这个mini_partial的值,那么多余的slab就会被释放。

4)     size:分配的object size

5)     object_size:实际的object size,就是创建kmem_cache时候传递进来的参数。和size的关系就是,size是各种地址对齐之后的大小。因此,size要大于等于object_size。

6)     offset:slub分配在管理object的时候采用的方法是:既然每个object在没有分配之前不在乎每个object中存储的内容,那么完全可以在每个object中存储下一个object内存首地址,就形成了一个单链表。很巧妙的设计。那么这个地址数据存储在object什么位置呢?offset就是存储下个object地址数据相对于这个object首地址的偏移。

7)     cpu_partial:per cpu partial中所有slab的free object的数量的最大值,超过这个值就会将所有的slab转移到kmem_cache_node的partial链表。

8)     oo:低16位代表一个slab中所有object的数量(oo & ((1 << 16) - 1)),高16位代表一个slab管理的page数量((2^(oo  16)) pages)。

9)     max:看了代码好像就是等于oo。

10)  min:当按照oo大小分配内存的时候出现内存不足就会考虑min大小方式分配。min只需要可以容纳一个object即可。

11)  allocflags:从伙伴系统分配内存掩码。

12)  inuse:object_size按照word对齐之后的大小。

13)  align:字节对齐大小。

14)  name:sysfs文件系统显示使用。

15)  list:系统有一个slab_caches链表,所有的slab都会挂入此链表。

16)  node:slab节点。在NUMA系统中,每个node都有一个struct kmem_cache_node数据结构。

2)kmem_cache_cpu结构体

再来看看struct kmem_cache_cpu __percpu *cpu_slab,对于同一种kmem_cache来说,每个CPU对应有自己的struct kmem_cache_cpu结构,这个结构如下:

struct kmem_cache_cpu {/* 指向下一个空闲对象,用于快速找到对象 */void **freelist;/* 用于保证cmpxchg_double计算发生在正确的CPU上,并且可作为一个锁保证不会同时申请这个kmem_cache_cpu的对象 */unsigned long tid;    /* CPU当前所使用的slab缓冲区描述符,freelist会指向此slab的下一个空闲对象 */struct page *page;    /* CPU的部分空slab链表,放到CPU的部分空slab链表中的slab会被冻结,而放入node中的部分空slab链表则解冻,冻结标志在slab缓冲区描述符中 */struct page *partial;
#ifdef CONFIG_SLUB_STATSunsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};

在此结构中主要注意有个partial部分空slab链表以及page指针,page指针指向当前使用的slab缓冲区描述符,内核中slab缓冲区描述符与页描述符共用一个struct page结构。SLUB分配器与SLAB分配器有一部分不同就在此,SLAB分配器的每CPU结构中保存的是空闲对象链表,而SLUB分配器的每CPU结构中保存的是一个slab缓冲区。而对于tid,它主要用于检查是否有并发,对于一些操作,操作前读取其值,操作结束后再检查其值是否与之前读取的一致,非一致则要进行一些相应的处理,这个tid一般是递增状态,每分配一次对象加1。这个结构说明了一个问题,就是每个CPU有自己当前使用的slab缓冲区,CPU0不能够使用CPU1所在使用的slab缓存,CPU1也不能够使用CPU0正在使用的slab缓存。而CPU从node获取slab缓冲区时,一般倾向于从该CPU所在的node结点上分配,如果该node结点没有空闲的内存,则根据memcg以及node结点的zonelist从其他node获取slab缓冲区。

3)kmem_cache_node结构:

再看看kmem_cache_node结构:

struct kmem_cache_node {/* 锁 */spinlock_t list_lock;/* SLAB使用 */
#ifdef CONFIG_SLAB/* 只使用了部分对象的SLAB描述符的双向循环链表 */struct list_head slabs_partial;    /* partial list first, better asm code *//* 不包含空闲对象的SLAB描述符的双向循环链表 */struct list_head slabs_full;/* 只包含空闲对象的SLAB描述符的双向循环链表 */struct list_head slabs_free;/* 高速缓存中空闲对象个数(包括slabs_partial链表中和slabs_free链表中所有的空闲对象) */unsigned long free_objects;/* 高速缓存中空闲对象的上限 */unsigned int free_limit;/* 下一个被分配的SLAB使用的颜色 */unsigned int colour_next;    /* Per-node cache coloring *//* 指向这个结点上所有CPU共享的一个本地高速缓存 */struct array_cache *shared;    /* shared per node */struct alien_cache **alien;    /* on other nodes *//* 两次缓存收缩时的间隔,降低次数,提高性能 */unsigned long next_reap;    /* 0:收缩  1:获取一个对象 */int free_touched;        /* updated without locking */
#endif/* SLUB使用 */
#ifdef CONFIG_SLUBunsigned long nr_partial; struct list_head partial;  /* 只使用了部分对象的SLAB描述符的双向循环链表 */
#ifdef CONFIG_SLUB_DEBUG/* 该node中此kmem_cache的所有slab的数量 */atomic_long_t nr_slabs;/* 该node中此kmem_cache中所有对象的数量 */atomic_long_t total_objects;struct list_head full;
#endif
#endif
};

这个结构中对于slub,只需要看#ifdef CONFIG_SLUB部分,这个结构里正常情况下只有一个node结点部分空slab链表partial,如果在编译内核时选择了CONFIG_SLUB_DEBUG选项,则会有个node结点满slab链表full。对于SLAB分配器,SLUB分配器在这个结构也做出了相应的变化,去除了满slab缓冲区链表和空闲slab缓冲区链表,只使用了一个部分空slab缓冲区链表。对于所有的CPU来说,它们可以使用这个node结点里面部分空链表中保存的那些slab缓冲区,当它们需要使用时,要先将缓冲区拿到CPU对应自己的链表或者当前使用中,也就是说node结点上部分空slab缓冲区同一个时间只能让一个CPU使用。
而关于slub重用,这里只做一个简单的解释,其作用是为了减少slub的种类,比如我有个kmalloc-8类型的slub,里面每个对象大小是8,而我某个驱动想申请自己所属的slub,其对象大小是6,这时候系统会给驱动一个假象,让驱动申请了自己专属的slub,但系统实际把kmalloc-8这个类型的slub返回给了驱动,之后驱动中分配对象时实际上就是从kmalloc-8中分配对象,这就是slub重用,将相近大小的slub共用一个slub类型,虽然会造成一些内碎片,但是大大减少了slub种类过多以及减少使用了跟多的内存。

内存管理-内存slub分配器(二)相关推荐

  1. Linux内存管理:slub分配器

    概述: 我们知道内核中的物理内存由伙伴系统(buddy system)进行管理,它的分配粒度是以物理页帧(page)为单位的,但内核中有大量的数据结构只需要若干bytes的空间,倘若仍按页来分配,势必 ...

  2. 【Linux 内核 内存管理】memblock 分配器编程接口 ⑤ ( memblock_free 函数 | memblock_remove_range 函数 )

    文章目录 一.memblock_free 函数分析 二.memblock_remove_range 函数分析 memblock 分配器提供了如下编程接口 : ① 添加内存 : memblock_add ...

  3. Windows内存管理学习笔记(二)—— 物理内存的管理

    Windows内存管理学习笔记(二)-- 物理内存的管理 物理内存 实验一:理解MmNumberOfPhysicalPages MmPfnDatabase _MMPFN 物理页状态 六个链表 实验二: ...

  4. dpdk内存管理——内存初始化

    *说明:本系列博文源代码均来自dpdk17.02* 1.1内存初始化 1.1.1 hugepage技术 hugepage(2M/1G..)相对于普通的page(4K)来说有几个特点: (1) huge ...

  5. LwIP 之六 详解动态内存管理 内存池(memp.c/h)

      该文主要是接上一部分LwIP 之 详解动态内存管理 内存堆(mem.c/h),该部分许多内容需要用到上一篇的内容.该部分主要是详细介绍LwIP中的动态内存池.整个内存池的实现相较于内存堆来说,还是 ...

  6. LwIP 之五 详解动态内存管理 内存堆(mem.c/h)

    写在前面   目前网上有很多介绍LwIP内存的文章,但是绝大多数都不够详细,甚至很多介绍都是错误的!无论是代码的说明还是给出的图例,都欠佳!下面就从源代码,到图例详细进行说明.   目前,网络上多数文 ...

  7. 内存管理-内存池的实现

    内存池的实现 1 前言 2 内存池的原理 2.1 内存利用链表进行管理 2.2 分配固定大小 2.3 按块进行内存管理 3 内存池的实现 3.1 内存池的创建 3.2 内存池的销毁 3.3 内存分配 ...

  8. Linux内存管理之SLAB分配器

    注:本文讲述的SLAB相关代码是基于Linux内核v4.7,代码网址. 1.SLAB分配器的由来 在讲SLAB分配器之前先说两个概念: 内部碎片和外部碎片. 外部碎片指的是还没有被分配出去(不属于任何 ...

  9. 内存管理之slab分配器

    基本思想 与传统的内存管理模式相比, slab 缓存分配器提供了很多优点.首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配.slab 缓存分配器通过对类似大小的对象进行缓存而提 ...

  10. linux内存管理笔记(四十二)----内存规整

    伙伴系统是以页面为单位管理内存,内存碎片也是基于页面,即由大量离散且不连续的页面组成.从内核的角度,出现内存碎片不是什么好的事情,例如 有些情况下物理设备需要大量的连续的物理内存,如果内核无法满足,就 ...

最新文章

  1. 【青少年编程】【蓝桥杯】绘制莲花图形
  2. 【信息安全】职业发展之惑系列三 -- 我该选择怎样的职业发展道路
  3. Android应用开发之(通过ClipboardManager, ClipData进行复制粘贴)
  4. C语言定义一个头节点,一个关于C语言链表头结点的问题
  5. oracle查找异常中断的sqlid方法,Oracle查找锁定对象以及强制解除锁定的方法
  6. ViewResolvers
  7. java中多个输入框搜索_如何在一个搜索框中输入多个字段的值进行查询?
  8. 增加索引提高查询效率
  9. Xqk.Data数据框架开发指南:丰富的、灵活的查询方法(第一部分)
  10. wps 插件_【追加功能】OFFICE插件管理工具重整后再上路,更好用易用。
  11. 概念梳理:C++中iostream头文件和命名空间的基础介绍和拓展内容
  12. uniapp发行为小程序分享转发功能
  13. uni-app实现上传照片和个人信息
  14. 迅捷PDF转换成word转换器
  15. 脸皮厚了与哲哥合影!Cocos北京站沙龙带回的照片,猜猜我是谁?
  16. 创新数据库技术 成就IOD愿景携DB2 25载创新 IBM推新“信息议程”
  17. 西门子博图指令(定时器操作三)
  18. Georgia与Times字体的比较
  19. 如何变成一个有趣的人
  20. 这可能是由于CredSSP加密数据库修正

热门文章

  1. 13、XSI,信号量简介
  2. 使用VS开发基于Oracle程序的严重问题
  3. 计算机模块测试题,模拟计算机基础模块测试题.doc
  4. 检测到你的手机处于root环境_玩手游多开还在用模拟器?云手机了解一下
  5. Sass--占位符 %placeholder
  6. Redis 中的事件驱动模型
  7. hadoop 开发环境设置以及可运行jar包生成
  8. ARM/IBM左右夹攻 英特尔服务器举步维艰?
  9. 360与Bing合作上线英文搜索
  10. mybatis spring maven