关于slab几个结构体的关系和初始化的逻辑请见:
[linux kernel]slub内存管理分析(0) 导读
[linux kernel]slub内存管理分析(1) 结构体
[linux kernel]slub内存管理分析(2) 初始化
PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache
我们这里称为slab管理结构,它管理的真个slab 体系成为slab cache,struct kmem_cache_node
这里就叫node。单个堆块称为object或者堆块或内存对象。
开启CONFIG_SLAB_MERGE_DEFAULT
则户出现创建一个新的slab cache 的时候不创建新的而是重用已有slab cache 的情况。要求对方要满足条件才可重用:
flag
都不开启SLAB_NEVER_MERGE
usersize
都为0名为"kmalloc-xx"的通用slab 由于初始化的时候usersize
不为0,则无法被重用。
内核开启CONFIG_SLAB_MERGE_DEFAULT编译选项的时候可能会造成slab 的重用,也就是对于创建新的slab缓存(struct kmem_cache
)的时候可能会找一个大小和目前新建的相同的slab 来重用,但也并不是无脑重用的,下面从代码逻辑查看如何重用。
除了默认slab cache,linux内核支持各种结构体或内核模块自定义自己的特定slab cache,有些结构体如struct file
会分配自己的slab 专门用于struct file
结构体的申请。以免和其他slab cache通用导致另外的结构体出现漏洞可以复写struct file
来漏洞利用。
对于用户想要创建一个自己的slab,可以通过kmem_cache_create 函数拉创建:
kmem_cache_create
指定名字、大小等信息,初始化一个slab cache
kmem_cache_create_usercopy
相较于上面多指定了几个参数如usersize、useroffset
__kmem_cache_alias
查找是否有可重用的slab cache
find_mergeable
寻找可重用的slab cachecreate_cache
没有可重用的slab cache 则创建新的
kmem_cache_zalloc
__kmem_cache_create
如struct file
的slab "filp_cachep"初始化部分:
fs\file_table.c : files_init
void __init files_init(void)
{
filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT, NULL);
percpu_counter_init(&nr_files, 0, GFP_KERNEL);
}
其他内核模块可以调用kmem_cache_create
来创建自己的slab cache。
kmem_cache_create
函数就是直接调用kmem_cache_create_usercopy
函数:
mm\slab_common.c : kmem_cache_create, kmem_cache_create_usercopy
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_usercopykmem_cache_create_usercopy(name, size, align, flags, 0, 0,
ctor);
//[1] 调用kmem_cache_create_usercopy完成初始化。不过usersize和useroffset都是0
}
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 *))
{
struct kmem_cache *s = NULL;
const char *cache_name;
int err;
··· ···
if (!usersize)//[2]如果传入usersize 为0,调用__kmem_cache_alias 查看是否有可重用的slab cache
s = __kmem_cache_alias(name, size, align, flags, ctor);
if (s)//[2.1] 找到可重用的slab cache 之后直接返回
goto out_unlock;
··· ···
s = create_cache(cache_name, size,//[3]否则调用create_cache 创建一个新slab cache
calculate_alignment(flags, align, size),
flags, useroffset, usersize, ctor, NULL);
if (IS_ERR(s)) {
err = PTR_ERR(s);
kfree_const(cache_name);
}
out_unlock:
··· ···
}
[1] kmem_cache_create
函数中直接调用kmem_cache_create_usercopy
函数申请新的slab cache,但useroffset
和usersize
参数都是0。
[2] 在kmem_cache_create_usercopy
中药先调用__kmem_cache_alias
函数寻找是否有可重用的slab cache
[2.1] 如果找到可重用的,直接返回即可,重用该slab cache
[3] 否则调用create_cache
进行初始化
__kmem_cache_alias
函数检查是否有可重用的slab cache,有的话则直接返回该slab cache:
mm\slub.c : __kmem_cache_alias
struct kmem_cache *
__kmem_cache_alias(const char *name, unsigned int size, unsigned int align,
slab_flags_t flags, void (*ctor)(void *))
{
struct kmem_cache *s;
s = find_mergeable(size, align, flags, name, ctor);//[1]寻找可重用的slab cache
if (s) {//[2]如果找到的话
s->refcount++;//[2.1] 增加引用计数
/*
* Adjust the object sizes so that we clear
* the complete object on kzalloc.
*/
s->object_size = max(s->object_size, size);//object_size 为使用这个slab cache 的结构体中最大的
s->inuse = max(s->inuse, ALIGN(size, sizeof(void *)));
if (sysfs_slab_alias(s, name)) {
s->refcount--;
s = NULL;
}
}
return s;
}
[1] 调用find_mergeable
函数寻找一个可以重用的slab cache,下面会分析该函数
[2] 如果找到可重用的slab cache,则会更改一些成员
[2.1] 首先引用计数要增加
[2.2] 然后object_size
代表使用这个slab cache 的结构体大小,应修改为重用这个slab cache 的结构体中最大的大小。
该函数寻找一个可以直接重用的slab cache,找到直接返回,否则返回NULL
mm\slab_common.c : find_mergeable
struct kmem_cache *find_mergeable(unsigned int size, unsigned int align,
slab_flags_t flags, const char *name, void (*ctor)(void *))
{
struct kmem_cache *s;
if (slab_nomerge) //[1] 首先的开启CONFIG_SLAB_MERGE_DEFAULT
return NULL;
if (ctor)//[1] 必须不传入构造函数才能重用其他slab cache
return NULL;
size = ALIGN(size, sizeof(void *));
align = calculate_alignment(flags, align, size);
size = ALIGN(size, align);
flags = kmem_cache_flags(size, flags, name);
if (flags & SLAB_NEVER_MERGE)//[1] 如果flag 制定了never merge则不会重用
return NULL;
list_for_each_entry_reverse(s, &slab_caches, list) {//[2] 通过slab_caches列表遍历所有slab cache
if (slab_unmergeable(s))//[2.1] 查看是否可以被重用
continue;
if (size > s->size)//[2.2] 当前slab 大小不能大于重用的slab
continue;
if ((flags & SLAB_MERGE_SAME) != (s->flags & SLAB_MERGE_SAME))//[2.2] flag也得一样才行
continue;
/*
* Check if alignment is compatible.
* Courtesy of Adrian Drzewiecki
*/
if ((s->size & ~(align - 1)) != s->size)//[2.2] 对齐满足要求
continue;
if (s->size - size >= sizeof(void *))//[2.2] size得一样,要么浪费太多内存
continue;
if (IS_ENABLED(CONFIG_SLAB) && align &&//slub不涉及
(align > s->align || s->align % align))
continue;
return s;//[3] 通过以上的检查才算成功
}
return NULL;
}
[1] 首先进行一些全局检查是否可以重用:需要开启CONFIG_SLAB_MERGE_DEFAULT
;该slab cache不能有构造函数;flag 不设置SLAB_NEVER_MERGE
才行。
[2] 然后遍历slab_caches
全局列表(之前提到过,所有的slab cache都会被链入该链表),寻找可以重用的
[2.1] 调用slab_unmergeable
函数挨个被遍历的slab cache看是否可以被重用
[2.2] 即便规则上允许被重用,但能否重用还要看下面几点是否匹配:大小需要一样,小了没法重用,大了浪费内存;flag
也要相同;对齐满足要求。
[3] 满足上面的所有要求之后,就算是可以重用,返回。
该函数查看一个slab cache是否可以被重用:
mm\slab_common.c : slab_unmergeable
int slab_unmergeable(struct kmem_cache *s)
{
if (slab_nomerge || (s->flags & SLAB_NEVER_MERGE flag))
return 1;//没开启CONFIG_SLAB_MERGE_DEFAULT并且该slab cache有SLAB_NEVER_MERGE flag的话无法重用
if (s->ctor)//该slab cache 有构造函数无法重用
return 1;
if (s->usersize)//该slab 设置了usersize 无法重用
return 1;
/*
* We may have set a slab to be unmergeable during bootstrap.
*/
if (s->refcount < 0) //该slab 已经准备释放了无法重用
return 1;
return 0;//否则可以重用
}
create_cache
函数创建一个新的slab cache:
mm\slab_common.c : 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 kmem_cache *root_cache)
{
struct kmem_cache *s;
int err;
if (WARN_ON(useroffset + usersize > object_size))
useroffset = usersize = 0;
err = -ENOMEM;
s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);//分配一个slab 管理结构体
if (!s)
goto out;
s->name = name;//设置基本信息
s->size = s->object_size = object_size;
s->align = align;
s->ctor = ctor;
s->useroffset = useroffset;//这里设置useroffset 和usersize ,从kmem_cache_create走过来的则是0
s->usersize = usersize;
err = __kmem_cache_create(s, flags);//然后初始化。上一章分析过了
if (err)
goto out_free_cache;
s->refcount = 1;
list_add(&s->list, &slab_caches);
out:
if (err)
return ERR_PTR(err);
return s;
out_free_cache:
kmem_cache_free(kmem_cache, s);
goto out;
}
如果在上面的流程中没有找到可重用的,则会调用create_cache
申请新的slab cache,这里使用的kmem_cache_zalloc
和__kmem_cache_create
都在上一章分析过了。
这里的创建函数我们需要注意的是,如果是通过kmem_cache_create
函数流程走到这里,则设置usersize
和useroffset
的值是0,而之前slab_unmergeable
函数中我们知道usersize
不为0的话是无法被重用的。所以至少在通过kmem_cache_create
创建的slab cache 是默认可重用的。
在第二章中我们介绍过slab 的总体初始化流程,其中初始化了通用slab也就是名为"kmalloc-xx"的slab 的初始化过程,调用栈回顾:
create_kmalloc_caches
初始化每一个通用slab
new_kmalloc_cache
根据下标初始化相应的kmalloc_caches
create_kmalloc_cache
具体初始化操作,主要调用下面两个
kmem_cache_zalloc
->…->slab_alloc_node
从slab 中分配一段内存,参考正常申请流程create_boot_cache
初始化这两个slab 结构体
__kmem_cache_create
核心初始化函数create_kmalloc_cache(if CONFIG_ZONE_DMA)
初始化DAM相关空间init_freelist_randomization
是CONFIG_SLAB_FREELIST_RANDOM
的初始化整体流程我们熟悉了,我们只关注是否有和CONFIG_SLAB_MERGE_DEFAULT 相关的影响slab 重用逻辑的部分,其实只有一处:
mm\slab_common.c : new_kmalloc_cache
static void __init
new_kmalloc_cache(int idx, enum kmalloc_cache_type type, slab_flags_t flags)
{
if (type == KMALLOC_RECLAIM)
flags |= SLAB_RECLAIM_ACCOUNT;
kmalloc_caches[type][idx] = create_kmalloc_cache(
kmalloc_info[idx].name[type],
kmalloc_info[idx].size, flags, 0,
kmalloc_info[idx].size);//这里将usersize 设置为了slab size
}
struct kmem_cache *__init create_kmalloc_cache(const char *name,
unsigned int size, slab_flags_t flags,
unsigned int useroffset, unsigned int usersize);
在new_kmalloc_cache
函数对每个通用slab cache进行初始化的时候,调用create_kmalloc_cache
函数的时候,传入的usersize
并非是0,而是kmalloc_info[idx].size
,也就是和slab size一样大。之前我们分析过,如果usersize
非0,则该slab cache无法被重用,也就是说,通用slab 无法被重用。
开启CONFIG_SLAB_MERGE_DEFAULT
则户出现创建一个新的slab cache 的时候不创建新的而是重用已有slab cache 的情况。要求对方要满足条件才可重用:
flag
都不开启SLAB_NEVER_MERGE
usersize
都为0名为"kmalloc-xx"的通用slab 由于初始化的时候usersize
不为0,则无法被重用。