背景
省流
如果对代码细节不感兴趣,可以直接跳转底部内存释放逻辑总结。
前情回顾
关于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 page-
free_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 就是head
struct 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_slab
tid = 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]获取freelist
set_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的freelist
if (unlikely(n)) {//循环了多次才会走到这,加锁操作
spin_unlock_irqrestore(&n->list_lock, flags);
n = NULL;
}
prior = page->freelist;//[1.1]获取当前page的freelist
counters = page->counters;//获取当前page的counters,这是一个联合体,包括inuse、frozen等
set_freepointer(s, tail, prior);//将freelist 接到要释放内存块的后面*tail=prior
new.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->partial
remove_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_PARTIAL
struct page *oldpage;
int pages; //[1] 代表这个slab 后面大概还有多少个page
int 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数量清0
pages = 0;
stat(s, CPU_PARTIAL_DRAIN);
}
}
pages++;//[3]更新pages和pobjects数量
pobjects += page->objects - page->inuse;
page->pages = pages; //设置当前slab 的pages 和pobjects
page->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_PARTIAL
struct kmem_cache_node *n = NULL, *n2 = NULL;
struct page *page, *discard_page = NULL;
while ((page = slub_percpu_partial(c))) {//[1] 遍历cpu_slab->partial中的page
struct page new;
struct page old;
slub_set_percpu_partial(c, page);//取出头部的,更新新头部
n2 = get_node(s, page_to_nid(page));//获取page所在node
if (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 page
stat(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的释放
特殊slab使用kmem_cache_free
函数来释放堆块:
static void file_free_rcu(struct rcu_head *head)
{
struct file *f = container_of(head, struct file, f_u.fu_rcuhead);
put_cred(f->f_cred);
kmem_cache_free(filp_cachep, f);//专属slab的释放函数
}
kmem_cache_free
虽然也是直接封装的slab_free
,但释放前会使用cache_from_obj
函数校验一下,避免其他堆块的释放到错误的slab cache中:
void kmem_cache_free(struct kmem_cache *s, void *x)
{
s = cache_from_obj(s, x);//先校验一下是不是这个slab的堆块
if (!s)
return;
//调用slab_free正常free掉x
slab_free(s, virt_to_head_page(x), x, NULL, 1, _RET_IP_);
trace_kmem_cache_free(_RET_IP_, x, s->name);
}
校验逻辑无非就是根据堆块找page,根据page找slab_cache,不多赘述了。
内存释放逻辑总结
内存对象释放主要思路就是,如果是页面对象,则直接伙伴系统释放,如果是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的状态转换关系图:文章来源:https://www.toymoban.com/news/detail-407581.html
文章来源地址https://www.toymoban.com/news/detail-407581.html
到了这里,关于[linux kernel]slub内存管理分析(5) kfree的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!