背景
前情回顧
關(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 cache,struct kmem_cache_node
這里就叫node。單個(gè)堆塊稱為object或者堆塊或內(nèi)存對象。
簡介
本篇主要就kmalloc
中的一些層數(shù)比較深并且很多邏輯中都要調(diào)用的操作和兩個(gè)安全加固CONFIG_SLAB_FREELIST_HARDENED
與CONFIG_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)存泄露獲得freelist
和freelist
成員的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è)object
的next
指針從真實(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è)object
的next
指針地址是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,如果要釋放的object
和list
指針一樣,則是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ì)打亂freelist
中object
的順序,即便是剛申請好的新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ù):page
和freelist
為要被下架slab page和它的freelist
,即cpu_slab
的page
和freelist
成員,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)體中的freelist
和inuse
就不會(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.freelist
為page
結(jié)構(gòu)體中的freelist
,由于page
被cpu_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] 否則說明沒有freelist
,object
全部分配出去了,slab 狀態(tài)為滿,標(biāo)記為FULL
,后續(xù)放入node->full
列表中。
? [4.4] 根據(jù)上面標(biāo)記的狀態(tài)進(jìn)行操作
[5] 把cpu_slab
的page
和freelist
都置空,完成下架。
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), 如果mode
為true
,則改變page
的frozen
、freelist
,讓page
達(dá)到"被cpu_slab->page
控制"的狀態(tài)。然后將page
從node->partial
鏈表中取出。返回page
的freelist
列表。
linux\mm\slub.c : acquire_slab文章來源:http://www.zghlxwxcb.cn/news/detail-406972.html
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)!