slab是通用的分配器;
slob针对微小的嵌入式系统,其算法较简单(最先适配算法);
slub是面向配备大量物理内存的大规模并行系统。
Slab分配器内容概述
slab描述符创建概览
创建slab描述符
kmem_cache数据结构
kmem_cache数据结构是slab分配器中的核心数据结构,成为slab描述符。定义在
struct kmem_cache {
unsigned int object_size;//对象的实际大小
unsigned int size; //对象的长度,该长度已经加上对齐字节
unsigned int align; //对齐的长度
slab_flags_t flags; //对象的分配掩码
unsigned int useroffset;//usercopy区域的偏移量
unsigned int usersize; //usercopy区域的大小
const char *name; //slab描述符的名称
int refcount; //表示该slab描述符的引用计数,当创建其他slab需要引用该slab时,增加引用计数值
void (*ctor)(void *); //构造函数
struct list_head list; //链表节点,用于把slab描述符添加到全局链表slab_caches中
};
array_cache数据结构
该数据结构用于描述本地对象缓冲池,也可以用于描述共享对象缓冲池,定义在
struct array_cache {
unsigned int avail; //对象缓冲池中可用的对象数目
unsigned int limit; //对象缓冲池可用对象数目的最大阈值
unsigned int batchcount; //迁移对象的数目,如从共享对象缓冲池或者其他slab中迁移空闲对象到该对象缓冲池的数量
unsigned int touched; //表示该对象缓冲池最近使用过
//entry[],指向存储对象的变长数组,每个成员存放一个对象的指针,这个数组最初最多有limit个成员
void *entry[];
};
kmem_cache_create()函数
kmem_cache_create()实现
//name:要创建的slab对象名称,会显示在/proc/slabinfo中
//size:slab对象的大小
//align:slab对象的对齐要求
//flags:slab分配器的分配掩码和标志位
//ctor:对象的构造函数
struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size, unsigned int align,
slab_flags_t flags, void (*ctor)(void *))
{
return kmem_cache_create_usercopy(name, size, align, flags, 0, 0,
ctor);
}
EXPORT_SYMBOL(kmem_cache_create);
kmem_cache_create_usercopy()函数
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 *))
{
struct kmem_cache *s = NULL;
const char *cache_name;
int err;
get_online_cpus();
get_online_mems();
memcg_get_cache_ids();
//创建slab对象描述符时需要申请一个名为slab_mutex的互斥量进行保护
mutex_lock(&slab_mutex);
//kmem_cache_sanity_check做必要检查
err = kmem_cache_sanity_check(name, size);
if (err) {
goto out_unlock;
}
if (flags & ~SLAB_FLAGS_PERMITTED) {
err = -EINVAL;
goto out_unlock;
}
//用于约束创建slab描述符的标志位,定义在<mm/slab.h>
flags &= CACHE_CREATE_MASK;
/* Fail closed on bad usersize of useroffset values. */
if (WARN_ON(!usersize && useroffset) ||
WARN_ON(size < usersize || size - usersize < useroffset))
usersize = useroffset = 0;
if (!usersize)
//__kmem_cache_alias函数查找是否有现成的slab描述符可以复用
//如果有,则直接跳转到out_unlock标签处
s = __kmem_cache_alias(name, size, align, flags, ctor);
if (s)
goto out_unlock;
//重新创建分配一个缓存区存放slab描述符的名称
cache_name = kstrdup_const(name, GFP_KERNEL);
if (!cache_name) {
err = -ENOMEM;
goto out_unlock;
}
//调用create_cache函数创建slab描述符
s = create_cache(cache_name, size,
calculate_alignment(flags, align, size),
flags, useroffset, usersize, ctor, NULL, NULL);
if (IS_ERR(s)) {
err = PTR_ERR(s);
kfree_const(cache_name);
}
out_unlock:
mutex_unlock(&slab_mutex);
memcg_put_cache_ids();
put_online_mems();
put_online_cpus();
if (err) {
if (flags & SLAB_PANIC)
panic("kmem_cache_create: Failed to create slab '%s'. Error %d\n",
name, err);
else {
pr_warn("kmem_cache_create(%s) failed with error %d\n",
name, err);
dump_stack();
}
return NULL;
}
return s;
}
EXPORT_SYMBOL(kmem_cache_create_usercopy);
create_cache()函数
static struct kmem_cache *create_cache(const char *name,
unsigned int object_size, unsigned int align,
slab_flags_t flags, unsigned int useroffset,
unsigned int usersize, void (*ctor)(void *),
struct mem_cgroup *memcg, struct kmem_cache *root_cache)
{
struct kmem_cache *s;
int err;
if (WARN_ON(useroffset + usersize > object_size))
useroffset = usersize = 0;
err = -ENOMEM;
//调用kmem_cache_zalloc函数分配一个kmem_cache数据结构
s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
if (!s)
goto out;
//填充kmem_cache数据结构,如name、size等
s->name = name;
s->size = s->object_size = object_size;
s->align = align;
s->ctor = ctor;
s->useroffset = useroffset;
s->usersize = usersize;
err = init_memcg_params(s, root_cache);
if (err)
goto out_free_cache;
//调用__kmem_cache_create函数创建slab缓存描述符
err = __kmem_cache_create(s, flags);
if (err)
goto out_free_cache;
s->refcount = 1;
//将slab缓存描述符加入全局链表slab_caches中
list_add(&s->list, &slab_caches);
memcg_link_cache(s, memcg);
out:
if (err)
return ERR_PTR(err);
return s;
out_free_cache:
destroy_memcg_params(s);
kmem_cache_free(kmem_cache, s);
goto out;
}
__kmem_cache_create()函数
slab分配器有slab、slob、slub三种,这三种在该函数中有不同的实现方式。函数位于
源码较多,节选部分
int __kmem_cache_create(struct kmem_cache *cachep, slab_flags_t flags)
{
size_t ralign = BYTES_PER_WORD;
gfp_t gfp;
int err;
unsigned int size = cachep->size;
......
#endif
//让slab描述符大小size和系统的word长度对齐(BYTES_PER_WORD)
//如果size小于word长度,以word长度为准进行创建
size = ALIGN(size, BYTES_PER_WORD);
//检查SLAB_RED_ZONE是否溢出,实现调试功能
if (flags & SLAB_RED_ZONE) {
ralign = REDZONE_ALIGN;
size = ALIGN(size, REDZONE_ALIGN);
}
//slab对象对齐
if (ralign < cachep->align) {
ralign = cachep->align;
}
......
cachep->align = ralign;
//colour_off表示一个着色区长度,他和L1高速缓存对齐
cachep->colour_off = cache_line_size();
if (cachep->colour_off < cachep->align)
cachep->colour_off = cachep->align;
//slab_is_available表示当slab分配器处于UP或者FULL状态时,
//分配掩码可以使用GFP_KERNEL,否则只能使用GFP_NOWAIT
if (slab_is_available())
gfp = GFP_KERNEL;
else
gfp = GFP_NOWAIT;
......
done:
//freelist_size表示一个slab分配器中管理区 freelist大小
cachep->freelist_size = cachep->num * sizeof(freelist_idx_t);
cachep->flags = flags;
cachep->allocflags = __GFP_COMP;
if (flags & SLAB_CACHE_DMA)
cachep->allocflags |= GFP_DMA;
if (flags & SLAB_CACHE_DMA32)
cachep->allocflags |= GFP_DMA32;
if (flags & SLAB_RECLAIM_ACCOUNT)
cachep->allocflags |= __GFP_RECLAIMABLE;
//size表示一个slab对象的大小
cachep->size = size;
cachep->reciprocal_buffer_size = reciprocal_value(size);
......
if (OFF_SLAB(cachep)) {
cachep->freelist_cache =
kmalloc_slab(cachep->freelist_size, 0u);
}
//调用setup_cpu_cache继续配置slab描述符
err = setup_cpu_cache(cachep, gfp);
if (err) {
__kmem_cache_release(cachep);
return err;
}
return 0;
}
slab分配器内存布局
slab分配器布局模式
代码集成在__kmem_cache_create()函数内
//如果数组freelist小于一个slab对象的大小并且没有指定构造函数,
//那么slab分配器就可以采用CFLGS_OBJFREELIST_SLAB模式
if (set_objfreelist_slab_cache(cachep, size, flags)) {
flags |= CFLGS_OBJFREELIST_SLAB;
goto done;
}
//如果一个slab分配器的剩余空间(leftover)小于freelist数组大小,
//那么slab分配器就可以采用CFLGS_OFF_SLAB模式
if (set_off_slab_cache(cachep, size, flags)) {
flags |= CFLGS_OFF_SLAB;
goto done;
}
//如果一个slab分配器的剩余空间(leftover)大于slab管理数组大小,
//那么slab分配器就可以采用正常模式
if (set_on_slab_cache(cachep, size, flags))
goto done;
calculate_slab_order
set_objfreelist_slab_cache、set_off_slab_cache、set_on_slab_cache三个函数都会调用calculate_slab_order进行计算
calculate_slab_order是计算slab分配器核心参数的函数。主要处理如下内容
1个slab分配器中需要多少个连续的物理页面
1个slab分配器中能高寒多少个slab对象
1个slab分配器中包含多少个着色区
static size_t calculate_slab_order(struct kmem_cache *cachep,
size_t size, slab_flags_t flags)
{
size_t left_over = 0;
int gfporder;
//从0阶开始计算合适的gfporder值,最多支持的页面数量是2^KMALLOC_MAX_ORDER,
//KMALLOC_MAX_ORDER = MAX_ORDER - 1, 即等于10阶
for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
unsigned int num;
size_t remainder;
//调用cache_estimate计算在2^gfporder个页面大小的情况下可以容纳多少个对象,剩下的空间用于着色
num = cache_estimate(gfporder, size, flags, &remainder);
if (!num)
//1个slab分配器中的对象不能超过SLAB_OBJ_MAX_NUM
if (num > SLAB_OBJ_MAX_NUM)
break;
if (flags & CFLGS_OFF_SLAB) {
struct kmem_cache *freelist_cache;
size_t freelist_size;
freelist_size = num * sizeof(freelist_idx_t);
freelist_cache = kmalloc_slab(freelist_size, 0u);
if (!freelist_cache)
continue;
if (OFF_SLAB(freelist_cache))
continue;
if (freelist_cache->size > cachep->size / 2)
continue;
}
cachep->num = num;
cachep->gfporder = gfporder;
left_over = remainder;
if (flags & SLAB_RECLAIM_ACCOUNT)
break;
//尽可能选择gfporder小的方案
if (gfporder >= slab_max_order)
break;
//left_over表示一个slab分配器中剩余的空间,要检查剩余空间是否太大,太大会浪费空间
if (left_over * 8 <= (PAGE_SIZE << gfporder))
break;
}
return left_over; //返回slab分配器的剩余空间
}
配置slab描述符
确认slab分配器的内存布局后,调用setup_cpu_cache()函数继续进行slab描述符配置。当slab_state=FULL时,直接调用enable_cpu_cache函数
enable_cpucache
static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp)
{
int err;
int limit = 0;
int shared = 0;
int batchcount = 0;
err = cache_random_seq_create(cachep, cachep->num, gfp);
if (err)
goto end;
if (!is_root_cache(cachep)) {
struct kmem_cache *root = memcg_root_cache(cachep);
limit = root->limit;
shared = root->shared;
batchcount = root->batchcount;
}
if (limit && shared && batchcount)
goto skip_setup;
//根据对象的大小计算空闲对象的最大阈值limit
if (cachep->size > 131072)
limit = 1;
else if (cachep->size > PAGE_SIZE)
limit = 8;
else if (cachep->size > 1024)
limit = 24;
else if (cachep->size > 256)
limit = 54;
else
limit = 120;
//在SMP系统中切slab对象大小不大于一个页面的情况下,shared的变量为8,否则为0
shared = 0;
if (cachep->size <= PAGE_SIZE && num_possible_cpus() > 1)
shared = 8;
#if DEBUG
if (limit > 32)
limit = 32;
#endif
//计算batchcount,通常是最大阈值limit的一半,
//用于表示本地对象缓冲池和共享对象缓冲池之间填充对象的数量
batchcount = (limit + 1) / 2;
skip_setup:
err = do_tune_cpucache(cachep, limit, batchcount, shared, gfp);
end:
if (err)
pr_err("enable_cpucache failed for %s, error %d\n",
cachep->name, -err);
return err;
}
do_tune_cpucache -> __do_tune_cpucache
static int do_tune_cpucache(struct kmem_cache *cachep, int limit,
int batchcount, int shared, gfp_t gfp)
{
...
ret = __do_tune_cpucache(cachep, limit, batchcount, shared, gfp);
...
}
static int __do_tune_cpucache(struct kmem_cache *cachep, int limit,
int batchcount, int shared, gfp_t gfp)
{
struct array_cache __percpu *cpu_cache, *prev;
int cpu;
//通过alloc_kmem_cache_cpus函数分配Per-CPU类型的array_cache数据结构
//称之为对象缓冲池
cpu_cache = alloc_kmem_cache_cpus(cachep, limit, batchcount);
if (!cpu_cache)
return -ENOMEM;
prev = cachep->cpu_cache;
cachep->cpu_cache = cpu_cache;
/*
* Without a previous cpu_cache there's no need to synchronize remote
* cpus, so skip the IPIs.
*/
if (prev)
kick_all_cpus_sync();
check_irq_on();
cachep->batchcount = batchcount;
cachep->limit = limit;
cachep->shared = shared;
if (!prev)
goto setup_node;
//遍历在线CPU,调用free_block清空本地对象缓冲池
//正常初始化时,不需要清空。
for_each_online_cpu(cpu) {
LIST_HEAD(list);
int node;
struct kmem_cache_node *n;
struct array_cache *ac = per_cpu_ptr(prev, cpu);
node = cpu_to_mem(cpu);
n = get_node(cachep, node);
spin_lock_irq(&n->list_lock);
free_block(cachep, ac->entry, ac->avail, node, &list);
spin_unlock_irq(&n->list_lock);
slabs_destroy(cachep, &list);
}
free_percpu(prev);
setup_node:
//调用setup_kmem_cache_nodes,继续配置slab描述符
//setup_kmem_cache_nodes会遍历系统素有内存节点,
//并调用setup_kmem_cache_node函数进行初始化slab信息
return setup_kmem_cache_nodes(cachep, gfp);
}
setup_kmem_cache_nodes->setup_kmem_cache_node
static int setup_kmem_cache_nodes(struct kmem_cache *cachep, gfp_t gfp)
{
...
for_each_online_node(node) {
ret = setup_kmem_cache_node(cachep, node, gfp, true);
if (ret)
goto fail;
...
}
static int setup_kmem_cache_node(struct kmem_cache *cachep,
int node, gfp_t gfp, bool force_change)
{
......
//通过init_cache_node函数分配一个新的kmem_cache_node节点
//简称为slab节点
ret = init_cache_node(cachep, node, gfp);
if (ret)
goto fail;
......
}
分配slab对象
kmem_cache_alloc()是分配slab缓存对象的核心函数,内部调用slab_alloc()函数。在slab分配对象过程中是全程关闭本地中断的。
kmem_cache_alloc() -> slab_alloc()
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *ret = slab_alloc(cachep, flags, _RET_IP_);
trace_kmem_cache_alloc(_RET_IP_, ret,
cachep->object_size, cachep->size, flags);
return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc);
slab_alloc(struct kmem_cache *cachep, gfp_t flags, unsigned long caller)
{
unsigned long save_flags;
void *objp;
flags &= gfp_allowed_mask;
cachep = slab_pre_alloc_hook(cachep, flags);
if (unlikely(!cachep))
return NULL;
cache_alloc_debugcheck_before(cachep, flags);
//关闭本地中断
local_irq_save(save_flags);
//调用__do_cache_alloc函数获取slab对象
/*
__do_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
return ____cache_alloc(cachep, flags);
}
__do_cache_alloc内部调用 ____cache_alloc函数
*/
objp = __do_cache_alloc(cachep, flags);
//恢复本地中断
local_irq_restore(save_flags);
objp = cache_alloc_debugcheck_after(cachep, flags, objp, caller);
prefetchw(objp);
if (unlikely(slab_want_init_on_alloc(flags, cachep)) && objp)
memset(objp, 0, cachep->object_size);
slab_post_alloc_hook(cachep, flags, 1, &objp);
return objp; //返回slab对象
}
__do_cache_alloc ->____cache_alloc
__do_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
return ____cache_alloc(cachep, flags);
}
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *objp;
struct array_cache *ac;
check_irq_off();
//通过cpu_cache_get获取slab描述符cachep中的本地对象缓冲池ac
ac = cpu_cache_get(cachep);
//判断本地对象缓冲池ac中是否有空闲独享
if (likely(ac->avail)) {
ac->touched = 1;
objp = ac->entry[--ac->avail];
STATS_INC_ALLOCHIT(cachep);
goto out;
}
STATS_INC_ALLOCMISS(cachep);
//如果没有或者为第一次分配,则进入cache_alloc_refill
objp = cache_alloc_refill(cachep, flags);
ac = cpu_cache_get(cachep);
out:
if (objp)
kmemleak_erase(&ac->entry[ac->avail]);
return objp;
}
cache_alloc_refill()
static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
{
int batchcount;
struct kmem_cache_node *n;
struct array_cache *ac, *shared;
int node;
void *list = NULL;
struct page *page;
check_irq_off();
node = numa_mem_id();
//获取本地对象缓冲池ac
ac = cpu_cache_get(cachep);
batchcount = ac->batchcount;
if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
batchcount = BATCHREFILL_LIMIT;
}
//通过get_node获取slab节点
n = get_node(cachep, node);
BUG_ON(ac->avail > 0 || !n);
shared = READ_ONCE(n->shared);
//如果slab节点没有空闲对象且共享对象池shared为空或者共享对象缓冲池也没有空闲对象
//直接跳转到标签direct_grow
if (!n->free_objects && (!shared || !shared->avail))
goto direct_grow;
spin_lock(&n->list_lock);
shared = READ_ONCE(n->shared);
//如果共享对象缓冲池有空闲对象,尝试迁移batchcount个空闲对象到本地对象缓冲池ac中
//transfer_objects函数用于从共享对象缓存池把空闲对象迁移至本地对象缓冲池
if (shared && transfer_objects(ac, shared, batchcount)) {
shared->touched = 1;
goto alloc_done;
}
//如果共享对象缓冲池没有空闲对象,通过get_first_slab函数查看slab节点的
//slabs_partial链表(部分空闲链表)和slab_free链表(全部空闲链表)
//返回slab分配器第一个物理页面的page数据结构
while (batchcount > 0) {
page = get_first_slab(n, false);
if (!page)
goto must_grow;
check_spinlock_acquired(cachep);
//通过alloc_block函数将slab分配器中迁移batchcount个空闲对象到本地对象缓冲池
batchcount = alloc_block(cachep, ac, page, batchcount);
fixup_slab_list(cachep, n, page, &list);
}
must_grow:
//更新free_object计数值
n->free_objects -= ac->avail;
alloc_done:
spin_unlock(&n->list_lock);
fixup_objfreelist_debug(cachep, &list);
//如跳转到direct_grow,则表示slab节点没有空闲对象并且共享对象缓冲池中也没有空闲对象
//说明这个内存节点没有slab空闲对象。这种情况下只能重新分配slab分配器
//调用cache_grow_begin()函数分配一个slab分配器
direct_grow:
if (unlikely(!ac->avail)) {
/* Check if we can use obj in pfmemalloc slab */
if (sk_memalloc_socks()) {
void *obj = cache_alloc_pfmemalloc(cachep, n, flags);
if (obj)
return obj;
}
page = cache_grow_begin(cachep, gfp_exact_node(flags), node);
ac = cpu_cache_get(cachep);
//通过alloc_block函数将slab分配器中迁移batchcount个空闲对象到本地对象缓冲池
if (!ac->avail && page)
alloc_block(cachep, ac, page, batchcount);
cache_grow_end(cachep, page);
if (!ac->avail)
return NULL;
}
//touched为1表示刚刚使用过本地对象缓冲池
ac->touched = 1;
return ac->entry[--ac->avail];
}
cache_grow_begin ->kmem_getpages->cache_init_objs
cache_grow_begin:负责 重新分配一个slab分配器
kmem_getpages:负责分配slab分配器需要的物理页面
cache_init_objs:初始化空闲对象
static struct page *cache_grow_begin(struct kmem_cache *cachep,
gfp_t flags, int nodeid)
{
......
page = kmem_getpages(cachep, local_flags, nodeid);
if (!page)
goto failed;
......
cache_init_objs(cachep, page);
......
opps1:
kmem_freepages(cachep, page);
failed:
if (gfpflags_allow_blocking(local_flags))
local_irq_disable();
return NULL;
释放slab缓存对象
slab系统有两种缓存释放方式:
第一种是通过kmem_cache_free()释放对象
另一种是注册了一个定时器,定时扫描所有的slab描述符,回收一部分空闲对象,达到条件的slab分配器会被销毁,实现函数为cache_reap()
kmem_cache_free() -> __cache_free
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{
unsigned long flags;
cachep = cache_from_obj(cachep, objp);
if (!cachep)
return;
local_irq_save(flags);
debug_check_no_locks_freed(objp, cachep->object_size);
if (!(cachep->flags & SLAB_DEBUG_OBJECTS))
debug_check_no_obj_freed(objp, cachep->object_size);
__cache_free(cachep, objp, _RET_IP_);
local_irq_restore(flags);
trace_kmem_cache_free(_RET_IP_, objp);
}
void ___cache_free(struct kmem_cache *cachep, void *objp,
unsigned long caller)
{
......
//如果本地对象缓存池的空闲对象ac->avail大于或等于ac->limit
//就会调用cache_flusharray函数,尝试回收空闲对象
if (ac->avail < ac->limit) {
STATS_INC_FREEHIT(cachep);
} else {
STATS_INC_FREEMISS(cachep);
cache_flusharray(cachep, ac);
......
//将对象释放到本地对象缓冲池ac中
ac->entry[ac->avail++] = objp;
}
cache_flusharray()
cache_flusharra()函数主要用于回收slab分配器
static void cache_flusharray(struct kmem_cache *cachep, struct array_cache *ac)
{
......
//销毁slab分配器
slabs_destroy(cachep, &list);
ac->avail -= batchcount;
//将本地对象缓冲池中的剩余的空闲对象迁移到缓存的头部
memmove(ac->entry, &(ac->entry[batchcount]), sizeof(void *)*ac->avail);
}
其他知识点
名词解释
零长数组
零长数组是一种空数组,即长度为0的数组。它可以用来表示一个没有元素的序列或集合。
着色区
Slab分配器是一种高效的内存管理机制,用于分配和释放内核对象的内存。在Slab分配器中,内存被划分为不同的大小,每个大小的内存块都由一个或多个用于管理内存的Slab对象组成。Slab对象包含着色区(color cache)这一概念。
着色区(color cache)实际上是Slab对象中的一块内存区域,用于缓存已分配的内存块。它可以加快内存分配的速度,减少频繁的内存申请和释放带来的开销。着色区的目的是提供一种快速分配内存的机制,避免频繁地搜索合适的内存块。
Slab分配器的着色区策略可以根据实际需求进行调整,以便更好地满足内存分配的性能要求。它在内核中的作用是提供高效的内存分配和释放,以优化系统的性能。
着色区可以让每一个slab分配器对应不同数量的高速缓存行,可以使不同slab分配器上同一个相对位置slab对象的起始地址在高速缓存中互相错开,有利于提高高速缓存的访问效率