Slub allocator 分析

本文的内核源码分析基于linux-6.8.7版本

Overview

Linux 中内核经常会有需要为各种对象分配内存的场景,但直接用 page allocator 分配的话有浪费和内存碎片的问题,于是出现了 slab allocator 来应对小内存对象的分配需求。slab allocator 基于 cache 的思想。对于内核的每个对象有其对应的 slab cache ,首先 slab allocator 会向 buddy system 申请一个或多个连续页(看作一个复合页)作为 slab ,再将 slab 划分成若干个同样大小的小内存块,一个 slab cache 就是若干个同种 slab 的集合,此后当内核需要分配对象或者释放时,就可以直接从相应的 slab cache 的 slab 中取放 object。

关于 slab 对象池的三种实现:slab,slub,slob。

  • slab:臃肿复杂
  • slub:简化高效,是默认且使用最广泛的版本
  • slob:为嵌入式小型机器小内存的场景设计

本文仅讨论 slub allocator,下文提到的 slab 均指 slub 实现。

object & slab layout

如果配置了CONFIG_SLUB_DEBUG_ON又配置了CONFIG_KASAN的话,slab 中每个 object 的布局就是这样:

但是一般日常生活中我们不会开以上配置,那么单个 object 的布局如下:

slab 里的所有空闲 object 组织成链表 freelist,free pointer(FP)指向下一个空闲 object 。FP 是内嵌在 object 里的,偏移是kmem_cache->offset。每个 object 需要加 padding 对齐到 word size 。

slab 的布局如下:

正常初始化时 freelist 里 object 是顺序连接,但如果配置了CONFIG_SLAB_FREELIST_RANDOM,就会随机化 freelist 里 object 的连接顺序

kmem_cache

不同内核对象有相应的 slab cache,每个 slab cache 都由 kmem_cache 这个对象来表示,该对象包含管理 slab cache 的所有信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct kmem_cache {
#ifndef CONFIG_SLUB_TINY
struct kmem_cache_cpu __percpu *cpu_slab; // 会指向当前 cpu 的 kmem_cache_cpu 对象
#endif
/* Used for retrieving partial slabs, etc. */
slab_flags_t flags; // slab 的一些属性
unsigned long min_partial; // kmem_cache_node->partial 上 slab 数量的一个阈值,小于这个值时 empty slab 也可以放在 kmem_cache_node->partial 里,大于这个值时就要把 empty slab 释放到 buddy system 里
unsigned int size; // object 在内存中的实际大小,除数据部分外还包括 metadata,red zone 和 padding
unsigned int object_size; // object 的数据部分的大小
struct reciprocal_value reciprocal_size;
unsigned int offset; // object 的 FP 在 object 中的偏移
#ifdef CONFIG_SLUB_CPU_PARTIAL
/* Number of per cpu partial objects to keep around */
unsigned int cpu_partial;
/* Number of per cpu partial slabs to keep around */
unsigned int cpu_partial_slabs; // kmem_cache_cpu->partial 上的 slab 的最大数目,如果超过了这个数就要把整条 partial 都移到对应 node 的 kmem_cache_node->partial 上
#endif
struct kmem_cache_order_objects oo; // 低 16 位表示一个 slab 中 object 数,高 16 位为一个 slab 的 order,即这个 slab 的页数为 1 << order

/* Allocation and freeing of slabs */
struct kmem_cache_order_objects min; // 当按照 oo 的尺寸为 slab 申请内存时,如果内存紧张,会采用 min 的尺寸为 slab 申请内存
gfp_t allocflags; // 向 buddy system 申请页时使用的 gfpflags
int refcount; // 这个 kmem_cache 对象本身的引用计数
void (*ctor)(void *object); // 在 slab 初始化时调用,用于初始化其中的 object
unsigned int inuse; // object_size 按 word size 对齐后的长度
unsigned int align; // object 按照指定 align 对齐
unsigned int red_left_pad; // 配置了 CONFIG_SLUB_DEBUG 的话 object 左右都会加上 red zone ,这是左边 red zone 的长度
const char *name; // 该 slab cache 的名称
struct list_head list; // 所有 kmem_cache 会组织成一个双向链表
------
#ifdef CONFIG_SLAB_FREELIST_HARDENED
unsigned long random; // 开了 CONFIG_SLAB_FREELIST_HARDENED 的话会用这个值对 object 的 freepointer 加密
#endif
------
#ifdef CONFIG_SLAB_FREELIST_RANDOM
unsigned int *random_seq; // CONFIG_SLAB_FREELIST_RANDOM 机制相关,指向一个 map ,这个 map 用于在初始化 slab 时打乱 object 的情况下,将原 object 下标映射到打乱后的 object 在 slab 内偏移
#endif
------
#ifdef CONFIG_HARDENED_USERCOPY // 开了 CONFIG_HARDENED_USERCOPY 后,如果某个内核对象里的内容会 copy_from_user 或者 copy_to_user 的话,那么内核在为这个对象创建 kmem_cache 时可以限定一个内核的地址范围, 以后调用 copy_from_user() 或 copy_to_user() 时会检查有没有超出这个范围
unsigned int useroffset;
unsigned int usersize;
#endif

struct kmem_cache_node *node[MAX_NUMNODES]; // 指针数组,指向不同 node 对应的 kmem_cache_node 对象
};

内核各处都会用kmem_cache_create()首先为内核对象创建kmem_cache对象,管理其 slab cache ,举个例子cred对象的kmem_cache是这样创建的:

1
2
3
4
5
6
7
// >>> linux-6.8.7/kernel/cred.c:606
/* 606 */ void __init cred_init(void)
/* 607 */ {
/* 608 */ /* allocate a slab in which we can store credentials */
/* 609 */ cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred), 0,
/* 610 */ SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_ACCOUNT, NULL);
/* 611 */ }

然后就可以用kmem_cache_alloc()从这个 slab cache 中申请 object 了:

1
2
3
4
5
6
7
8
// >>> linux-6.8.7/kernel/cred.c:206
/* 206 */ struct cred *prepare_creds(void)
/* 207 */ {
/* 208 */ struct task_struct *task = current;
/* 209 */ const struct cred *old;
/* 210 */ struct cred *new;
/* 211 */
/* 212 */ new = kmem_cache_alloc(cred_jar, GFP_KERNEL);

struct slab

就像 struct page 对象管理一张 page 一样,一张 slab 由 struct slab 对象管理,因为 slab 本质是单页或复合页,所以 struct slab 复用 struct folio 以及 struct page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct slab {
unsigned long __page_flags; // slab 所处页的相关属性

struct kmem_cache *slab_cache; // 指向 slab 所属的 slab cache 的 kmem_cache 对象
union {
struct {
union {
struct list_head slab_list; // slab 在 kmem_cache_node 上以双向链表组织(partial list 或是 full list)
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct {
struct slab *next; // slab 在 kmem_cache_cpu->partial 上以单向链表组织
int slabs; // slab 在 kmem_cache_cpu->partial 上时后面 slab 的数量(包括自己)
};
#endif
};
/* Double-word boundary */
union {
struct {
void *freelist; // 指向 slab 内第一个空闲 object
union {
unsigned long counters; // 注意对 counters 赋值操作就是同时对 inuse objects frozen 赋值
struct {
unsigned inuse:16; // slab 中已分配的 object 数量
unsigned objects:15; // slab 中 object 总数
unsigned frozen:1; // slab 是否是某个 cpu 的 kmem_cache_cpu->slab
};
};
};
#ifdef system_has_freelist_aba
freelist_aba_t freelist_counter; // 用于 cmpxchg128 时同时操作 freelist 和 counters
#endif
};
};
struct rcu_head rcu_head;
};
unsigned int __unused;

atomic_t __page_refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
};

slab cache layout

slab cache 的这些 slabs 是如何组织的?接下来看 slab cache 的布局:

slab cache 在每个 cpu 上有个本地缓存,这个本地缓存用 kmem_cache_cpu 对象表示,可以通过 kmem_cache 的 percpu 变量成员 cpu_slab 找到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct kmem_cache_cpu {
union {
struct {
void **freelist; // 指向 kmem_cache_cpu->slab 上的空闲 object
unsigned long tid; // 全局标识
};
freelist_aba_t freelist_tid;
};
struct slab *slab; // 指向当前缓存的 slab
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct slab *partial; // 备用 slab 列表
#endif
local_lock_t lock; /* Protects the fields above */
#ifdef CONFIG_SLUB_STATS
unsigned int stat[NR_SLUB_STAT_ITEMS];
#endif
};

kmem_cache_cpu 缓存了一张 slab 和一个备用 slab 列表(需要配置CONFIG_SLUB_CPU_PARTIAL)。

从 slab cache 的布局图中可以看出kmem_cache_cpu->freelistkmem_cache_cpu->slab->freelist都指向kmem_cache_cpu->slab上的空闲 object,但是它们是两个不同的 freelist,前者是无锁 freelist ,后者是常规 freelist ,在对象分配一章会进一步讨论。

当进程需要分配对象时,首先找到当前 cpu 的kmem_cache_cpu对象

  • 一开始尝试到kmem_cache_cpu->freelist取 object ,这个过程无锁且不用禁用中断和抢占
  • 如果kmem_cache_cpu->freelist没有空闲 object ,会到kmem_cache_cpu->slab->freelist取 object
  • 如果以上两步都失败,说明本地缓存的 slab 没有空闲 object 了,就将kmem_cache_cpu->partial上第一张 slab 提升为kmem_cache_cpu->slab,将其 freelist 移交给kmem_cache_cpu->freelistslab->freelist置 0 )

如果 slab cache 的 per-cpu 本地缓存没有空闲 object 了,就要从本地 node 的后备内存池补充一些 slab,这个 per-node 的内存池由kmem_cache_node对象管理

1
2
3
4
5
6
7
8
9
10
struct kmem_cache_node {
spinlock_t list_lock;
unsigned long nr_partial; // partial list 上的 slab 数
struct list_head partial; // partial list
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs;
atomic_long_t total_objects;
struct list_head full;
#endif
};

kmem_cache_cpu一样有一个 partial list 缓存了一些 slab,当尝试从 cpu 本地缓存分配失败时就会从本地 node 的kmem_cache_node->partial上取一张 slab 提升为kmem_cache_cpu->slab并且移一些 slab 到kmem_cache_cpu->partial

除非配置了CONFIG_SLUB_DEBUG否则没必要去维护一个 full list ,因为当一个 full slab 中的 object 被释放时,我们可以由 object 地址找到这个 slab 并且得知这个 slab 由 full 变成 partial,就可以将这个 slab 放入正确的 partial list 中

虽然 kmem_cache_cpukmem_cache_node的 slab 池都叫 “ partial list ” 但其实上面的 slab 可以是半空的也可以是全空的

Allocate an object

上层 API

内核会为核心对象创建专属 slab cache ,例如:

每个不同的 slab cache 分别由kmem_cache对象管理,当内核需要分配对象时就会从其专属的 slab cache 中取

除了专属 slab cache 以外,内核还准备了尺寸从 8 到 8K 字节的通用 slab cache:

这是因为内核还经常有分配通用内存块的需求,就会从合适尺寸的通用 slab cache 中取

kmem_cache_alloc() 系列

从专属 slab cache 中分配的 API 是kmem_cache_alloc()

参数s指定从哪个 slab cache 中分配,参数gfpflags是内存分配标志位(具体见

1
2
3
4
5
6
7
8
9
10
// >>> linux-6.8.7/mm/slub.c:3865
/* 3865 */ void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)
/* 3866 */ {
/* 3867 */ void *ret = slab_alloc_node(s, NULL, gfpflags, NUMA_NO_NODE, _RET_IP_,
/* 3868 */ s->object_size);
/* 3869 */
/* 3870 */ trace_kmem_cache_alloc(_RET_IP_, ret, s, gfpflags, NUMA_NO_NODE);
/* 3871 */
/* 3872 */ return ret;
/* 3873 */ }

如果还要指定从哪个 node 上分配,就用kmem_cache_alloc_node()

kmalloc() 系列

kmalloc()系列函数用来从通用 slab cache 中分配内存,这块内存可以用于存储任何信息。

先看kmalloc()

参数size指定分配的大小,参数gfpflags是内存分配标志位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// >>> linux-6.8.7/include/linux/slab.h:581
/* 581 */ static __always_inline __alloc_size(1) void *kmalloc(size_t size, gfp_t flags)
/* 582 */ {
/* 583 */ if (__builtin_constant_p(size) && size) {
/* 584 */ unsigned int index;
/* 585 */
/* 586 */ if (size > KMALLOC_MAX_CACHE_SIZE)
/* 587 */ return kmalloc_large(size, flags);
/* 588 */
/* 589 */ index = kmalloc_index(size);
/* 590 */ return kmalloc_trace(
/* 591 */ kmalloc_caches[kmalloc_type(flags, _RET_IP_)][index],
/* 592 */ flags, size);
/* 593 */ }
/* 594 */ return __kmalloc(size, flags);
/* 595 */ }
1
2
3
4
5
// >>> linux-6.8.7/mm/slub.c:3992
/* 3992 */ void *__kmalloc(size_t size, gfp_t flags)
/* 3993 */ {
/* 3994 */ return __do_kmalloc_node(size, flags, NUMA_NO_NODE, _RET_IP_);
/* 3995 */ }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// >>> linux-6.8.7/mm/slub.c:3961
/* 3961 */ static __always_inline
/* 3962 */ void *__do_kmalloc_node(size_t size, gfp_t flags, int node,
/* 3963 */ unsigned long caller)
/* 3964 */ {
/* 3965 */ struct kmem_cache *s;
/* 3966 */ void *ret;
/* 3967 */
/* 3968 */ if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) {
/* 3969 */ ret = __kmalloc_large_node(size, flags, node);
------
/* 3972 */ return ret;
/* 3973 */ }
------
/* 3978 */ s = kmalloc_slab(size, flags, caller);
/* 3979 */
/* 3980 */ ret = slab_alloc_node(s, NULL, flags, node, caller, size);
------
/* 3983 */ return ret;
/* 3984 */ }
1
2
3
4
5
6
7
8
// >>> linux-6.8.7/mm/slub.c:4005
/* 4005 */ void *kmalloc_trace(struct kmem_cache *s, gfp_t gfpflags, size_t size)
/* 4006 */ {
/* 4007 */ void *ret = slab_alloc_node(s, NULL, gfpflags, NUMA_NO_NODE,
/* 4008 */ _RET_IP_, size);
------
/* 4013 */ return ret;
/* 4014 */ }

kmalloc()kmem_cache_alloc()一样最后都会调用到slab_alloc_node()使用底层 slab allocator ,但在那之前kmalloc()的首要任务是通过参数sizegfpflags来确定到底应该从哪个 slab cache 中分配

kmalloc()的内存池有以下 slab cache:

kmalloc()根据gfpflags判断 slab cache 类型:(如果同时指定了多个 flag 就按以下优先级顺序)

  • dma-kmalloc-* :内存来自ZONE_DMA区域,指定 flag 为GFP_DMA
  • kmalloc-rcl-* :可以被回收的内存,指定 flag 为__GFP_RECLAIMABLE
  • kmalloc-cg-* :需要统计( memcg 相关)的内存,指定 flag 为GFP_KERNEL_ACCOUNT
  • kmalloc-* :通用类型,如果没有指定以上 flag 就用这个

再根据size计算找到合适尺寸的 slab cache 的kmem_cache对象,然后调用slab_alloc_node()

但如果size大于 8K 字节的话就不会走 slab allocator 而是会调用__kmalloc_large_node()直接用 page allocator 向 buddy system 请求连续的页
kmalloc()里根据size是否是编译时常量选择了两种计算kmem_cache的方式,其中一个方式是编译时优化的,所以看起来绕了个弯


kmalloc()系列还有:

  • kzalloc():和kmalloc()一样但会将内存初始化为 0
  • kmalloc_array():用kmalloc()分配大小为n * size的内存
  • kcalloc():和kmalloc_array()一样但会将内存初始化为 0
  • *_node():指定从哪个 node 上分配,比如kmalloc_node(),以上那些没指定 node 的 API 都默认从最近的 node 上分配,如果分配失败的话就会到其他 node 上尝试,但是如果一开始指定了 node 的话就不会到其他 node 上尝试

slab allocator 层

slab_alloc_node()

以上的 API 都会调用到slab_alloc_node()即 slab allocator 的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// >>> linux-6.8.7/mm/slub.c:3835
/* 3835 */ static __fastpath_inline void *slab_alloc_node(struct kmem_cache *s, struct list_lru *lru,
/* 3836 */ gfp_t gfpflags, int node, unsigned long addr, size_t orig_size)
/* 3837 */ {
/* 3838 */ void *object;
/* 3839 */ struct obj_cgroup *objcg = NULL;
/* 3840 */ bool init = false;
/* 3841 */
/* 3842 */ s = slab_pre_alloc_hook(s, lru, &objcg, 1, gfpflags);
/* 3843 */ if (unlikely(!s))
/* 3844 */ return NULL;
/* 3845 */
/* 3846 */ object = kfence_alloc(s, orig_size, gfpflags);
/* 3847 */ if (unlikely(object))
/* 3848 */ goto out;
/* 3849 */
/* 3850 */ object = __slab_alloc_node(s, gfpflags, node, addr, orig_size);
/* 3851 */
/* 3852 */ maybe_wipe_obj_freeptr(s, object);
/* 3853 */ init = slab_want_init_on_alloc(gfpflags, s);
/* 3854 */
/* 3855 */ out:
------
/* 3860 */ slab_post_alloc_hook(s, objcg, gfpflags, 1, &object, init, orig_size);
/* 3861 */
/* 3862 */ return object;
/* 3863 */ }

slab_alloc_node()__slab_alloc_node()的 wrapper,还包含前后处理:

  • slab_pre_alloc_hook()内会处理 memcg 相关
  • 3846 - 3848 行用于 kfence 机制采样
  • maybe_wipe_obj_freeptr()里在一定条件下会把分配下来的 object 的 FP 置 0
  • 如果gfpflags还有__GFP_ZERO,则slab_post_alloc_hook()会把 object 初始化为 0 ,此外还有 KASan ,KMSan ,kmemleak 相关以及 memcg 的后处理

__slab_alloc_node():fastpath

__slab_alloc_node()首先尝试从当前 cpu 的本地缓存的无锁 freelist ,即kmem_cache_cpu->freelist上分配 object ,这个过程无锁且不禁用抢占和中断,是开销最小的路径,称为 fastpath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// >>> linux-6.8.7/mm/slub.c:3632
/* 3632 */ static __always_inline void *__slab_alloc_node(struct kmem_cache *s,
/* 3633 */ gfp_t gfpflags, int node, unsigned long addr, size_t orig_size)
/* 3634 */ {
/* 3635 */ struct kmem_cache_cpu *c;
/* 3636 */ struct slab *slab;
/* 3637 */ unsigned long tid;
/* 3638 */ void *object;
/* 3639 */
/* 3640 */ redo:
------
/* 3653 */ c = raw_cpu_ptr(s->cpu_slab);
/* 3654 */ tid = READ_ONCE(c->tid);
------
/* 3664 */ barrier();
------
/* 3673 */ object = c->freelist;
/* 3674 */ slab = c->slab;
/* 3675 */
/* 3676 */ if (!USE_LOCKLESS_FAST_PATH() ||
/* 3677 */ unlikely(!object || !slab || !node_match(slab, node))) {
/* 3678 */ object = __slab_alloc(s, gfpflags, node, addr, c, orig_size);
/* 3679 */ } else {
/* 3680 */ void *next_object = get_freepointer_safe(s, object);
------
/* 3696 */ if (unlikely(!__update_cpu_freelist_fast(s, object, next_object, tid))) {
/* 3697 */ note_cmpxchg_failure("slab_alloc", s, tid);
/* 3698 */ goto redo;
/* 3699 */ }
/* 3700 */ prefetch_freepointer(s, next_object);
/* 3701 */ stat(s, ALLOC_FASTPATH);
/* 3702 */ }
/* 3703 */
/* 3704 */ return object;
/* 3705 */ }

如果kmem_cache_cpu->freelist上有 object 就取出来返回
第 3696 行的__update_cpu_freelist_fast()cmpxchg128原子地做了以下事情:

  1. 将当前 cpu 的kmem_cache_cpu->tid与之前读取的tid比较
  2. 如果前后tid一致就更新kmem_cache_cpu->freelist = next_object以及kmem_cache_cpu->tid = tid + 1,否则说明发生过抢占:当前进程被调度到其他 cpu 上了,需要回到redo用当前 cpu 的本地缓存重新分配

___slab_alloc():slowpath

如果kmem_cache_cpu的 slab 或 freelist 为空或者分配的时候指定了 node 而 slab 不在这个 node 上,就会进入 slowpath,首先__slab_alloc()会禁抢占和中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// >>> linux-6.8.7/mm/slub.c:3611
/* 3611 */ static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
/* 3612 */ unsigned long addr, struct kmem_cache_cpu *c, unsigned int orig_size)
/* 3613 */ {
/* 3614 */ void *p;
/* 3615 */
/* 3616 */ #ifdef CONFIG_PREEMPT_COUNT
------
/* 3622 */ c = slub_get_cpu_ptr(s->cpu_slab);
/* 3623 */ #endif
/* 3624 */
/* 3625 */ p = ___slab_alloc(s, gfpflags, node, addr, c, orig_size);
/* 3626 */ #ifdef CONFIG_PREEMPT_COUNT
/* 3627 */ slub_put_cpu_ptr(s->cpu_slab);
/* 3628 */ #endif
/* 3629 */ return p;
/* 3630 */ }

然后进入___slab_alloc()

1
2
3
4
5
6
7
8
// >>> linux-6.8.7/mm/slub.c:3388
/* 3388 */ static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
/* 3389 */ unsigned long addr, struct kmem_cache_cpu *c, unsigned int orig_size)
/* 3390 */ {
/* 3391 */ void *freelist;
/* 3392 */ struct slab *slab;
/* 3393 */ unsigned long flags;
/* 3394 */ struct partial_context pc;

首先在reread_slab处读kmem_cache_cpu->slab,然后:

  • 如果kmem_cache_cpu没有 slab ,需要跳转到new_slab处缓存一张新的 slab
  • 如果该 slab 的 node 不是指定的 node 或者该 slab 属于pfmemalloc池而gfpflags表明不允许从该区域分配内存,则需要跳转到deactivate_slab处把这张 slab 从kmem_cache_cpu上取下来
  • 检查一下前面读取的 slab 是否还是kmem_cache_cpu->slab,如果不是说明发生过抢占并且kmem_cache_cpu->slab被修改了,需要回到reread_slab重来
  • 先看kmem_cache_cpu->freelist,如果不为空那么跳转到load_freelist处把 object 取出
  • 再看kmem_cache_cpu->slab->freelist,如果不为空就移交给kmem_cache_cpu->freelist,然后往下走到load_freelist处把 object 取出,否则说明kmem_cache_cpu->slab没空闲 object 了,解冻(置 slab->frozen 为 0 ,意味着这张 slab 被移出本地缓存kmem_cache_cpu了)并到new_slab处缓存一张新的 slab

    kmem_cache_cpu上有两个 freelist ,但它们在同一个 slab 也就是kmem_cache_cpu->slab上。区别在于kmem_cache_cpu->freelist是无锁 freelist,也就是用于 fastpath 分配的 freelist。当一个 slab 成为kmem_cache_cpu->slab时,其 freelist 会被移交给kmem_cache_cpu->freelist,而原 freelist 会被置 0 ,此后当这张 slab 上的 object 被其他 cpu 释放时会进入slab->freelist,于是就形成了两个 freelist。slowpath 中如果kmem_cache_cpu->freelist为空但kmem_cache_cpu->slab->freelist不为空,还要将后者移交给前者。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // >>> linux-6.8.7/mm/slub.c:3398
    /* 3398 */ reread_slab:
    /* 3399 */
    /* 3400 */ slab = READ_ONCE(c->slab);
    /* 3401 */ if (!slab) {
    ------
    /* 3406 */ if (unlikely(node != NUMA_NO_NODE &&
    /* 3407 */ !node_isset(node, slab_nodes)))
    /* 3408 */ node = NUMA_NO_NODE;
    /* 3409 */ goto new_slab;
    /* 3410 */ }
    /* 3411 */
    /* 3412 */ if (unlikely(!node_match(slab, node))) {
    ------
    /* 3417 */ if (!node_isset(node, slab_nodes)) {
    /* 3418 */ node = NUMA_NO_NODE;
    /* 3419 */ } else {
    /* 3420 */ stat(s, ALLOC_NODE_MISMATCH);
    /* 3421 */ goto deactivate_slab;
    /* 3422 */ }
    /* 3423 */ }
    ------
    /* 3430 */ if (unlikely(!pfmemalloc_match(slab, gfpflags)))
    /* 3431 */ goto deactivate_slab;
    /* 3432 */
    /* 3433 */ /* must check again c->slab in case we got preempted and it changed */
    /* 3434 */ local_lock_irqsave(&s->cpu_slab->lock, flags);
    /* 3435 */ if (unlikely(slab != c->slab)) {
    /* 3436 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3437 */ goto reread_slab;
    /* 3438 */ }
    /* 3439 */ freelist = c->freelist;
    /* 3440 */ if (freelist)
    /* 3441 */ goto load_freelist;
    /* 3442 */
    /* 3443 */ freelist = get_freelist(s, slab);
    /* 3444 */
    /* 3445 */ if (!freelist) {
    /* 3446 */ c->slab = NULL;
    /* 3447 */ c->tid = next_tid(c->tid);
    /* 3448 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3449 */ stat(s, DEACTIVATE_BYPASS);
    /* 3450 */ goto new_slab;
    /* 3451 */ }
    /* 3452 */
    /* 3453 */ stat(s, ALLOC_REFILL);
    /* 3454 */
    如果来到load_freelist,说明分配成功,返回空闲 object ,更新kmem_cache_cpu->freelist(指向下一个空闲 object )和kmem_cache_cpu->tid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // >>> linux-6.8.7/mm/slub.c:3455
    /* 3455 */ load_freelist:
    /* 3456 */
    /* 3457 */ lockdep_assert_held(this_cpu_ptr(&s->cpu_slab->lock));
    ------
    /* 3464 */ VM_BUG_ON(!c->slab->frozen);
    /* 3465 */ c->freelist = get_freepointer(s, freelist);
    /* 3466 */ c->tid = next_tid(c->tid);
    /* 3467 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3468 */ return freelist;
    deactivate_slab处说明需要解冻当前缓存的 slab ,即将kmem_cache_cpu->slabkmem_cache_cpu上移除。将kmem_cache_cpu->slabkmem_cache_cpu->freelist置 0 ,更新kmem_cache_cpu->tid,然后调用deactivate_slab()deactivate_slab()做了以下工作:
  1. kmem_cache_cpu->freelist移交给slab->freelist
  2. slab->frozen 为 0
  3. 将这个 slab 放入合适的 list

    关于上述第 3 点的进一步解释:根据 slab 的状态

    • 如果是 full slab 则不管
    • 如果是 partial slab 就放入其所在 node 的kmem_cache_node->partial
    • 如果是 empty slab 则要看kmem_cache_node->partial上已有的 slab 数是否大于阈值kmem_cache->min_partial,如果大于就销毁这个 slab ,即将其还给 buddy system,否则还是放入kmem_cache_node->partial
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // >>> linux-6.8.7/mm/slub.c:3470
    /* 3470 */ deactivate_slab:
    /* 3471 */
    /* 3472 */ local_lock_irqsave(&s->cpu_slab->lock, flags);
    /* 3473 */ if (slab != c->slab) {
    /* 3474 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3475 */ goto reread_slab;
    /* 3476 */ }
    /* 3477 */ freelist = c->freelist;
    /* 3478 */ c->slab = NULL;
    /* 3479 */ c->freelist = NULL;
    /* 3480 */ c->tid = next_tid(c->tid);
    /* 3481 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3482 */ deactivate_slab(s, slab, freelist);
    走到new_slab处说明kmem_cache_cpu需要缓存一张新的 slab,首先遍历kmem_cache_cpu->partial,从上面取一张 slab
  • 如果该 slab 的 node 不是指定的 node 或者该 slab 属于pfmemalloc池而gfpflags表明不允许从该区域分配内存,就用__put_partials()把这张 slab 放回其所属 node 的kmem_cache_node->partial上,同样如果这张 slab 为 empty 且kmem_cache_node->partial上 slab 数超过阈值就要销毁这张 slab
  • 否则就冻结这张 slab 并跳转到retry_load_slab处准备缓存这张 slab 作为kmem_cache_cpu->slab
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // >>> linux-6.8.7/mm/slub.c:3484
    /* 3484 */ new_slab:
    /* 3485 */
    /* 3486 */ #ifdef CONFIG_SLUB_CPU_PARTIAL
    /* 3487 */ while (slub_percpu_partial(c)) {
    /* 3488 */ local_lock_irqsave(&s->cpu_slab->lock, flags);
    /* 3489 */ if (unlikely(c->slab)) {
    /* 3490 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3491 */ goto reread_slab;
    /* 3492 */ }
    /* 3493 */ if (unlikely(!slub_percpu_partial(c))) {
    /* 3494 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3495 */ /* we were preempted and partial list got empty */
    /* 3496 */ goto new_objects;
    /* 3497 */ }
    /* 3498 */
    /* 3499 */ slab = slub_percpu_partial(c);
    /* 3500 */ slub_set_percpu_partial(c, slab);
    /* 3501 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3502 */ stat(s, CPU_PARTIAL_ALLOC);
    /* 3503 */
    /* 3504 */ if (unlikely(!node_match(slab, node) ||
    /* 3505 */ !pfmemalloc_match(slab, gfpflags))) {
    /* 3506 */ slab->next = NULL;
    /* 3507 */ __put_partials(s, slab);
    /* 3508 */ continue;
    /* 3509 */ }
    /* 3510 */
    /* 3511 */ freelist = freeze_slab(s, slab);
    /* 3512 */ goto retry_load_slab;
    /* 3513 */ }
    /* 3514 */ #endif
    如果kmem_cache_cpu->partial没有 slab ,则会走到new_objects
  • get_partial()会到别的 partial list 上取一些 slab 补充到kmem_cache_cpu->partial里:
    • 首先到本地 node 的kmem_cache_node->partial中取一张 slab ,然后将剩下的最多kmem_cache->cpu_partial_slabs / 2个 slab 放入kmem_cache_cpu->partial
    • 如果kmem_cache_node->partial也没有 slab ,分配的时候也没有指定 node ,就去别的 node 上取(从近到远)
  • 如果取到了 slab ,就冻结这张 slab 并跳转到retry_load_slab处准备缓存这张 slab 作为kmem_cache_cpu->slab
  • 否则 per-cpu 和 per-node 的 slab cache 都没有 slab 了,只能调用new_slab(),最后会用alloc_pages_node()向 buddy system 申请内存作为一张 slab ,如果还是失败只能返回 NULL,冻结这张 slab,然后向下走到retry_load_slab处准备缓存这张 slab 作为kmem_cache_cpu->slab
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    // >>> linux-6.8.7/mm/slub.c:3516
    /* 3516 */ new_objects:
    /* 3517 */
    /* 3518 */ pc.flags = gfpflags;
    /* 3519 */ pc.orig_size = orig_size;
    /* 3520 */ slab = get_partial(s, node, &pc);
    /* 3521 */ if (slab) {
    /* 3522 */ if (kmem_cache_debug(s)) {
    /* 3523 */ freelist = pc.object;
    ------
    /* 3529 */ if (s->flags & SLAB_STORE_USER)
    /* 3530 */ set_track(s, freelist, TRACK_ALLOC, addr);
    /* 3531 */
    /* 3532 */ return freelist;
    /* 3533 */ }
    /* 3534 */
    /* 3535 */ freelist = freeze_slab(s, slab);
    /* 3536 */ goto retry_load_slab;
    /* 3537 */ }
    /* 3538 */
    /* 3539 */ slub_put_cpu_ptr(s->cpu_slab);
    /* 3540 */ slab = new_slab(s, gfpflags, node);
    /* 3541 */ c = slub_get_cpu_ptr(s->cpu_slab);
    /* 3542 */
    /* 3543 */ if (unlikely(!slab)) {
    /* 3544 */ slab_out_of_memory(s, gfpflags, node);
    /* 3545 */ return NULL;
    /* 3546 */ }
    /* 3547 */
    /* 3548 */ stat(s, ALLOC_SLAB);
    /* 3549 */
    /* 3550 */ if (kmem_cache_debug(s)) {
    /* 3551 */ freelist = alloc_single_from_new_slab(s, slab, orig_size);
    /* 3552 */
    /* 3553 */ if (unlikely(!freelist))
    /* 3554 */ goto new_objects;
    /* 3555 */
    /* 3556 */ if (s->flags & SLAB_STORE_USER)
    /* 3557 */ set_track(s, freelist, TRACK_ALLOC, addr);
    /* 3558 */
    /* 3559 */ return freelist;
    /* 3560 */ }
    ------
    /* 3566 */ freelist = slab->freelist;
    /* 3567 */ slab->freelist = NULL;
    /* 3568 */ slab->inuse = slab->objects;
    /* 3569 */ slab->frozen = 1;
    /* 3570 */
    /* 3571 */ inc_slabs_node(s, slab_nid(slab), slab->objects);
    /* 3572 */
    /* 3573 */ if (unlikely(!pfmemalloc_match(slab, gfpflags))) {
    ------
    /* 3578 */ deactivate_slab(s, slab, get_freepointer(s, freelist));
    /* 3579 */ return freelist;
    /* 3580 */ }
    retry_load_slab处,如果kmem_cache_cpu上本来缓存了 slab ,则要先调用deactivate_slab()将旧 slab 从kmem_cache_cpu上移除,令新 slab 作为kmem_cache_cpu->slab并跳转到 load_freelist取空闲 object
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // >>> linux-6.8.7/mm/slub.c:3582
    /* 3582 */ retry_load_slab:
    /* 3583 */
    /* 3584 */ local_lock_irqsave(&s->cpu_slab->lock, flags);
    /* 3585 */ if (unlikely(c->slab)) {
    /* 3586 */ void *flush_freelist = c->freelist;
    /* 3587 */ struct slab *flush_slab = c->slab;
    /* 3588 */
    /* 3589 */ c->slab = NULL;
    /* 3590 */ c->freelist = NULL;
    /* 3591 */ c->tid = next_tid(c->tid);
    /* 3592 */
    /* 3593 */ local_unlock_irqrestore(&s->cpu_slab->lock, flags);
    /* 3594 */
    /* 3595 */ deactivate_slab(s, flush_slab, flush_freelist);
    /* 3596 */
    /* 3597 */ stat(s, CPUSLAB_FLUSH);
    /* 3598 */
    /* 3599 */ goto retry_load_slab;
    /* 3600 */ }
    /* 3601 */ c->slab = slab;
    /* 3602 */
    /* 3603 */ goto load_freelist;
    /* 3604 */ }

全流程

总结一下 slab allocator 中对象分配的全流程:

Free an object

上层 API

  • kfree()用于释放任意对象
  • kmem_cache_free()用于释放指定 slab cache 里的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// >>> linux-6.8.7/mm/slub.c:4389
/* 4389 */ void kfree(const void *object)
/* 4390 */ {
/* 4391 */ struct folio *folio;
/* 4392 */ struct slab *slab;
/* 4393 */ struct kmem_cache *s;
/* 4394 */ void *x = (void *)object;
------
/* 4398 */ if (unlikely(ZERO_OR_NULL_PTR(object)))
/* 4399 */ return;
/* 4400 */
/* 4401 */ folio = virt_to_folio(object);
/* 4402 */ if (unlikely(!folio_test_slab(folio))) {
/* 4403 */ free_large_kmalloc(folio, (void *)object);
/* 4404 */ return;
/* 4405 */ }
/* 4406 */
/* 4407 */ slab = folio_slab(folio);
/* 4408 */ s = slab->slab_cache;
/* 4409 */ slab_free(s, slab, x, _RET_IP_);
/* 4410 */ }

kree()里如果 slab 所在页没有设置PG_slab这个 flag ,说明当时分配的时候是直接用的 page allocator,需要调用free_large_kmalloc(),然后调用folio_put()交给 page allocator

1
2
3
4
5
6
// >>> linux-6.8.7/mm/slub.c:4367
/* 4367 */ static void free_large_kmalloc(struct folio *folio, void *object)
/* 4368 */ {
------
/* 4380 */ folio_put(folio);
/* 4381 */ }


kmem_cache_free()cache_from_obj()检查要释放的对象是不是真的来自指定的 kmem_cache,如果不是的话就直接返回

1
2
3
4
5
6
7
8
9
// >>> linux-6.8.7/mm/slub.c:4357
/* 4357 */ void kmem_cache_free(struct kmem_cache *s, void *x)
/* 4358 */ {
/* 4359 */ s = cache_from_obj(s, x);
/* 4360 */ if (!s)
/* 4361 */ return;
/* 4362 */ trace_kmem_cache_free(_RET_IP_, x, s);
/* 4363 */ slab_free(s, virt_to_slab(x), x, _RET_IP_);
/* 4364 */ }

slab allocator 层

kfree()kmem_cache_free()都会调用到slab_free()进而到do_slab_free()

这里也有一个 memcg 相关的处理

1
2
3
4
5
6
7
8
9
10
// >>> linux-6.8.7/mm/slub.c:4293
/* 4293 */ static __fastpath_inline
/* 4294 */ void slab_free(struct kmem_cache *s, struct slab *slab, void *object,
/* 4295 */ unsigned long addr)
/* 4296 */ {
/* 4297 */ memcg_slab_free_hook(s, slab, &object, 1);
/* 4298 */
/* 4299 */ if (likely(slab_free_hook(s, object, slab_want_init_on_free(s))))
/* 4301 */ do_slab_free(s, slab, object, object, 1, addr);
/* 4302 */ }


do_slab_free()中首先看 kmem_cache_cpu->slab,如果就是要释放的 object 所在 slab,直接将 object 放入kmem_cache_cpu->freelist就 ok(按 LIFO 顺序),否则就走 slowpath :__slab_free()

一般来说会走无锁的 fastpath ,但如果配置了CONFIG_PREEMPT_RT即实时内核,则需要加锁,然而这两条分支把 object 放回 slab 的逻辑都是一样的:使 kmem_cache_cpu->freelist 指向释放的 object ,使释放 object 的 FP 指向原来的 kmem_cache_cpu->freelist,更新kmem_cache_cpu->tid 。此外,还需要检测前后kmem_cache_cpu->slab是否一致,如果不一致说明发生过抢占就跳回redo重来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// >>> linux-6.8.7/mm/slub.c:4230
/* 4230 */ static __always_inline void do_slab_free(struct kmem_cache *s,
/* 4231 */ struct slab *slab, void *head, void *tail,
/* 4232 */ int cnt, unsigned long addr)
/* 4233 */ {
/* 4234 */ struct kmem_cache_cpu *c;
/* 4235 */ unsigned long tid;
/* 4236 */ void **freelist;
/* 4237 */
/* 4238 */ redo:
------
/* 4245 */ c = raw_cpu_ptr(s->cpu_slab);
/* 4246 */ tid = READ_ONCE(c->tid);
------
/* 4249 */ barrier();
/* 4250 */
/* 4251 */ if (unlikely(slab != c->slab)) {
/* 4252 */ __slab_free(s, slab, head, tail, cnt, addr);
/* 4253 */ return;
/* 4254 */ }
/* 4255 */
/* 4256 */ if (USE_LOCKLESS_FAST_PATH()) {
/* 4257 */ freelist = READ_ONCE(c->freelist);
/* 4258 */
/* 4259 */ set_freepointer(s, tail, freelist);
/* 4260 */
/* 4261 */ if (unlikely(!__update_cpu_freelist_fast(s, freelist, head, tid))) {
/* 4262 */ note_cmpxchg_failure("slab_free", s, tid);
/* 4263 */ goto redo;
/* 4264 */ }
/* 4265 */ } else {
/* 4266 */ /* Update the free list under the local lock */
/* 4267 */ local_lock(&s->cpu_slab->lock);
/* 4268 */ c = this_cpu_ptr(s->cpu_slab);
/* 4269 */ if (unlikely(slab != c->slab)) {
/* 4270 */ local_unlock(&s->cpu_slab->lock);
/* 4271 */ goto redo;
/* 4272 */ }
/* 4273 */ tid = c->tid;
/* 4274 */ freelist = c->freelist;
/* 4275 */
/* 4276 */ set_freepointer(s, tail, freelist);
/* 4277 */ c->freelist = head;
/* 4278 */ c->tid = next_tid(tid);
/* 4279 */
/* 4280 */ local_unlock(&s->cpu_slab->lock);
/* 4281 */ }
/* 4282 */ stat_add(s, FREE_FASTPATH, cnt);
/* 4283 */ }


如果释放的 object 不在kmem_cache_cpu->slab上,就要到__slab_free()里处理两件事:

  1. 将 object 放到合适的 freelist 中
  2. 如果 object 所在的 slab 的状态因 object 的释放而发生了改变(例如由 full 变为 partial),可能需要将这张 slab 移到合适的 list 上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // >>> linux-6.8.7/mm/slub.c:4098
    /* 4098 */ static void __slab_free(struct kmem_cache *s, struct slab *slab,
    /* 4099 */ void *head, void *tail, int cnt,
    /* 4100 */ unsigned long addr)
    /* 4101 */
    /* 4102 */ {
    /* 4103 */ void *prior;
    /* 4104 */ int was_frozen;
    /* 4105 */ struct slab new;
    /* 4106 */ unsigned long counters;
    /* 4107 */ struct kmem_cache_node *n = NULL;
    /* 4108 */ unsigned long flags;
    /* 4109 */ bool on_node_partial;

首先先把 object 放回 slab 里,用 do-while 循环保证slab_update_freelist()成功更新 slab 的 freelist 和 counter (可能由于并发问题更新失败)

与第4131行分支相关的几个条件:

  • new.inuse -= cnt!new.inuse的话说明这张 slab 由于 object 的释放将会变成 empty slab
  • !priorslab->freelist == null,说明这张 slab 是 full slab ,由于 object 的释放将会变成 partial slab
  • !was_frozen说明这张 slab 不是kmem_cache_cpu->slab
  • kmem_cache_has_cpu_partial()判断有没有配置CONFIG_SLUB_CPU_PARTIAL,即kmem_cache_cpu有没有 partial list

要进入分支,首先 slab 不能是 kmem_cache_cpu->slab,有以下两种可能:

  1. kmem_cache_cpu没有 partial list ,slab 原来是 full 或者即将变为 empty
  2. kmem_cache_cpu有 partial list ,slab 原来是 partial 即将变为 empty

进入分支说明可能需要移动这张 slab 到kmem_cache_node->partial上或者从kmem_cache_node->partial上移出来,不管怎样先获取 slab 所在 node 和 list 锁以及判断 slab 是不是在该 node 的kmem_cache_node->partial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// >>> linux-6.8.7/mm/slub.c:4118
/* 4118 */ do {
/* 4119 */ if (unlikely(n)) {
/* 4120 */ spin_unlock_irqrestore(&n->list_lock, flags);
/* 4121 */ n = NULL;
/* 4122 */ }
/* 4123 */ prior = slab->freelist;
/* 4124 */ counters = slab->counters;
/* 4125 */ set_freepointer(s, tail, prior);
/* 4126 */ new.counters = counters;
/* 4127 */ was_frozen = new.frozen;
/* 4128 */ new.inuse -= cnt;
/* 4129 */ if ((!new.inuse || !prior) && !was_frozen) {
/* 4130 */ /* Needs to be taken off a list */
/* 4131 */ if (!kmem_cache_has_cpu_partial(s) || prior) {
/* 4132 */
/* 4133 */ n = get_node(s, slab_nid(slab));
------
/* 4142 */ spin_lock_irqsave(&n->list_lock, flags);
/* 4143 */
/* 4144 */ on_node_partial = slab_test_node_partial(slab);
/* 4145 */ }
/* 4146 */ }
/* 4147 */
/* 4148 */ } while (!slab_update_freelist(s, slab,
/* 4149 */ prior, counters,
/* 4150 */ head, new.counters,
/* 4151 */ "__slab_free"));

如果!n,对应没有进入第4131行的情况,说明 slab 无需移动到kmem_cache_node的 list 上或从中移出,而且:

  • 如果这张 slab 由 full slab 变为了 partial slab ,且kmem_cache_cpu有 partial list,则用put_cpu_partial()把其放入kmem_cache_cpu->partial,但如果此时kmem_cache_cpu->partial上 slab 的数量超过了阈值 kmem_cache->cpu_partial_slabs,就会将整条kmem_cache_cpu->partial都移到kmem_cache_node->partial
  • 否则 slab 无需移动,则直接返回
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // >>> linux-6.8.7/mm/slub.c:4153
    /* 4153 */ if (likely(!n)) {
    /* 4154 */
    /* 4155 */ if (likely(was_frozen)) {
    ------
    /* 4160 */ stat(s, FREE_FROZEN);
    /* 4161 */ } else if (kmem_cache_has_cpu_partial(s) && !prior) {
    ------
    /* 4166 */ put_cpu_partial(s, slab, 1);
    /* 4167 */ stat(s, CPU_PARTIAL_FREE);
    /* 4168 */ }
    /* 4169 */
    /* 4170 */ return;
    /* 4171 */ }
    这张 slab 原本不在 kmem_cache_node->partial 上(有可能在kmem_cache_cpu->partial上或者是 full slab),也不是由 full slab 变为 partial slab 的情况,则说明它在kmem_cache_cpu->partial上,无需移动这张 slab ,开锁后返回
    1
    2
    3
    4
    5
    // >>> linux-6.8.7/mm/slub.c:4177
    /* 4177 */ if (prior && !on_node_partial) {
    /* 4178 */ spin_unlock_irqrestore(&n->list_lock, flags);
    /* 4179 */ return;
    /* 4180 */ }
    如果这张在kmem_cache_node->partial上的 slab 变为 empty 且 kmem_cache_node->partial上 slab 的数量超过阈值kmem_cache->min_partial,就到slab_empty处销毁这张 slab
    1
    2
    3
    // >>> linux-6.8.7/mm/slub.c:4182
    /* 4182 */ if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
    /* 4183 */ goto slab_empty;
    如果kmem_cache_cpu没有 partial list(这种情况一般是配置了CONFIG_SLUB_DEBUG,于是也会启用kmem_cache_node的 full list)且 slab 由 full slab 变为 partial slab,就将这张 slab 从kmem_cache_node->full上移除,移到kmem_cache_node->partial
    1
    2
    3
    4
    5
    6
    7
    8
    // >>> linux-6.8.7/mm/slub.c:4189
    /* 4189 */ if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {
    /* 4190 */ remove_full(s, n, slab);
    /* 4191 */ add_partial(n, slab, DEACTIVATE_TO_TAIL);
    /* 4192 */ stat(s, FREE_ADD_PARTIAL);
    /* 4193 */ }
    /* 4194 */ spin_unlock_irqrestore(&n->list_lock, flags);
    /* 4195 */ return;
    slab_empty处理把 empty slab 还给 buddy system 的场景
    首先将 slab 从原所在的 list 上移除(kmem_cache_node->partialkmem_cache_node->full),然后调用discard_slab()最后调用__free_pages()释放回 buddy system
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // >>> linux-6.8.7/mm/slub.c:4197
    /* 4197 */ slab_empty:
    /* 4198 */ if (prior) {
    ------
    /* 4202 */ remove_partial(n, slab);
    /* 4203 */ stat(s, FREE_REMOVE_PARTIAL);
    /* 4204 */ } else {
    /* 4205 */ /* Slab must be on the full list */
    /* 4206 */ remove_full(s, n, slab);
    /* 4207 */ }
    /* 4208 */
    /* 4209 */ spin_unlock_irqrestore(&n->list_lock, flags);
    /* 4210 */ stat(s, FREE_SLAB);
    /* 4211 */ discard_slab(s, slab);
    /* 4212 */ }

全流程

总结一下 slab allocator 中对象释放的全流程:

Reference