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

这篇具有很好参考价值的文章主要介绍了[linux kernel]slub内存管理分析(4) 细节操作以及安全加固。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

前情回顾

关于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文章来源地址https://www.toymoban.com/news/detail-406972.html

到了这里,关于[linux kernel]slub内存管理分析(4) 细节操作以及安全加固的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux 内核源码分析】内存管理——Slab 分配器

    在Linux内核中,伙伴分配器是一种内存管理方式,以页为单位进行内存的管理和分配。但是在内核中,经常会面临结构体内存分配问题,而这些结构体的大小通常是小于一页的。如果使用伙伴分配器来分配这些小内存,将造成很大的内存浪费。因此,为了解决这个问题,Sun公

    2024年02月22日
    浏览(61)
  • 初识FreeRTOS入门,对FreeRTOS简介、任务调度、内存管理、通信机制以及IO操作,控制两个led不同频率闪烁

    当代嵌入式系统的开发越来越复杂,实时性要求也越来越高。为了满足这些需求,开发者需要使用实时操作系统(RTOS),其中一个流行的选择是FreeRTOS(Free Real-Time Operating System)。本篇博客将详细介绍FreeRTOS的特性、任务调度、内存管理、通信机制以及一些示例代码。 FreeR

    2024年02月14日
    浏览(40)
  • ubuntu linux kernel内核操作

    1.内核编译前的准备工作 2.下载内核 4. 编译新内核 5. 内核安装 6. 安装模块 7. 生成initrd.img文件 8. 切换到/boot/grub/目录下,自动查找新内核,并添加至grub引导 9. 重启Ubantu,在previous version中选择启动新编译的内核 VMware虚拟机Ubantu20.04,Linux5.8.1内核源代码包 1.内核编译前的准备

    2024年02月19日
    浏览(43)
  • AVL树原理以及插入代码讲解(插入操作画图~细节)

    目录 原理 平衡因子 AVL树的插入insert 1. 新节点插入较高左子树的左侧---左左:右单旋 2.新节点插入较高右子树的右侧---右右:左单旋 新节点插入较高左子树的右侧---左右:先左单旋再右单旋  新节点插入较高右子树的左侧---右左:先右单旋再左单旋​编辑 AVL 树是一种平衡搜

    2024年02月09日
    浏览(39)
  • linux kernel:devres模块架构分析

    https://www.kernel.org/doc/html/latest/driver-api/driver-model/devres.html https://www.cnblogs.com/sammei/p/3498052.html devres in linux driver 相关文件: include/linux/device.h drivers/base/devres.c 初始数据结构图: struct device里面的devres_head 链表头,用于管理devres 相关文件:drivers/pinctrl/core.c 该文件中只使用了如下

    2024年02月02日
    浏览(40)
  • Unity性能优化篇(十四) 其他优化细节以及UPR优化分析器

    代码优化: 1. 使用AssetBundle作为资源加载方案。 而且经常一起使用的资源可以打在同一个AssetBundle包中。尽量避免同一个资源被打包进多个AB包中。压缩方式尽量使用LZ4,少用或不要用LZMA的压缩方式。如果确定后续开发不会升级Unity版本,则可以尝试启用打包选项BuildAssetBun

    2024年04月28日
    浏览(36)
  • Linux Kernel 4.12 或将新增优化分析工具

    到 7 月初,Linux Kernel 4.12 预计将为修复所有安全漏洞而奠定基础,另外新增的是一个分析工具,对于开发者优化启动时间时会有所帮助。 新的「个别任务统一模型」(Per-Task Consistency Model)为主要核心实时修补(Kernel Live Patching,KLP)提供了基础,该修补应可以解决 Linux 核心

    2024年02月12日
    浏览(31)
  • Linux-0.11 kernel目录进程管理asm.s详解

    该模块和CPU异常处理相关,在代码结构上asm.s和traps.c强相关。 CPU探测到异常时,主要分为两种处理方式,一种是有错误码,另一种是没有错误码,对应的方法就是 error_code 和 no_error_code 。在下面的函数详解中,将主要以两个函数展开。 no_error_code 对于一些异常而言,CPU在出现

    2024年02月07日
    浏览(60)
  • 【Linux0.11代码分析】07 之 kernel execve() 函数 实现原理

    系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 https://blog.csdn.net/Ciellee/article/details/130510069 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》 3.《

    2024年02月03日
    浏览(48)
  • 遍历slub分配器申请的object(linux3.16)

    我只是把active_obj的数量基本凑齐了,细节还没有去研究,可能有些地方是错的 之前遇到了一个设备跑了一段直接之后内存降低无法回收的问题。当时通过slabinfo看到某些slab池子里面active_objs和num_objs差距很大。slub分配器无法自己回收 (无法回收的原因有种这个说法,如果你

    2024年01月20日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包