如果对代码细节不感兴趣,可以直接跳转底部kmem_cache销毁操作总结
关于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) 细节操作以及安全加固
[linux kernel]slub内存管理分析(5) kfree
PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache
我们这里称为slab管理结构,它管理的真个slab 体系成为slab cache,struct kmem_cache_node
这里就叫node。单个堆块称为object或者堆块或内存对象。
slab销毁操作的入口是kmem_cache_destroy,主要是销毁一整个slab 管理结构和其中的node、cpu_slab 和各个slab。一般是很多临时slab 用完后销毁使用。里面分步释放了kmem_cache 下面的多个字结构体和各个slab page,最后再释放kmem_cache结构。
kmem_cache_destroy
shutdown_cache
核心销毁函数
__kmem_cache_shutdown
处理各个slab page
flush_all
把所有cpu_slab
里的slab page
下架
flush_cpu_slab
-> __flush_cpu_slab
flush_slab
下架并刷新cpu_slab
deactivate_slab
强制下架unfreeze_partials
: cpu_slab->partial
转移到node->partial
discard_slab
销毁空slab page
free_slab
-> __free_slab
__free_pages
free_partial
处理node->partial
中的slab page
remove_partial
移出partial
discard_slab
销毁空slab page
free_slab
-> __free_slab
__free_pages
slab_kmem_cache_release
释放slub管理相关结构
__kmem_cache_release
释放次级结构体
cache_random_seq_destroy
freelist 随机化相关释放free_percpu
释放所有cpu_slubfree_kmem_cache_nodes
释放所有node
kmem_cache_free
释放 kmem_cache_node
结构
slab_free
kmem_cache_free
释放kmem_cache
结构
slab_free
kmem_cache_destroy
是slab销毁函数的入口:
linux\mm\slab_common.c : kmem_cache_destroy
void kmem_cache_destroy(struct kmem_cache *s)//销毁入口
{
int err;
if (unlikely(!s))
return;
mutex_lock(&slab_mutex);//加锁操作
s->refcount--;//引用-1
if (s->refcount)//如果引用不为0说明还有引用它的,则不操作
goto out_unlock;
err = shutdown_cache(s);//核心操作
if (err) {
pr_err("kmem_cache_destroy %s: Slab cache still has objects\n",
s->name);
dump_stack();
}
out_unlock:
mutex_unlock(&slab_mutex);
}
简单进行了加锁和引用判断便调用了shutdown_cache
,接下来分析shutdown_cache
:
linux\mm\slab_common.c : shutdown_cache
static int shutdown_cache(struct kmem_cache *s)
{
/* free asan quarantined objects */
kasan_cache_shutdown(s);
if (__kmem_cache_shutdown(s) != 0)//[1] 处理slab管理结构下面的各个slab page。
return -EBUSY;
list_del(&s->list);//[2] 释放完毕后从链表中删除
if (s->flags & SLAB_TYPESAFE_BY_RCU) {//RCU相关,默认不开启
#ifdef SLAB_SUPPORTS_SYSFS
sysfs_slab_unlink(s);
#endif
list_add_tail(&s->list, &slab_caches_to_rcu_destroy);
schedule_work(&slab_caches_to_rcu_destroy_work);
} else {
kfence_shutdown_cache(s);//kfence相关
#ifdef SLAB_SUPPORTS_SYSFS//不开启
sysfs_slab_unlink(s);
sysfs_slab_release(s);
#else
slab_kmem_cache_release(s);//[3] 释放node、cpu_slab等结构体
#endif
}
return 0;
}
[1] 先调用__kmem_cache_shutdown
函数对该slab管理的各个slab page进行处理,包括cpu_slab
下面的、node下面的。主要对page
进行释放操作。
[2] 然后要把slab 管理结构kmem_cache
从链表中删除。
[3] 最后把kmem_cache
结构中的cpu_slab
和node 等结构体的内存释放,并释放自己。
__kmem_cache_shutdown
函数负责释放slab 管理结构下面的所有slab page (cpu_slab
中的和node中的)
linux\mm\slub.c : __kmem_cache_shutdown
int __kmem_cache_shutdown(struct kmem_cache *s)
{
int node;
struct kmem_cache_node *n;
flush_all(s);//[1] 将所有cpu_slab下架
/* Attempt to free all objects */
for_each_kmem_cache_node(s, node, n) {//[2] 释放所有node的partial
free_partial(s, n);
if (n->nr_partial || slabs_node(s, node))
return 1;
}
return 0;
}
[1] 先调用flush_all
刷新所有cpu,其实刷新就是将cpu_slab
现有的slab page下架放入node对应状态的list中,然后cpu_slab->page
和freelist
指针都置空。
[2] 然后再调用for_each_kmem_cache_node
释放node里的slab。
linux\mm\slub.c : flush_all
static void flush_all(struct kmem_cache *s)
{
on_each_cpu_cond(has_cpu_slab, flush_cpu_slab, s, 1);
}
其实直接对每个cpu,如果存在cpu_slab
,则调用flush_cpu_slab
:
linux\mm\slub.c : flush_cpu_slab、__flush_cpu_slab
static void flush_cpu_slab(void *d)
{
struct kmem_cache *s = d;
__flush_cpu_slab(s, smp_processor_id());
}
static inline void __flush_cpu_slab(struct kmem_cache *s, int cpu)
{
struct kmem_cache_cpu *c = per_cpu_ptr(s->cpu_slab, cpu);
if (c->page)
flush_slab(s, c); //最后掉用的是flush_slab函数
unfreeze_partials(s, c);//将cpu_slab->partial 列表里的slab 加入node->partial中
}
对每一个cpu_slab
,如果page
不为空就会调用flush_slab
刷新该cpu_slab
,将该slab page强制下架并将cpu_slab->freelist
、cpu_slab->page
都置空。然后再将每个cpu_slab->partial
中的slab page都移动到node->partial
中。
linux\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
在之前分析过,主要是调用deactivate_slab
对cpu_slab
当前的slab page进行强制下架,将其根据状态放入对应的node的列表中或释放,然后再将cpu_slab->freelist
、cpu_slab->page
都置空。
unfreeze_partials
函数解冻所有cpu_slab->partial
中的slab page,然后将他们加入到node->partial
list中。
linux\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中第一个slab page,也就是遍历
struct page new;
struct page old;
slub_set_percpu_partial(c, page);//[1] cpu_slab->partial向后移动一个
n2 = get_node(s, page_to_nid(page));//获取node
if (n != n2) {
if (n)
spin_unlock(&n->list_lock);
n = n2;
spin_lock(&n->list_lock);
}
do {//[2] 循环直到成功,尝试更新slab page 的冻结状态
old.freelist = page->freelist;
old.counters = page->counters;
VM_BUG_ON(!old.frozen);
new.counters = old.counters;
new.freelist = old.freelist;
new.frozen = 0;
} while (!__cmpxchg_double_slab(s, page,//[2] 原子结构,就是更新page状态,主要是frozen=0解冻
old.freelist, old.counters,
new.freelist, new.counters,
"unfreezing slab"));
//[3] 如果该slab为空,则加入到discard_page列表,后面会直接释放该slab
if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {
page->next = discard_page;
discard_page = page;
} else {//[4] 否则加入到node->partial中
add_partial(n, page, DEACTIVATE_TO_TAIL);
stat(s, FREE_ADD_PARTIAL);
}
}
if (n)
spin_unlock(&n->list_lock);
while (discard_page) {//[3.1] discard_page列表中的都是空slab,调用discard_slab释放掉。
page = discard_page;
discard_page = discard_page->next;
stat(s, DEACTIVATE_EMPTY);
discard_slab(s, page);//[3.1] 释放slab page
stat(s, FREE_SLAB);
}
#endif /* CONFIG_SLUB_CPU_PARTIAL */
}
[1] 遍历操作cpu_slab->partial
中所有的page
,要对每个page
都进行解冻操作,并且空的slab要在这里直接释放掉。
[2] 循环尝试原子操作,该原子操作就是同事操作两个值,但这里主要是尝试更新frozen
。对page
进行解冻
[3] 如果inuse=1
说明该slab为空,则加入到discard_page
列表里,准备释放。
[3.1] 这里会将discard_page
列表里所有的空page
都调用discard_slab
释放掉,discard_slab
后面分析。
[4] 非空的加入到node->partial
列表中。
for_each_kmem_cache_node
主要是处理node中的slab page,刚已经把cpu_slab
的page
和partial
中的slab都释放或者加入到了node->partial
中,在这里进行集中处理:
linux\mm\slub.c : free_partial
static void free_partial(struct kmem_cache *s, struct kmem_cache_node *n)
{
LIST_HEAD(discard);//[1] discard用来存放准备释放的slab page
struct page *page, *h;
BUG_ON(irqs_disabled());
spin_lock_irq(&n->list_lock);
list_for_each_entry_safe(page, h, &n->partial, slab_list) {
if (!page->inuse) {//[1] 没有正在使用的内存块了则可以释放
remove_partial(n, page);//从partial 中移除
list_add(&page->slab_list, &discard);//加入discard队列准备删除
} else {
list_slab_objects(s, page,//如果还有正在使用的,报个错。
"Objects remaining in %s on __kmem_cache_shutdown()");
}
}
spin_unlock_irq(&n->list_lock);
list_for_each_entry_safe(page, h, &discard, slab_list)
discard_slab(s, page);//[1] 调用discard_slab销毁page
}
[1] 该函数认为node->partial
中的slab page到这时已经大概率都为空,所以遍历node->partial
中的slab page,如果为空,则加入discard
列表在末尾统一调用discard_slab
释放掉。如果还有没空的slab page,那就报错。
这一部分在之前章节kfree
中分析过
discard_slab
用于释放一个slab page,在前面的函数中涉及到slab page的释放都有调用:
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);//更新统计信息
free_slab(s, page);//free_slab 释放slab
}
discard_slab
中先调用dec_slabs_node
进行一些统计,更新一下slab page被释放后node中的slab page数量和object
数量。然后调用free_slab
进行正式的slab 释放:
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,不太关心,默认不开启。正常流程调用__free_slab
释放slab:
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
}
逻辑很简单,在__free_slab
中进行一些常规的检查和统计更新,最后调用__free_pages
将页面释放。
slab_kmem_cache_release
分别释放cpu_slab
结构体和kmem_cache_node
结构体、 kmem_cache->name
字符串和kmem_cache
自己:
linux\mm\slab_common.c : slab_kmem_cache_release
void slab_kmem_cache_release(struct kmem_cache *s)//释放管理结构
{
__kmem_cache_release(s);//释放cpu_slab 和node
kfree_const(s->name);//释放name,name不是rodata只读
kmem_cache_free(kmem_cache, s);//释放掉slab管理结构s
}
void __kmem_cache_release(struct kmem_cache *s)
{
cache_random_seq_destroy(s);//[1] freelist random相关
free_percpu(s->cpu_slab);//[2]释放所有cpu_slab
free_kmem_cache_nodes(s);//[3]释放每个node
}
[1] 开启CONFIG_SLAB_FREELIST_RANDOM
宏则释放掉cachep->random_seq
,因为这个也是kamlloc
申请的内存:
void cache_random_seq_destroy(struct kmem_cache *cachep)
{
kfree(cachep->random_seq);
cachep->random_seq = NULL;
}
[2] 调用free_percpu
在cpu缓存中释放掉cpu_slab
。
[3] 释放kmem_cache_node
结构体,将自己的node指针置空之后直接调用kmem_cache_free
释放掉
linux\mm\slub.c : free_kmem_cache_nodes
static void free_kmem_cache_nodes(struct kmem_cache *s)//释放每个node
{
int node;
struct kmem_cache_node *n;
for_each_kmem_cache_node(s, node, n) {
s->node[node] = NULL;
kmem_cache_free(kmem_cache_node, n); //释放每个node
}
}
其实就是调用正常free逻辑中的slab_free
函数释放内存,slab_free
在kfree
那一章节分析过了,这里不继续分析了node和kmem_cache
都是在这里释放(因为他们也是普通kmalloc
分配的):
linux\mm\slub.c : kmem_cache_free
void kmem_cache_free(struct kmem_cache *s, void *x)
{
s = cache_from_obj(s, x);
if (!s)
return;
slab_free(s, virt_to_head_page(x), x, NULL, 1, _RET_IP_);//调用slab_free正常free掉x
trace_kmem_cache_free(_RET_IP_, x, s->name);
}
整体逻辑比较简单,先释放所有slab page,然后释放slab的各种结构体,如果slab 中还有没被释放的堆块,则说明还繁忙,则无法销毁:
page
链表中的page
处理一下
cpu_slab
中的page
下架cpu_slab
中的partial
page放到node中的partial
列表中node->partial
中所有slab page
page
调用discard_slab
释放掉slab_caches
全局列表中删除kmem_cache_cpu
和kmem_cache_node
结构体name
kmem_cache
结构体