内存管理-15-slab、slob和slub分配器-初探
一、slab简介
1. 简介
首先,“slab”已成为一个通用名称,指的是使用对象缓存的内存分配策略,可实现内核对象的高效分配和释放。它最初由 Sun 工程师 Jeff Bonwick 记录下来,并在 Solaris 2.4 内核中实现。
Linux 目前为其“slab”分配器提供了三种选择:
Slab 是最初的分配器,基于 Bonwick 的开创性论文,自 Linux-2.2 开始可用。它是 Bonwick 提案的忠实实现,并由 Bonwick 的后续论文2 中描述的多处理器更改进行了增强。
Slub 是下一代替代内存分配器,自 Linux-2.6.23 以来一直是内核中的默认分配器。它继续采用基本的“slab”模型,但修复了 Slab 设计中的几个缺陷,特别是在具有大量处理器的系统周围。Slub 比 Slab 更简单。
Slob(简单块列表)是一种针对内存非常少(大约兆字节)的嵌入式系统进行优化后的内存分配器。它对块列表应用非常简单的首次适合算法,与旧的 K&R 式堆分配器类似。Slob 消除了内存分配器中几乎所有的过载,非常适合内存极其受限的系统,但它不提供 Slab/Slub 中描述的任何好处,并且可能会出现病态碎片。
2. 概述
slab机制一般和伙伴系统配合使用,它是对伙伴系统的改进和补充。伙伴系统对物理内存的管理是以page为单位的,粒度比较大。slab是将页拆分成小内存块进行管理。slab首先通过伙伴系统的接口向伙伴系统申请一个或多个物理页,然后将其切割成固定大小的块,缓存起来,当分配此大小的内存块的时候,直接返回缓存的内存块,用户释放时,会释放给slab继续缓存起来。slab中的内存块使用链表链起来。对于常用的结构体,比如 task_struct,可以提前创建slab缓存,使用时直接去拿即可。
有3种类似的分配器,slab、slob、slub
slab: 是内核中最早出现的分配器,随着内核的发展,其实现越来越臃肿,
slob: 面向小型嵌入式系统很少内存的简单管理,比如32MB.
slub: 是一个轻量级的slab,一般使用在嵌入式系统中。
这三种可以通过内核deconfig进行配置,配置不同结构体定义和函数走的分支不同。
CONFIG_SLAB //没使能
CONFIG_SLOB //没使能
CONFIG_SLUB //使能
CONFIG_SLUB_DEBUG //使能
CONFIG_SLUB_CPU_PARTIAL //使能
msm-4.14 和 msm-5.4 都是这样的配置。
二、相关数据结构
kmem_cache kmem_cache_cpu //每cpu变量 kmem_cache_node struct kmem_cache_cpu { /* 每个内存块内部也占用一部分空间,作为一个指针指向下一个内存块,组成一个单链表, * 将所有的空闲内存块链接起来。此变量时刻指向下一个可用的内存块。 */ void **freelist; } struct kmem_cache_node { /* 上面串联的都是大小相同的内存块 */ struct list_head partial; }
如下,通过这三个结构体就把slab的框架给搭建起来了。
kmem_cache 每个大小的slab内存块都会使用一个 kem_cache 进行管理。所有的 kmem_cache 使用一个双循环链表链接起来,链表头是全局变量 slab_caches。申请内存时,通过这个全局链表头就可以找到要申请大小对应的 kmem_cache,进而找到其 partial 链表,拿到对应大小的内存块。
kmem_cache_cpu 是个每cpu的变量,每个cpu都缓存一部分slab, 也就是一个page,这样就可以避免链表访问拿锁。当这个page上的slab内存块被使用完了后,重新通过伙伴系统申请一个page来使用。
当申请slab缓存的时候,优先从cpu本地缓存上申请,即先从 kmem_cache_cpu 的 page 内申请,没有的话再看 partial 链表上是否有,若也没有的话才会去全局链表上去申请,若全局链表上也没有,则会向伙伴系统申请物理页再次创建slab缓存
三、相关文件节点
1. /proc/slabinfo
/proc/slabinfo 里面的 <pagesperslab> 表示每个slab占几个物理页。
四、编程接口
/* 创建一个 kmem_cache, 并对每一个内存块调用ctor回调 */ struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (*ctor)(void *)) //slab_common.c struct kmem_cache *kmem_cache_create_usercopy(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, unsigned int useroffset, unsigned int usersize, void (*ctor)(void *)) //slab_common.c TODO: 看它两个的区别,视频上说,用户空间经常使用 /* 销毁一个 kmem_cache */ void kmem_cache_destroy(struct kmem_cache *s) //slab_common.c /* 从 kmem_cache 对应的slab中分配一个object */ void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags) //slub.c /* 释放object给原先的slab */ void kmem_cache_free(struct kmem_cache *s, void *x) //slub.c
slab_flags_t 比如 SLAB_CACHE_DMA
ctor: 当创建完kmem_cache后,会回调这个函数,可以在这个函数中进行结构体成员初始化,当一次性创建多个slab块时,此函数会多次针对每个块都进行调用。
若是需要频繁申请释放一个大小的内存块,你也可以使用 kmem_cache_create 创建一个slab.
注意:写上标志的作用。注意这几个机制的文件是如何组织的
五、实验
1. 创建slab缓存
#include <linux/slab.h> /* 32B */ struct student { int id; int age; float score; void (*print_score)(int id); void (*print_age)(int id); }; struct mm_test_2 { int unique_id; struct kmem_cache *stu_c; struct student *stu_p; }; struct mm_test_2 mt2; void stu_init(void *p) { struct student *stu_p = p; stu_p->id = ++mt2.unique_id; pr_info("%s called, stu_p->id=%d\n", __func__, stu_p->id); //打印从1--22 } static void mem_test_2_init(void) { mt2.stu_c = kmem_cache_create("student", sizeof(struct student), 0, SLAB_PANIC, stu_init); if (!mt2.stu_c) { pr_info("%s NOMEM\n", __func__); return; } mt2.stu_p = kmem_cache_alloc(mt2.stu_c, GFP_KERNEL); if (mt2.stu_p) { pr_info("sizeof(*mt2.stu_p)=%d, sizeof(struct student)=%d\n", sizeof(*mt2.stu_p), sizeof(struct student)); //32 32 } } static void mem_test_2_exit(void) { kmem_cache_free(mt2.stu_c, mt2.stu_p); kmem_cache_destroy(mt2.stu_c); }
实验结果:
# dmesg -c | grep mm_test [ 221.397263] (3)[6366:sh]mm_test: stu_init called, stu_p->id=1 [ 221.397429] (3)[6366:sh]mm_test: stu_init called, stu_p->id=2 ... [ 221.401152] (1)[6366:sh]mm_test: stu_init called, stu_p->id=22 [ 221.401332] (1)[6366:sh]mm_test: sizeof(*mt2.stu_p)=32, sizeof(struct student)=32 # cat /proc/slabinfo | grep student slabinfo - version: 2.1 # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> student 1 22 368 22 2 : tunables 0 0 0 : slabdata 1 1 0
TODO: objsize 为啥差那么多(32/368)?
参考:
图解slub:http://www.wowotech.net/memory_management/426.html //蜗蜗 TODO
linux内核之slob、slab、slub: https://blog.csdn.net/Rong_Toa/article/details/106440497 //优秀,有英文文档 TODO
posted on 2024-07-06 10:20 Hello-World3 阅读(460) 评论(0) 编辑 收藏 举报