Slub代码流程分析:
slub的代码晦涩难懂,在看书或者相关资料时看似简单,再去对照代码分析时会发现被打回原形,就像数学老师推导公式和自己去推导公式一样。因此,需要静下来心来仔细研读,看完原理之后分析源码,一遍不理解,两遍,三遍,每一遍都会对slub有更深的理解,再多看多想,进行咀嚼消化,自问自答,等所有的自问都能自答时也就形成自己的理解了,也就开始懂了。
slab_alloc
享受阅读代码带来的快乐,尤其是每一次的豁然开朗。
-
从per-cpu的
cpu_slab->freelist
上取object,如果成功直接返回,否则转2。 -
将
cpu_slab->page->freelist
赋值给cpu_slab->freelist
,并设置cpu_slab->page->freelist
为NULL,然后从cpu_slab->freelist取object,如果成功直接返回,若失败转下一步。
展开来说:
2.1 先查看cpu_slab->page
(当前cpu正在使用的slab)是否为空,如果为空转3,不为空则转2.2,
2.2 若cpu正在使用的slab不属于当前节点或slab设置了PFMEMALLOC
标志,但是申请内存时没有设置ALLOC_NO_WATERMARK
,这两种 情况会deactivate_slab
,也就是将当前cpu的slab清空,然后转3。否则重新判断cpu_slab->freelist
是否有空闲object(因为可能由于cpu迁移和被中断打断),如果有空闲的object则直接取一个返回。如果freelist为空且cpu_slab->page
不为空(走到这cpu_slab->page肯定不为空)且这个slab是frozen的,调用 getfreelist 获取freelist,也就是将cpu_slab->page->freelist
摘下来给freelist,然后将cpu_slab->page->freelist
置为NULL(这是一个常规动作,摘了cpu_slab->page->freelist
的链表就得要清空它),重加载load_freelist并返回给用户。若getfreelist返回的是NULL,则说明这个cpu正在使用的slab(即cpu_slab->page)已经用完了,那么就要将其deactivate_slab
,将当前slab移出当前cpu挂入node的full链表。然后转3。 -
从cpu的
cpu_slab->partial
部分空闲链上取一个slab(partial上的slab均为frozen)下来给cpu_slab->page
,然后重新走2.2去取object。若cpu_slab->partial
没有slab则转下一步4。 -
从kmem_cache_node的
node->partial
链上取,若node->partial上
没有slab转5,否则继续,get_partial_node
会遍历node->partial
上的slab并摘取若干个slab给cpu_slab,条件在put_cpu_partial(s, page, 0);判断,只要不超过cpu_slab的objects阈值kmem_cache->cpu_partial即可,先是对slab进行frozen(cpu->partial上的slab均为frozen的),然后将当前cpu->freelist = cpu->page_freelist
,cpu->page->freelist=NULL
,重新load_freelist。 -
从buddy系统申请pages(如果申请失败会将order),根据kmem_cache的成员oo获取order,然后调用alloc_pages申请2^order个pages。若申请成功则将pages切割成objects并用链表串起来(注意kasan也就是在这个时候kasan_poison_slab对slab进行poison),并进行初始化(根据SLUB_DEBUG init_object->setup_object初始化
left_red_pad
、red_zone
、alloc/free track
,例如对user的内容设置为0x6b6b6b...a5
,而red_zone
设置为oxbb
),设置page->inuse = page->objects,并将slab冻结frozen(在cpu_slab->partial
和cpu_slab->page
上的slab都是frozen的)。在进入new_slab_objects
之前必定cpu->page为NULL且cpu->freelist为NULL,那么从buddy申请之后还要判断一下cpu_slab->page
是否有slab,这期间可能会free内存挂接到cpu_slab->page->freelist
,故要flush_slab
。然后load_freelist
将cpu_slab->page->freelsit
给cpu_slab->freelist
,设置cpu_slab->page->freelist
为NULL。
slab_free:
享受分析代码带来的快乐,尤其是每一次的豁然开朗。
1,先获取object所在的page,如果page没有带PageSlab标记则直接走__free_pages
释放给buddy。
2,如果object所在的slab是当前cpu正在使用的slab,那么就直接将object放入cpu_slab->freelist。否则走慢路径转3,先将object放回cpu_slab->page->freelist
,根据这个slab的状况来决定如何处理。
① slab未冻结且释放该object后slab为全部空闲empty slab,即释放的这个object是该slab唯一被使用的object,是一个partai slab。
② slab未冻结且释放该object后slab为只有1个object的部分空闲partial slab,即释放当前object前已经没有可以用的object了,是一个full slab。
③ slab未冻结且释放该object后slab大于1个object小于page->objects-1(即在slab未冻结情况下排除①②两种情况)。
④ slab冻结。(cpu的所有slab都是冻结的,如果未冻结说明slab不属于任何一个cpu)
对于情况①的处理:
当前这个slab会变成完全空闲(该slab在node->partial链表上或者在full链表上),会获取node,然后看node->partial是否大于等于kmem_cache->min_partial,如果是则说明node的空闲slab比较多,应该还一部分给buddy,故而会先将该slab从partial链表上移除,然后直接将该slab释放给buddy系统。
对于情况②的处理:
当前这个slab不属于任何一个CPU,需要对其frozen冻结,然后remove_full
(slab可能挂node->full也可能是游离状态,具体与配置项有关)将该slab从node->full链表上摘除,并将put_cpu_partial
放入到当前cpu_slab->partial
,put_cpu_partial
这个流程会判断cpu_slab->partial
的object数量(注意不是slab数量,直接读取第一个slab的page->pobjects)超过kmem_cache的 cpu_slab->partial
阈值则会将其清空,详见该函数put_cpu_partial
。
对于情况③④的处理:那么直接将object放入该slab的page->freelist即可,更新cpu_slab->page->pobjects(object空闲总数)、cpu_slab->page->pages(slab数量)
·后直接返回。
综上:只有①②两种情况才会特殊处理一下,因为①这种情况说明释放的内存还挺多,那我们看看是否有多余的内存释放给buddy
。②这种情况说明使用的内存比较频繁,看看把释放的object所属slab给cpu_slab->partial
。
部分函数解析
享受分析代码带来的快乐,尤其是每一次的豁然开朗。
cpu_slab->freelist和cpu_slab->page->freelist的关系?
他们上面挂接的object都属于同一个slab,例如若cpu1的object传给了cpu0,最后在cpu0上释放cpu1的object时,cpu0会帮cpu1将object挂到cpu1的cpu_slab->page->freelist上,也就是该cpu_slab->freelist和cpu_slab->page->freelist的object都是cpu1的slab的object。同时用while循环来保证并发问题。
static void __slab_free(struct kmem_cache *s, struct page *page,
void *head, void *tail, int cnt,
unsigned long addr)
{
void *prior;
int was_frozen;
struct page new;
unsigned long counters;
struct kmem_cache_node *n = NULL;
unsigned long uninitialized_var(flags);
stat(s, FREE_SLOWPATH);
if (kmem_cache_debug(s) &&
!(n = free_debug_processing(s, page, head, tail, cnt,
addr, &flags)))
return;
do {
if (unlikely(n)) {
spin_unlock_irqrestore(&n->list_lock, flags);
n = NULL;
}
prior = page->freelist;
counters = page->counters;
set_freepointer(s, tail, prior); 1,将page->freelist挂接在tail object上
new.counters = counters;
was_frozen = new.frozen;
new.inuse -= cnt;
这里为什么要减掉cnt?因为初始化是全部在用,当申请object时是不会减的,而释放时会减
之后等到!new.inuse == true时,说明整个slab都是空闲的,而当整个slab都用完时则
new.inuse就是名副其实的全部在用了
if ((!new.inuse || !prior) && !was_frozen) {
if (kmem_cache_has_cpu_partial(s) && !prior) {
/*
* Slab was on no list before and will be
* partially empty
* We can defer the list move and instead
* freeze it.
*/
new.frozen = 1;
} else { /* Needs to be taken off a list */
n = get_node(s, page_to_nid(page));
/*
* Speculatively acquire the list_lock.
* If the cmpxchg does not succeed then we may
* drop the list_lock without any processing.
*
* Otherwise the list_lock will synchronize with
* other processors updating the list of slabs.
*/
spin_lock_irqsave(&n->list_lock, flags);
}
}
} while (!cmpxchg_double_slab(s, page,
prior, counters,
head, new.counters,
"__slab_free"));
2,page->freelist=head;就是将head->..->tail这一串object都挂入到page->freelist上,
例如在cpu0上释放cpu1的object时,cpu0会帮cpu1将这一串object都挂入到cpu1的page->freelist上。
...
}
deactivate_slab
:主要功能就是将slab从CPU上移除释放给node或直接还给buddy。
在__slab_alloc函数会调用这个,有如下两种情况:
① 如果CPU正在使用的slab不属于当前节点或者slab设置PFMEMALLOC但是申请内存时没有设置ALLOC_NO_WATERMARK,就会将slab移出当前CPU。
② 如果cpu_slab->freelist
没有可用内存了且cpu_slab->page->freelist
也没有空闲内存,也就是当前这个CPU正在使用的slab没有内存了,那么就需要将该slab从当前CPU移除,最后add_full
挂入到node的full list(这需要开启CONFIG_SLUB_DEBUG
)。
针对①②两种情况都需要将slab移除当前cpu,说明当前cpu的slab已经耗尽了,需要腾出位置来给新的有空闲object的slab。
static void deactivate_slab(struct kmem_cache *s, struct page *page,
void *freelist)
{
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;
enum slab_modes l = M_NONE, m = M_NONE;
void *nextfree;
int tail = DEACTIVATE_TO_HEAD;
struct page new;
struct page old;
if (page->freelist) {
stat(s, DEACTIVATE_REMOTE_FREES);
tail = DEACTIVATE_TO_TAIL;
}
/*
* Stage one: Free all available per cpu objects back
* to the page freelist while it is still frozen. Leave the
* last one.
*
* There is no need to take the list->lock because the page
* is still frozen.
*/
这while循环是保留freelist上最后一个可用的object,将其他object都挂到page->freelist上
while (freelist && (nextfree = get_freepointer(s, freelist))) {
void *prior;
unsigned long counters;
do {
prior = page->freelist;
counters = page->counters;
set_freepointer(s, freelist, prior); 将freelist指向
new.counters = counters;
new.inuse--;
因为这里deactive就相当于free,故而要减1,
下面即将判断new.inuse的值,表明是否slab已经完全空闲,则可以释放给node或buddy了
否则就挂入到node的partial链表上。
VM_BUG_ON(!new.frozen); 因为是CPU的slab故这个slab肯定是frozen
} while (!__cmpxchg_double_slab(s, page,
prior, counters,
freelist, new.counters,
"drain percpu freelist"));
page->freelist=freelist,将freelist上object加到page->freelist头部。
freelist = nextfree;
}
/*
* Stage two: 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:
old.freelist = page->freelist;
old.counters = page->counters;
VM_BUG_ON(!old.frozen);
/* Determine target state of the slab */
new.counters = old.counters;
if (freelist) {
new.inuse--;
set_freepointer(s, freelist, old.freelist);前面残留的那个object的next指向page->freelist来
new.freelist = freelist;
将所有的object都一股脑由page->freelist指向,此时所有空闲的object都在page->freelist上了
为什么搞这么一通?
我得理解是因为每次cpu_slab->freelist为NULL时都是直接
cpu_slab->freelist=cpu_slab->page->freelist
这样能一次性就把可用object全部给cpu_slab->freelist了,这里的整合就是为了在freelist为null
时一把就把page->freelist全部freelist,也能处理好page.inuse。比如get_freelist。
} else
new.freelist = old.freelist;
new.frozen = 0;
if (!new.inuse && n->nr_partial >= s->min_partial)
m = M_FREE; 将freelist的object全部移除到page->freelist上
else if (new.freelist) {
m = M_PARTIAL;
if (!lock) {
lock = 1;
/*
* Taking the spinlock removes the possiblity
* that acquire_slab() will see a slab page that
* is frozen
*/
spin_lock(&n->list_lock);
}
} else {
m = M_FULL;
if (kmem_cache_debug(s) && !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)——这里是为了redo而设置的,chg失败才会执行这里
remove_partial(n, page);
else if (l == M_FULL)
remove_full(s, n, page);
if (m == M_PARTIAL) {——这里是另外一个if,不要跟上面搞混了
add_partial(n, page, tail);
stat(s, tail);
} else if (m == M_FULL) {
stat(s, DEACTIVATE_FULL);
add_full(s, n, page);
}
}
l = m;
if (!__cmpxchg_double_slab(s, page,
old.freelist, old.counters,
new.freelist, new.counters,
"unfreezing slab"))
goto redo; 如果这次cmpxchg失败了还要redo,那么需要先删除再插入,在cmpxchg,保证一致性
if (lock)
spin_unlock(&n->list_lock);
if (m == M_FREE) {
stat(s, DEACTIVATE_EMPTY);
discard_slab(s, page);
stat(s, FREE_SLAB);
}
}
put_cpu_partial
:主要功能是将②类型的slab放入到当前cpu,如果当前cpu->partial
先查看一下cpu_slab->partial->page->pobjects
,其表示cpu partial链表中所有slab可分配obj的总数(第一个slab page的pobjects和pages分别表示链表中空闲object总数和slab总数)。当释放一个obj给full slab(已经耗尽的slab)时,该slab会添加到cpu partial链表,因此,每次添加的时候都会判断当前的pobjects是否大于kmem_cache->cpu_partial
阈值(用于限制CPU slab的空闲object数量),如果大于,会将cpu_slab->partial
链表中所有的slab移送到node->partial
,然后再将刚刚释放obj的slab插入到cpu_slab->partial
链表。如果不大于,则更新pobjects和pages成员,并将slab插入到cpu_slab->partial
链表。
static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *oldpage;
int pages;
int pobjects;
preempt_disable();
do {
pages = 0;
pobjects = 0;
oldpage = this_cpu_read(s->cpu_slab->partial);
if (oldpage) {
pobjects = oldpage->pobjects;
pages = oldpage->pages;
if (drain && pobjects > s->cpu_partial) {
unsigned long flags;
/*
* partial array is full. Move the existing
* set to the per node partial list.
*/
local_irq_save(flags);
unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
local_irq_restore(flags);
oldpage = NULL;
pobjects = 0;
pages = 0;
stat(s, CPU_PARTIAL_DRAIN);
}
}
pages++;
pobjects += page->objects - page->inuse;
page->pages = pages; 第一个slab的page的pages表示整个partial链表的slab数量
page->pobjects = pobjects;第一个slab的page的pobjects表示整个partial链表的空闲object数量
page->next = oldpage;将传入slab加入到当前CPU partial链表的头部
} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page)
!= oldpage);
if (unlikely(!s->cpu_partial)) {
unsigned long flags;
local_irq_save(flags);
unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
local_irq_restore(flags);
}
preempt_enable();
#endif
}
·get_freelist
:主要是就将cpu_slab->page->freelist返回,然后cpu_slab->page->freelist置位NULL,更新counter(含objects、inuse、frozen),如果cpu_slab->page->freelist给了cpu_slab->freelist就认为inuse为全部在用状态,如果部分在page->freelist则inuse就需要减掉这部分;
static inline void *get_freelist(struct kmem_cache *s, struct page *page)
{
struct page new;
unsigned long counters;
void *freelist;
do {
freelist = page->freelist;
counters = page->counters;
struct page new没有初始化,这里只有new.counter=counters,下面怎么就new.inuse -= cnt?
原因是struct page的counter是一个union,包含了inuse(当前slab正在使用的object数量)、
objects(表示slab中所有object的数量,allocate_slab赋初值,在check_slab会判断page->objects
是否超过该slab能容纳的最大object)、frozen(slab是否冻结)。
new.counters = counters;
VM_BUG_ON(!new.frozen);
new.inuse = page->objects;
new.frozen = freelist != NULL;
} while (!__cmpxchg_double_slab(s, page,
freelist, counters,
NULL, new.counters,
"get_freelist")); 标准操作freelist=page->freelist; page->freelist = NULL;更新counter;
return freelist;
}
unfreeze_partials
:若cpu_slab->partial的空闲object数量高于cache->cpu_partial时,会将当前cpu_slab的所有slab都解冻并添加到node->partial链表上去。
discard_slab
:主要功能是将slab的pages还给buddy。
add_partial/remove_partial/remove_full
:主要功能就是从struct page的struct list_head lru的链表中添加删除。
由于本人水平有限,无法保证对slub的理解都准确无误,所以读者发现其中有什么错误,请勿见怪,若方便,可以来信讨论,邮箱ldys2014@foxmail.com。
参考资料
1,http://www.wowotech.net/memory_management/426.html
2,https://www.cnblogs.com/adera/p/11718765.html
3,https://blog.youkuaiyun.com/jasonactions/article/details/109332167
4,https://blog.youkuaiyun.com/monkeyzh123/article/details/119840691