国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

[linux kernel]slub內(nèi)存管理分析(4) 細(xì)節(jié)操作以及安全加固

這篇具有很好參考價(jià)值的文章主要介紹了[linux kernel]slub內(nèi)存管理分析(4) 細(xì)節(jié)操作以及安全加固。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

背景

前情回顧

關(guān)于slab幾個(gè)結(jié)構(gòu)體的關(guān)系和初始化和內(nèi)存分配的邏輯請見:

[linux kernel]slub內(nèi)存管理分析(0) 導(dǎo)讀

[linux kernel]slub內(nèi)存管理分析(1) 結(jié)構(gòu)體

[linux kernel]slub內(nèi)存管理分析(2) 初始化

[linux kernel]slub內(nèi)存管理分析(2.5) slab重用

[linux kernel]slub內(nèi)存管理分析(3) kmalloc

描述方法約定

PS:為了方便描述,這里我們將一個(gè)用來切割分配內(nèi)存的page 稱為一個(gè)slab page,而struct kmem_cache我們這里稱為slab管理結(jié)構(gòu),它管理的真?zhèn)€slab 體系成為slab cachestruct kmem_cache_node這里就叫node。單個(gè)堆塊稱為object或者堆塊內(nèi)存對象

簡介

本篇主要就kmalloc 中的一些層數(shù)比較深并且很多邏輯中都要調(diào)用的操作和兩個(gè)安全加固CONFIG_SLAB_FREELIST_HARDENEDCONFIG_SLAB_FREELIST_RANDOM進(jìn)行分析,比如(安全加固下)對freelist的操作、cpu_slab->page的強(qiáng)制下架等。

本章介紹的內(nèi)容不影響分析slub算法的整體邏輯,對細(xì)節(jié)沒有興趣可以跳過。

freelist操作與CONFIG_SLAB_FREELIST_HARDENED

CONFIG_SLAB_FREELIST_HARDENED簡介

CONFIG_SLAB_FREELIST_HARDENED 安全加固宏是給freelist 鏈表指針進(jìn)行混淆:

混淆后的指針=原指針 ^ 隨機(jī)數(shù)random ^ 指針地址

直接在內(nèi)存中/通過內(nèi)存泄露獲得freelistfreelist成員的next指針是混淆后的,避免直接泄露地址或直接修改地址。在每次存取freelist 指針之前都會(huì)進(jìn)行一次上面的異或操作得到真實(shí)指針或從真實(shí)指針轉(zhuǎn)換成混淆指針。

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宏會(huì)多一個(gè)random 成員
#endif
··· ···
};

開啟CONFIG_SLAB_FREELIST_HARDENED宏會(huì)多一個(gè)random 成員,在初始化流程中的kmem_cache_open 函數(shù)中會(huì)對random 進(jìn)行初始化(完整初始化流程在前文已經(jīng)分析過了):

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

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

這里調(diào)用get_random_long 函數(shù)獲取一個(gè)隨機(jī)的long 類型數(shù)據(jù),所以random 就是一個(gè)隨機(jī)數(shù)而已。

CONFIG_SLAB_FREELIST_HARDENED實(shí)現(xiàn)與freelist相關(guān)操作

freelist_ptr 混淆/去混淆指針

freelist_ptr 函數(shù)用于將freelist中一個(gè)object 的指向下一個(gè)objectnext指針從真實(shí)值計(jì)算出混淆值或從混淆值還原出真實(shí)值:

linux\mm\slub.c : freelist_ptr

static inline void *freelist_ptr(const struct kmem_cache *s, void *ptr,
				 unsigned long ptr_addr)//[1] 傳入?yún)?shù)
{
#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 參數(shù)就是要去混淆的指針,ptr_addr 是該指針的地址。

[2] 混淆公式為:混淆后的指針=原指針 ^ 隨機(jī)數(shù)random ^ 指針地址,所以去混淆就是 混淆后的指針 ^ 隨機(jī)數(shù)random ^ 指針地址 。因?yàn)槭钱惢颍圆僮飨嗤?/p>

[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);//默認(rèn)直接返回地址
	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]直接調(diào)用freelist_ptr
{
	return freelist_ptr(s, (void *)*(unsigned long *)(ptr_addr),
			    (unsigned long)ptr_addr);
}

[1] 這里調(diào)用freelist_dereference,根據(jù)參數(shù),傳入的ptr_addr 也就是指向下一個(gè)objectnext指針地址是object + s->offset,s->offset 一般是該slab 大小的一半。

[2] freelist_dereference函數(shù)中直接調(diào)用上面分析的freelist_ptr 獲取真實(shí)指針

set_freepointer 設(shè)置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] 調(diào)用freelist_ptr 混淆指針值
}

[1] 首先會(huì)在開啟CONFIG_SLAB_FREELIST_HARDENED的時(shí)候檢測double free,如果要釋放的objectlist 指針一樣,則是double free,類似用戶層malloc。

[2] 在給指針復(fù)制之前調(diào)用了freelist_ptr 進(jìn)行混淆操作,如果沒有開啟混淆,則freelist_ptr 會(huì)直接返回指針原本的值。

CONFIG_SLAB_FREELIST_RANDOM

CONFIG_SLAB_FREELIST_RANDOM簡介

CONFIG_SLAB_FREELIST_RANDOM 安全加固是將freelist 列表順序隨機(jī)化,正常freelist列表就是從開始往后按順序排列,但如果開啟了CONFIG_SLAB_FREELIST_RANDOM 則會(huì)打亂freelistobject 的順序,即便是剛申請好的新slab,其中的freelist 列表順序也是隨機(jī)的。

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 之后會(huì)多一個(gè)叫做random_seq 的數(shù)組,以指針成員的形式出現(xiàn)在kmem_cache也就是slab 管理結(jié)構(gòu)體中。在 kmem_cache_init中會(huì)進(jìn)行初始化:

linux\mm\slub.c : kmem_cache_init

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

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

	··· ···
}

create_kmalloc_caches函數(shù)已經(jīng)完成了各個(gè)slab 的初始化之后,調(diào)用init_freelist_randomization對所有slab 進(jìn)行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調(diào)用init_cache_random_seq

	mutex_unlock(&slab_mutex);
}

每個(gè)slab 都會(huì)加入slab_caches 列表,這里對列表中每個(gè)成員都調(diào)用init_cache_random_seq進(jìn)行初始化:

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中有多少個(gè)object對象
	int err;

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

	err = cache_random_seq_create(s, count, GFP_KERNEL);
    //[2] 調(diào)用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此時(shí)是打亂的序號,size 是一塊內(nèi)存的大小,相乘得到的就是隨機(jī)的某一塊內(nèi)存的偏移
	}
	return 0;
}

[1] 調(diào)用oo_objects 獲得該slab 中每個(gè)slab 中分出的內(nèi)存塊的數(shù)量。

[2] 調(diào)用cache_random_seq_create 對沒初始化s->random_seq 的slab 進(jìn)行初始化,其實(shí)就是s->random_seq 的值賦值成內(nèi)存塊的編號,然后打亂順序。

[3] s->random_seq此時(shí)是打亂的序號,size是一塊內(nèi)存的大小,相乘得到的就是隨機(jī)的某一塊內(nèi)存的偏移。后面用于shuffle_freelist。

cache_random_seq_create

然后是cache_random_seq_create函數(shù),用于制造一個(gè)混亂順序的列表:

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 的不需要隨機(jī)
		return 0;

	cachep->random_seq = kcalloc(count, sizeof(unsigned int), gfp);//[1] 申請數(shù)組內(nèi)存count * sizeof(int)
	if (!cachep->random_seq)
		return -ENOMEM;

	/* Get best entropy at this stage of boot */
	prandom_seed_state(&state, get_random_long());//獲取隨機(jī)種子

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

[1] 首先根據(jù)該slab 中的內(nèi)存塊數(shù)量 申請random_seq數(shù)組的內(nèi)存空間count * sizeof(int)

[2] 然后調(diào)用freelist_randomize 函數(shù)制造一個(gè)亂序列表,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]先按照下標(biāo)排序

	/* Fisher-Yates shuffle */
	for (i = count - 1; i > 0; i--) { //[2]然后隨機(jī)打亂順序
		rand = prandom_u32_state(state);
		rand %= (i + 1);
		swap(list[i], list[rand]);
	}
}

[1] 先將這個(gè)數(shù)組按照下標(biāo)排序,每個(gè)值就是下標(biāo)

[2] 然后跟隨機(jī)下標(biāo)的另一個(gè)數(shù)互換位置。這樣這個(gè)數(shù)組就被打亂順序了。

CONFIG_SLAB_FREELIST_RANDOM使用

CONFIG_SLAB_FREELIST_RANDOM生效主要是在新slab 申請的時(shí)候調(diào)用的shuffle_freelist 函數(shù):

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,則會(huì)調(diào)用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)//內(nèi)存塊數(shù)量小于2 則無需亂序
		return false;

	freelist_count = oo_objects(s->oo);//獲取slab page中內(nèi)存塊數(shù)量
	pos = get_random_int() % freelist_count;//隨機(jī)一個(gè)起始下標(biāo)

	page_limit = page->objects * s->size; //page大小為 內(nèi)存塊數(shù)量*每塊大小
	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);//隨機(jī)獲取第一個(gè)內(nèi)存塊地址
	cur = setup_object(s, page, cur);
	page->freelist = cur;//將第一個(gè)內(nèi)存塊加入freelist

	for (idx = 1; idx < page->objects; idx++) {//依次從next_freelist_entry獲取隨機(jī)內(nèi)存塊加入freelist
		next = next_freelist_entry(s, page, &pos, start, page_limit,
			freelist_count);//獲取一個(gè)隨機(jī)內(nèi)存塊地址
		next = setup_object(s, page, next);
		set_freepointer(s, cur, next);//加入freelist后面
		cur = next;
	}
	set_freepointer(s, cur, NULL);

	return true;
}

根據(jù)代碼中的注釋,shuffle_freelist起始就是結(jié)合下面的next_freelist_entry函數(shù),根據(jù)random_seq記錄的隨機(jī)的內(nèi)存塊順序,依次加入到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 里是隨機(jī)一個(gè)內(nèi)存塊的偏移
		*pos += 1;
		if (*pos >= freelist_count)
			*pos = 0;
	} while (unlikely(idx >= page_limit));

	return (char *)start + idx;//[2]start + 偏移 即返回該內(nèi)存塊的地址
}

[1] s->random_seq 是之前打亂順序的各個(gè)內(nèi)存塊的地址偏移,這里的意思是獲取pos 下標(biāo)的內(nèi)存塊偏移

[2] 然后返回start+idx 起始地址+偏移,即內(nèi)存塊實(shí)際地址,所以next_freelist_entry 函數(shù)就是根據(jù)random_seq 記錄的順序獲取下一個(gè)隨機(jī)內(nèi)存塊的地址

deactivate_slab強(qiáng)制下架

deactivate_slab 函數(shù)比較長,強(qiáng)制下架當(dāng)前cpu_slab->page,也就是強(qiáng)制切換cpu_slab控制的slab page,大部分使用場景都是unlinkely 分支,也就是大部分都在異常情況下出現(xiàn),除了在flush_slab 函數(shù)中:

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);//強(qiáng)制下架

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

flush_slab

flush_slab 函數(shù)應(yīng)用場景主要在調(diào)用new_slab_objects申請新slab page時(shí)和銷毀slab 時(shí)會(huì)調(diào)用flush_all對所有cpu調(diào)用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]調(diào)用new_slab分配新page
	if (page) {
		c = raw_cpu_ptr(s->cpu_slab);//[2.1]獲取當(dāng)前cpu
		if (c->page)
			flush_slab(s, c);//[2.2]如果當(dāng)前cpu有page則flush_slab刷新嗎,這里會(huì)將page強(qiáng)制下架

		freelist = page->freelist;//[2.3]更新freelist、page
		page->freelist = NULL;//被cpu_slab控制的page 的freelist 都要設(shè)置為NULL

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

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

如果申請好了新slab page,當(dāng)前cpu_slab->page不為空,則會(huì)調(diào)用flush_slab 然后調(diào)用deactivate_slab 強(qiáng)制下架cpu_slab->page。也就是我現(xiàn)在有新的了,必須得把老的下架放回node中,否則新的slab page不知道放哪(總不能剛申請完就放到node中)。

deactivate_slab

deactivate_slab 函數(shù)完成強(qiáng)制下架,把cpu_slab當(dāng)前控制的slab page狀態(tài)更新,并根據(jù)現(xiàn)在的slab 分配情況(半空、滿、空)放入對應(yīng)的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);//設(shè)置為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]循環(huán)遍歷找到cpu_slab->freelist里最后一個(gè)free iter,并計(jì)數(shù)
        //cpu_slab 的freelist 和page freelist 是不同步的
        //cpu分配的時(shí)候page這邊不動(dòng),在下架的時(shí)候才同步
		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]用于統(tǒng)計(jì)freelist 里有多少個(gè)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 最后一個(gè)對象
		new.inuse -= free_delta;//更新page inuse數(shù)
		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]解凍,準(zhǔn)備從cpu_slab下架

	if (!new.inuse && n->nr_partial >= s->min_partial)//[4]判斷slab 狀態(tài)
		m = M_FREE;//[4.1]page 為空,且node中partail數(shù)量滿足最小要求,則設(shè)置free狀態(tài)準(zhǔn)備釋放
	else if (new.freelist) {
		m = M_PARTIAL;//[4.2]說明node中partial 不滿足最小要求,inuse 不為0,page不為空,則設(shè)置partial狀態(tài)
		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狀態(tài)
		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) {//根據(jù)狀態(tài)進(jìn)行操作
		if (l == M_PARTIAL)
			remove_partial(n, page);
		else if (l == M_FULL)
			remove_full(s, n, page);

		if (m == M_PARTIAL)//[4.4]根據(jù)狀態(tài)放入對應(yīng)的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] 傳入?yún)?shù):pagefreelist 為要被下架slab page和它的freelist,即cpu_slabpagefreelist 成員,c為要被下架的slab page所在的cpu_slab。

[2] 循環(huán)遍歷找到freelist 最后一個(gè)object,順便用free_delta統(tǒng)計(jì)freelist中現(xiàn)存object 數(shù)量。

[3] 之前在kmalloc 章節(jié)中分析過,當(dāng)slab page被cpu_slab控制之后,page結(jié)構(gòu)體中的freelistinuse 就不會(huì)隨著該slab 中的內(nèi)存被分配而更新了。即freelist 已經(jīng)給了cpu_slab 控制,page結(jié)構(gòu)體中的freelist 被設(shè)置成了NULL?,F(xiàn)在要從cpu_slab中下架,所以要把最新的freelist 狀態(tài)和inuse數(shù)量更新回page 結(jié)構(gòu)體。

? [3.1] old.freelistpage 結(jié)構(gòu)體中的freelist,由于pagecpu_slab控制,所以是NULL

? [3.2] freelist_tail不為空,則說明該slab 沒有分配光,還有現(xiàn)存的free object,這里要根據(jù)剛剛統(tǒng)計(jì)的現(xiàn)存free object 數(shù)量更新inuse 信息,然后將現(xiàn)在的freelist 更新回page結(jié)構(gòu)體。

? [3.3] frozen 設(shè)置為0,準(zhǔn)備解凍,從cpu_slab中下架。該標(biāo)志位代表是否被cpu_slab控制。

[4] 判斷下架的slab 狀態(tài),分為空、部分空和滿三種

? [4.1] 如果inuse = 0,則說明沒有任何object 分配出去(或全部釋放了),如果node中的partial list中的slab 數(shù)量滿足slab 要求的最小數(shù)量,則該(當(dāng)前被下架的)slab可以標(biāo)記為FREE,在后面會(huì)調(diào)用discard_slab銷毀掉。

? [4.2] 否則,如果inuse 不為0 或不滿足最小條件,說明不能銷毀該slab,則根據(jù)是否有freelist,有freelist 說明還有可以分配的,可以標(biāo)記為PARTIAL 半空,后面會(huì)放入node->partial列表中。

? [4.3] 否則說明沒有freelistobject全部分配出去了,slab 狀態(tài)為滿,標(biāo)記為FULL,后續(xù)放入node->full列表中。

? [4.4] 根據(jù)上面標(biāo)記的狀態(tài)進(jìn)行操作

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

get_partial 從node->partial向cpu_slab補(bǔ)充slab

這一部分在上一章中已經(jīng)進(jìn)行過簡要分析;get_partial 是在申請新slab 的new_slab_objects 中調(diào)用的,在申請一個(gè)全新的slab page 之前,會(huì)先看看node 的partial列表中有沒有可用的半滿slab page,如果有的話則會(huì)將其上架給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獲取當(dāng)前node
		searchnode = numa_mem_id();

	object = get_partial_node(s, get_node(s, searchnode), c, flags);//[2]從這個(gè)node獲取partial 鏈表
	if (object || node != NUMA_NO_NODE)
		return object;

	return get_any_partial(s, flags, c);//[3]隨便來一個(gè)
}

該函數(shù)雖然在之前kmalloc 章節(jié)分析過了,但這里[2] 處調(diào)用了get_partial_node 函數(shù),其中有一些細(xì)節(jié)操作需要在這里分析一下:

get_partial_node

get_partial_node 不止會(huì)從node->partial 找到一個(gè)可用slab page并返回一個(gè)可用object,還會(huì)將node->partial 中大部分可用slab page填充進(jìn)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,接下來找到了影響第四個(gè)參數(shù)
		//如果是第一次,將page從partial中拿下來,然后設(shè)置page的一些counters參數(shù),獲取堆塊數(shù)量,返回page的freelist
		//否則統(tǒng)計(jì)數(shù)量,然后把page從node->partial拿出來,但不改變page狀態(tài)
		t = acquire_slab(s, n, page, object == NULL, &objects);
		if (!t)
			break;

		available += objects;//統(tǒng)計(jì)可用堆塊數(shù)量
		if (!object) {//[3] 剛找到合適的page的時(shí)候
			c->page = page;//設(shè)置cpu_slab->page
			stat(s, ALLOC_FROM_PARTIAL);
			object = t;//設(shè)置object用于返回
		} else {//[4] 已經(jīng)找到合適的page了
			put_cpu_partial(s, page, 0);//將page放到cpu_slab->partial中
			stat(s, CPU_PARTIAL_NODE);
		}
		if (!kmem_cache_has_cpu_partial(s)//[4] 如果可用堆塊數(shù)量達(dá)到了cpu_partial 的二分之一就停止
			|| available > slub_cpu_partial(s) / 2)
			break;

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

[1] 遍歷node->partial中的page,找到的第一個(gè)合適的page用于給cpu_slab->page用于分配的page,并且其他合適的添加到cpu_slab->partial中用于補(bǔ)充。

[2] 對每一個(gè)page,調(diào)用acquire_slab 函數(shù)處理,第四個(gè)參數(shù)代表是否將該頁設(shè)置為cpu_slab控制的頁(修改freelist、frozen等)。這個(gè)函數(shù)的代碼和簡要邏輯在下面

[3] 如果還沒找到適合返回的freelist的話,則將剛剛通過acquire_slab函數(shù)獲得的freelist設(shè)置為返回的freelist,將cpu_slab->page設(shè)置為其所屬page。

[4] 否則說明剛剛已經(jīng)找到用于返回的freelist了,將新找到的page補(bǔ)充到cpu_slab->partial中。直到滿足kmem_cache->cpu_partial要求的數(shù)量的一半為止。put_cpu_partial函數(shù)會(huì)在后面分析。

acquire_slab

acquire_slab 函數(shù)找到一個(gè)可用slab,并根據(jù)第四個(gè)參數(shù)mode,對page設(shè)置狀態(tài), 如果modetrue,則改變pagefrozenfreelist,讓page達(dá)到"被cpu_slab->page控制"的狀態(tài)。然后將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;//獲取剩余堆塊數(shù)量
	if (mode) {//如果還沒有獲取到object
		new.inuse = page->objects;//將page inuse 設(shè)置滿,因?yàn)橐湃隿pu_slab->page里了
		new.freelist = NULL;//cpu_slab->page中的page的freelist要置空
	} else {
		new.freelist = freelist;//否則不變
	}

	VM_BUG_ON(new.frozen);
	new.frozen = 1;//準(zhǔn)備放入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
}

總結(jié)

pass文章來源地址http://www.zghlxwxcb.cn/news/detail-406972.html

到了這里,關(guān)于[linux kernel]slub內(nèi)存管理分析(4) 細(xì)節(jié)操作以及安全加固的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【Linux 內(nèi)核源碼分析】內(nèi)存管理——Slab 分配器

    在Linux內(nèi)核中,伙伴分配器是一種內(nèi)存管理方式,以頁為單位進(jìn)行內(nèi)存的管理和分配。但是在內(nèi)核中,經(jīng)常會(huì)面臨結(jié)構(gòu)體內(nèi)存分配問題,而這些結(jié)構(gòu)體的大小通常是小于一頁的。如果使用伙伴分配器來分配這些小內(nèi)存,將造成很大的內(nèi)存浪費(fèi)。因此,為了解決這個(gè)問題,Sun公

    2024年02月22日
    瀏覽(26)
  • 初識FreeRTOS入門,對FreeRTOS簡介、任務(wù)調(diào)度、內(nèi)存管理、通信機(jī)制以及IO操作,控制兩個(gè)led不同頻率閃爍

    當(dāng)代嵌入式系統(tǒng)的開發(fā)越來越復(fù)雜,實(shí)時(shí)性要求也越來越高。為了滿足這些需求,開發(fā)者需要使用實(shí)時(shí)操作系統(tǒng)(RTOS),其中一個(gè)流行的選擇是FreeRTOS(Free Real-Time Operating System)。本篇博客將詳細(xì)介紹FreeRTOS的特性、任務(wù)調(diào)度、內(nèi)存管理、通信機(jī)制以及一些示例代碼。 FreeR

    2024年02月14日
    瀏覽(20)
  • ubuntu linux kernel內(nèi)核操作

    1.內(nèi)核編譯前的準(zhǔn)備工作 2.下載內(nèi)核 4. 編譯新內(nèi)核 5. 內(nèi)核安裝 6. 安裝模塊 7. 生成initrd.img文件 8. 切換到/boot/grub/目錄下,自動(dòng)查找新內(nèi)核,并添加至grub引導(dǎo) 9. 重啟Ubantu,在previous version中選擇啟動(dòng)新編譯的內(nèi)核 VMware虛擬機(jī)Ubantu20.04,Linux5.8.1內(nèi)核源代碼包 1.內(nèi)核編譯前的準(zhǔn)備

    2024年02月19日
    瀏覽(26)
  • AVL樹原理以及插入代碼講解(插入操作畫圖~細(xì)節(jié))

    AVL樹原理以及插入代碼講解(插入操作畫圖~細(xì)節(jié))

    目錄 原理 平衡因子 AVL樹的插入insert 1. 新節(jié)點(diǎn)插入較高左子樹的左側(cè)---左左:右單旋 2.新節(jié)點(diǎn)插入較高右子樹的右側(cè)---右右:左單旋 新節(jié)點(diǎn)插入較高左子樹的右側(cè)---左右:先左單旋再右單旋 ?新節(jié)點(diǎn)插入較高右子樹的左側(cè)---右左:先右單旋再左單旋?編輯 AVL 樹是一種平衡搜

    2024年02月09日
    瀏覽(14)
  • linux kernel:devres模塊架構(gòu)分析

    linux kernel:devres模塊架構(gòu)分析

    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 相關(guān)文件: include/linux/device.h drivers/base/devres.c 初始數(shù)據(jù)結(jié)構(gòu)圖: struct device里面的devres_head 鏈表頭,用于管理devres 相關(guān)文件:drivers/pinctrl/core.c 該文件中只使用了如下

    2024年02月02日
    瀏覽(25)
  • Unity性能優(yōu)化篇(十四) 其他優(yōu)化細(xì)節(jié)以及UPR優(yōu)化分析器

    Unity性能優(yōu)化篇(十四) 其他優(yōu)化細(xì)節(jié)以及UPR優(yōu)化分析器

    代碼優(yōu)化: 1. 使用AssetBundle作為資源加載方案。 而且經(jīng)常一起使用的資源可以打在同一個(gè)AssetBundle包中。盡量避免同一個(gè)資源被打包進(jìn)多個(gè)AB包中。壓縮方式盡量使用LZ4,少用或不要用LZMA的壓縮方式。如果確定后續(xù)開發(fā)不會(huì)升級Unity版本,則可以嘗試啟用打包選項(xiàng)BuildAssetBun

    2024年04月28日
    瀏覽(20)
  • Linux Kernel 4.12 或?qū)⑿略鰞?yōu)化分析工具

    Linux Kernel 4.12 或?qū)⑿略鰞?yōu)化分析工具

    到 7 月初,Linux?Kernel 4.12 預(yù)計(jì)將為修復(fù)所有安全漏洞而奠定基礎(chǔ),另外新增的是一個(gè)分析工具,對于開發(fā)者優(yōu)化啟動(dòng)時(shí)間時(shí)會(huì)有所幫助。 新的「個(gè)別任務(wù)統(tǒng)一模型」(Per-Task Consistency Model)為主要核心實(shí)時(shí)修補(bǔ)(Kernel Live Patching,KLP)提供了基礎(chǔ),該修補(bǔ)應(yīng)可以解決 Linux 核心

    2024年02月12日
    瀏覽(22)
  • Linux-0.11 kernel目錄進(jìn)程管理asm.s詳解

    Linux-0.11 kernel目錄進(jìn)程管理asm.s詳解

    該模塊和CPU異常處理相關(guān),在代碼結(jié)構(gòu)上asm.s和traps.c強(qiáng)相關(guān)。 CPU探測到異常時(shí),主要分為兩種處理方式,一種是有錯(cuò)誤碼,另一種是沒有錯(cuò)誤碼,對應(yīng)的方法就是 error_code 和 no_error_code 。在下面的函數(shù)詳解中,將主要以兩個(gè)函數(shù)展開。 no_error_code 對于一些異常而言,CPU在出現(xiàn)

    2024年02月07日
    瀏覽(20)
  • 【Linux0.11代碼分析】07 之 kernel execve() 函數(shù) 實(shí)現(xiàn)原理

    系列文章如下: 系列文章匯總:《【Linux0.11代碼分析】之 系列文章鏈接匯總(全)》 https://blog.csdn.net/Ciellee/article/details/130510069 . 1.《【Linux0.11代碼分析】01 之 代碼目錄分析》 2.《【Linux0.11代碼分析】02 之 bootsect.s 啟動(dòng)流程》 3.《

    2024年02月03日
    瀏覽(26)
  • 遍歷slub分配器申請的object(linux3.16)

    遍歷slub分配器申請的object(linux3.16)

    我只是把a(bǔ)ctive_obj的數(shù)量基本湊齊了,細(xì)節(jié)還沒有去研究,可能有些地方是錯(cuò)的 之前遇到了一個(gè)設(shè)備跑了一段直接之后內(nèi)存降低無法回收的問題。當(dāng)時(shí)通過slabinfo看到某些slab池子里面active_objs和num_objs差距很大。slub分配器無法自己回收 (無法回收的原因有種這個(gè)說法,如果你

    2024年01月20日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包