AI智能摘要
梳理Linux内核启动早期内存管理的四个阶段,文章深入分析了slab分配器自举的“鸡生蛋”难题:分配器本身依赖尚未初始化的自身结构体。详细解读slab分配器如何通过静态变量及多层自举逻辑,巧妙解决这一循环依赖困境,进而实现对象级缓存的高效分配与初始化,为内核后续稳定运行打下坚实基础。
此摘要由AI分析文章内容生成,仅供参考。

当我们的分析来到了slab分配器时,我们先总结一下之前的内核启动早期的内存的流程:

第一个阶段:Fixmap 固定映射阶段

setup_arch() → early_fixmap_init()

  • 建立 fixmap 区域

    • 通过 fixmap 可以动态建立更多页表项

    • 此时内存管理: memblock(物理内存位图管理)

第二阶段:线性映射阶段

paging_init() → map_memory()

  • 将所有物理内存线性映射到内核虚拟地址空间

    • 映射关系: virt_addr = phys_addr + PAGE_OFFSET

    • 此后,内核就可以通过虚拟地址访问所有的物理内存

第三阶段:Buddy 分配器初始化

mem_init() → free_area_init()

  • 遍历 memblock 获取所有空闲物理内存信息

  • 建立 free_area[] 数据结构

  • 将可用页面加入 buddy 空闲链表

  • 输出: 页面级分配器 ready,可以 alloc_pages()/free_pages()

第四阶段:Slab 分配器初始化(自举)

kmem_cache_init

  • 静态变量: boot_kmem_cache, boot_kmem_cache_node

  • 用 buddy 分配页面给第一个 slab

  • 创建核心缓存:建立 kmem_cachekmem_cache_node 的对象缓存

  • 构建缓存体系:创建完整的 kmalloc 缓存族

  • 输出:对象级缓存分配器 ready,可以kmalloc

各层职责:

  • Slab:管理小对象缓存,快速分配/释放

  • Buddy:管理物理页面,按 2^order 分配连续页面

  • 线性映射:提供物理到虚拟地址的直接转换

  • Fixmap:保留用于特殊场景的动态映射

而本章我们要探索的就是slab分配器的自举!为什么说是自举呢?

什么是"自举"?

"自举"原意是 "拽着自己的鞋带把自己提起来"(pull oneself up by one's bootstraps),在计算机科学中指:

一个系统需要利用自己来初始化自己

这是一个经典的 "鸡生蛋,蛋生鸡" 问题。

Slab 分配器的自举困境

矛盾的核心:

// 我们需要分配内存来创建分配器
struct kmem_cache *cache = 分配内存(sizeof(struct kmem_cache));

// 但是分配内存需要分配器!
// kmalloc() 需要 slab,slab 需要 kmem_cache...

具体困境:

  1. 要分配 struct kmem_cache,需要内存分配器

  2. 要分配 struct kmem_cache_node,也需要内存分配器

  3. 但是 slab 分配器还没初始化,无法分配这些结构

  4. 没有这些结构,slab 分配器无法工作

slab分配器的自举逻辑

定义静态变量

void __init kmem_cache_init(void)
{
	// 定义静态数据段变量
	static __initdata struct kmem_cache boot_kmem_cache, boot_kmem_cache_node;
//...
}

第一阶段:创建静态启动缓存

伪装“slab”缓存对象

void __init kmem_cache_init(void)
{
    //...
	create_boot_cache(kmem_cache_node, "kmem_cache_node",
		sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN, 0, 0);
	create_boot_cache(kmem_cache, "kmem_cache",
			offsetof(struct kmem_cache, node) +
				nr_node_ids * sizeof(struct kmem_cache_node *),
		       SLAB_HWCACHE_ALIGN, 0, 0);
    //...
}

create_boot_cache的实现

void __init create_boot_cache(struct kmem_cache *s, const char *name,
		unsigned int size, slab_flags_t flags,
		unsigned int useroffset, unsigned int usersize)
{
	int err;
	unsigned int align = ARCH_KMALLOC_MINALIGN; //默认对齐是 128字节

	s->name = name;
	s->size = s->object_size = size;

	/*
	 * For power of two sizes, guarantee natural alignment for kmalloc
	 * caches, regardless of SL*B debugging options.
	 */
	// 计算 align,首先如果size 是2的整数幂,那么最小的align 为初始值128,
	// 如果不是2的整数幂,则通过calculate_alignment函数计算
	if (is_power_of_2(size))
		align = max(align, size);
	s->align = calculate_alignment(flags, align, size);

	s->useroffset = useroffset;
	s->usersize = usersize;

	// 创建slab 描述符的核心函数
	err = __kmem_cache_create(s, flags);

	if (err)
		panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n",
					name, size, err);

	// 初始化kmeme cache 引用计数为-1
	s->refcount = -1;	/* Exempt from merging for now */
}

__kmem_cache_create

int __kmem_cache_create(struct kmem_cache *s, slab_flags_t flags)
{
	int err;

	//创建剩余成员,例如 flags、random、size、object_size、oo、min、
	// max、min_partial、cpu_partial、random_seq、node、cpu_slab 等等;
	err = kmem_cache_open(s, flags);
	if (err)
		return err;

	/* Mutex is not taken during early boot */
	if (slab_state <= UP)
		return 0;
	//将slab 信息添加到 /sys/kernel/slab/kmem_cache_node和kmem_cache 
	err = sysfs_slab_add(s);
	if (err) {
		__kmem_cache_release(s);
		return err;
	}

	if (s->flags & SLAB_STORE_USER)
		debugfs_slab_add(s);

	return 0;
}

创建kmem_cache_node缓存

static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags)
{
	// 根据 slub_debug 确定最终的flags,如果CONFIG_SLUB_DEBUG没有使能,返回值就是flags
	s->flags = kmem_cache_flags(s->size, flags, s->name);
#ifdef CONFIG_SLAB_FREELIST_HARDENED
	s->random = get_random_long();
#endif

    // 根据object 大小,计算出最佳order值和这个slab缓存的object 数目,
    // 然后初始化kmem_cache 结构中的oo、min、max,计算异常返回0,正常为1
	if (!calculate_sizes(s, -1))
		goto error;
	// 开启slub debug后,disable_higher_order_debug 为1,否则为0
	if (disable_higher_order_debug) {
		/*
		 * Disable debugging flags that store metadata if the min slab
		 * order increased.
		 */
		if (get_order(s->size) > get_order(s->object_size)) {
			s->flags &= ~DEBUG_METADATA_FLAGS;
			s->offset = 0;
			if (!calculate_sizes(s, -1))
				goto error;
		}
	}

// 使能快速模式,后面slab缓存对象分配的时候用到
#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
    defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
	if (system_has_cmpxchg_double() && (s->flags & SLAB_NO_CMPXCHG) == 0)
		/* Enable fast mode */
		s->flags |= __CMPXCHG_DOUBLE;
#endif

    // 根据object size设定s->min_partial
	set_min_partial(s, ilog2(s->size) / 2);
	//设定s->cpu_partial
	set_cpu_partial(s);

#ifdef CONFIG_NUMA
	s->remote_node_defrag_ratio = 1000;
#endif

	/* Initialize the pre-computed randomized freelist if slab is up */
	if (slab_state >= UP) {
		if (init_cache_random_seq(s))
			goto error;
	}

    //初始化s->node,如成功则返回1
	if (!init_kmem_cache_nodes(s))
		goto error;
	//初始化s->cpu_slab,如成功则返回1,然后退出kmem_cache_open函数
	if (alloc_kmem_cache_cpus(s))
		return 0;

error:
	//如果初始化期间出现error,则释放描述符
	__kmem_cache_release(s);
	return -EINVAL;
}

继续调init_kmem_cache_nodes

static int init_kmem_cache_nodes(struct kmem_cache *s)
{
	int node;

	for_each_node_mask(node, slab_nodes) {
		struct kmem_cache_node *n;

		// 当kmem_cache_node还没有初始化完成,slab_state默认为DOWN,故第一次使用early_kmem_cache_node_alloc
		if (slab_state == DOWN) {
			early_kmem_cache_node_alloc(node);
			continue;
		}
		// 待kmem_cache_node初始化完成后,slab_state被设置为PARTIAL,因此后面的kmem_cache初始化或者创建新的slub描述符时走这里
		n = kmem_cache_alloc_node(kmem_cache_node,
						GFP_KERNEL, node);

		if (!n) {
			free_kmem_cache_nodes(s);
			return 0;
		}

		init_kmem_cache_node(n);
		s->node[node] = n;
	}
	return 1;
}

当kmem_cache_node还没有初始化完成,slab_state默认为DOWN,故第一次使用early_kmem_cache_node_alloc

static void early_kmem_cache_node_alloc(int node)
{
	struct page *page;
	struct kmem_cache_node *n;

	BUG_ON(kmem_cache_node->size < sizeof(struct kmem_cache_node));

    // 通过调用 allocate_slab()从buddy中申请order阶数的page,并将page按照objects的数目打乱object list顺序
    // 除此,通过new_slab()申请的页块还初始化了objects、slab_cache、inuse、frozen
	page = new_slab(kmem_cache_node, GFP_NOWAIT, node);

	BUG_ON(!page);
	if (page_to_nid(page) != node) {
		pr_err("SLUB: Unable to allocate memory from node %d\n", node);
		pr_err("SLUB: Allocating a useless per node structure in order to be able to continue\n");
	}

    // 通过调用 allocate_slab()从buddy中申请order阶数的page,并将page按照objects的数目打乱object list顺序
    // 除此,通过new_slab()申请的页块还初始化了objects、slab_cache、inuse、frozen
	n = page->freelist;
	BUG_ON(!n);
#ifdef CONFIG_SLUB_DEBUG
	init_object(kmem_cache_node, n, SLUB_RED_ACTIVE);
	init_tracking(kmem_cache_node, n);
#endif
	n = kasan_slab_alloc(kmem_cache_node, n, GFP_KERNEL, false);
	// 上面n 已经去到对象了,page->freelist指向下一个free object
	page->freelist = get_freepointer(kmem_cache_node, n);
	// page->inuse在new_slab()中初始化为page->objects,这里恢复,第一次使用所以该值为1
    // 后面如果在申请page->inuse会自加
	page->inuse = 1;
	// 在new_slab()中进行冻结,这里表示slab进入正常使用了,解冻
	page->frozen = 0;
	kmem_cache_node->node[node] = n;
	// 初始化该node的nr_partial和partial
	init_kmem_cache_node(n);
	inc_slabs_node(kmem_cache_node, node, page->objects);

	/*
	 * No locks need to be taken here as it has just been
	 * initialized and there is no concurrent access.
	 */
	__add_partial(n, page, DEACTIVATE_TO_HEAD);
}

allocate_slab分配page

static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	if (unlikely(flags & GFP_SLAB_BUG_MASK))
		flags = kmalloc_fix_flags(flags);

	WARN_ON_ONCE(s->ctor && (flags & __GFP_ZERO));

	return allocate_slab(s,
		flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
}
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	struct page *page;
	struct kmem_cache_order_objects oo = s->oo;
	gfp_t alloc_gfp;
	void *start, *p, *next;
	int idx;
	bool shuffle;

	flags &= gfp_allowed_mask;

	flags |= s->allocflags;

	/*
	 * Let the initial higher-order allocation fail under memory pressure
	 * so we fall-back to the minimum order allocation.
	 */
	alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;
	if ((alloc_gfp & __GFP_DIRECT_RECLAIM) && oo_order(oo) > oo_order(s->min))
		alloc_gfp = (alloc_gfp | __GFP_NOMEMALLOC) & ~(__GFP_RECLAIM|__GFP_NOFAIL);
	// 从buddy 分配器中分配页块
	page = alloc_slab_page(s, alloc_gfp, node, oo);
	if (unlikely(!page)) {
		oo = s->min;
		alloc_gfp = flags;
		/*
		 * Allocation may have failed due to fragmentation.
		 * Try a lower order alloc if possible
		 */
		page = alloc_slab_page(s, alloc_gfp, node, oo);
		if (unlikely(!page))
			goto out;
		stat(s, ORDER_FALLBACK);
	}
    // page申请成功,初始化page数据结构中slub相关的成员
	// 这里更新page->objects,标记页块的objects数量
	page->objects = oo_objects(oo);

	account_slab_page(page, oo_order(oo), s, flags);

	page->slab_cache = s;
	__SetPageSlab(page); // 标记页块为slab专用
	if (page_is_pfmemalloc(page))
		SetPageSlabPfmemalloc(page);  //标记页块为prmemalloc

	kasan_poison_slab(page);
	// 获取page 虚拟地址
	start = page_address(page);

	setup_page_debug(s, page, start);
	//需要使能CONFIG_SLAB_FREELIST_RANDOM,主要是打乱slab中object,形成一个随机的object list,
    //函数shuffle_freelist()除了上面这个功能,还会对页块中object进行setup
    //因为slab空间是连续的,这样做降低可预测性,若objects >= 2,都会返回true
    //如果没有使能config,则返回false
	shuffle = shuffle_freelist(s, page);

	if (!shuffle) {
		start = fixup_red_left(s, start);
		start = setup_object(s, page, start);
		page->freelist = start;
		for (idx = 0, p = start; idx < page->objects - 1; idx++) {
			next = p + s->size;
			next = setup_object(s, page, next);
			set_freepointer(s, p, next);
			p = next;
		}
		set_freepointer(s, p, NULL);
	}

	page->inuse = page->objects;
	page->frozen = 1;

out:
	if (!page)
		return NULL;

	inc_slabs_node(s, page_to_nid(page), page->objects);

	return page;
}

调用buddy接口分配page

static inline struct page *alloc_slab_page(struct kmem_cache *s,
		gfp_t flags, int node, struct kmem_cache_order_objects oo)
{
	struct page *page;
	// 获取需要分配的order
	unsigned int order = oo_order(oo);

	if (node == NUMA_NO_NODE)
		page = alloc_pages(flags, order); 
	else
		page = __alloc_pages_node(node, flags, order); // 调用buddy分配器的接口

	return page;
}

此时:

  1. 有了 slab 页面(物理页面)

  1. 页面被划分为多个 slab 对象

  1. freelist 指向第一个可用的 slab 对象

  1. 可以从这个 slab 分配对象了

第二阶段:引导创建动态缓存

在第一阶段结束后,会设置

slab_state = PARTIAL; 
    kmem_cache = bootstrap(&boot_kmem_cache);          
    kmem_cache_node = bootstrap(&boot_kmem_cache_node);
static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
	int node;
	// 通过kmem_cache_zalloc() 从之前创建好的kmem_cache 中申请slub 空间
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
	struct kmem_cache_node *n;

	// 将描述符信息通过memcpy() 拷贝到新申请的空间中
	memcpy(s, static_cache, kmem_cache->object_size);

	/*
	 * This runs very early, and only the boot processor is supposed to be
	 * up.  Even if it weren't true, IRQs are not up so we couldn't fire
	 * IPIs around.
	 */
	 // 刷新cpu的slab信息,主要更新c->page,c->freelist和c->partial
	__flush_cpu_slab(s, smp_processor_id());
	
    // 循环遍历s->node数组中的所有node,然后遍历每个node上面的partial链表,
    // 修改上面的struct page的slab_cache指针,指向当前新申请的kmem_cache
	for_each_kmem_cache_node(s, node, n) {
		struct page *p;

		list_for_each_entry(p, &n->partial, slab_list)
			p->slab_cache = s;

#ifdef CONFIG_SLUB_DEBUG
		list_for_each_entry(p, &n->full, slab_list)
			p->slab_cache = s;
#endif
	}

    // 将kmem_cache添加到全局slab_caches链表中
	list_add(&s->list, &slab_caches);
	return s;
}

分配新的 kmem_cache 结构

struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);

这里有三个容易混的东西:

  • static_cache:传进来的,是 静态的 boot_kmem_cache / boot_kmem_cache_node

  • 全局变量 kmem_cache:此时仍然指向 boot_kmem_cache,是“用于存放 struct kmem_cache 的 cache”。

  • 局部变量 skmem_cache 这个 cache 里新分配出的一个 struct kmem_cache 对象

也就是说:

临时的 cache(kmem_cache = &boot_kmem_cache
它自己 管理的 slab 里,malloc 出一个新的 struct kmem_cache,叫 s

这一步的底层实际还是走 SLUB 的页分配逻辑(allocate_slab()new_slab()alloc_pages()),只是当前的 s 还没完全接入全局 list。

把静态描述符“深拷贝”到新对象里

memcpy(s, static_cache, kmem_cache->object_size);

这里有几点细节:

  1. kmem_cache->object_size 就是 一个 struct kmem_cache 的大小(加上 debug 扩展时也可能略有差别,但总之能覆盖整个结构)。

  2. static_cachekmem_cache 当前其实都是指向 boot_kmem_cache,所以可以理解为:

    “把 boot_kmem_cache 这个临时的 struct kmem_cache 原封不动复制 到新分配出来的 s 里”。

  3. 复制后,s 里已经包含:

    • 名字、flags、object_size、size、对齐等属性;

    • 指向 per-node kmem_cache_node 数组的指针;

    • 指向 percpu struct kmem_cache_cpu 的指针 s->cpu_slab

    • 等等。

换句话说,s 是一个 完全克隆出来的“真正对象”版 kmem_cache 描述符

刷新 cpu 的 slab 信息

__flush_cpu_slab(s, smp_processor_id());
  • __flush_cpu_slab() 做的事情(看实现):

    • 找到 s->cpu_slab 里的 struct kmem_cache_cpu *c

    • 如果 c->page 非空,调用 flush_slab(),把 per-CPU freelist 上残留的对象都退回到对应的 struct page

    • 然后 unfreeze_partials() 把 per-CPU partial slabs 挪回每个 node 的 kmem_cache_node::partial 链表;

    • 最后把 c->pagec->freelistc->partial 清空。

为什么要在 bootstrap 阶段做这个?

  • 因为这时所有 per-CPU 的 slab 状态(c->page、partial 链表等)里,页的 page->slab_cache 指针还是指向旧的 static_cache

  • 后面我们会去修改各个 struct page 里的 slab_cache 指针,如果不先把 per-CPU 的东西刷回去,可能会有“挂在 per-CPU 上但指针还没改”的残留状态。

注意这里传的是新对象 s,但因为我们刚刚 memcpy(s, static_cache, ...)s->cpu_slab 和旧的相同,所以刷的是同一套 per-CPU 数据。

修正所有 slab 页的 slab_cache 指针

    // 循环遍历s->node数组中的所有node,然后遍历每个node上面的partial链表,
    // 修改上面的struct page的slab_cache指针,指向当前新申请的kmem_cache
    for_each_kmem_cache_node(s, node, n) {
        struct page *p;

        list_for_each_entry(p, &n->partial, slab_list)
            p->slab_cache = s;

#ifdef CONFIG_SLUB_DEBUG
        list_for_each_entry(p, &n->full, slab_list)
            p->slab_cache = s;
#endif
    }

这里的逻辑是:

  1. for_each_kmem_cache_node(s, node, n) 遍历 s 对应的所有 NUMA node 上的 kmem_cache_node
    s->node[node] 数组是 create_boot_cache() 时就准备好的,刚刚已经被 memcpy 到 s

  2. 对每个 node:

    • 先遍历 n->partial 链表上的所有 struct page *p

    • 如果编译了 CONFIG_SLUB_DEBUG,再遍历 n->full 链表。

  3. 对每一个 page,把 p->slab_cache 从原来的 static_cache 改成新对象 s

为什么只改 partial/full,而不改 per-CPU 链表上那堆?

  • 上一步 __flush_cpu_slab() 已经把 per-CPU 的 slab 状态全部“刷新”回 node 层,per-CPU 层不再持有 slab 页了。

  • 所以现在所有 slab 页要么在 n->partial 要么在 n->full,逐个改它们的 slab_cache 就够了。

这一步完成之后:

所有属于这个 cache 的 slab 页(不论 partial 还是 full),
它们的 page->slab_cache 都统一指向了新的 s
再也没人指向那个静态的 boot_kmem_cache 了。

把新 cache 挂到全局 slab_caches 链表

    // 将kmem_cache添加到全局slab_caches链表中
    list_add(&s->list, &slab_caches);

slab_cachesSLUB 维护的“所有 slab cache 的全局链表”,后面 debug、收缩 (kmem_cache_shrink())、统计等功能都会遍历这个链表。

  • 这里直接 list_add() 不加锁,是因为还在 早期启动阶段,只有 boot CPU、且 slab_state <= PARTIAL,SLUB 的注释里也明确写了 “Mutex is not taken during early boot”。

  • 静态的 boot_kmem_cache不会 被挂到 slab_caches 上,它只是启动阶段的“踏脚石”。

之后,kmem_cache_init() 里会用返回值覆盖全局指针:

kmem_cache      = bootstrap(&boot_kmem_cache);

kmem_cache_node = bootstrap(&boot_kmem_cache_node);

从这一刻起:

  • kmem_cache 指向“真正的、动态分配的 struct kmem_cache 对象 s”;

  • kmem_cache_node 也同理;

  • 所有之前已经分配出来的 slab 页、per-CPU 状态都已经指向 s;

  • SLUB 的“专用 cache of kmem_cache / kmem_cache_node”算是完全自举完成。

bootstrap总结

把这段函数和 ARM64 启动时机放一起看,可以这么理解:

  1. mem_init() 之后,buddy allocator 就绪;

  2. kmem_cache_init()create_boot_cache()静态 struct kmem_cache 上“硬编码”了一套最小可用的 SLUB cache(只支持 kmem_cache / kmem_cache_node 两种对象);

  3. bootstrap() 做四件事:

    • 用这套临时 cache self-hostingkmem_cache_zalloc() 出一个新的 struct kmem_cache

    • 把静态 boot_kmem_cache 的内容 memcpy 进去;

    • 刷干净当前 CPU 的 slab 状态,并修正所有 page->slab_cache 指针指向新的 s

    • s 加入全局 slab_caches 链表。

  4. 然后用返回的 s 覆盖全局指针 kmem_cache / kmem_cache_node
    接下来创建各种 kmalloc-xx cache、普通模块自己的 cache,就都走正常的 SLUB 创建路径了。

flowchart TD %% 阶段 0:初始状态,使用 boot_kmem_cache subgraph S0["阶段 0:使用 boot_kmem_cache"] BKC["boot_kmem_cache (静态 struct kmem_cache)"] BKC_NODE["boot_kmem_cache_node (静态 struct kmem_cache_node[])"] KMEM_PTR["全局指针 kmem_cache = &boot_kmem_cache"] KMEM_NODE_PTR["全局指针 kmem_cache_node = &boot_kmem_cache_node"] PAGES0["所有 slab page: page->slab_cache = &boot_kmem_cache"] KMEM_PTR --> BKC KMEM_NODE_PTR --> BKC_NODE BKC --> PAGES0 end S0 --> S1 %% 阶段 1:进入 bootstrap() subgraph S1["阶段 1:进入 bootstrap(static_cache)"] A1["调用 bootstrap(&boot_kmem_cache)"] A2["kmem_cache_zalloc(kmem_cache) 分配新的 struct kmem_cache *s"] S["新分配的 struct kmem_cache 对象 s"] A1 --> A2 --> S end S1 --> S2 %% 阶段 2:memcpy 拷贝描述符 subgraph S2["阶段 2:memcpy 拷贝描述符"] A3["memcpy(s, static_cache, kmem_cache->object_size)"] DESC["复制 boot_kmem_cache 的所有字段到 s"] A3 --> DESC end S2 --> S3 %% 阶段 3:刷新 CPU slab 状态 subgraph S3["阶段 3:刷新 CPU slab 状态"] A4["__flush_cpu_slab(s, smp_processor_id())"] CSTATE["清空 per-CPU kmem_cache_cpu: c->page / c->freelist / c->partial"] A4 --> CSTATE end S3 --> S4 %% 阶段 4:修正所有 slab page 的 slab_cache 指针 subgraph S4["阶段 4:修正 page->slab_cache"] A5["for_each_kmem_cache_node(s, node, n)"] A6["遍历 n->partial: p->slab_cache = s"] A7["(可选) 遍历 n->full: p->slab_cache = s"] PAGES1["所有属于该 cache 的 slab page: page->slab_cache = s"] A5 --> A6 --> PAGES1 A5 --> A7 --> PAGES1 end S4 --> S5 %% 阶段 5:挂入全局链表并接管全局指针 subgraph S5["阶段 5:挂入全局链表并接管指针"] A8["list_add(&s->list, &slab_caches)"] RET["返回 s"] KMEM_PTR_NEW["全局指针 kmem_cache = s(真正的 kmem_cache cache)"] A8 --> RET --> KMEM_PTR_NEW end