[linux kernel]slub内存管理分析(2.5) slab重用

文章详细介绍了Linux内核slab内存管理机制中的slab重用策略,当CONFIG_SLAB_MERGE_DEFAULT开启时,如何查找并重用已有slabcache。条件包括双方flag、构造函数、usersize的匹配。同时,文章指出通用slabkmalloc-xx由于初始化时usersize非0,因此无法被重用。整个流程涉及kmem_cache_create、__kmem_cache_alias和create_cache等函数的调用和逻辑判断。
摘要由CSDN通过智能技术生成

背景

前情回顾

关于slab几个结构体的关系和初始化的逻辑请见:

[linux kernel]slub内存管理分析(0) 导读

[linux kernel]slub内存管理分析(1) 结构体

[linux kernel]slub内存管理分析(2) 初始化

描述方法约定

PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache我们这里称为slab管理结构,它管理的真个slab 体系成为slab cachestruct kmem_cache_node这里就叫node。单个堆块称为object或者堆块内存对象

SLAB重用总览

省流总结

开启CONFIG_SLAB_MERGE_DEFAULT 则户出现创建一个新的slab cache 的时候不创建新的而是重用已有slab cache 的情况。要求对方要满足条件才可重用:

  • 对方slab cache 和自己的flag 都不开启SLAB_NEVER_MERGE
  • 对方slab cache 和自己都没有构造函数
  • 对方的slab cache和自己的usersize 都为0
  • 自己的slab 大小和对方相同、对齐相同

名为"kmalloc-xx"的通用slab 由于初始化的时候usersize不为0,则无法被重用。

简介

内核开启CONFIG_SLAB_MERGE_DEFAULT编译选项的时候可能会造成slab 的重用,也就是对于创建新的slab缓存(struct kmem_cache)的时候可能会找一个大小和目前新建的相同的slab 来重用,但也并不是无脑重用的,下面从代码逻辑查看如何重用。

自定义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 cache
      • create_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

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,但useroffsetusersize 参数都是0。

[2] 在kmem_cache_create_usercopy中药先调用__kmem_cache_alias 函数寻找是否有可重用的slab cache

​ [2.1] 如果找到可重用的,直接返回即可,重用该slab cache

[3] 否则调用create_cache 进行初始化

__kmem_cache_alias

__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 的结构体中最大的大小。

find_mergeable

该函数寻找一个可以直接重用的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_unmergeable

该函数查看一个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

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 函数流程走到这里,则设置usersizeuseroffset 的值是0,而之前slab_unmergeable函数中我们知道usersize 不为0的话是无法被重用的。所以至少在通过kmem_cache_create 创建的slab cache 是默认可重用的。

通用slab “kmalloc-xx” 初始化流程

调用栈

在第二章中我们介绍过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_randomizationCONFIG_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 的情况。要求对方要满足条件才可重用:

  • 对方slab cache 和自己的flag 都不开启SLAB_NEVER_MERGE
  • 对方slab cache 和自己都没有构造函数
  • 对方的slab cache和自己的usersize 都为0
  • 自己的slab 大小和对方相同、对齐相同

名为"kmalloc-xx"的通用slab 由于初始化的时候usersize不为0,则无法被重用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值