[linux kernel]slub内存管理分析(4) 细节操作以及安全加固

本文详细分析了Linux内核slab管理系统中的两个安全加固机制:CONFIG_SLAB_FREELIST_HARDENED和CONFIG_SLAB_FREELIST_RANDOM。前者通过对freelist指针进行混淆以增强内存安全,防止泄露或非法修改;后者通过随机化freelist顺序,增加攻击难度。文章还介绍了这两个机制的初始化和具体操作,如freelist的混淆与去混淆、随机化过程以及slab的强制下架和补充流程。
摘要由CSDN通过智能技术生成

背景

前情回顾

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

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

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

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

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

[linux kernel]slub内存管理分析(3) kmalloc

描述方法约定

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

简介

本篇主要就kmalloc 中的一些层数比较深并且很多逻辑中都要调用的操作和两个安全加固CONFIG_SLAB_FREELIST_HARDENEDCONFIG_SLAB_FREELIST_RANDOM进行分析,比如(安全加固下)对freelist的操作、cpu_slab->page的强制下架等。

本章介绍的内容不影响分析slub算法的整体逻辑,对细节没有兴趣可以跳过。

freelist操作与CONFIG_SLAB_FREELIST_HARDENED

CONFIG_SLAB_FREELIST_HARDENED简介

CONFIG_SLAB_FREELIST_HARDENED 安全加固宏是给freelist 链表指针进行混淆:

混淆后的指针=原指针 ^ 随机数random ^ 指针地址

直接在内存中/通过内存泄露获得freelistfreelist成员的next指针是混淆后的,避免直接泄露地址或直接修改地址。在每次存取freelist 指针之前都会进行一次上面的异或操作得到真实指针或从真实指针转换成混淆指针。

CONFIG_SLAB_FREELIST_HARDENED初始化

linux\include\linux\slub_def.h : struct kmem_cache

struct kmem_cache {
··· ···
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	unsigned long random; //开启CONFIG_SLAB_FREELIST_HARDENED宏会多一个random 成员
#endif
··· ···
};

开启CONFIG_SLAB_FREELIST_HARDENED宏会多一个random 成员,在初始化流程中的kmem_cache_open 函数中会对random 进行初始化(完整初始化流程在前文已经分析过了):

linux\mm\slub.c : kmem_cache_open

static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags)
{
	s->flags = kmem_cache_flags(s->size, flags, s->name);
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	s->random = get_random_long(); //初始化random 成员
#endif

	··· ···
    ··· ···
}

这里调用get_random_long 函数获取一个随机的long 类型数据,所以random 就是一个随机数而已。

CONFIG_SLAB_FREELIST_HARDENED实现与freelist相关操作

freelist_ptr 混淆/去混淆指针

freelist_ptr 函数用于将freelist中一个object 的指向下一个objectnext指针从真实值计算出混淆值或从混淆值还原出真实值:

linux\mm\slub.c : freelist_ptr

static inline void *freelist_ptr(const struct kmem_cache *s, void *ptr,
				 unsigned long ptr_addr)//[1] 传入参数
{
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	/*
	 * When CONFIG_KASAN_SW/HW_TAGS is enabled, ptr_addr might be tagged.
	 * Normally, this doesn't cause any issues, as both set_freepointer()
	 * and get_freepointer() are called with a pointer with the same tag.
	 * However, there are some issues with CONFIG_SLUB_DEBUG code. For
	 * example, when __free_slub() iterates over objects in a cache, it
	 * passes untagged pointers to check_object(). check_object() in turns
	 * calls get_freepointer() with an untagged pointer, which causes the
	 * freepointer to be restored incorrectly.
	 */
	return (void *)((unsigned long)ptr ^ s->random ^   //[2] 混淆/去混淆操作
			swab((unsigned long)kasan_reset_tag((void *)ptr_addr)));
#else
	return ptr; // [3] 没开启混淆则直接返回
#endif
}

[1] 传入的ptr 参数就是要去混淆的指针,ptr_addr 是该指针的地址。

[2] 混淆公式为:混淆后的指针=原指针 ^ 随机数random ^ 指针地址,所以去混淆就是 混淆后的指针 ^ 随机数random ^ 指针地址 。因为是异或,所以操作相同。

[3] 如果没开启CONFIG_SLAB_FREELIST_HARDENED 则直接返回ptr即可。

get_freepointer 获取next指针

linux\mm\slub.c : get_freepointer

static inline void *get_freepointer(struct kmem_cache *s, void *object)
{
	object = kasan_reset_tag(object);//默认直接返回地址
	return freelist_dereference(s, object + s->offset);//[1]next指针为object+s->offset
}

static inline void *freelist_dereference(const struct kmem_cache *s,
					 void *ptr_addr) //[2]直接调用freelist_ptr
{
	return freelist_ptr(s, (void *)*(unsigned long *)(ptr_addr),
			    (unsigned long)ptr_addr);
}

[1] 这里调用freelist_dereference,根据参数,传入的ptr_addr 也就是指向下一个objectnext指针地址是object + s->offsets->offset 一般是该slab 大小的一半。

[2] freelist_dereference函数中直接调用上面分析的freelist_ptr 获取真实指针

set_freepointer 设置next指针

linux\mm\slub.c : set_freepointer

static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
	unsigned long freeptr_addr = (unsigned long)object + s->offset; //获得next指针地址

#ifdef CONFIG_SLAB_FREELIST_HARDENED
	BUG_ON(object == fp); /* naive detection of double free or corruption */
#endif//[1] 这里检测double free

	freeptr_addr = (unsigned long)kasan_reset_tag((void *)freeptr_addr);
	*(void **)freeptr_addr = freelist_ptr(s, fp, freeptr_addr); //[2] 调用freelist_ptr 混淆指针值
}

[1] 首先会在开启CONFIG_SLAB_FREELIST_HARDENED的时候检测double free,如果要释放的objectlist 指针一样,则是double free,类似用户层malloc

[2] 在给指针复制之前调用了freelist_ptr 进行混淆操作,如果没有开启混淆,则freelist_ptr 会直接返回指针原本的值。

CONFIG_SLAB_FREELIST_RANDOM

CONFIG_SLAB_FREELIST_RANDOM简介

CONFIG_SLAB_FREELIST_RANDOM 安全加固是将freelist 列表顺序随机化,正常freelist列表就是从开始往后按顺序排列,但如果开启了CONFIG_SLAB_FREELIST_RANDOM 则会打乱freelistobject 的顺序,即便是刚申请好的新slab,其中的freelist 列表顺序也是随机的。

CONFIG_SLAB_FREELIST_RANDOM初始化

linux\include\linux\slub_def.h : struct kmem_cache

struct kmem_cache {
··· ···
#ifdef CONFIG_SLAB_FREELIST_RANDOM
	unsigned int *random_seq;
#endif
··· ···
};

开启CONFIG_SLAB_FREELIST_RANDOM 之后会多一个叫做random_seq 的数组,以指针成员的形式出现在kmem_cache也就是slab 管理结构体中。在 kmem_cache_init中会进行初始化:

linux\mm\slub.c : kmem_cache_init

void __init kmem_cache_init(void)
{
	··· ···
	create_kmalloc_caches(0);//正式初始化各个slab

	/* Setup random freelists for each cache */
	init_freelist_randomization();//开启了CONFIG_SLAB_FREELIST_RANDOM需要操作一下

	··· ···
}

create_kmalloc_caches函数已经完成了各个slab 的初始化之后,调用init_freelist_randomization对所有slab 进行freelist random初始化:

linux\mm\slub.c : init_freelist_randomization

static void __init init_freelist_randomization(void)
{
	struct kmem_cache *s;

	mutex_lock(&slab_mutex);

	list_for_each_entry(s, &slab_caches, list)
		init_cache_random_seq(s);//对slab_caches 列表中所有slab调用init_cache_random_seq

	mutex_unlock(&slab_mutex);
}

每个slab 都会加入slab_caches 列表,这里对列表中每个成员都调用init_cache_random_seq进行初始化:

linux\mm\slub.c : init_cache_random_seq

static int init_cache_random_seq(struct kmem_cache *s)
{
	unsigned int count = oo_objects(s->oo);//[1] 获取该slab中有多少个object对象
	int err;

	/* Bailout if already initialised */
	if (s->random_seq)//已经初始化好就不管了
		return 0;

	err = cache_random_seq_create(s, count, GFP_KERNEL);
    //[2] 调用cache_random_seq_create 对s->random_seq初始化
	if (err) {
		pr_err("SLUB: Unable to initialize free list for %s\n",
			s->name);
		return err;
	}

	/* Transform to an offset on the set of pages */
	if (s->random_seq) {//如果初始化好
		unsigned int i;

		for (i = 0; i < count; i++)
			s->random_seq[i] *= s->size;
        //[3] s->random_seq此时是打乱的序号,size 是一块内存的大小,相乘得到的就是随机的某一块内存的偏移
	}
	return 0;
}

[1] 调用oo_objects 获得该slab 中每个slab 中分出的内存块的数量。

[2] 调用cache_random_seq_create 对没初始化s->random_seq 的slab 进行初始化,其实就是s->random_seq 的值赋值成内存块的编号,然后打乱顺序。

[3] s->random_seq此时是打乱的序号,size是一块内存的大小,相乘得到的就是随机的某一块内存的偏移。后面用于shuffle_freelist

cache_random_seq_create

然后是cache_random_seq_create函数,用于制造一个混乱顺序的列表:

linux\mm\slab_common.c : cache_random_seq_create

int cache_random_seq_create(struct kmem_cache *cachep, unsigned int count,
				    gfp_t gfp)
{
	struct rnd_state state;

	if (count < 2 || cachep->random_seq) //小于2 的不需要随机
		return 0;

	cachep->random_seq = kcalloc(count, sizeof(unsigned int), gfp);//[1] 申请数组内存count * sizeof(int)
	if (!cachep->random_seq)
		return -ENOMEM;

	/* Get best entropy at this stage of boot */
	prandom_seed_state(&state, get_random_long());//获取随机种子

	freelist_randomize(&state, cachep->random_seq, count);//[2] 打乱顺序
	return 0;
}

[1] 首先根据该slab 中的内存块数量 申请random_seq数组的内存空间count * sizeof(int)

[2] 然后调用freelist_randomize 函数制造一个乱序列表,freelist_randomize

linux\mm\slab_common.c : freelist_randomize

static void freelist_randomize(struct rnd_state *state, unsigned int *list,
			       unsigned int count)
{
	unsigned int rand;
	unsigned int i;

	for (i = 0; i < count; i++)
		list[i] = i; //[1]先按照下标排序

	/* Fisher-Yates shuffle */
	for (i = count - 1; i > 0; i--) { //[2]然后随机打乱顺序
		rand = prandom_u32_state(state);
		rand %= (i + 1);
		swap(list[i], list[rand]);
	}
}

[1] 先将这个数组按照下标排序,每个值就是下标

[2] 然后跟随机下标的另一个数互换位置。这样这个数组就被打乱顺序了。

CONFIG_SLAB_FREELIST_RANDOM使用

CONFIG_SLAB_FREELIST_RANDOM生效主要是在新slab 申请的时候调用的shuffle_freelist 函数:

linux\mm\slub.c : allocate_slab

static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	··· ···
	shuffle = shuffle_freelist(s, page);//打乱freelist 顺序

	if (!shuffle) {//freelist 正常顺序
		start = fixup_red_left(s, start);
		start = setup_object(s, page, start);
		page->freelist = start;
		for (idx = 0, p = start; idx < page->objects - 1; idx++) {
			next = p + s->size;
			next = setup_object(s, page, next);
			set_freepointer(s, p, next);
			p = next;
		}
		set_freepointer(s, p, NULL);
	}
	··· ···
}

可以看出,正常顺序就是从前往后依次链接,如果开启CONFIG_SLAB_FREELIST_RANDOM,则会调用shuffle_freelist 打乱freelist 顺序。

shuffle_freelist

linux\mm\slub.c : shuffle_freelist

static bool shuffle_freelist(struct kmem_cache *s, struct page *page)
{
	void *start;
	void *cur;
	void *next;
	unsigned long idx, pos, page_limit, freelist_count;

	if (page->objects < 2 || !s->random_seq)//内存块数量小于2 则无需乱序
		return false;

	freelist_count = oo_objects(s->oo);//获取slab page中内存块数量
	pos = get_random_int() % freelist_count;//随机一个起始下标

	page_limit = page->objects * s->size; //page大小为 内存块数量*每块大小
	start = fixup_red_left(s, page_address(page));

	/* First entry is used as the base of the freelist */
	cur = next_freelist_entry(s, page, &pos, start, page_limit,
				freelist_count);//随机获取第一个内存块地址
	cur = setup_object(s, page, cur);
	page->freelist = cur;//将第一个内存块加入freelist

	for (idx = 1; idx < page->objects; idx++) {//依次从next_freelist_entry获取随机内存块加入freelist
		next = next_freelist_entry(s, page, &pos, start, page_limit,
			freelist_count);//获取一个随机内存块地址
		next = setup_object(s, page, next);
		set_freepointer(s, cur, next);//加入freelist后面
		cur = next;
	}
	set_freepointer(s, cur, NULL);

	return true;
}

根据代码中的注释,shuffle_freelist起始就是结合下面的next_freelist_entry函数,根据random_seq记录的随机的内存块顺序,依次加入到freelist中。

linux\mm\slub.c : next_freelist_entry

static void *next_freelist_entry(struct kmem_cache *s, struct page *page,
				unsigned long *pos, void *start,
				unsigned long page_limit,
				unsigned long freelist_count)
{
	unsigned int idx;

	/*
	 * If the target page allocation failed, the number of objects on the
	 * page might be smaller than the usual size defined by the cache.
	 */
	do {
		idx = s->random_seq[*pos];//[1]s->random_seq 里是随机一个内存块的偏移
		*pos += 1;
		if (*pos >= freelist_count)
			*pos = 0;
	} while (unlikely(idx >= page_limit));

	return (char *)start + idx;//[2]start + 偏移 即返回该内存块的地址
}

[1] s->random_seq 是之前打乱顺序的各个内存块的地址偏移,这里的意思是获取pos 下标的内存块偏移

[2] 然后返回start+idx 起始地址+偏移,即内存块实际地址,所以next_freelist_entry 函数就是根据random_seq 记录的顺序获取下一个随机内存块的地址

deactivate_slab强制下架

deactivate_slab 函数比较长,强制下架当前cpu_slab->page,也就是强制切换cpu_slab控制的slab page,大部分使用场景都是unlinkely 分支,也就是大部分都在异常情况下出现,除了在flush_slab 函数中:

mm\slub.c : flush_slab

static inline void flush_slab(struct kmem_cache *s, struct kmem_cache_cpu *c)
{
	stat(s, CPUSLAB_FLUSH);
	deactivate_slab(s, c->page, c->freelist, c);//强制下架

	c->tid = next_tid(c->tid);
}

flush_slab

flush_slab 函数应用场景主要在调用new_slab_objects申请新slab page时和销毁slab 时会调用flush_all对所有cpu调用flush_slab 。其中在new_slab_objects中:

mm\slub.c : new_slab_objects

static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags,
			int node, struct kmem_cache_cpu **pc)
{
	void *freelist;
	struct kmem_cache_cpu *c = *pc;
	struct page *page;

	WARN_ON_ONCE(s->ctor && (flags & __GFP_ZERO));

	freelist = get_partial(s, flags, node, c);//[1]获取node 的partical slab

	if (freelist)
		return freelist;

	page = new_slab(s, flags, node);//[2]调用new_slab分配新page
	if (page) {
		c = raw_cpu_ptr(s->cpu_slab);//[2.1]获取当前cpu
		if (c->page)
			flush_slab(s, c);//[2.2]如果当前cpu有page则flush_slab刷新吗,这里会将page强制下架

		freelist = page->freelist;//[2.3]更新freelist、page
		page->freelist = NULL;//被cpu_slab控制的page 的freelist 都要设置为NULL

		stat(s, ALLOC_SLAB);
		c->page = page;//该page被cpu_slab控制
		*pc = c;
	}

	return freelist;//[3]返回freelist
}

如果申请好了新slab page,当前cpu_slab->page不为空,则会调用flush_slab 然后调用deactivate_slab 强制下架cpu_slab->page。也就是我现在有新的了,必须得把老的下架放回node中,否则新的slab page不知道放哪(总不能刚申请完就放到node中)。

deactivate_slab

deactivate_slab 函数完成强制下架,把cpu_slab当前控制的slab page状态更新,并根据现在的slab 分配情况(半空、满、空)放入对应的node的list中或者销毁掉。

linux\mm\slub.c : deactivate_slab

static void deactivate_slab(struct kmem_cache *s, struct page *page,
				void *freelist, struct kmem_cache_cpu *c)//[1]page为要下架的page
{
	enum slab_modes { M_NONE, M_PARTIAL, M_FULL, M_FREE };
	struct kmem_cache_node *n = get_node(s, page_to_nid(page));
	int lock = 0, free_delta = 0;
	enum slab_modes l = M_NONE, m = M_NONE;
	void *nextfree, *freelist_iter, *freelist_tail;
	int tail = DEACTIVATE_TO_HEAD;
	struct page new;
	struct page old;

	if (page->freelist) {
		stat(s, DEACTIVATE_REMOTE_FREES);//设置为1
		tail = DEACTIVATE_TO_TAIL;
	}

	/*
	 * Stage one: Count the objects on cpu's freelist as free_delta and
	 * remember the last object in freelist_tail for later splicing.
	 */
	freelist_tail = NULL;
	freelist_iter = freelist;
	while (freelist_iter) {
        //[2]循环遍历找到cpu_slab->freelist里最后一个free iter,并计数
        //cpu_slab 的freelist 和page freelist 是不同步的
        //cpu分配的时候page这边不动,在下架的时候才同步
		nextfree = get_freepointer(s, freelist_iter);

		/*
		 * If 'nextfree' is invalid, it is possible that the object at
		 * 'freelist_iter' is already corrupted.  So isolate all objects
		 * starting at 'freelist_iter' by skipping them.
		 //检测是否corrupted
		 */
		if (freelist_corrupted(s, page, &freelist_iter, nextfree))
			break;

		freelist_tail = freelist_iter;
		free_delta++;//[2]用于统计freelist 里有多少个object

		freelist_iter = nextfree;
	}

	/*
	 * Stage two: Unfreeze the page while splicing the per-cpu
	 * freelist to the head of page's freelist.
	 *
	 * Ensure that the page is unfrozen while the list presence
	 * reflects the actual number of objects during unfreeze.
	 *
	 * We setup the list membership and then perform a cmpxchg
	 * with the count. If there is a mismatch then the page
	 * is not unfrozen but the page is on the wrong list.
	 *
	 * Then we restart the process which may have to remove
	 * the page from the list that we just put it on again
	 * because the number of objects in the slab may have
	 * changed.
	 */
redo://[3]

	old.freelist = READ_ONCE(page->freelist);//[3.1]page在cpu_slab 控制下,所以old.freelist 为NULL
	old.counters = READ_ONCE(page->counters);
	VM_BUG_ON(!old.frozen);//slab必须在CPU_slab中

	/* Determine target state of the slab */
	new.counters = old.counters;
	if (freelist_tail) {//[3.2]freelist 不为空,找到了freelist 最后一个对象
		new.inuse -= free_delta;//更新page inuse数
		set_freepointer(s, freelist_tail, old.freelist);//freelist_tail(next)=old.freelist
		new.freelist = freelist;//更新freelist
	} else//freelist为空,则更新new.freelist = NULL
		new.freelist = old.freelist;

	new.frozen = 0;//[3.3]解冻,准备从cpu_slab下架

	if (!new.inuse && n->nr_partial >= s->min_partial)//[4]判断slab 状态
		m = M_FREE;//[4.1]page 为空,且node中partail数量满足最小要求,则设置free状态准备释放
	else if (new.freelist) {
		m = M_PARTIAL;//[4.2]说明node中partial 不满足最小要求,inuse 不为0,page不为空,则设置partial状态
		if (!lock) {
			lock = 1;
			/*
			 * Taking the spinlock removes the possibility
			 * that acquire_slab() will see a slab page that
			 * is frozen
			 */
			spin_lock(&n->list_lock);
		}
	} else {//[4.3]说明没有freelist了,所有object 都分配出去,page 是full状态
		m = M_FULL;
		if (kmem_cache_debug_flags(s, SLAB_STORE_USER) && !lock) {
			lock = 1;
			/*
			 * This also ensures that the scanning of full
			 * slabs from diagnostic functions will not see
			 * any frozen slabs.
			 */
			spin_lock(&n->list_lock);
		}
	}

	if (l != m) {//根据状态进行操作
		if (l == M_PARTIAL)
			remove_partial(n, page);
		else if (l == M_FULL)
			remove_full(s, n, page);

		if (m == M_PARTIAL)//[4.4]根据状态放入对应的node表中
			add_partial(n, page, tail); //放入partial 列表
		else if (m == M_FULL)
			add_full(s, n, page); //放入full列表
	}

	l = m;
	if (!__cmpxchg_double_slab(s, page,
				old.freelist, old.counters, //[3.4]page->freelist = new.freelist
				new.freelist, new.counters, //page->counters = new.counters
				"unfreezing slab"))
		goto redo;

	if (lock)
		spin_unlock(&n->list_lock);

	if (m == M_PARTIAL)
		stat(s, tail);
	else if (m == M_FULL)
		stat(s, DEACTIVATE_FULL);
	else if (m == M_FREE) {
		stat(s, DEACTIVATE_EMPTY);
		discard_slab(s, page);//[4.1]如果为空,则销毁slab
		stat(s, FREE_SLAB);
	}

	c->page = NULL;//[5]清空cpu_slab 的page 和freelist,完成下架
	c->freelist = NULL;
}

[1] 传入参数:pagefreelist 为要被下架slab page和它的freelist,即cpu_slabpagefreelist 成员,c为要被下架的slab page所在的cpu_slab

[2] 循环遍历找到freelist 最后一个object,顺便用free_delta统计freelist中现存object 数量。

[3] 之前在kmalloc 章节中分析过,当slab page被cpu_slab控制之后,page结构体中的freelistinuse 就不会随着该slab 中的内存被分配而更新了。即freelist 已经给了cpu_slab 控制,page结构体中的freelist 被设置成了NULL。现在要从cpu_slab中下架,所以要把最新的freelist 状态和inuse数量更新回page 结构体。

​ [3.1] old.freelistpage 结构体中的freelist,由于pagecpu_slab控制,所以是NULL

​ [3.2] freelist_tail不为空,则说明该slab 没有分配光,还有现存的free object,这里要根据刚刚统计的现存free object 数量更新inuse 信息,然后将现在的freelist 更新回page结构体。

​ [3.3] frozen 设置为0,准备解冻,从cpu_slab中下架。该标志位代表是否被cpu_slab控制。

[4] 判断下架的slab 状态,分为空、部分空和满三种

​ [4.1] 如果inuse = 0,则说明没有任何object 分配出去(或全部释放了),如果node中的partial list中的slab 数量满足slab 要求的最小数量,则该(当前被下架的)slab可以标记为FREE,在后面会调用discard_slab销毁掉。

​ [4.2] 否则,如果inuse 不为0 或不满足最小条件,说明不能销毁该slab,则根据是否有freelist,有freelist 说明还有可以分配的,可以标记为PARTIAL 半空,后面会放入node->partial列表中。

​ [4.3] 否则说明没有freelistobject全部分配出去了,slab 状态为满,标记为FULL,后续放入node->full列表中。

​ [4.4] 根据上面标记的状态进行操作

[5] 把cpu_slabpagefreelist 都置空,完成下架。

get_partial 从node->partial向cpu_slab补充slab

这一部分在上一章中已经进行过简要分析;get_partial 是在申请新slab 的new_slab_objects 中调用的,在申请一个全新的slab page 之前,会先看看node 的partial列表中有没有可用的半满slab page,如果有的话则会将其上架给cpu_slab

linux\mm\slub.c : get_partial

static void *get_partial(struct kmem_cache *s, gfp_t flags, int node,
		struct kmem_cache_cpu *c)
{
	void *object;
	int searchnode = node;

	if (node == NUMA_NO_NODE)//[1]NUMA_NO_NODE获取当前node
		searchnode = numa_mem_id();

	object = get_partial_node(s, get_node(s, searchnode), c, flags);//[2]从这个node获取partial 链表
	if (object || node != NUMA_NO_NODE)
		return object;

	return get_any_partial(s, flags, c);//[3]随便来一个
}

该函数虽然在之前kmalloc 章节分析过了,但这里[2] 处调用了get_partial_node 函数,其中有一些细节操作需要在这里分析一下:

get_partial_node

get_partial_node 不止会从node->partial 找到一个可用slab page并返回一个可用object,还会将node->partial 中大部分可用slab page填充进cpu_slab->partial中:

linux\mm\slub.c : get_partial_node

static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,
				struct kmem_cache_cpu *c, gfp_t flags)
{
	struct page *page, *page2;
	void *object = NULL;
	unsigned int available = 0;
	int objects;

	/*
	 * Racy check. If we mistakenly see no partial slabs then we
	 * just allocate an empty slab. If we mistakenly try to get a
	 * partial slab and there is none available then get_partial()
	 * will return NULL.
	 */
	if (!n || !n->nr_partial)//node还没正式使用或者里面没partial就直接返回
		return NULL;

	spin_lock(&n->list_lock);
	list_for_each_entry_safe(page, page2, &n->partial, slab_list) {//[1] 遍历node->partial中的page
		void *t;

		if (!pfmemalloc_match(page, flags))//flag不匹配就过
			continue;
		//[2] 第一次还没找到object,接下来找到了影响第四个参数
		//如果是第一次,将page从partial中拿下来,然后设置page的一些counters参数,获取堆块数量,返回page的freelist
		//否则统计数量,然后把page从node->partial拿出来,但不改变page状态
		t = acquire_slab(s, n, page, object == NULL, &objects);
		if (!t)
			break;

		available += objects;//统计可用堆块数量
		if (!object) {//[3] 刚找到合适的page的时候
			c->page = page;//设置cpu_slab->page
			stat(s, ALLOC_FROM_PARTIAL);
			object = t;//设置object用于返回
		} else {//[4] 已经找到合适的page了
			put_cpu_partial(s, page, 0);//将page放到cpu_slab->partial中
			stat(s, CPU_PARTIAL_NODE);
		}
		if (!kmem_cache_has_cpu_partial(s)//[4] 如果可用堆块数量达到了cpu_partial 的二分之一就停止
			|| available > slub_cpu_partial(s) / 2)
			break;

	}
	spin_unlock(&n->list_lock);
	return object;
}

[1] 遍历node->partial中的page,找到的第一个合适的page用于给cpu_slab->page用于分配的page,并且其他合适的添加到cpu_slab->partial中用于补充。

[2] 对每一个page,调用acquire_slab 函数处理,第四个参数代表是否将该页设置为cpu_slab控制的页(修改freelistfrozen等)。这个函数的代码和简要逻辑在下面

[3] 如果还没找到适合返回的freelist的话,则将刚刚通过acquire_slab函数获得的freelist设置为返回的freelist,将cpu_slab->page设置为其所属page

[4] 否则说明刚刚已经找到用于返回的freelist了,将新找到的page补充到cpu_slab->partial中。直到满足kmem_cache->cpu_partial要求的数量的一半为止。put_cpu_partial函数会在后面分析。

acquire_slab

acquire_slab 函数找到一个可用slab,并根据第四个参数mode,对page设置状态, 如果modetrue,则改变pagefrozenfreelist,让page达到"被cpu_slab->page控制"的状态。然后将pagenode->partial链表中取出。返回pagefreelist列表。

linux\mm\slub.c : acquire_slab

static inline void *acquire_slab(struct kmem_cache *s,
		struct kmem_cache_node *n, struct page *page,
		int mode, int *objects)
{
	void *freelist;
	unsigned long counters;
	struct page new;

	lockdep_assert_held(&n->list_lock);

	/*
	 * Zap the freelist and set the frozen bit.
	 * The old freelist is the list of objects for the
	 * per cpu allocation list.
	 */
	freelist = page->freelist;//获取page的freelist counters等
	counters = page->counters;
	new.counters = counters;
	*objects = new.objects - new.inuse;//获取剩余堆块数量
	if (mode) {//如果还没有获取到object
		new.inuse = page->objects;//将page inuse 设置满,因为要放入cpu_slab->page里了
		new.freelist = NULL;//cpu_slab->page中的page的freelist要置空
	} else {
		new.freelist = freelist;//否则不变
	}

	VM_BUG_ON(new.frozen);
	new.frozen = 1;//准备放入cpu_slab->page

	if (!__cmpxchg_double_slab(s, page,//更新page的一些信息
			freelist, counters,
			new.freelist, new.counters,
			"acquire_slab"))
		return NULL;

	remove_partial(n, page);//从partial列表中删除
	WARN_ON(!freelist);
	return freelist;//返回freelist
}

总结

pass

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值