背景
前情回顾
关于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 cache,struct kmem_cache_node
这里就叫node。单个堆块称为object或者堆块或内存对象。
简介
本篇主要就kmalloc
中的一些层数比较深并且很多逻辑中都要调用的操作和两个安全加固CONFIG_SLAB_FREELIST_HARDENED
与CONFIG_SLAB_FREELIST_RANDOM
进行分析,比如(安全加固下)对freelist
的操作、cpu_slab->page
的强制下架等。
本章介绍的内容不影响分析slub算法的整体逻辑,对细节没有兴趣可以跳过。
freelist操作与CONFIG_SLAB_FREELIST_HARDENED
CONFIG_SLAB_FREELIST_HARDENED简介
CONFIG_SLAB_FREELIST_HARDENED
安全加固宏是给freelist
链表指针进行混淆:
混淆后的指针=原指针 ^ 随机数random ^ 指针地址
直接在内存中/通过内存泄露获得freelist
和freelist
成员的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
的指向下一个object
的next
指针从真实值计算出混淆值或从混淆值还原出真实值:
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
也就是指向下一个object
的next
指针地址是object + s->offset
,s->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,如果要释放的object
和list
指针一样,则是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
则会打乱freelist
中object
的顺序,即便是刚申请好的新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] 传入参数:page
和freelist
为要被下架slab page和它的freelist
,即cpu_slab
的page
和freelist
成员,c
为要被下架的slab page所在的cpu_slab
。
[2] 循环遍历找到freelist
最后一个object
,顺便用free_delta
统计freelist
中现存object
数量。
[3] 之前在kmalloc
章节中分析过,当slab page被cpu_slab
控制之后,page
结构体中的freelist
和inuse
就不会随着该slab 中的内存被分配而更新了。即freelist
已经给了cpu_slab
控制,page
结构体中的freelist
被设置成了NULL
。现在要从cpu_slab
中下架,所以要把最新的freelist
状态和inuse
数量更新回page
结构体。
[3.1] old.freelist
为page
结构体中的freelist
,由于page
被cpu_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] 否则说明没有freelist
,object
全部分配出去了,slab 状态为满,标记为FULL
,后续放入node->full
列表中。
[4.4] 根据上面标记的状态进行操作
[5] 把cpu_slab
的page
和freelist
都置空,完成下架。
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
控制的页(修改freelist
、frozen
等)。这个函数的代码和简要逻辑在下面
[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
设置状态, 如果mode
为true
,则改变page
的frozen
、freelist
,让page
达到"被cpu_slab->page
控制"的状态。然后将page
从node->partial
链表中取出。返回page
的freelist
列表。
linux\mm\slub.c : acquire_slab文章来源:https://www.toymoban.com/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;//获取剩余堆块数量
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模板网!