文章目录
- 背景
- 省流
- 前情回顾
- 描述方法约定
- kfree 操作总览
- 简介
- 逻辑图预览
- 释放逻辑
- slab page各个状态转化
- 调用栈
- 详细分析
- kfree
- slab_free
- __slab_free
- put_cpu_partial
- unfreeze_partials
- discard_slab->free_slab
- 内存释放逻辑总结
- slab page状态转换关系图
背景
省流
如果对代码细节不感兴趣,可以直接跳转底部内存释放逻辑总结。
前情回顾
关于slab几个结构体的关系和初始化和内存分配的逻辑请见:
[linux kernel]slub内存管理分析(0) 导读
[linux kernel]slub内存管理分析(1) 结构体
[linux kernel]slub内存管理分析(2) 初始化
[linux kernel]slub内存管理分析(2.5) slab重用
[linux kernel]slub内存管理分析(3) kmalloc
[linux kernel]slub内存管理分析(4) 细节操作以及安全加固
描述方法约定
PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache
我们这里称为slab管理结构,它管理的真个slab 体系成为slab cache,struct kmem_cache_node
这里就叫node。单个堆块称为object或者堆块或内存对象。
kfree 操作总览
简介
kfree
是用来回收kmalloc
分配的内存的函数,这里详细分析流程。
kfree
之中同样有很多设计cpu抢占、NUMA架构node相关的逻辑,这里不详细分析了,因为我们分析的目的是为了搞内核安全,而写漏洞利用的时候可以通过将程序绑定在一个cpu 来避免涉及这些逻辑的代码发生。
还有一些和Kasan、slub_debug 等检测内存越界等的操作,也不过多分析。
逻辑图预览
释放逻辑
slab page各个状态转化
调用栈
kfree
__free_pages
释放页面(大块)slab_free
释放slab 的内存slab_free_freelist_hook
do_slab_free
入口,和cpu_slab
快速释放memcg_slab_free_hook memcg
相关计数操作__slab_free
慢速释放kfence_free
、kmem_cache_debug
、free_debug_processing
检查- 释放逻辑
put_cpu_partial
unfreeze_partials
discard_slab slab
为空则销毁该slab pagefree_slab
->__free_slab
详细分析
kfree
kfree
总入口:
linux\mm\slub.c : kfree
void kfree(const void *x)
{struct page *page;void *object = (void *)x;trace_kfree(_RET_IP_, x);if (unlikely(ZERO_OR_NULL_PTR(x)))return;page = virt_to_head_page(x);//[1]根据地址找到对应的page结构体if (unlikely(!PageSlab(page))) {//[2]如果该page 不是slab,那么就是大块内存了,调用free_page释放unsigned int order = compound_order(page);BUG_ON(!PageCompound(page));kfree_hook(object);mod_lruvec_page_state(page, NR_SLAB_UNRECLAIMABLE_B,-(PAGE_SIZE << order));__free_pages(page, order);return;}slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);//[3]释放slab 分配的内存块
}
[1] 调用kfree
的都是要释放的内存地址,所以首先要根据内存虚拟地址找到它所在页的页结构体。
[2] 判断该页是不是slab page,如果不是,说明这个要释放的内存不是slab切割分配的,而是一个大内存块,当初是直接分配的页,所以这里页调用__free_pages
来释放内存页。
[3] 否则说明这个内存块是slab page分配的内存块,进入slab 的释放流程slab_free
。参数是通过page
获得page
的slab管理结构体kmem_cache
。
slab_free
释放slab 内存的入口slab_free
:
linux\mm\slub.c : slab_free
static __always_inline void slab_free(struct kmem_cache *s, struct page *page,void *head, void *tail, int cnt,unsigned long addr)
{if (slab_free_freelist_hook(s, &head, &tail))do_slab_free(s, page, head, tail, cnt, addr);//[1]调用do_slab_free
}
[1] slab_free_freelist_hook
是些kasan 的东西没啥用,这里直接调用了do_slab_free
,传入参数有head
和tail
,由于slab_free
设计上可以释放多个内存块,所以这里head
和tail
和释放多个内存块相关,如果只释放一个的话,不影响。
linux\mm\slub.c : do_slab_free
static __always_inline void do_slab_free(struct kmem_cache *s,struct page *page, void *head, void *tail,int cnt, unsigned long addr)
{void *tail_obj = tail ? : head;//[1]获得尾部,这里tail 传入是0, 所以tail_obj 就是headstruct kmem_cache_cpu *c;unsigned long tid;memcg_slab_free_hook(s, &head, 1);//[2]memcg 相关
redo:/** Determine the currently cpus per cpu slab.* The cpu may change afterward. However that does not matter since* data is retrieved via this pointer. If we are on the same cpu* during the cmpxchg then the free will succeed.*/do {//[3]获取cpu_slabtid = this_cpu_read(s->cpu_slab->tid);c = raw_cpu_ptr(s->cpu_slab);} while (IS_ENABLED(CONFIG_PREEMPTION) &&unlikely(tid != READ_ONCE(c->tid)));/* Same with comment on barrier() in slab_alloc_node() */barrier();//同步一下if (likely(page == c->page)) {//[4]释放的对象正好属于当前cpu_slab正在使用的slab则快速释放void **freelist = READ_ONCE(c->freelist);//[4.1]获取freelistset_freepointer(s, tail_obj, freelist);//[4.2]将新释放内存块插入freelist 开头if (unlikely(!this_cpu_cmpxchg_double(//[4.3]this_cpu_cmpxchg_double()原子指令操作存放s->cpu_slab->freelist, s->cpu_slab->tid,freelist, tid,head, next_tid(tid)))) {note_cmpxchg_failure("slab_free", s, tid);//失败记录goto redo;}stat(s, FREE_FASTPATH);} else__slab_free(s, page, head, tail_obj, cnt, addr);//[5]否则说明释放的内存不是当前cpu_slab立马能释放的}
[1] 获取一下尾部,由于我们传入的tail
指针为0,则获取head
指针,也就是要释放的内存地址。换句话说,当只释放一个内存块的时候,head=tail
。
[2] memcg相关,这里不详细分析。
[3] 跟kmalloc
的时候一样,获取一下当前使用cpu的cpu_slab
。
[4] 如果要释放的内存块的所在page
正好是当前cpu_slab
管理的slab,那么可以快速释放。
[4.1] 获取cpu_slab
的freelist
[4.2] 调用set_freepointer
将要释放的内存块放到freelist
链表的开头,在前一章分析过set_freepointer
了。
[4.3] 跟kmalloc
时候相同的原子操作,这里等价于,也就是更新一下cpu_slab->freelist
:
freelist = s->cpu_slab->freelist;
s->cpu_slab->freelist=head;
tid=s->cpu_slab->tid;
s->cpu_slab->tid=next_tid(tid)
[5] 到这里说明释放的内存不是当前cpu_slab
立马能释放的,那么调用__slab_free
慢速分配。
__slab_free
linux\mm\slub.c : __slab_free
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 flags;stat(s, FREE_SLOWPATH);if (kfence_free(head))return;if (kmem_cache_debug(s) && //这两个里面就是各种检测,实际没有对free环节有影响的操作!free_debug_processing(s, page, head, tail, cnt, addr))return; do {//[1] 尝试释放内存块到所属page的freelistif (unlikely(n)) {//循环了多次才会走到这,加锁操作spin_unlock_irqrestore(&n->list_lock, flags);n = NULL;}prior = page->freelist;//[1.1]获取当前page的freelistcounters = page->counters;//获取当前page的counters,这是一个联合体,包括inuse、frozen等set_freepointer(s, tail, prior);//将freelist 接到要释放内存块的后面*tail=priornew.counters = counters;was_frozen = new.frozen;new.inuse -= cnt;//正在使用内存块减掉要释放的数量if ((!new.inuse || !prior) && !was_frozen) {//[2]释放之后全空或释放之前为满并且不在cpu_slab中//释放前为满有2种情况:游离中,在cpu_slab->page中,但走到这里确定不在cpu_slab中//调试模式时可能在node->full中,默认不开启,不考虑if (kmem_cache_has_cpu_partial(s) && !prior) {//[2.1]释放前为满(不管你释放后空不空)//并且开启了cpu_slab->partial//走到这可以确定slab page为游离状态/** Slab was on no list before and will be* partially empty* We can defer the list move and instead* freeze it.*/new.frozen = 1;//准备放入cpu_slab中} else { /* Needs to be taken off a list *///[2.2]释放之后就为空slab了n = get_node(s, page_to_nid(page));//获取page所在node/** 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, //[1.2]成功之前一直尝试prior, counters,head, new.counters,"__slab_free"));/* if (page->freelist == prior && page->counters == counters)* page->freelist = head* page->counters = new.counters 完成释放*/if (likely(!n)) {//[3]n为空,说明这里释放之前为满,或释放之前之后都半满if (likely(was_frozen)) {//[3.1]原本就在cpu_slab中,啥也不用操作/* 走到这里说明该slab page在当前cpu_slab->partial中或其他cpu_slab的page或partial中直接释放到page->freelist即可(已经释放完毕),其他cpu我们管不到* The list lock was not taken therefore no list* activity can be necessary.*/stat(s, FREE_FROZEN);} else if (new.frozen) {//[3.2]释放之前是满的也就是游离状态,放入cpu_slab->partial中/** If we just froze the page then put it onto the* per cpu partial list.*/put_cpu_partial(s, page, 1);//放到cpu_slab->partial中,这里有一个bug,他没有考虑node->full的情况stat(s, CPU_PARTIAL_FREE);}return;//[3.3]释放之前在node->partial中,并且释放之后也不为空,直接返回啥也不用干}if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))//[4]释放之后为空 node 中的partial 满足最小要求goto slab_empty;//去释放这个page/** Objects left in the slab. If it was not on the partial list before* then add it.*/if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {//[5]释放前为full,但没开启cpu_slab->partialremove_full(s, n, page);//从full 中移除,如果是游离状态不在链表中会直接返回add_partial(n, page, DEACTIVATE_TO_TAIL); //添加到partial中stat(s, FREE_ADD_PARTIAL);}spin_unlock_irqrestore(&n->list_lock, flags);return;slab_empty:if (prior) {//[4.1]释放之前不是full,在partial中/** Slab on the partial list.*/remove_partial(n, page);//从partial 删除stat(s, FREE_REMOVE_PARTIAL);} else {//[4.2]释放之前在full 中/* Slab must be on the full list */remove_full(s, n, page);//从full 中删除}spin_unlock_irqrestore(&n->list_lock, flags);stat(s, FREE_SLAB);discard_slab(s, page);//[4]slab为空,则直接删除整个空slab
}
[1] 尝试释放内存块到所属page
的freelist
,由于涉及到锁的操作/原子操作,这里会一直循环尝试直到成功为止
[1.1] 获取一下释放堆块之前page
的freelist
、counters
等信息,counters
是一个联合体,同时包括page
的frozen
(代表page
是在cpu_slab
中还是在node
中)、inuse
(代表正在使用的内存数量)等。然后将page->freelist
拼到准备释放的内存块后面,也就是新释放的放到freelist
表头,但目前还没更新到page
,然后计算新inuse
等。
[1.2] 进行原子操作尝试完成分配,cmpxchg_double_slab
函数完成的操作等价于如下操作,即将新freelist
头更新到page
中,再更新counters
:
if (page->freelist == prior && page->counters == counters)
{page->freelist = head;page->counters = new.counters;
}
目前已经将堆块释放到page->freelist
中了,接下来要对page
的种类进行判断。目前一共有6种(有一种是DEBUG场景)可能:
- 在当前
cpu_slab->partial
中 - 是
cpu_slab->page
- 在其他
cpu_slab->partial
中 - 游离状态(分配满了),不在任何列表之中
- 在所属
node
的partial
列表中 - 如果开启
CONFIG_SLUB_DEBUG
,则可能会是分配满的状态而在所属node的full
列表中,默认不开启
[2] !new.inuse
代表释放之后已经没有inuse
的内存了,说明释放之后该slab里面全是free 的堆块,也就是释放后整个page
都空了;!prior
说明释放之前没有freelist
,即释放之前该slab 为分配满的slab page;!was_frozen
说明释放之前该slab不在cpu_slab
之中。也就是说,当该slab 不在是cpu_slab
正在使用的slab 时,如果释放后为空slab或释放前为full slab,则说明游离状态、node->full
中或node->partial
中的page
满足该分支条件:
[2.1] 释放前为full slab,对应游离状态和node->full
的slab page;kmem_cache_has_cpu_partial
判断是否开启了cpu_slab->partial
;若开启则new.frozen
设置为1,打算将该slab 放入cpu_slab->partial
中,因为释放后就变成了半满状态了,可以使用了。这里为什么放入cpu_slab
的partial
而不是放入node的partial
,是因为cpu_slab
永远都是最近在使用的slab。
[2.2] 接下来就是slab page在node->partial
中且释放之后就为空slab page的情况,获取一下page
所属node。方便后续操作。
[3] n
为空,说明之前没有获取node,也就是说不是释放之后就为空slab。满足这个分支的有三种cpu_slab
中的情况和游离状态的slab page情况:
[3.1] 原本就在cpu_slab
中(的三种情况),什么也不用改变,因为我们已经将堆块释放到page->freelist
中了,如果在本cpu_slab->partial
无论释放完是否为空都无关紧要,因为不需要释放page
;其次在其他cpu_slab
中的话,不管是在page
还是在partial
中我们都是无权干涉的,直接释放到page->freelist
然后返回即可。
[3.2] 走到这里说明**page
是申请满的游离状态或node->full
中**,则释放后为可用状态,将其放入cpu_slab->partial
中然后返回(put_cpu_partial
函数逻辑不止于此,下面会详细分析)。这个地方有一个问题就是,他没有考虑如果开启了CONFIG_SLUB_DEBUG
的情况下,满的page可能会存在于node->full
列表中,如果补充node->full
双向链表中把page
删掉直接加入到cpu_slab->partial
中会破坏node->full
双链表,后续插入操作可能造成unlink崩溃,由于默认不开启该CONFIG,再加上page
进入node->full
需要触发强制下架的时候cpu_slab->page
必须是满的,概率非常低,用户也不可控,一般不影响。不过内核最新版已经重写了开启CONFIG_SLUB_DEBUG
的逻辑,不存在这样的问题了,我这里的代码是5.13 版本,不影响其他逻辑。
[3.3] 最后说明释放之前在node->partial
中,并且释放之后也不为空,那么还继续呆在node->partial
中就行,直接啥也不用干返回。
[4] 走到这里说明slab page在node->partial
中(小概率在node->full
中),且释放之后为空且node 中当前partial slab数量比slab 规定的最小partial
数量多,可以释放空的slab page,则进入空slab处理流程:
[4.1] 释放之前的状态是半满,则调用remove_partial
从node->partial
列表中删除,然后调用discard_slab
释放该空slab page。
[4.2] 释放之前在node->full
中,则调用remove_full
从full
列表中删除,之所以会有释放之后直接从full 变为空,我猜是考虑到这个函数可以一次释放多个堆块的原因。还有一点就是,正常full状态的page
在上面就会加入到cpu_slab->partial
或者node->partial
中了,无论释放完是否为空,所以是走不到这个分支的,可见这个版本的代码对CONFIG_SLUB_DEBUG
的处理还是比较粗糙的,目前已经在后续版本都会更新完善。
[5] 否则说明**cpu_slab
没开启partial
列表,游离状态的slab page释放后仍需要添加到一个列表中**,则将其加入node->partial
中。
put_cpu_partial
put_cpu_partial
函数不止是将slab page 放到cpu_slab->partial
链表中,如果cpu_slab->partial
链表中的页面数量已经达到最大值,则还会进行洗牌操作:
mm\slub.c : put_cpu_partial
static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
#ifdef CONFIG_SLUB_CPU_PARTIALstruct page *oldpage;int pages; //[1] 代表这个slab 后面大概还有多少个pageint pobjects;//代表这个slab 后面还有多少个object 可用preempt_disable();do {pages = 0;pobjects = 0;oldpage = this_cpu_read(s->cpu_slab->partial);//获取cpu->partial列表头部的那个if (oldpage) {//[1] 获取之前的pages 和pobjects 值用来之后更新pobjects = oldpage->pobjects;pages = oldpage->pages;if (drain && pobjects > slub_cpu_partial(s)) {//[2] 当前cpu_slab->partial中的页数量已经达到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));//把cpu_slab->partial中现存的slab page都放到node->partial中local_irq_restore(flags);oldpage = NULL;pobjects = 0;//cpu_slab->partial数量清0pages = 0;stat(s, CPU_PARTIAL_DRAIN);}}pages++;//[3]更新pages和pobjects数量pobjects += page->objects - page->inuse;page->pages = pages; //设置当前slab 的pages 和pobjectspage->pobjects = pobjects;page->next = oldpage;//加入链表头部} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page)//[4] 最后将当前页放入cpu_slab->partial中!= oldpage);if (unlikely(!slub_cpu_partial(s))) {unsigned long flags;local_irq_save(flags);unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));local_irq_restore(flags);}preempt_enable();
#endif /* CONFIG_SLUB_CPU_PARTIAL */
}
[1] cpu_slab->partial
中每个页面中的pobjects
和pages
都代表这个页以及后面的单链表中有多少内存块数和页数,这样我们只访问第一个页即可了解全部信息。
[2] 如果cpu_slab->partial
中的页数已经达到了slab 规定的cpu_partial
最大值,则要进行unfreeze_partial
操作。下面分析该函数。主要逻辑就是将cpu_slab->partial
中的slab page都放到node->partial
中,然后更新统计信息,当前cpu_slab->partial
为空。
[3] 将该slab page加入到cpu_slab->partial
链表头部。
[4] 最后将整个链表更新到cpu_slab->partial
中
unfreeze_partials
unfreeze_partials
函数主要是在cpu_slab->partial
中slab page数量已经达到最大值的时候将cpu_slab->partial
中的slab page都转移到node->partial
中,如果遇到空的slab page,则会释放掉。
mm\slub.c : unfreeze_partials
static void unfreeze_partials(struct kmem_cache *s,struct kmem_cache_cpu *c)
{
#ifdef CONFIG_SLUB_CPU_PARTIALstruct kmem_cache_node *n = NULL, *n2 = NULL;struct page *page, *discard_page = NULL;while ((page = slub_percpu_partial(c))) {//[1] 遍历cpu_slab->partial中的pagestruct page new;struct page old;slub_set_percpu_partial(c, page);//取出头部的,更新新头部n2 = get_node(s, page_to_nid(page));//获取page所在nodeif (n != n2) {if (n)spin_unlock(&n->list_lock);n = n2;spin_lock(&n->list_lock);}do {old.freelist = page->freelist;old.counters = page->counters;//[2] 获取page 的基本信息联合体VM_BUG_ON(!old.frozen);new.counters = old.counters;new.freelist = old.freelist;new.frozen = 0;//要从cpu_slab->partial中拿出来,肯定要解冻} while (!__cmpxchg_double_slab(s, page,old.freelist, old.counters,//更新page 信息new.freelist, new.counters,"unfreezing slab"));if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {//[3] 如果page 为空,并且node->partial数量满足最小要求page->next = discard_page;//把page加入到要释放的page列表discard_page = page;} else {//[3.1] 否则就加入到node的的partial中add_partial(n, page, DEACTIVATE_TO_TAIL);stat(s, FREE_ADD_PARTIAL);}}if (n)spin_unlock(&n->list_lock);while (discard_page) {//[4] 如果释放page 列表不为空page = discard_page;discard_page = discard_page->next;stat(s, DEACTIVATE_EMPTY);discard_slab(s, page);//依次调用discard_slab 释放掉slab pagestat(s, FREE_SLAB);}
#endif /* CONFIG_SLUB_CPU_PARTIAL */
}
[1] 遍历cpu_slab->partial
中的每一个slab page,并从链表中依次取出
[2] 获取page
的基本信息,然后更新基本信息,这里是将frozen
设为0,也就是解冻,从cpu_slab
中移除
[3] 如果slab page的inuse
为0,说明该slab page已经为空,并且如果node->partial
页满足最小数量要求,则将其加入到待释放的slab page的列表
[3.1] 否则将其加入到node->partial
[4] 将所有空的可以释放的page
调用discard_slab
释放掉
discard_slab->free_slab
linux\mm\slub.c : discard_slab
static void discard_slab(struct kmem_cache *s, struct page *page)
{dec_slabs_node(s, page_to_nid(page), page->objects);//更新node中的slab 和object数量,释放了要减少free_slab(s, page);//free_slab 释放slab
}
discard_slab
中先调用dec_slabs_node
进行一些统计,更新一下slab page被释放后node中的slab page数量和堆块数量。
然后调用free_slab
进行正式的slab page释放:
linux\mm\slub.c : free_slab
static void free_slab(struct kmem_cache *s, struct page *page)
{if (unlikely(s->flags & SLAB_TYPESAFE_BY_RCU)) {//RCU延迟释放call_rcu(&page->rcu_head, rcu_free_slab);} else__free_slab(s, page);
}
如果开启了SLAB_TYPESAFE_BY_RCU
则使用RCU延迟释放slab page,不太关心,默认不开启。正常流程调用__free_slab
释放slab page:
linux\mm\slub.c : __free_slab
static void __free_slab(struct kmem_cache *s, struct page *page)
{int order = compound_order(page); //获取slab所属page阶数int pages = 1 << order; //page 页数if (kmem_cache_debug_flags(s, SLAB_CONSISTENCY_CHECKS)) {//如果开启了debug,则进行一些检测void *p;slab_pad_check(s, page);//slab 检测for_each_object(p, s, page_address(page),page->objects)check_object(s, page, p, SLUB_RED_INACTIVE);//slab中的object 检测}__ClearPageSlabPfmemalloc(page);//这两个都是修改page->flags的宏,清楚slab相关标志位__ClearPageSlab(page);/* In union with page->mapping where page allocator expects NULL */page->slab_cache = NULL;//取消page指向slab 的指针if (current->reclaim_state)current->reclaim_state->reclaimed_slab += pages;//更新数据unaccount_slab_page(page, order, s);//如果开启了memcg相关会进行memcg去注册__free_pages(page, order); //释放掉page
}
逻辑很简单,因为在之前已经将该slab page从node中unlink
出来了,那么接下来只需要释放该page
即可。在__free_slab
中进行一些常规的检查和统计更新,最后调用__free_pages
将页面释放。
内存释放逻辑总结
内存对象释放主要思路就是,如果是页面对象,则直接伙伴系统释放,如果是slab 对象,如果在cpu_slab中,在cpu_slab中释放,如果不是,则根据释放之前之后的状态(为空、为满、半满),进行不同的操作。
-
首先找到释放的内存对象所在的page 的page结构体。如果该page不是slab,也就是说内存对象不是通过slab分配的,而是直接分配的页(大块内存),那么直接释放页。
- 大块内存分配的时候就是从伙伴系统直接分配的页,释放的时候页通过伙伴系统释放页面(
__free_pages
)
- 大块内存分配的时候就是从伙伴系统直接分配的页,释放的时候页通过伙伴系统释放页面(
-
其他内存是通过slab分配,则通过slab释放(
slab_free
)-
memcg相关处理(
memcg_slab_free_hook
) -
获取当前
cpu_slab
,如果要释放的内存对象正好属于当前cpu_slab
(可以理解为是否是从当前cpu_slab
分配的),则快速释放- 获取
cpu_slab
的freelist
,将该内存对象插入freelist
头部,刷新cpu_slab
相关信息(do_slab_free
)
- 获取
-
如果要释放的内存对象不属于当前
cpu_slab
,(当前slab page在cpu_slab->partial
、别的cpu_slab->page
、别的cpu_slab->partial
、游离状态、node->partial
、node->full
6种情况),需要慢速释放(__slab_free
)- 先把内存对象释放到slab page的
freelist
头部,更新slab page相关统计信息 - 如果该slab page为冻结状态(说明是在
cpu_slab
中的三种情况)- 则直接结束(已经将
object
放到page->freelist
了,剩下的就不用管了)
- 则直接结束(已经将
- 如果释放前该slab page是满的(freelist为空),则说明
page
目前是游离状态(不在任何列表中)或node->full
中- 如果开启
cpu->partial
,则将该slab page放到cpu_slab->partial
中(从node->full
中移除)- 如果
cpu_slab->partial
满了,则要将当前cpu_slab->partial
中的所有slab page放到node->partial
中,然后再将新的slab page放到cpu_slab->partial
中
- 如果
- 否则放入
node->partial
中(从node->full
中移除)
- 如果开启
- 如果释放后为空,则说明目前该slab page一定处在
node->partial
列表中,因为如果在cpu_slab
或者游离状态或node->full
中不管释放完是否为空,都会在上面的步骤中处理完毕- 如果当前slab 管理的
partial
页面数量满足最小要求,则将该释放后为空的slab page释放掉(__free_pages
) - 否则不变,继续呆在
node->partial
中
- 如果当前slab 管理的
- 否则说明该
page
是本来就呆在node->partial
中的半满page
,并且释放后还是半满,则什么也不操作。
- 先把内存对象释放到slab page的
-
slab page状态转换关系图
结合kmalloc
和kfree
的逻辑,可以画出slab page的状态转换关系图: