[Linux]学习笔记系列 -- mm/slub.c SLUB内存分配器(The SLUB Allocator) 现代内核对象缓存的核心

发布于:2025-09-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

文章目录


在这里插入图片描述

https://github.com/wdfk-prog/linux-study

mm/slub.c SLUB内存分配器(The SLUB Allocator) 现代内核对象缓存的核心

历史与背景

这项技术是为了解决什么特定问题而诞生的?

这项技术以及它所实现的SLUB分配器,是为了解决内核中一个基础且关键的性能问题:如何高效地分配和释放大量小的、固定大小的内存对象

  • 对抗内部碎片(Internal Fragmentation):内核需要频繁创建大量的小对象(如inode, dentry, task_struct等),它们的大小通常远小于一个物理内存页(通常是4KB)。如果直接使用页分配器(Buddy System)来为这些小对象分配整个页,会造成巨大的内存浪费。例如,为一个128字节的对象分配一个4096字节的页,97%的内存就被浪费了。
  • 提升分配性能:通用的内存分配器需要处理任意大小的请求,其算法相对复杂。而对于特定类型的对象,我们可以创建一个专用的“对象缓存池”。SLUB分配器就是这种缓存池的实现者。
  • 利用对象构造/析构:很多内核对象在首次使用时需要进行初始化。SLUB框架允许在创建缓存时指定一个“构造函数”(constructor)。当从缓存中分配一个新对象时,这个构造函数会被自动调用,避免了在每次分配后都重复编写初始化代码。
  • 提升硬件缓存利用率:通过将相同类型的对象紧凑地存放在一起,可以提高CPU缓存的命中率,从而提升性能。
它的发展经历了哪些重要的里程碑或版本迭代?

Linux内核的Slab分配器经历了三个主要实现:SLAB, SLOB, 和 SLUB。

  1. SLAB:这是最初的、经典的Slab分配器实现。它的设计非常精巧,为每个NUMA节点和每个CPU维护了三类Slab链表:完全满的(full)、部分满的(partial)和完全空的(empty)。这种设计能很好地回收部分使用的slab,但其内部逻辑非常复杂,管理这些链表本身也带来了不可忽略的性能和内存开销。
  2. SLOB (Simple List Of Blocks):这是一个极其简单的分配器,专为内存占用是首要考虑因素的嵌入式系统设计。它使用首次适应算法(first-fit)在页面中分配内存,内存开销极小,但性能和扩展性较差,且容易产生碎片。
  3. SLUB (The Unqueued Allocator):SLUB是作为SLAB的现代替代品而被设计的。它的核心设计目标是简化提升性能,特别是在大型多核(SMP)系统上。它废除了SLAB中复杂的队列管理,其核心思想是:一个slab页要么完全属于一个CPU,要么位于一个全局的、按NUMA节点组织的链表中。这种简化的设计减少了锁争用,降低了元数据开销,并最终在大多数工作负载下提供了比SLAB更好的性能。由于其优越性,SLUB现在是绝大多数Linux发行版的默认Slab分配器
目前该技术的社区活跃度和主流应用情况如何?

SLUB是Linux内存管理子系统的核心和默认组件,其地位稳固。

  • 社区活跃度:作为性能关键路径,SLUB的代码非常稳定,但仍在持续优化。社区的关注点包括进一步提升NUMA系统的性能、改进调试特性、以及微调其与页分配器和内存回收机制的交互。
  • 主流应用:SLUB是整个内核的基础设施。
    • 对象缓存:内核中几乎所有的数据结构(进程、文件、网络套接字等)都是通过kmem_cache_alloc从SLUB缓存中分配的。
    • kmalloc的后端:通用的内核内存分配接口kmalloc()kfree(),其后端就是一系列预设好大小(8, 16, 32, … , 8192字节等)的SLUB缓存。可以说,内核中绝大部分的动态内存分配都最终由SLUB处理。

核心原理与设计

它的核心工作原理是什么?

SLUB的核心是将一个或多个连续的物理内存页(称为一个slab)分割成多个固定大小的对象,并高效地管理这些对象的分配与释放。

  1. kmem_cache:代表一类特定对象的缓存。通过kmem_cache_create()创建,它定义了对象的大小、对齐方式、构造函数等属性。
  2. Slab:从伙伴系统(Buddy System)分配的一个或多个连续的物理页。一个slab被格式化后,专门用于存放一种kmem_cache的对象。
  3. Per-CPU缓存:这是SLUB高性能的关键。每个CPU核心都有一个私有的、当前活动的slab。当一个CPU上的代码需要分配对象时,它会首先尝试从这个私有slab中获取。因为访问的是CPU私有数据,所以这个过程完全无锁,速度极快。
  4. 简单的Freelist管理:当一个对象被释放时,它会被放回当前CPU的活动slab的freelist中。SLUB的freelist设计非常巧妙和简单:它将指向下一个空闲对象的指针直接存储在前一个空闲对象的内存空间中。这几乎消除了所有额外的元数据开销。
  5. Slab的流转
    • 分配路径:当一个CPU的活动slab用完时,它会去一个per-NUMA节点的部分空闲slab链表中获取一个新的slab。如果这个链表也为空,它才会向伙伴系统申请新的页来创建一个全新的slab。
    • 释放路径:当一个CPU释放对象,导致其活动slab变满(所有对象都空闲)时,这个slab可能会被放回到per-NUMA节点的链表中,供其他CPU使用。
  6. 调试特性:SLUB集成了强大的调试功能,如Red Zoning(在对象前后放置“红区”以检测溢出)、Poisoning(用特定值填充已释放的对象以检测use-after-free)、以及所有权跟踪。
它的主要优势体现在哪些方面?
  • 高性能与高扩展性:基于Per-CPU的无锁快速路径,极大地减少了多核系统中的锁争用。
  • 设计简洁:相比SLAB,其内部逻辑大大简化,代码更易于理解和维护。
  • 低内存开销:元数据极少,大部分slab页面几乎完全用于存储对象本身。
  • 优秀的NUMA亲和性:Slab和对象都优先在当前CPU所在的NUMA节点上分配,减少了跨节点内存访问的延迟。
它存在哪些已知的劣势、局-限性或在特定场景下的不适用性?
  • 部分slab回收可能延迟:由于其简化的设计,相比SLAB,SLUB在回收那些仅有少量对象被使用的“部分空闲”slab时,可能没有那么积极。但这通常被认为是一个合理的权衡。
  • 内部碎片仍然存在:虽然解决了页分配器的内部碎片问题,但如果对象的大小不能很好地适配slab页的大小,slab内部仍然会存在一些无法利用的“边角料”内存。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

SLUB是内核中进行小对象和通用内存分配默认和首选方案。

  • 创建内核数据结构:当一个新进程被创建时,内核需要分配一个struct task_struct。它会调用kmem_cache_alloc(task_struct_cache, ...),这会从专门为task_struct优化的SLUB缓存中快速获取一个对象。
  • 通用kmalloc分配:当一个设备驱动需要一个中等大小的缓冲区(例如200字节)来处理DMA描述符时,它会调用kmalloc(200, GFP_KERNEL)kmalloc的实现会找到最适合200字节的SLUB缓存(可能是kmalloc-256缓存),并从中分配一个对象。
  • 任何性能敏感的对象池:网络栈为每个网络包分配的sk_buff结构,VFS为每个打开的文件分配的struct file,都是通过各自的SLUB缓存进行高效管理的。
是否有不推荐使用该技术的场景?为什么?
  • 大块内存分配:SLUB不适合用于分配大的、跨越多个页面的内存块。这是页分配器(Buddy System)的工作,应直接使用alloc_pages()vmalloc()kmalloc的上限通常是几兆字节,超过这个大小的请求会失败或转向其他分配器。
  • 用户空间内存分配:SLUB是纯粹的内核机制。用户空间的应用程序使用malloc()(由glibc等C库提供),它有自己的、在用户态实现的内存分配器(如ptmalloc, jemalloc, tcmalloc)。

对比分析

请将其 与 其他相似技术 进行详细对比。

对比一:SLUB vs. SLAB

特性 SLUB (The Unqueued Allocator) SLAB (The Original Slab Allocator)
设计复杂度 简单 复杂
Slab管理 无复杂的队列。Slab要么在CPU本地,要么在一个简单的per-node链表中。 每个CPU和每个node都有三个队列:full, partial, empty。
Freelist管理 将freelist指针存储在对象内部。 需要额外的元数据来管理freelist。
内存开销 。元数据极少。 较高。管理队列和freelist需要额外的内存。
性能 通常更高,尤其是在大型SMP系统上,得益于简单的无锁路径。 在某些特定的、碎片化严重的负载下,其更积极的部分slab回收策略可能有优势。
默认状态 现代内核的默认选择 仍然保留在内核中,可通过启动选项启用,但已不常用。

对比二:SLUB vs. 伙伴系统 (Buddy System / Page Allocator)

特性 SLUB 分配器 伙伴系统 (Buddy System)
分配单位 对象(Object),大小通常远小于一个页。 物理页(Page),以2的幂次方(1, 2, 4, 8…页)为单位。
主要目标 解决内部碎片问题,高效管理小对象的生命周期。 管理整个系统的物理内存页,解决外部碎片问题。
碎片类型 内部碎片(slab内的边角料)。 外部碎片(小的空闲页块无法合并成大的块)。
关系 客户端/上层。SLUB自身需要的大块内存(用于创建slab)是从伙伴系统申请的。 基础/底层。是内核内存管理的最底层,为SLUB和其他所有需要物理页的地方提供服务。

mm/folio.c 内存页束(Memory Folios) 简化大页和页面缓存管理

历史与背景

这项技术是为了解决什么特定问题而诞生的?

这项技术以及其核心数据结构struct folio,是为了解决Linux内存管理中一个长期存在的、导致代码复杂且极易出错的根本性问题:如何清晰、安全地处理大于单个页面(4KB)的连续物理内存块

  • struct page的歧义性:在Linux中,struct page是管理单个物理页面的基本单元。然而,为了性能,内核在很多地方(如透明大页THP、页面缓存、网络缓冲区)会分配连续的多个页面,这被称为“复合页”(Compound Page)或“高阶页”(High-order Page)。
  • 复合页的笨拙处理:在Folio出现之前,一个复合页由一个struct page数组表示。其中第一个页面page[0]是“头页”(head page),包含了整个内存块的信息,而后续的page[1], page[2]…都是“尾页”(tail pages)。这种设计的缺陷是灾难性的:
    1. API混乱:函数需要传递多个参数,如(struct page *page, unsigned int order),增加了复杂性。
    2. 逻辑脆弱且易错:代码中到处都需要检查一个给定的struct page指针是否是尾页。如果是,就必须调用compound_head(page)来找到真正的头页才能获取正确信息。忘记这个检查是内核bug的一个常见来源。
    3. 概念不清:一个struct page*指针的含义变得模糊不清,它可能指向一个4KB的页,也可能指向一个2MB大页的中间某个4KB部分。

Folio的诞生就是为了终结这种混乱。它提供了一个新的、清晰的抽象:struct folio,它明确地代表一个完整的、大小可能不一的内存块,无论它是一个4KB的普通页还是一个2MB的大页。

它的发展经历了哪些重要的里程碑或版本迭代?

Folio是由内核资深开发者Matthew Wilcox主导的一项重大重构工作,它不是一次性完成的,而是一个持续的、遍及整个内核的演进过程。

  • 引入:Folio的概念和初步实现大约在Linux内核5.16版本中被正式引入。
  • 页面缓存的转换:最重要的里程碑之一是将内核中最核心的缓存——页面缓存(Page Cache)——完全从使用struct page转换为使用struct folio。这极大地简化了文件I/O和内存管理之间的交互代码。
  • 持续的内核转换:自引入以来,内核社区一直在进行一项庞大的工作,即逐步将内核的其他子系统(如驱动程序、网络栈、各种文件系统)的API从接收struct page改为接收struct folio。这是一个仍在进行中的过程。
目前该技术的社区活跃度和主流应用情况如何?

Folio是当前Linux内存管理子系统最活跃的开发领域之一,它代表了未来内核内存管理的方向。

  • 社区活跃度:极高。大量的补丁集仍在不断地提交,以将内核的各个角落转换为使用Folio。
  • 主流应用:它已经成为内核MM子系统的核心组件。所有新的内存管理相关的开发都鼓励或强制使用Folio。它不是一个可选功能,而是对内核底层架构的根本性改进。

核心原理与设计

它的核心工作原理是什么?

Folio的核心原理是提供一个更高级的、无歧义的内存管理单元,并巧妙地将其与现有的struct page基础设施集成,以实现平滑过渡。

  1. 数据结构struct folio并非一个全新的、独立分配的结构。为了节省内存,它被巧妙地嵌入(通过union)到复合页的头页struct page结构中。因此,一个Folio本质上是头页的一个“更结构化的视图”,它没有带来额外的内存开销。
  2. 明确的大小:一个struct folio内部直接包含了它所代表的内存块的大小(或阶数order)。因此,一个struct folio*指针就足以描述整个内存块,不再需要额外的sizeorder参数。
  3. API的演进
    • 旧API:void function(struct page *page, unsigned int order);
    • 新API:void function(struct folio *folio);
      新API更简洁,并且类型安全。
  4. 关系与转换:内核提供了一套完整的函数来在foliopage之间转换:
    • page_folio(page):从任意一个(头或尾)struct page指针找到其所属的struct folio
    • &folio->page:从一个struct folio获取其头页的struct page指针。
它的主要优势体现在哪些方面?
  • 代码简化与可读性:这是最直接、最重要的优势。它消除了大量的compound_head()调用和相关的条件判断,使得代码更短、更清晰、更易于理解和维护。
  • 减少Bug:通过消除处理尾页的复杂逻辑,从根本上杜绝了一整类常见的内核bug。
  • 性能:虽然主要目标是简化代码,但更清晰的逻辑和更少的函数参数有时也能带来微小的性能提升。例如,编译器可以更好地优化代码,同时减少了函数调用时的栈操作。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
  • 庞大的转换工作:最大的“劣势”是将其应用到整个内核所需的工作量是巨大的。这是一个持续数年的重构项目。
  • 过渡期的复杂性:在所有代码都被转换之前,开发者需要在新旧API之间进行转换,这在过渡期间会引入一些额外的复杂性。
  • 学习成本:对于习惯了旧的struct page模型的内核开发者来说,需要一个适应和学习新API和新概念的过程。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

Folio是处理任何可能大于4KB的内存块现代、首选的内核编程方式。

  • 页面缓存(Page Cache):这是Folio最典型的应用场景。当读写一个文件时,内核可能会为它分配一个2MB的透明大页(THP)来作为缓存。使用Folio,整个2MB的块可以被一个struct folio对象管理。文件系统代码在处理这个缓存时,只需传递这一个对象,而无需关心它是由512个4KB的页面组成的。
  • 透明大页(THP)和HugeTLB:管理大页的内核代码(分配、分裂、合并)是Folio的直接受益者,其内部逻辑得到了极大的简化。
  • I/O操作:在进行块设备I/O或网络数据包处理时,经常会处理跨越多个页面的大数据块。将这些数据块表示为Folio,可以简化驱动程序和协议栈的代码。
是否有不推荐使用该技术的场景?为什么?
  • 底层页分配器:内核最底层的物理页分配器——伙伴系统(Buddy System)——仍然直接操作struct pageorder。这是因为它就是构建所有上层抽象(包括Folio)的基础。Folio是更高层次的抽象。
  • 必须操作单个物理页的场景:在极少数的、需要直接与硬件交互并精确控制单个4KB物理页的底层代码中,可能仍然需要直接使用struct page。但即使在这种情况下,通常也可以从Folio中获取其包含的第一个或第N个struct page来进行操作。

对比分析

请将其 与 其他相似技术 进行详细对比。

Folio最直接的对比对象就是它所要取代的——传统的复合页(Compound Page)机制。

特性 内存页束 (Memory Folios) 复合页 (Compound Pages)
核心抽象 struct folio,一个明确代表完整内存块的对象。 struct page,一个有歧义的指针,可能是头页或尾页。
API签名 简洁:function(struct folio *folio) 繁琐:function(struct page *page, int order)
查找元信息 直接从folio对象获取。 必须先检查是否为尾页,若是,则需调用compound_head()找到头页。
大小/阶数信息 内置于struct folio中。 存储在头页的struct page中,需要间接获取。
代码复杂度 。逻辑清晰,线性。 。充满了条件判断和compound_head()调用。
出错可能性 。类型系统提供了更强的保护,消除了尾页问题。

include/asm-generic/getorder.h

get_order 计算给定大小的内存块所需的页数

/**
 * get_order - 确定内存大小的分配顺序
 * @size:要获取订单的大小
 *
 * 确定特定大小的内存块的分配顺序。 这是对数刻度,其中:
 *
 * order = 0:表示内存块大小小于等于一个页面。
 * order = 1:表示内存块大小在 1 * PAGE_SIZE 到 2 * PAGE_SIZE 之间。
 * order = 2:表示内存块大小在 2 * PAGE_SIZE 到 4 * PAGE_SIZE 之间
 *	...
 *
 * 返回的订单用于查找保存指定大小的对象所需的最小分配颗粒。
 *
 * 如果大小为 0,则结果为 undefined。
 */
static __always_inline __attribute_const__ int get_order(unsigned long size)
{
	if (__builtin_constant_p(size)) {
		if (!size)	//如果 size 为 0,返回 BITS_PER_LONG - PAGE_SHIFT(通常是一个较大的值,用于表示错误或未定义行为)
			return BITS_PER_LONG - PAGE_SHIFT;

		if (size < (1UL << PAGE_SHIFT))	//size 小于一个页面大小(PAGE_SIZE),返回 0,表示只需要一个页面即可满足分配需求
			return 0;

		return ilog2((size) - 1) - PAGE_SHIFT + 1;
	}

	size--;
	size >>= PAGE_SHIFT;
#if BITS_PER_LONG == 32
	return fls(size);
#else
	return fls64(size);
#endif
}

mm/slab.h

folio_slab 将 folio 转换为 slab 结构体

/**
 * folio_slab - 从作品集转换为板。
 * @folio:对开页。
 *
 * 目前,struct slab 是作品集的不同表示形式,其中 folio_test_slab() 为 true。
 *
 * 返回:包含此作品集的板。
 */
#define folio_slab(folio)	(_Generic((folio),			\
	const struct folio *:	(const struct slab *)(folio),		\
	struct folio *:		(struct slab *)(folio)))

slab folio 结构体

/* struct folio 是 Linux 内核中用于表示一组连续内存页面的抽象。
 * 它是对 struct page 的扩展,旨在提高内存管理的效率,特别是在处理大页面(huge pages)和文件缓存时。
*/
struct slab {
	unsigned long __page_flags;	//页面的标志位

	struct kmem_cache *slab_cache;	//表示该 slab 所属的缓存
	union {
		struct {
			struct list_head slab_list;
			/* Double-word boundary */
			void *freelist;		/* first free object */
		};
		struct rcu_head rcu_head;	//用于 RCU 回收的头部
	};

	unsigned int __page_type;		//页面的类型
	atomic_t __page_refcount;		//页面的引用计数
};

/**
  * folio是一组在物理上、虚拟上和逻辑上连续的字节。 
  * 它的大小是 2 的幂,并且与相同的 2 的幂对齐。 它至少与 %PAGE_SIZE 一样大。 
  * 如果它在页面缓存中,则它位于文件偏移量是 2 的幂数的倍数。 
  * 它可能被映射到位于任意页面偏移量的地址的用户空间,但其内核虚拟地址与其大小对齐。
 */
struct folio {
	/* private: don't document the anon union */
	struct page page;
	struct page __page_1;
	struct page __page_2;
	struct page __page_3;
};

kmem_cache_args 结构体

  • 在 Linux 内核中,kmem_cache_create 提供了一个构造函数(ctor),用于在分配内存时对对象进行自定义初始化。然而,内核并没有直接提供析构函数的机制。这是因为内核的内存管理模型与用户态的对象生命周期管理不同,析构操作通常是由调用者显式完成的。
    原因分析
  1. 内存管理的简单性:
    内核的内存管理尽量保持简单高效,避免增加额外的复杂性。构造函数的主要作用是确保分配的内存对象在被使用之前处于已知的初始状态,而析构函数则需要在释放内存时执行清理逻辑,这会增加额外的开销。

  2. 释放内存的不可预测性:
    在内核中,内存释放操作通常是由调用者决定的,内核并不知道对象的具体用途或释放时需要进行什么操作。因此,析构逻辑更适合由调用者在释放前手动完成。

  3. RCU 和延迟释放机制的影响:
    在某些情况下,内核使用 RCU(Read-Copy-Update)或其他延迟释放机制来管理内存。对象在被释放之前可能会经历一段延迟期,在这期间执行析构函数可能会导致不一致性或额外的复杂性。

struct kmem_cache_args {
	/**
	 * @align:对象所需的对齐方式。
	 *
	 * %0 表示未请求特定对齐。
	 */
	unsigned int align;
	/**
	 * @useroffset:Usercopy 区域偏移量。
	 *
	 * %0 是有效的偏移量,当 @usersize 为非 %0 时
	 */
	unsigned int useroffset;
	/**
	 * @usersize:Usercopy 区域大小。
	 *
	 * %0 表示未指定 usercopy 区域。
	 */
	unsigned int usersize;
	/**
	 * @freeptr_offset:自由指针的自定义偏移量
	 * 在 &SLAB_TYPESAFE_BY_RCU 缓存中
	 *
	 * 默认情况下 &SLAB_TYPESAFE_BY_RCU 缓存将空闲指针放在对象外部。这可能会导致对象变大。有理由避免这种情况的缓存创建者可以在其结构体中指定自定义自由指针偏移量,自由指针将放置在其中。
	 *
	 * 请注意,将空闲指针放在对象内需要调用方确保没有防止对象回收所需的字段无效(有关详细信息,请参阅 &SLAB_TYPESAFE_BY_RCU)。
	 *
	 * 使用 %0 作为 @freeptr_offset 的值是有效的。如果指定了 @freeptr_offset,则必须将 %use_freeptr_offset 设置为 %true。
	 *
	 * 请注意,自定义自由指针目前不支持 @ctor,因为@ctor需要外部自由指针。
	 */
	unsigned int freeptr_offset;
	/**
	 * @use_freeptr_offset: Whether a @freeptr_offset is used.
	 */
	bool use_freeptr_offset;
	/**
	 * @ctor:对象的构造函数。
	 *
	 * 为新分配的 slab 页面中的每个对象调用构造函数。缓存用户有责任释放与调用构造函数后相同状态的对象,或者适当处理新构造的对象和重新分配的对象之间的任何差异。
	 *
	 * %NULL 表示没有构造函数。
	 */
	void (*ctor)(void *);
};

kmalloc_index 计算 kmalloc 的索引

/*
 * 确定分配特定大小的 kmalloc 板
 * 属于。
 * 0 = 零分配
 * 1 = 65 ..96 字节
 * 2 = 129 ..192 字节
 * n = 2^(n-1) 1 ..2^n
 *
 * 注意:__kmalloc_index() 是编译时优化的,而不是运行时优化的;典型用法是通过 kmalloc_index() 进行的,因此在编译时进行评估。其中 !size_is_constant 应该只应该是测试模块,可以容忍 __kmalloc_index() 的运行时开销。 另请参阅 kmalloc_slab()。
 */
static __always_inline unsigned int __kmalloc_index(size_t size,
						    bool size_is_constant)
{
	if (!size)
		return 0;

	if (size <= KMALLOC_MIN_SIZE)
		return KMALLOC_SHIFT_LOW;

	if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
		return 1;
	if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
		return 2;
	if (size <=          8) return 3;
	if (size <=         16) return 4;
	if (size <=         32) return 5;
	if (size <=         64) return 6;
	if (size <=        128) return 7;
	if (size <=        256) return 8;
	if (size <=        512) return 9;
	if (size <=       1024) return 10;
	if (size <=   2 * 1024) return 11;
	if (size <=   4 * 1024) return 12;
	if (size <=   8 * 1024) return 13;
	if (size <=  16 * 1024) return 14;
	if (size <=  32 * 1024) return 15;
	if (size <=  64 * 1024) return 16;
	if (size <= 128 * 1024) return 17;
	if (size <= 256 * 1024) return 18;
	if (size <= 512 * 1024) return 19;
	if (size <= 1024 * 1024) return 20;
	if (size <=  2 * 1024 * 1024) return 21;

	if (!IS_ENABLED(CONFIG_PROFILE_ALL_BRANCHES) && size_is_constant)
		BUILD_BUG_ON_MSG(1, "unexpected size in kmalloc_index()");
	else
		BUG();

	/* Will never be reached. Needed because the compiler may complain */
	return -1;
}
static_assert(PAGE_SHIFT <= 20);
#define kmalloc_index(s) __kmalloc_index(s, true)

kmalloc_type 计算 kmalloc 的类型

static __always_inline enum kmalloc_cache_type kmalloc_type(gfp_t flags, unsigned long caller)
{
	/*
	 * 最常见的情况是 KMALLOC_NORMAL,因此请使用所有相关标志的单个分支进行测试。
	 */
	if (likely((flags & KMALLOC_NOT_NORMAL_BITS) == 0))
#ifdef CONFIG_RANDOM_KMALLOC_CACHES
		/* RANDOM_KMALLOC_CACHES_NR (=15) copies + the KMALLOC_NORMAL */
		return KMALLOC_RANDOM_START + hash_64(caller ^ random_kmalloc_seed,
						      ilog2(RANDOM_KMALLOC_CACHES_NR + 1));
#else
		return KMALLOC_NORMAL;
#endif

	/*
	 * At least one of the flags has to be set. Their priorities in
	 * decreasing order are:
	 *  1) __GFP_DMA
	 *  2) __GFP_RECLAIMABLE
	 *  3) __GFP_ACCOUNT
	 */
	if (IS_ENABLED(CONFIG_ZONE_DMA) && (flags & __GFP_DMA))
		return KMALLOC_DMA;
	if (!IS_ENABLED(CONFIG_MEMCG) || (flags & __GFP_RECLAIMABLE))
		return KMALLOC_RECLAIM;
	else
		return KMALLOC_CGROUP;
}

kmalloc 分配内核内存

/**
 * kmalloc - allocate kernel memory
 * @size: how many bytes of memory are required.
 * @flags: describe the allocation context
 *
1. kmalloc 的用途
kmalloc 用于分配内核内存,主要针对小于页面大小的对象。分配的内存地址至少对齐到 ARCH_KMALLOC_MINALIGN 字节,并根据分配大小提供额外的对齐保证:

如果分配大小是 2 的幂,则对齐到该大小。
对于其他大小,对齐到分配大小的最大 2 的幂因子。
这种对齐方式确保了内存分配的高效性和兼容性,特别是在需要特定对齐的硬件或数据结构中。

2. GFP 标志
kmalloc 的第二个参数 flags 是 GFP(Get Free Pages)标志,用于描述分配的上下文和行为。常用的 GFP 标志包括:

GFP_KERNEL:分配普通内核内存,可能会导致睡眠。
GFP_NOWAIT:分配不会导致睡眠。
GFP_ATOMIC:分配不会导致睡眠,并可能使用紧急内存池。
__GFP_ZERO:分配的内存会被初始化为零。
__GFP_NOFAIL:分配不能失败(需谨慎使用)。
__GFP_NORETRY:如果内存不足,立即放弃分配。
__GFP_NOWARN:分配失败时不打印警告。
__GFP_RETRY_MAYFAIL:尽力分配,但允许最终失败。
这些标志通过按位或(|)组合,可以灵活地指定分配行为。
 */
static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t flags)
{
	if (__builtin_constant_p(size) && size) {
		unsigned int index;

		if (size > KMALLOC_MAX_CACHE_SIZE)
			return __kmalloc_large_noprof(size, flags);

		index = kmalloc_index(size);

		return __kmalloc_cache_noprof(
				kmalloc_caches[kmalloc_type(flags, _RET_IP_)][index],
				flags, size);
	}
	return __kmalloc_noprof(size, flags);
}
#define kmalloc(...)				alloc_hooks(kmalloc_noprof(__VA_ARGS__))

kzalloc 分配并清零内核内存

/**
 * kzalloc - 分配内存。内存设置为零。
 * @size:需要多少字节的内存。
 * @flags:要分配的内存类型(参见 kmalloc)。
 */
static inline __alloc_size(1) void *kzalloc_noprof(size_t size, gfp_t flags)
{
	return kmalloc_noprof(size, flags | __GFP_ZERO);
}
#define kzalloc(...)				alloc_hooks(kzalloc_noprof(__VA_ARGS__))

kcalloc 分配数组并清零

/**
 * kmalloc_array - allocate memory for an array.
 * @n: number of elements.
 * @size: element size.
 * @flags: the type of memory to allocate (see kmalloc).
 */
static inline __alloc_size(1, 2) void *kmalloc_array_noprof(size_t n, size_t size, gfp_t flags)
{
	size_t bytes;

	if (unlikely(check_mul_overflow(n, size, &bytes)))
		return NULL;
	return kmalloc_noprof(bytes, flags);
}
#define kmalloc_array(...)			alloc_hooks(kmalloc_array_noprof(__VA_ARGS__))

/**
 * kcalloc - 为数组分配内存。内存设置为零。
 * @n:元素数量。
 * @size:元素大小。
 * @flags:要分配的内存类型(参见 kmalloc)。
 */
#define kcalloc(n, size, flags)		kmalloc_array(n, size, (flags) | __GFP_ZERO)

kmem_cache_zalloc 分配 slab 缓存中的内存

void *kmem_cache_alloc_noprof(struct kmem_cache *s, gfp_t gfpflags)
{
	void *ret = slab_alloc_node(s, NULL, gfpflags, NUMA_NO_NODE, _RET_IP_,
				    s->object_size);

	trace_kmem_cache_alloc(_RET_IP_, ret, s, gfpflags, NUMA_NO_NODE);

	return ret;
}

/**
 * kmem_cache_alloc - 分配对象
 * @cachep:要从中分配的缓存。
 * @flags:请参阅 kmalloc()。
 *
 * 从此缓存中分配对象。
 * 请参阅 kmem_cache_zalloc() 以获取向标志添加__GFP_ZERO的快捷方式。
 *
 * 返回:指向新对象的指针或 %NULL(如果出现错误)
 */
void *kmem_cache_alloc_noprof(struct kmem_cache *cachep,
			      gfp_t flags) __assume_slab_alignment __malloc;
#define kmem_cache_alloc(...)			alloc_hooks(kmem_cache_alloc_noprof(__VA_ARGS__))
/*
 * Shortcuts
 */
#define kmem_cache_zalloc(_k, _flags)		kmem_cache_alloc(_k, (_flags)|__GFP_ZERO)

kmem_cache_create_usercopy 创建用户拷贝缓存

  • useroffset(用户复制区域偏移量):

    useroffset 表示用户可复制区域相对于内存块起始地址的偏移量。
    通过指定偏移量,可以确保用户只能访问内存块中的特定区域,而不会影响其他敏感数据。

  • usersize(用户复制区域大小):

    usersize 定义了用户可复制区域的大小。
    这确保用户只能操作指定大小的内存数据,从而避免越界访问或数据损坏。

/**
 * kmem_cache_create_usercopy - 创建具有适合复制到用户空间的区域的 kmem 缓存。
 * @name:在 /proc/slabinfo 中用于标识此缓存的字符串。
 * @size:要在此缓存中创建的对象的大小。
 * @align:对象所需的对齐方式。
 * @flags:SLAB 标志
 * @useroffset:Usercopy 区域偏移
 * @usersize:Usercopy 区域大小
 * @ctor:对象的构造函数,或 %NULL。
 *
 * 这是一个遗留的包装器,如果将单个字段列入白名单就足够了,新代码应该使用 KMEM_CACHE_USERCOPY() ,或者使用 kmem_cache_create() 并通过 args 参数传递必要的参数(参见 &struct kmem_cache_args)
 *
 * 返回:成功时指向缓存的指针,失败时返回 NULL。
 */
static inline struct kmem_cache *
kmem_cache_create_usercopy(const char *name, unsigned int size,
			   unsigned int align, slab_flags_t flags,
			   unsigned int useroffset, unsigned int usersize,
			   void (*ctor)(void *))
{
	struct kmem_cache_args kmem_args = {
		.align		= align,
		.ctor		= ctor,
		.useroffset	= useroffset,
		.usersize	= usersize,
	};

	return __kmem_cache_create_args(name, size, &kmem_args, flags);
}

__kmem_cache_default_args 创建默认参数的 slab 缓存

/* If NULL is passed for @args, use this variant with default arguments. */
static inline struct kmem_cache *
__kmem_cache_default_args(const char *name, unsigned int size,
			  struct kmem_cache_args *args,
			  slab_flags_t flags)
{
	struct kmem_cache_args kmem_default_args = {};

	/* Make sure we don't get passed garbage. */
	if (WARN_ON_ONCE(args))
		return ERR_PTR(-EINVAL);

	return __kmem_cache_create_args(name, size, &kmem_default_args, flags);
}

__kmem_cache_create 创建 slab 缓存 传入构造函数与对齐值

struct kmem_cache *__kmem_cache_create_args(const char *name,
					    unsigned int object_size,
					    struct kmem_cache_args *args,
					    slab_flags_t flags);
static inline struct kmem_cache *
__kmem_cache_create(const char *name, unsigned int size, unsigned int align,
		    slab_flags_t flags, void (*ctor)(void *))
{
	struct kmem_cache_args kmem_args = {
		.align	= align,
		.ctor	= ctor,
	};

	return __kmem_cache_create_args(name, size, &kmem_args, flags);
}

kmem_cache_create 创建 slab 缓存

/**
* kmem_cache_create - 创建 kmem 缓存。
 * @__name:在 /proc/slabinfo 中用于标识此缓存的字符串。
 * @__object_size:要在此缓存中创建的对象的大小。
 * @__args:可选参数,请参阅 &struct kmem_cache_args。传递 %NULL 意味着默认值将用于所有参数。
 *
 * 目前,这是作为宏实现的,使用 ''_Generic()'' 调用
 * 函数的新变体或旧变体。
 *
 * 新变体有 4 个参数:
 * ''kmem_cache_create(名称、object_size、参数、标志)''
 *
 * 参见 __kmem_cache_create_args() 实现此功能。
 *
 * 旧版变体有 5 个参数:
 * ''kmem_cache_create(名称、object_size、对齐、标志、ctor)''
 *
 * align 和 ctor 参数映射到 &struct 的相应字段kmem_cache_args
 *
 * 上下文:不能在中断中调用,但可以中断。
 *
 * 返回:成功时指向缓存的指针,失败时返回 NULL。
 */
#define kmem_cache_create(__name, __object_size, __args, ...)           \
	_Generic((__args),                                              \
		struct kmem_cache_args *: __kmem_cache_create_args,	\
		void *: __kmem_cache_default_args,			\
		default: __kmem_cache_create)(__name, __object_size, __args, __VA_ARGS__)

KMEM_CACHE 创建 slab 缓存

/*
 * 请使用此宏创建 slab 缓存。只需指定结构的名称,也许还可以指定上面列出的一些标志。
 *
 * 结构体的对齐方式决定了对象的对齐方式。如果你在 struct 声明中添加 ____cacheline_aligned_in_smp,那么对象将在 SMP 配置中正确对齐。
 */
#define KMEM_CACHE(__struct, __flags)                                   \
	__kmem_cache_create_args(#__struct, sizeof(struct __struct),    \
			&(struct kmem_cache_args) {			\
				.align	= __alignof__(struct __struct), \
			}, (__flags))

kmem_cache_alloc_lru_noprof 分配 LRU 缓存中的对象

#define kmem_cache_alloc_lru(...)	alloc_hooks(kmem_cache_alloc_lru_noprof(__VA_ARGS__))

kvmalloc_array 分配大数组

#define kvmalloc_node_noprof(size, flags, node)	\
	__kvmalloc_node_noprof(PASS_BUCKET_PARAMS(size, NULL), flags, node)

static inline __alloc_size(1, 2) void *
kvmalloc_array_node_noprof(size_t n, size_t size, gfp_t flags, int node)
{
	size_t bytes;

	if (unlikely(check_mul_overflow(n, size, &bytes)))
		return NULL;

	return kvmalloc_node_noprof(bytes, flags, node);
}

#define kvmalloc_array_noprof(...)		kvmalloc_array_node_noprof(__VA_ARGS__, NUMA_NO_NODE)
#define kvmalloc_array(...)			alloc_hooks(kvmalloc_array_noprof(__VA_ARGS__))

kmalloc_slab 查找 kmalloc 缓存

/*
* 找到用于给定分配大小的 kmem_cache 结构  
*  
* 假设 size 大于零且不超过 KMALLOC_MAX_CACHE_SIZE,  
* 调用者必须检查这一点。  
*/
static inline struct kmem_cache *
kmalloc_slab(size_t size, kmem_buckets *b, gfp_t flags, unsigned long caller)
{
	unsigned int index;

	if (!b)
		b = &kmalloc_caches[kmalloc_type(flags, caller)];
	if (size <= 192)
		index = kmalloc_size_index[size_index_elem(size)];
	else
		index = fls(size - 1);

	return (*b)[index];
}

include/linux/slab.h

kmem_cache_alloc_node 在指定节点上分配对象

#define kmem_cache_alloc_node(...)	alloc_hooks(kmem_cache_alloc_node_noprof(__VA_ARGS__))

/**
 * kmem_cache_alloc_node - 在指定节点上分配对象
 * @s:要从中分配的缓存。
 * @gfpflags:请参见 kmalloc()。
 * @node:目标节点的节点号。
 *
 * 与 kmem_cache_alloc 相同,但它将在给定节点上分配内存,这可以提高 cpu 绑定结构的性能。
 *
 * 如果未设置__GFP_THISNODE,则可以回退到其他节点。
 *
 * 返回:指向新对象的指针或 %NULL(如果出现错误)
 */
void *kmem_cache_alloc_node_noprof(struct kmem_cache *s, gfp_t gfpflags, int node)
{
	void *ret = slab_alloc_node(s, NULL, gfpflags, node, _RET_IP_, s->object_size);

	trace_kmem_cache_alloc(_RET_IP_, ret, s, gfpflags, node);

	return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_node_noprof);

include/linux/vmalloc.h

__vmalloc 分配虚拟内存

extern void *__vmalloc_noprof(unsigned long size, gfp_t gfp_mask) __alloc_size(1);
#define __vmalloc(...)		alloc_hooks(__vmalloc_noprof(__VA_ARGS__))

mm/slab_common.c

calculate_alignment 计算对齐要求

/*
 * 确定对象的对齐方式,给定一组标志、用户指定的对齐方式和对象的大小。
 */
static unsigned int calculate_alignment(slab_flags_t flags,
		unsigned int align, unsigned int size)
{
/*
	 * 如果用户想要硬件缓存对齐的对象,则在对象足够大的情况下遵循该建议。
	 *
	 * 但是,硬件缓存对齐方式无法覆盖指定的对齐方式。如果这更大,那就使用它。
	 */
	if (flags & SLAB_HWCACHE_ALIGN) {
		unsigned int ralign;

		ralign = cache_line_size();
		while (size <= ralign / 2)
			ralign /= 2;
		align = max(align, ralign);
	}

	align = max(align, arch_slab_minalign());

	return ALIGN(align, sizeof(void *));
}

create_boot_cache 在系统启动阶段创建一个 slab 缓存

/* 在引导期间,当尚无可用的 slab 服务时创建缓存 */
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;
	struct kmem_cache_args kmem_args = {};

	/*
	 * kmalloc 高速缓存保证至少对齐该大小的最大 2 次方除数。对于 2 的幂次方大小,它是大小本身。
	 */
	if (flags & SLAB_KMALLOC)	//包含 SLAB_KMALLOC 标志,则根据对象大小计算对齐值
		align = max(align, 1U << (ffs(size) - 1));
	kmem_args.align = calculate_alignment(flags, align, size);	//计算最终的对齐要求

	err = do_kmem_cache_create(s, name, size, &kmem_args, flags);	//实际创建 slab 缓存

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

	s->refcount = -1;	/* 暂时免于合并 */
}

slab_unmergeable 检查 slab 是否不可合并

/*
 * Find a mergeable slab cache
 */
int slab_unmergeable(struct kmem_cache *s)
{
	if (slab_nomerge || (s->flags & SLAB_NEVER_MERGE))
		return 1;

	if (s->ctor)
		return 1;

#ifdef CONFIG_HARDENED_USERCOPY
	if (s->usersize)
		return 1;
#endif

	/*
	 * We may have set a slab to be unmergeable during bootstrap.
	 */
	if (s->refcount < 0)
		return 1;

	return 0;
}

setup_kmalloc_cache_index_table 设置 kmalloc 缓存索引表

/*
 * 小板大小的转换表 / 8 到 kmalloc 数组中的索引。这对于 192 <的 slab 是必需的,因为我们那里没有 2 个缓存大小的幂。较大板的尺寸可以使用 fls 确定。
 */
u8 kmalloc_size_index[24] __ro_after_init = {
	3,	/* 8 */
	4,	/* 16 */
	5,	/* 24 */
	5,	/* 32 */
	6,	/* 40 */
	6,	/* 48 */
	6,	/* 56 */
	6,	/* 64 */
	1,	/* 72 */
	1,	/* 80 */
	1,	/* 88 */
	1,	/* 96 */
	7,	/* 104 */
	7,	/* 112 */
	7,	/* 120 */
	7,	/* 128 */
	2,	/* 136 */
	2,	/* 144 */
	2,	/* 152 */
	2,	/* 160 */
	2,	/* 168 */
	2,	/* 176 */
	2,	/* 184 */
	2	/* 192 */
};

/*
 * 如果我们对 kmalloc 数组有奇怪的大对齐要求,请修补 size_index 表。这似乎只是 MIPS 的情况。标准拱门不会在此处生成任何代码。
 *
 * 允许的最大对齐方式为 256 字节,因为我们处理较小缓存的索引确定的方式。
 *
 * 确保如果有人开始摆弄 ARCH_KMALLOC_MINALIGN 不会发生任何疯狂的事情
 */
void __init setup_kmalloc_cache_index_table(void)
{
	unsigned int i;
	//KMALLOC_MIN_SIZE = 32
	BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
		!is_power_of_2(KMALLOC_MIN_SIZE));

	for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) {
		unsigned int elem = size_index_elem(i);	//(bytes - 1) / 8;

		if (elem >= ARRAY_SIZE(kmalloc_size_index))
			break;
		kmalloc_size_index[elem] = KMALLOC_SHIFT_LOW;	//ilog2(KMALLOC_MIN_SIZE)
	}

	if (KMALLOC_MIN_SIZE >= 64) {
		/*
		 * 如果对齐方式为 64 字节,则不使用 96 字节大小的高速缓存。
		 */
		for (i = 64 + 8; i <= 96; i += 8)
			kmalloc_size_index[size_index_elem(i)] = 7;

	}

	if (KMALLOC_MIN_SIZE >= 128) {
		/*
		 * 如果对齐方式为 128 字节,则不使用 192 字节大小的高速缓存。重定向 kmalloc 以改用 256 字节缓存。
		 */
		for (i = 128 + 8; i <= 192; i += 8)
			kmalloc_size_index[size_index_elem(i)] = 8;
	}
}

new_kmalloc_cache 创建新的 kmalloc 缓存

static struct kmem_cache *__init create_kmalloc_cache(const char *name,
						      unsigned int size,
						      slab_flags_t flags)
{
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);

	if (!s)
		panic("Out of memory when creating slab %s\n", name);

	create_boot_cache(s, name, size, flags | SLAB_KMALLOC, 0, size);
	list_add(&s->list, &slab_caches);
	s->refcount = 1;
	return s;
}

static void __init
new_kmalloc_cache(int idx, enum kmalloc_cache_type type)
{
	slab_flags_t flags = 0;
	unsigned int minalign = __kmalloc_minalign();
	unsigned int aligned_size = kmalloc_info[idx].size;
	int aligned_idx = idx;

	if ((KMALLOC_RECLAIM != KMALLOC_NORMAL) && (type == KMALLOC_RECLAIM)) {
		flags |= SLAB_RECLAIM_ACCOUNT;
	} else if (IS_ENABLED(CONFIG_MEMCG) && (type == KMALLOC_CGROUP)) {
		if (mem_cgroup_kmem_disabled()) {
			kmalloc_caches[type][idx] = kmalloc_caches[KMALLOC_NORMAL][idx];
			return;
		}
		flags |= SLAB_ACCOUNT;
	} else if (IS_ENABLED(CONFIG_ZONE_DMA) && (type == KMALLOC_DMA)) {
		flags |= SLAB_CACHE_DMA;
	}

#ifdef CONFIG_RANDOM_KMALLOC_CACHES
	if (type >= KMALLOC_RANDOM_START && type <= KMALLOC_RANDOM_END)
		flags |= SLAB_NO_MERGE;
#endif

	/*
	 * If CONFIG_MEMCG is enabled, disable cache merging for
	 * KMALLOC_NORMAL caches.
	 */
	if (IS_ENABLED(CONFIG_MEMCG) && (type == KMALLOC_NORMAL))
		flags |= SLAB_NO_MERGE;

	if (minalign > ARCH_KMALLOC_MINALIGN) {
		aligned_size = ALIGN(aligned_size, minalign);
		aligned_idx = __kmalloc_index(aligned_size, false);
	}

	if (!kmalloc_caches[type][aligned_idx])
		kmalloc_caches[type][aligned_idx] = create_kmalloc_cache(
					kmalloc_info[aligned_idx].name[type],
					aligned_size, flags);
	if (idx != aligned_idx)
		kmalloc_caches[type][idx] = kmalloc_caches[type][aligned_idx];
}

create_kmalloc_caches 创建 kmalloc 缓存

/*
* 创建 kmalloc 数组。一些常规的 kmalloc 数组可能已经创建,
* 因为需要它们来启用用于创建 slab 的分配。
 */
void __init create_kmalloc_caches(void)
{
	int i;
	enum kmalloc_cache_type type;

	/*
	 * 包括 KMALLOC_CGROUP (如果已定义CONFIG_MEMCG
	 */
	for (type = KMALLOC_NORMAL; type < NR_KMALLOC_TYPES; type++) {
		/* 大小不是 2 次方的缓存。 */
		if (KMALLOC_MIN_SIZE <= 32)
			new_kmalloc_cache(1, type);
		if (KMALLOC_MIN_SIZE <= 64)
			new_kmalloc_cache(2, type);

		/*大小为 2 次方的缓存。*/
		for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++)
			new_kmalloc_cache(i, type);
	}
#ifdef CONFIG_RANDOM_KMALLOC_CACHES
	random_kmalloc_seed = get_random_u64();
#endif

	/*Kmalloc 数组现在可用 */
	slab_state = UP;

	if (IS_ENABLED(CONFIG_SLAB_BUCKETS))
		kmem_buckets_cache = kmem_cache_create("kmalloc_buckets",
						       sizeof(kmem_buckets),
						       0, SLAB_NO_MERGE, NULL);
}

create_cache 创建 slab 缓存

static struct kmem_cache *create_cache(const char *name,
				       unsigned int object_size,
				       struct kmem_cache_args *args,
				       slab_flags_t flags)
{
	struct kmem_cache *s;
	int err;

	/* If a custom freelist pointer is requested make sure it's sane. */
	err = -EINVAL;
	if (args->use_freeptr_offset &&
	    (args->freeptr_offset >= object_size ||
	     !(flags & SLAB_TYPESAFE_BY_RCU) ||
	     !IS_ALIGNED(args->freeptr_offset, __alignof__(freeptr_t))))
		goto out;

	err = -ENOMEM;
	s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
	if (!s)
		goto out;
	err = do_kmem_cache_create(s, name, object_size, args, flags);
	if (err)
		goto out_free_cache;

	s->refcount = 1;
	list_add(&s->list, &slab_caches);
	return s;

out_free_cache:
	kmem_cache_free(kmem_cache, s);
out:
	return ERR_PTR(err);
}

__kmem_cache_create_args 创建 kmem_cache

/**
 * __kmem_cache_create_args - 创建 kmem 缓存。
 * @name:在 /proc/slabinfo 中用于标识此缓存的字符串。
 * @object_size:要在此缓存中创建的对象的大小。
 * @args:用于创建缓存的附加参数(参见 &struct kmem_cache_args)。
 * @flags:请参阅单个标志的描述。常见的在下面的描述中列出。
 *
 * 不要直接调用,请使用具有相同参数的 kmem_cache_create() 包装器。
 *
 * 常用@flags:
 *
 * &SLAB_ACCOUNT - memcg 的账户分配。
 *
 * &SLAB_HWCACHE_ALIGN - 在缓存行边界上对齐对象。
 *
 * &SLAB_RECLAIM_ACCOUNT - 对象是可回收的。
 *
 * &SLAB_TYPESAFE_BY_RCU - Slab 页面(不是单个对象)的释放因宽限期而延迟 - 使用前请参阅完整说明。
 *
 * 上下文:不能在中断中调用,但可以中断。
 *
 * 返回:成功时指向缓存的指针,失败时返回 NULL。
 */
struct kmem_cache *__kmem_cache_create_args(const char *name,
					    unsigned int object_size,
					    struct kmem_cache_args *args,
					    slab_flags_t flags)
{
	struct kmem_cache *s = NULL;
	const char *cache_name;
	int err;

#ifdef CONFIG_SLUB_DEBUG
	/*
	 * If no slab_debug was enabled globally, the static key is not yet
	 * enabled by setup_slub_debug(). Enable it if the cache is being
	 * created with any of the debugging flags passed explicitly.
	 * It's also possible that this is the first cache created with
	 * SLAB_STORE_USER and we should init stack_depot for it.
	 */
	if (flags & SLAB_DEBUG_FLAGS)
		static_branch_enable(&slub_debug_enabled);
	if (flags & SLAB_STORE_USER)
		stack_depot_init();
#else
	flags &= ~SLAB_DEBUG_FLAGS;
#endif

	mutex_lock(&slab_mutex);

	err = kmem_cache_sanity_check(name, object_size);
	if (err) {
		goto out_unlock;
	}

	if (flags & ~SLAB_FLAGS_PERMITTED) {
		err = -EINVAL;
		goto out_unlock;
	}

	/* 在 useroffset 值的 usersize 错误时失败关闭。 */
	if (!IS_ENABLED(CONFIG_HARDENED_USERCOPY) ||
	    WARN_ON(!args->usersize && args->useroffset) ||
	    WARN_ON(object_size < args->usersize ||
		    object_size - args->usersize < args->useroffset))
		args->usersize = args->useroffset = 0;

	if (!args->usersize)
		s = __kmem_cache_alias(name, object_size, args->align, flags,
				       args->ctor);
	if (s)
		goto out_unlock;

	cache_name = kstrdup_const(name, GFP_KERNEL);
	if (!cache_name) {
		err = -ENOMEM;
		goto out_unlock;
	}

	args->align = calculate_alignment(flags, args->align, object_size);
	s = create_cache(cache_name, object_size, args, flags);
	if (IS_ERR(s)) {
		err = PTR_ERR(s);
		kfree_const(cache_name);
	}

out_unlock:
	mutex_unlock(&slab_mutex);

	if (err) {
		if (flags & SLAB_PANIC)
			panic("%s: Failed to create slab '%s'. Error %d\n",
				__func__, name, err);
		else {
			pr_warn("%s(%s) failed with error %d\n",
				__func__, name, err);
			dump_stack();
		}
		return NULL;
	}
	return s;
}

mm/slub.c

order_objects 计算在给定阶数(order)下,一个 slab 中可以容纳的对象数量

static inline unsigned int order_objects(unsigned int order, unsigned int size)
{
	return ((unsigned int)PAGE_SIZE << order) / size;
}

calc_slab_order 根据 slab 对象的大小(size)计算分配的阶数(order)

/*
calc_slab_order:根据对象大小、最小阶数、最大阶数和允许的浪费比例,计算 slab 的分配阶数。
参数:
	size:slab 中单个对象的大小。
	min_order:分配的最小阶数。
	max_order:分配的最大阶数。
	fract_leftover:允许的浪费比例(分数形式,如 16 表示 1/16)。
返回值:
	返回计算出的 slab 分配阶数。

阶数的影响:
	阶数为 order 时,slab 的大小为 PAGE_SIZE * 2^order。
	较低的阶数(如 order = 0)优先被使用,因为它不会导致页面分配器的碎片化。
	较高的阶数虽然可以容纳更多对象,但可能会浪费更多内存。

浪费控制:
	如果 slab 的浪费空间超过总大小的 1/16(或其他指定比例),则尝试更高的阶数。
	通过这种方式,函数在内存利用率和性能之间取得平衡。

最小对象数:
	为了提高性能,slab 中必须包含足够数量的对象。如果对象数量过少,会导致频繁操作部分 slab 列表(partial list),增加锁争用。

最大阶数限制:
	当达到 max_order 时,函数优先选择较低的阶数,即使这会导致更多的内存浪费。
 */
static inline unsigned int calc_slab_order(unsigned int size,
		unsigned int min_order, unsigned int max_order,
		unsigned int fract_leftover)
{
	unsigned int order;

	//从 min_order 开始,逐步尝试更高的阶数,直到 max_order
	for (order = min_order; order <= max_order; order++) {

		unsigned int slab_size = (unsigned int)PAGE_SIZE << order;	//计算 slab 大小
		unsigned int rem;

		rem = slab_size % size;	//算 slab 大小与对象大小的余数,表示浪费的空间

		if (rem <= slab_size / fract_leftover)	//检查浪费比例
			break;
	}

	return order;
}

calculate_order 计算 slab 的阶数(order)

static unsigned int slub_min_order;
static unsigned int slub_max_order =
	IS_ENABLED(CONFIG_SLUB_TINY) ? 1 : PAGE_ALLOC_COSTLY_ORDER;
static unsigned int slub_min_objects;


static inline int calculate_order(unsigned int size)
{
	unsigned int order;
	unsigned int min_objects;
	unsigned int max_objects;
	unsigned int min_order;

	min_objects = slub_min_objects;
	if (!min_objects) {	//如果 slub_min_objects 未设置(为 0),则根据系统的 CPU 数量计算最小对象数
		/*
		 * 一些架构只会在内联 cpu 时更新它们,所以如果它只是 1,请不要相信这个数字。
		 * 但是我们也不想总是使用 nr_cpu_ids,就像在其他一些架构上一样,可能有很多可能的 cpu,但永远不会在线。
		 * 在这里,我们权衡了尝试避免在看起来比实际大的系统上出现过高的 order 和在看起来比它们实际小的系统上出现过低的 order 过低。
		 */
		unsigned int nr_cpus = num_present_cpus();
		if (nr_cpus <= 1)
			nr_cpus = nr_cpu_ids;
		//	fls返回nr_cpus最高有效位的位置
		min_objects = 4 * (fls(nr_cpus) + 1);
	}
	/* min_objects 不能为 0,因为 get_order (0) 未定义 */
	max_objects = max(order_objects(slub_max_order, size), 1U);
	min_objects = min(min_objects, max_objects);

	min_order = max_t(unsigned int, slub_min_order,
			  get_order(min_objects * size));
	if (order_objects(min_order, size) > MAX_OBJS_PER_PAGE)
		return get_order(size * MAX_OBJS_PER_PAGE) - 1;

	/*
	 * 尝试查找 slab 的最佳配置。其工作原理是首先尝试生成具有最佳配置的布局,然后逐渐退出。
	 *
	 * 我们从最多接受 1/16 的浪费开始,并尝试从 min_objects 派生/slab_min_order 到 slab_max_order 中找到满足约束的最小顺序。请注意,增加 Order 只会导致相同或更少的小数浪费,而不会更多。
	 *
	 * 如果失败,我们将提高可接受的废弃物比例,然后重试。最后一次迭代的分数为 1/2 将有效地接受任何浪费,并给我们由 min_objects 确定的顺序,只要至少单个对象适合slab_max_order。
	 */
	for (unsigned int fraction = 16; fraction > 1; fraction /= 2) {
		order = calc_slab_order(size, min_order, slub_max_order,
					fraction);
		if (order <= slub_max_order)
			return order;
	}

	/*
	 * 回退到默认阶数
	 */
	order = get_order(size);
	if (order <= MAX_PAGE_ORDER)
		return order;
	return -ENOSYS;
}

oo_make 描述 slab 的分配配置

/*
 * 字长结构,可以原子更新或读取,并且包含给定顺序的 slab 将包含的对象的顺序和数量。
 */
struct kmem_cache_order_objects {
	unsigned int x;
};


static inline struct kmem_cache_order_objects oo_make(unsigned int order,
		unsigned int size)
{
	// #define OO_SHIFT	16
	struct kmem_cache_order_objects x = {
		(order << OO_SHIFT) + order_objects(order, size)
	};

	return x;
}

oo_objects 获取对象数量

#define OO_SHIFT	16
#define OO_MASK		((1 << OO_SHIFT) - 1)

static inline unsigned int oo_objects(struct kmem_cache_order_objects x)
{
	return x.x & OO_MASK;
}

calculate_sizes 计算 slab 缓存中对象的布局

/*
 * calculate_sizes() determines the order and the distribution of data within
 * a slab object.
 */
static int calculate_sizes(struct kmem_cache_args *args, struct kmem_cache *s)
{
	slab_flags_t flags = s->flags;
	unsigned int size = s->object_size;
	unsigned int order;

	/*
	 * 将对象大小向上舍入到下一个单词边界。我们只能将自由指针放在字边界处,这决定了自由指针的可能位置。
	 */
	size = ALIGN(size, sizeof(void *));

	/*
	 * 对象的实际使用大小,包括对象本身和调试相关的元数据(如红区)
	 */
	s->inuse = size;

	if (((flags & SLAB_TYPESAFE_BY_RCU) && !args->use_freeptr_offset) ||	//RCU 安全
	    (flags & SLAB_POISON) || s->ctor ||									//对象填充 构造函数
	    ((flags & SLAB_RED_ZONE) &&											//红区
	     (s->object_size < sizeof(void *) || slub_debug_orig_size(s)))) {

		/* 将空闲指针放置在对象末尾 */
		s->offset = size;	//自由指针偏移量
		size += sizeof(void *);
	} else if ((flags & SLAB_TYPESAFE_BY_RCU) && args->use_freeptr_offset) {
		s->offset = args->freeptr_offset;
	} else {
		/*
		 * 如果没有特殊要求,则将空闲指针放置在对象的中间位置,以减少越界访问的风险。
		 */
		//自由指针偏移量
		s->offset = ALIGN_DOWN(s->object_size / 2, sizeof(void *));
	}

	kasan_cache_create(s, &size, &s->flags);
	/*
	 *SLUB 从偏移量 0 开始,紧接着存储一个对象。为了对齐对象,我们只需调整每个对象的大小以符合对齐方式即可。
	 */
	size = ALIGN(size, s->align);
	s->size = size;
	s->reciprocal_size = reciprocal_value(size);	//每个对象的大小的倒数,用于快速计算对象数量
	order = calculate_order(size);	// 计算 slab 的阶数(order),即每个 slab 占用的页面数

	if ((int)order < 0)
		return 0;

	s->allocflags = __GFP_COMP;

	if (s->flags & SLAB_CACHE_DMA)
		s->allocflags |= GFP_DMA;

	if (s->flags & SLAB_CACHE_DMA32)
		s->allocflags |= GFP_DMA32;

	if (s->flags & SLAB_RECLAIM_ACCOUNT)
		s->allocflags |= __GFP_RECLAIMABLE;

	/*
	 * Determine the number of objects per slab
	 */
	s->oo = oo_make(order, size);	//每个 slab 中的对象数量
	s->min = oo_make(get_order(size), size);	//每个 slab 的最小对象数量

	return !!oo_objects(s->oo);
}

alloc_slab_page 分配一个新的 slab 页面

/*
 * Slab allocation and freeing
 */
static inline struct slab *alloc_slab_page(gfp_t flags, int node,
		struct kmem_cache_order_objects oo)
{
	struct folio *folio;
	struct slab *slab;
	//从 oo 中提取分配的阶数(order)。阶数决定了分配的页数,2^order 表示分配的页数
	unsigned int order = oo_order(oo);
	//#define	NUMA_NO_NODE	(-1)
	if (node == NUMA_NO_NODE)	// node 为 NUMA_NO_NODE,表示不指定 NUMA 节点,调用 alloc_frozen_pages 分配页
		folio = (struct folio *)alloc_frozen_pages(flags, order);
	else
		//指定了 NUMA 节点,调用 __alloc_frozen_pages 在指定节点上分配页
		folio = (struct folio *)__alloc_frozen_pages(flags, order, node, NULL);

	if (!folio)
		return NULL;

	slab = folio_slab(folio);	//将 folio 转换为 slab 结构体
	__folio_set_slab(folio);	//设置 folio 的 slab 标志,表明该 folio 已被用作 slab
	//检查内存回收标志
	if (folio_is_pfmemalloc(folio))	//检查 folio 是否是由内存回收路径分配的(pfmemalloc 标志)
		slab_set_pfmemalloc(slab);	//slab_set_pfmemalloc 设置 slab 的 pfmemalloc 标志,表明该 slab 是为内存回收保留的

	return slab;
}

set_freepointer 设置空闲指针

/*
 * 返回自由列表指针 (ptr)。通过强化,这将使用保存指针的地址的 XOR 和每个缓存的随机数进行混淆。
 */
static inline freeptr_t freelist_ptr_encode(const struct kmem_cache *s,
					    void *ptr, unsigned long ptr_addr)
{
	unsigned long encoded;
	encoded = (unsigned long)ptr;

	return (freeptr_t){.v = encoded};
}

static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
	unsigned long freeptr_addr = (unsigned long)object + s->offset;

	*(freeptr_t *)freeptr_addr = freelist_ptr_encode(s, fp, freeptr_addr);
}

setup_object 初始化对象

static void *setup_object(struct kmem_cache *s, void *object)
{
	setup_object_debug(s, object);
	object = kasan_init_slab_obj(s, object);
	if (unlikely(s->ctor)) {
		kasan_unpoison_new_object(s, object);
		s->ctor(object);
		kasan_poison_new_object(s, object);
	}
	return object;
}

allocate_slab 分配一个新的 slab

static struct slab *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	struct slab *slab;
	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;

	/*
	 * 让初始高阶分配在内存压力下失败,因此我们回退到最小订单分配。
	 */
	alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;	//避免警告和重试,同时移除 __GFP_NOFAIL
	//允许直接回收(__GFP_DIRECT_RECLAIM),且当前分配的阶数(oo_order(oo))大于最小阶数(oo_order(s->min)),则调整标志以限制内存分配的行为
	if ((alloc_gfp & __GFP_DIRECT_RECLAIM) && oo_order(oo) > oo_order(s->min))
		alloc_gfp = (alloc_gfp | __GFP_NOMEMALLOC) & ~__GFP_RECLAIM;

	slab = alloc_slab_page(alloc_gfp, node, oo);
	if (unlikely(!slab)) {
		//如果失败(slab 为 NULL),则降级到最小阶数(s->min)并重试
		oo = s->min;
		alloc_gfp = flags;
		/*
		 * 分配可能由于碎片而失败。
		 * 如果可能,请尝试较低顺序的分配
		 */
		slab = alloc_slab_page(alloc_gfp, node, oo);
		if (unlikely(!slab))
			return NULL;
		stat(s, ORDER_FALLBACK);	//如果降级成功,调用 stat 更新统计信息,记录降级事件
	}

	slab->objects = oo_objects(oo);	//每个 slab 中的对象数量
	slab->inuse = 0;				//已使用对象数
	slab->frozen = 0;				//冻结对象数

	account_slab(slab, oo_order(oo), s, flags);	//更新 slab 的内存使用统计

	slab->slab_cache = s;			//设置 slab 的缓存指针(slab_cache)为当前 kmem_cache
	//设置对象链表
	start = slab_address(slab);		//获取 slab 的起始地址

	shuffle = shuffle_freelist(s, slab);	//调用 shuffle_freelist 决定是否需要随机化对象链表

	if (!shuffle) {
		slab->freelist = start;
		start = setup_object(s, start);
		//设置 slab 的空闲链表(freelist),并通过循环将所有对象链接起来
		//从第一个对象开始,遍历 slab 中的所有对象,直到倒数第二个对象(slab->objects - 1)
		for (idx = 0, p = start; idx < slab->objects - 1; idx++) {
			next = p + s->size;	//对象大小(s->size),计算下一个对象的地址
			next = setup_object(s, next);
			set_freepointer(s, p, next);	//将它们链接到空闲链表中
			p = next;			//将当前指针移动到下一个对象
		}
		set_freepointer(s, p, NULL);	//最后一个对象的空闲指针设置为 NULL
	}

	return slab;
}

new_slab 为指定的 kmem_cache 分配一个新的 slab

static struct slab *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	if (unlikely(flags & GFP_SLAB_BUG_MASK))
		flags = kmalloc_fix_flags(flags);
	//构造函数和 __GFP_ZERO 标志可能会冲突:__GFP_ZERO 会将分配的内存清零,而构造函数可能依赖未清零的内存
	WARN_ON_ONCE(s->ctor && (flags & __GFP_ZERO));

	return allocate_slab(s,
		flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);	//保留与内存回收和约束相关的标志
}

----------------内存初始化相关函数------------------

init_kmem_cache_node 初始化 kmem_cache_node 结构体

static void
init_kmem_cache_node(struct kmem_cache_node *n)
{
	n->nr_partial = 0;
	spin_lock_init(&n->list_lock);
	INIT_LIST_HEAD(&n->partial);
#ifdef CONFIG_SLUB_DEBUG
	atomic_long_set(&n->nr_slabs, 0);
	atomic_long_set(&n->total_objects, 0);
	INIT_LIST_HEAD(&n->full);
#endif
}

__add_partial 添加 slab 到部分空闲链表

/*
 * 管理部分分配的板。
 */
static inline void
__add_partial(struct kmem_cache_node *n, struct slab *slab, int tail)
{
	n->nr_partial++;	//表示当前节点中部分分配的 slab 数量增加了一个
	if (tail == DEACTIVATE_TO_TAIL)
		list_add_tail(&slab->slab_list, &n->partial);
	else
		list_add(&slab->slab_list, &n->partial);
	slab_set_node_partial(slab);	//标记该 slab 属于部分分配状态
}

early_kmem_cache_node_alloc 早期分配 kmem_cache_node

static struct kmem_cache *kmem_cache_node;

/*
 * 当前没有 kmalloc_node 的支持,因此需要手动分配。
 * 这是为 slab 缓存的第一个 slab 分配内存,因此不存在并发访问问题。
 * 该函数仅用于为 kmem_cache_node 分配内存,主要用于引导尚未初始化的节点。
 */
static void early_kmem_cache_node_alloc(int node)
{
	struct slab *slab;
	struct kmem_cache_node *n;

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

	slab = new_slab(kmem_cache_node, GFP_NOWAIT, node);

	BUG_ON(!slab);
	if (slab_nid(slab) != 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");
	}

	n = slab->freelist;
	BUG_ON(!n);

	slab->freelist = get_freepointer(kmem_cache_node, n);
	slab->inuse = 1;	//表示已使用一个对象
	kmem_cache_node->node[node] = n;	//将分配的 kmem_cache_node 对象存储到对应的 NUMA 节点中
	init_kmem_cache_node(n);

	/*
	 * 这里不需要锁,因为它刚刚初始化,没有并发访问。
	 * 将 slab 添加到部分空闲链表(partial list)中,便于后续分配和管理
	 * 使用 DEACTIVATE_TO_HEAD 标志表示将 slab 添加到链表头部
	 */
	__add_partial(n, slab, DEACTIVATE_TO_HEAD);
}

init_kmem_cache_nodes 初始化 NUMA 节点相关数据

#define for_each_node_mask(node, mask)                                  \
	for ((node) = 0; (node) < 1 && !nodes_empty(mask); (node)++)

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

	//遍历 slab_nodes 中的所有节点
	//slab_nodes 是一个位掩码,表示系统中启用的节点
	for_each_node_mask(node, slab_nodes) {
		struct kmem_cache_node *n;
		//表示系统还处于早期初始化阶段
		if (slab_state == DOWN) {
			early_kmem_cache_node_alloc(node);
			continue;
		}
		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;
}

alloc_kmem_cache_cpus 分配 CPU 相关的 slab 缓存数据

static void init_kmem_cache_cpus(struct kmem_cache *s)
{
	int cpu;
	struct kmem_cache_cpu *c;

	for_each_possible_cpu(cpu) {	//遍历所有可能的 CPU
		c = per_cpu_ptr(s->cpu_slab, cpu);	//获取每个 CPU 的缓存指针
		/* 初始化每个 CPU 缓存的本地锁(lock)。
		该锁用于保护 CPU 缓存的并发访问,确保分配和释放操作的线程安全 */
		local_lock_init(&c->lock);
		/*为当前 CPU 缓存生成一个初始事务 ID(tid)。
		tid 用于跟踪内存分配和释放的事务,帮助检测并发冲突或其他异常情况。 */
		c->tid = init_tid(cpu);
	}
}

static inline int alloc_kmem_cache_cpus(struct kmem_cache *s)
{
	BUILD_BUG_ON(PERCPU_DYNAMIC_EARLY_SIZE <
			NR_KMALLOC_TYPES * KMALLOC_SHIFT_HIGH *
			sizeof(struct kmem_cache_cpu));

	/*
	 *必须对齐到双字边界,双 cmpxchg 指令才能工作;请参见 __pcpu_double_call_return_bool()。
	 */
	s->cpu_slab = __alloc_percpu(sizeof(struct kmem_cache_cpu),
				     2 * sizeof(void *));	// 为每个 CPU 分配一个 kmem_cache_cpu 结构。

	if (!s->cpu_slab)
		return 0;

	init_kmem_cache_cpus(s);	//对分配的 cpu_slab 结构进行初始化

	return 1;
}

sysfs_slab_add 将 slab 缓存添加到 sysfs 文件系统中

  • slab 缓存对象暴露到 sysfs 文件系统中,以便用户或开发者可以通过文件系统查看和调试 slab 缓存的状态
/* Create a unique string id for a slab cache:
 *
 * Format	:[flags-]size
 */
static char *create_unique_id(struct kmem_cache *s)
{
	char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
	char *p = name;

	if (!name)
		return ERR_PTR(-ENOMEM);

	*p++ = ':';
	/*
	 * First flags affecting slabcache operations. We will only
	 * get here for aliasable slabs so we do not need to support
	 * too many flags. The flags here must cover all flags that
	 * are matched during merging to guarantee that the id is
	 * unique.
	 */
	if (s->flags & SLAB_CACHE_DMA)
		*p++ = 'd';
	if (s->flags & SLAB_CACHE_DMA32)
		*p++ = 'D';
	if (s->flags & SLAB_RECLAIM_ACCOUNT)
		*p++ = 'a';
	if (s->flags & SLAB_CONSISTENCY_CHECKS)
		*p++ = 'F';
	if (s->flags & SLAB_ACCOUNT)
		*p++ = 'A';
	if (p != name + 1)
		*p++ = '-';
	p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);

	if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
		kfree(name);
		return ERR_PTR(-EINVAL);
	}
	kmsan_unpoison_memory(name, p - name);
	return name;
}

static const struct kobj_type slab_ktype = {
	.sysfs_ops = &slab_sysfs_ops,
	.release = kmem_cache_release,
};

static const struct attribute_group slab_attr_group = {
	.attrs = slab_attrs,
};

static int sysfs_slab_add(struct kmem_cache *s)
{
	int err;
	const char *name;
	struct kset *kset = cache_kset(s);
	int unmergeable = slab_unmergeable(s);

	if (!unmergeable && disable_higher_order_debug &&
			(slub_debug & DEBUG_METADATA_FLAGS))
		unmergeable = 1;

	if (unmergeable) {
		/*
		 * Slabcache can never be merged so we can use the name proper.
		 * This is typically the case for debug situations. In that
		 * case we can catch duplicate names easily.
		 */
		sysfs_remove_link(&slab_kset->kobj, s->name);
		name = s->name;
	} else {
		/*
		 * 为板创建一个唯一的名称作为符号链接的目标。
		 */
		name = create_unique_id(s);
		if (IS_ERR(name))
			return PTR_ERR(name);
	}

	s->kobj.kset = kset;	//初始化并添加内核对象
	//将 kobject 添加到 sysfs
	err = kobject_init_and_add(&s->kobj, &slab_ktype, NULL, "%s", name);
	if (err)
		goto out;
	//创建属性组
	err = sysfs_create_group(&s->kobj, &slab_attr_group);
	if (err)
		goto out_del_kobj;

	if (!unmergeable) {
		/* Setup first alias */
		sysfs_slab_alias(s, s->name);
	}
out:
	if (!unmergeable)
		kfree(name);
	return err;
out_del_kobj:
	kobject_del(&s->kobj);
	goto out;
}

__kmem_cache_release 释放 slab 缓存

/**
 * kmem_cache_free - 解除分配对象
 * @s:分配来自的缓存。
 * @x:之前分配的对象。
 *
 * 释放以前从此缓存中分配的对象。
 */
void kmem_cache_free(struct kmem_cache *s, void *x)
{
	s = cache_from_obj(s, x);
	if (!s)
		return;
	trace_kmem_cache_free(_RET_IP_, x, s);
	slab_free(s, virt_to_slab(x), x, _RET_IP_);
}

static void free_kmem_cache_nodes(struct kmem_cache *s)
{
	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);
	}
}

void __kmem_cache_release(struct kmem_cache *s)
{
	cache_random_seq_destroy(s);
#ifndef CONFIG_SLUB_TINY
	free_percpu(s->cpu_slab);
#endif
	free_kmem_cache_nodes(s);
}

do_kmem_cache_create 创建一个新的 slab 缓存(slab cache)

int do_kmem_cache_create(struct kmem_cache *s, const char *name,
			 unsigned int size, struct kmem_cache_args *args,
			 slab_flags_t flags)
{
	int err = -EINVAL;

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

	s->flags = kmem_cache_flags(flags, s->name);

	s->align = args->align;
	s->ctor = args->ctor;

	if (!calculate_sizes(args, s))	//调用 calculate_sizes 函数计算 slab 缓存中对象的布局,包括对齐和元数据的偏移量
		goto out;

	//根据对象大小设置部分 slab 列表的最小值(min_partial)。
	//对象越大,部分 slab 列表的最小值越大,以减少对页面分配器的频繁调用。
	s->min_partial = min_t(unsigned long, MAX_PARTIAL, ilog2(s->size) / 2);
	s->min_partial = max_t(unsigned long, MIN_PARTIAL, s->min_partial);

	set_cpu_partial(s);	//调用 set_cpu_partial 函数初始化 CPU 局部缓存,用于优化 slab 分配的性能。

	/* 如果 slab 已启动,则初始化预先计算的随机空闲列表 */
	if (slab_state >= UP) {
		if (init_cache_random_seq(s))
			goto out;
	}

	if (!init_kmem_cache_nodes(s))	//初始化与 NUMA 节点相关的数据
		goto out;

	if (!alloc_kmem_cache_cpus(s))	//分配与 CPU 相关的 slab 缓存数据
		goto out;

	err = 0;

	/* 在早期启动期间不采用 Mutex */
	if (slab_state <= UP)
		goto out;

	/*
	 * 无法创建 sysfs 文件对于 SLUB 功能并不重要。
	 * 如果失败,请继续创建缓存,而不创建这些文件。
	 */
	if (sysfs_slab_add(s))	//尝试将 slab 缓存添加到 sysfs 文件系统中
		pr_err("SLUB: Unable to add cache %s to sysfs\n", s->name);

	if (s->flags & SLAB_STORE_USER)
		debugfs_slab_add(s);	//将 slab 缓存添加到 debugfs 文件系统中,用于调试目的

out:
	if (err)
		__kmem_cache_release(s);	//调用 __kmem_cache_release 释放已分配的资源
	return err;
}

bootstrap 初始化早期 kmem_cache 结构

/*
 * 用于使用页面分配器分配的早期 kmem_cache 结构。
 * 正确分配它们,然后修复可能指向错误 kmem_cache 结构的指针。
 */
static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
	int node;
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
	struct kmem_cache_node *n;

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

	/*
	 * 这会很早运行,只有引导处理器应该启动。 即使不是真的,IRQ 也没有上架,所以我们不能四处触发 IPI。
	 */
	//刷新 CPU slab
	__flush_cpu_slab(s, smp_processor_id());
	for_each_kmem_cache_node(s, node, n) {
		struct slab *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
	}
	list_add(&s->list, &slab_caches);
	return s;
}

kmem_cache_init 初始化 kmem_cache 和 kmem_cache_node

/*
 * 跟踪我们kmem_cache_nodes为其分配的 NUMA 节点。
 * 对应于 node_state[N_NORMAL_MEMORY],但在内存热插拔/热删除作期间可能会暂时不同。受 slab_mutex 保护。
*/
static nodemask_t slab_nodes;

void __init kmem_cache_init(void)
{
	static __initdata struct kmem_cache boot_kmem_cache,
		boot_kmem_cache_node;
	int node;

	kmem_cache_node = &boot_kmem_cache_node;
	kmem_cache = &boot_kmem_cache;

	/*
	 *初始化我们将为每个节点结构分配的 nodemask。在这里,我们还不需要slab_mutex。
	 */
	//遍历所有具有正常内存的节点(N_NORMAL_MEMORY),并将它们添加到 slab_nodes 节点掩码中
	for_each_node_state(node, N_NORMAL_MEMORY)
		node_set(node, slab_nodes);
	//创建基础 slab 缓存
	create_boot_cache(kmem_cache_node, "kmem_cache_node",
			sizeof(struct kmem_cache_node),
			SLAB_HWCACHE_ALIGN | SLAB_NO_OBJ_EXT, 0, 0);

	hotplug_memory_notifier(slab_memory_callback, SLAB_CALLBACK_PRI);

	//表示分配器已经部分初始化,可以分配节点结构
	slab_state = PARTIAL;
	//创建主 slab 缓存
	create_boot_cache(kmem_cache, "kmem_cache",
			offsetof(struct kmem_cache, node) +
				nr_node_ids * sizeof(struct kmem_cache_node *),
			SLAB_HWCACHE_ALIGN | SLAB_NO_OBJ_EXT, 0, 0);
	//调用 bootstrap 函数完成 kmem_cache 和 kmem_cache_node 的初始化,替换临时的 boot_kmem_cache 和 boot_kmem_cache_node
	kmem_cache = bootstrap(&boot_kmem_cache);
	kmem_cache_node = bootstrap(&boot_kmem_cache_node);

	/* 初始化 kmalloc 缓存*/
	setup_kmalloc_cache_index_table();	//设置 kmalloc 缓存索引表
	create_kmalloc_caches();			//创建 kmalloc 缓存

	/* 初始化 slab 缓存的空闲列表随机化功能,以增强安全性,防止内存攻击 */
	init_freelist_randomization();

	cpuhp_setup_state_nocalls(CPUHP_SLUB_DEAD, "slub:dead", NULL,
				  slub_cpu_dead);
	//SLUB: HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
	pr_info("SLUB: HWalign=%d, Order=%u-%u, MinObjects=%u, CPUs=%u, Nodes=%u\n",
		cache_line_size(),
		slub_min_order, slub_max_order, slub_min_objects,
		nr_cpu_ids, nr_node_ids);
}

kmem_cache_init_late Kmem 缓存初始化延迟

void __init kmem_cache_init_late(void)
{
#ifndef CONFIG_SLUB_TINY
	flushwq = alloc_workqueue("slub_flushwq", WQ_MEM_RECLAIM, 0);
	WARN_ON(!flushwq);
#endif
}

----------------内存分配相关函数------------------

slab_lock slab_unlock

/*
 * Per slab locking using the pagelock
 */
static __always_inline void slab_lock(struct slab *slab)
{
	bit_spin_lock(PG_locked, &slab->__page_flags);
}

static __always_inline void slab_unlock(struct slab *slab)
{
	bit_spin_unlock(PG_locked, &slab->__page_flags);
}

get_freelist 获取 slab 的空闲列表

static inline bool
__update_freelist_slow(struct slab *slab,
		      void *freelist_old, unsigned long counters_old,
		      void *freelist_new, unsigned long counters_new)
{
	bool ret = false;

	slab_lock(slab);
	if (slab->freelist == freelist_old &&
	    slab->counters == counters_old) {
		slab->freelist = freelist_new;
		slab->counters = counters_new;
		ret = true;
	}
	slab_unlock(slab);

	return ret;
}

/*
 * 必须禁用中断(以便回退代码正常工作),通常通过 _irqsave() 锁变体。在 PREEMPT_RT 上,preempt_disable() 是 bit_spin_lock() 的一部分,就足够了,因为该策略不允许在 hardirq 上下文中进行任何分配/释放作。因此,没有什么可以中断作。
 */
static inline bool __slab_update_freelist(struct kmem_cache *s, struct slab *slab,
		void *freelist_old, unsigned long counters_old,
		void *freelist_new, unsigned long counters_new,
		const char *n)
{
	bool ret;

	if (USE_LOCKLESS_FAST_PATH())
		lockdep_assert_irqs_disabled();

	if (s->flags & __CMPXCHG_DOUBLE) {
		ret = __update_freelist_fast(slab, freelist_old, counters_old,
				            freelist_new, counters_new);
	} else {
		ret = __update_freelist_slow(slab, freelist_old, counters_old,
				            freelist_new, counters_new);
	}
	if (likely(ret))
		return true;

	cpu_relax();
	stat(s, CMPXCHG_DOUBLE_FAIL);

#ifdef SLUB_DEBUG_CMPXCHG
	pr_info("%s %s: cmpxchg double redo ", n, s->name);
#endif

	return false;
}

/*
 * 检查 slab->freelist,然后将 freelist 转移到每 CPU freelist 或停用 slab。
 *
 * 如果返回值不为 NULL,则 slab 仍处于冻结状态。
 *
 * 如果此函数返回 NULL,则表示 slab 已解冻。
 */
static inline void *get_freelist(struct kmem_cache *s, struct slab *slab)
{
	struct slab new;
	unsigned long counters;
	void *freelist;

	lockdep_assert_held(this_cpu_ptr(&s->cpu_slab->lock));

	do {
		freelist = slab->freelist;
		counters = slab->counters;

		new.counters = counters;

		new.inuse = slab->objects;
		new.frozen = freelist != NULL;

	} while (!__slab_update_freelist(s, slab,
		freelist, counters,
		NULL, new.counters,
		"get_freelist"));

	return freelist;
}

get_partial 获取部分 slab 的空闲列表

/*
 * 尝试从特定节点分配部分 slab。
 */
static struct slab *get_partial_node(struct kmem_cache *s,
				     struct kmem_cache_node *n,
				     struct partial_context *pc)
{
	struct slab *slab, *slab2, *partial = NULL;
	unsigned long flags;
	unsigned int partial_slabs = 0;

	/*
	 * 猥亵检查。如果我们错误地没有看到 partial slab,那么我们只分配一个空 slab。如果我们错误地尝试获取部分 slab,但没有可用的 slab,则 get_partial() 将返回 NULL。
	 */
	if (!n || !n->nr_partial)
		return NULL;

	spin_lock_irqsave(&n->list_lock, flags);
	list_for_each_entry_safe(slab, slab2, &n->partial, slab_list) {
		if (!pfmemalloc_match(slab, pc->flags))
			continue;

		if (IS_ENABLED(CONFIG_SLUB_TINY) || kmem_cache_debug(s)) {
			void *object = alloc_single_from_partial(s, n, slab,
							pc->orig_size);
			if (object) {
				partial = slab;
				pc->object = object;
				break;
			}
			continue;
		}

		remove_partial(n, slab);

		if (!partial) {
			partial = slab;
			stat(s, ALLOC_FROM_PARTIAL);

			if ((slub_get_cpu_partial(s) == 0)) {
				break;
			}
		} else {
			put_cpu_partial(s, slab, 0);
			stat(s, CPU_PARTIAL_NODE);

			if (++partial_slabs > slub_get_cpu_partial(s) / 2) {
				break;
			}
		}
	}
	spin_unlock_irqrestore(&n->list_lock, flags);
	return partial;
}

/*
 *获取 partial slab,将其锁定并返回。
 */
static struct slab *get_partial(struct kmem_cache *s, int node,
				struct partial_context *pc)
{
	struct slab *slab;
	int searchnode = node;

	if (node == NUMA_NO_NODE)
		searchnode = numa_mem_id();

	slab = get_partial_node(s, get_node(s, searchnode), pc);
	if (slab || (node != NUMA_NO_NODE && (pc->flags & __GFP_THISNODE)))
		return slab;

	return get_any_partial(s, pc);
}

deactivate_slab 处理 slab 的去激活

/*
 * 将 CPU slab 的空闲对象链表(freelist)合并到 slab 的全局空闲链表中,解冻 slab,并将其放置到适当的 slab 列表中(如部分 slab 列表或空 slab 列表)
 */
static void deactivate_slab(struct kmem_cache *s, struct slab *slab,
			    void *freelist)
{
	struct kmem_cache_node *n = get_node(s, slab_nid(slab));
	int free_delta = 0;
	void *nextfree, *freelist_iter, *freelist_tail;
	int tail = DEACTIVATE_TO_HEAD;
	unsigned long flags = 0;
	struct slab new;
	struct slab old;

	if (READ_ONCE(slab->freelist)) {
		stat(s, DEACTIVATE_REMOTE_FREES);
		tail = DEACTIVATE_TO_TAIL;
	}

	/*
	 * 遍历 CPU slab 的空闲链表,统计空闲对象的数量(free_delta)
	 */
	freelist_tail = NULL;
	freelist_iter = freelist;
	while (freelist_iter) {
		nextfree = get_freepointer(s, freelist_iter);

		/*
		 * 链表中存在损坏的对象, 跳过损坏的部分
		 */
		if (freelist_corrupted(s, slab, &freelist_iter, nextfree))
			break;

		freelist_tail = freelist_iter;
		free_delta++;

		freelist_iter = nextfree;
	}

	/*
	 * 第二阶段:解冻 slab,同时将每 CPU 的空闲列表拼接到 slab 的空闲列表的头部。
	 */
	do {
		old.freelist = READ_ONCE(slab->freelist);
		old.counters = READ_ONCE(slab->counters);
		VM_BUG_ON(!old.frozen);

		/* Determine target state of the slab */
		new.counters = old.counters;
		new.frozen = 0;
		if (freelist_tail) {
			new.inuse -= free_delta;
			set_freepointer(s, freelist_tail, old.freelist);
			new.freelist = freelist;
		} else {
			new.freelist = old.freelist;
		}
	} while (!slab_update_freelist(s, slab,
		old.freelist, old.counters,
		new.freelist, new.counters,
		"unfreezing slab"));

	/*
	 * 第 3 阶段:根据更新的状态作 slab 列表。
	 */
	if (!new.inuse && n->nr_partial >= s->min_partial) {
		stat(s, DEACTIVATE_EMPTY);
		discard_slab(s, slab);
		stat(s, FREE_SLAB);
	} else if (new.freelist) {
		spin_lock_irqsave(&n->list_lock, flags);
		add_partial(n, slab, tail);
		spin_unlock_irqrestore(&n->list_lock, flags);
		stat(s, tail);
	} else {
		stat(s, DEACTIVATE_FULL);
	}
}

__slab_alloc 慢速分配函数

  • 1.尝试从无锁空闲列表中分配对象
  • 2.如果无锁空闲列表为空,则尝试从常规空闲列表中获取对象
  • 3.如果常规空闲列表也为空,则尝试从部分 slab 列表中获取对象
  • 4.如果部分 slab 列表也为空,则调用页面分配器分配一个新的 slab
  • 5.如果分配成功,则返回对象指针,否则返回 NULL
/*
1. 慢速路径的触发条件
无锁空闲列表为空:
快速路径依赖于无锁空闲列表来快速分配内存对象。如果该列表为空,则需要进入慢速路径。
调试任务:
如果启用了调试功能(如内存跟踪或一致性检查),慢速路径会执行额外的调试操作。
2. 处理逻辑
接管常规空闲列表:

如果常规空闲列表(regular freelist)中有新释放的对象,慢速路径会将其接管并转换为无锁空闲列表,同时清空常规空闲列表。
这种操作仍然非常高效,因为无需从其他数据结构中分配新对象。
回退到部分 slab 列表:

如果常规空闲列表也无法满足分配需求,慢速路径会尝试从部分 slab 列表(partial slab lists)中获取对象。
它会从部分 slab 的空闲列表中取出第一个对象用于分配,并将剩余的对象移动到无锁空闲列表中。
分配新 slab:

如果部分 slab 列表也无法提供对象,慢速路径会调用页面分配器(page allocator)分配一个新的 slab。
这是最慢的路径,因为它涉及到分配新的物理内存页并初始化 slab 数据结
 */
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
			  unsigned long addr, struct kmem_cache_cpu *c, unsigned int orig_size)
{
	void *freelist;
	struct slab *slab;
	unsigned long flags;
	struct partial_context pc;
	bool try_thisnode = true;

	stat(s, ALLOC_SLOWPATH);

reread_slab:

	slab = READ_ONCE(c->slab);
	if (!slab) {
		/*
		 * 如果节点不在线或没有正常内存,则忽略 Node constraint
		 */
		if (unlikely(node != NUMA_NO_NODE &&
			     !node_isset(node, slab_nodes)))
			node = NUMA_NO_NODE;
		goto new_slab;	//尝试分配一个新的 slab
	}

	if (unlikely(!node_match(slab, node))) {
		/*
		 * same as above but node_match() being false already
		 * implies node != NUMA_NO_NODE
		 */
		if (!node_isset(node, slab_nodes)) {
			node = NUMA_NO_NODE;
		} else {
			stat(s, ALLOC_NODE_MISMATCH);
			goto deactivate_slab;
		}
	}

	/*
	 * By rights, we should be searching for a slab page that was
	 * PFMEMALLOC but right now, we are losing the pfmemalloc
	 * information when the page leaves the per-cpu allocator
	 */
	if (unlikely(!pfmemalloc_match(slab, gfpflags)))
		goto deactivate_slab;

	/* 必须再次检查 c->slab,以防我们被抢占并更改 */
	local_lock_irqsave(&s->cpu_slab->lock, flags);
	if (unlikely(slab != c->slab)) {
		local_unlock_irqrestore(&s->cpu_slab->lock, flags);
		goto reread_slab;
	}
	freelist = c->freelist;
	if (freelist)
		goto load_freelist;

	freelist = get_freelist(s, slab);

	if (!freelist) {
		c->slab = NULL;
		c->tid = next_tid(c->tid);
		local_unlock_irqrestore(&s->cpu_slab->lock, flags);
		stat(s, DEACTIVATE_BYPASS);
		goto new_slab;
	}

	stat(s, ALLOC_REFILL);

load_freelist:

	lockdep_assert_held(this_cpu_ptr(&s->cpu_slab->lock));

	/*
	 * freelist 指向要使用的对象列表。slab 指向从中获取对象的 slab。该 slab 必须被冻结,每 CPU 分配才能正常工作。
	 */
	VM_BUG_ON(!c->slab->frozen);
	c->freelist = get_freepointer(s, freelist);
	c->tid = next_tid(c->tid);
	local_unlock_irqrestore(&s->cpu_slab->lock, flags);
	return freelist;

deactivate_slab:

	local_lock_irqsave(&s->cpu_slab->lock, flags);
	if (slab != c->slab) {
		local_unlock_irqrestore(&s->cpu_slab->lock, flags);
		goto reread_slab;
	}
	freelist = c->freelist;
	c->slab = NULL;
	c->freelist = NULL;
	c->tid = next_tid(c->tid);
	local_unlock_irqrestore(&s->cpu_slab->lock, flags);
	deactivate_slab(s, slab, freelist);

new_slab:

new_objects:

	pc.flags = gfpflags;
	/*
	 * 当指示首选节点但没有__GFP_THISNODE
	 *
	 * 1) 尝试仅通过在 pc.flags 中为 get_partial() __GFP_THISNODE来从目标节点获取部分 slab
	 * 2) 如果 1) 失败,请尝试使用 GPF_NOWAIT |__GFP_THISNODE机会主义
	 * 3) 如果 2) 失败,请使用原始 GFP标志重试,这将允许 get_partial() 在可能从其他节点分配新页面之前尝试其他节点的部分列表
	 */
	if (unlikely(node != NUMA_NO_NODE && !(gfpflags & __GFP_THISNODE)
		     && try_thisnode))
		pc.flags = GFP_NOWAIT | __GFP_THISNODE;

	pc.orig_size = orig_size;
	slab = get_partial(s, node, &pc);	//尝试从部分 slab 列表中获取一个 slab
	if (slab) {
		freelist = freeze_slab(s, slab);	//将 slab 冻结
		goto retry_load_slab;				//继续分配
	}
	//如果部分 slab 列表无法满足需求,调用 new_slab 分配一个新的 slab
	slub_put_cpu_ptr(s->cpu_slab);
	slab = new_slab(s, pc.flags, node);
	c = slub_get_cpu_ptr(s->cpu_slab);

	if (unlikely(!slab)) {	//分配失败
		//检查是否可以尝试其他节点
		if (node != NUMA_NO_NODE && !(gfpflags & __GFP_THISNODE)
		    && try_thisnode) {
			try_thisnode = false;
			goto new_objects;	//如果可以,重新尝试分配。
		}
		return NULL;
	}

	stat(s, ALLOC_SLAB);

	/*
	 * 还没有其他对 slab 的引用,因此我们可以在没有 cmpxchg 的情况下自由地使用它
	 */
	//初始化新 slab
	freelist = slab->freelist;
	slab->freelist = NULL;
	slab->inuse = slab->objects;
	slab->frozen = 1;

retry_load_slab:

	local_lock_irqsave(&s->cpu_slab->lock, flags);
	if (unlikely(c->slab)) {
		void *flush_freelist = c->freelist;
		struct slab *flush_slab = c->slab;

		c->slab = NULL;
		c->freelist = NULL;
		c->tid = next_tid(c->tid);

		local_unlock_irqrestore(&s->cpu_slab->lock, flags);

		deactivate_slab(s, flush_slab, flush_freelist);

		stat(s, CPUSLAB_FLUSH);

		goto retry_load_slab;
	}
	c->slab = slab;

	goto load_freelist;
}

/*
 * ___slab_alloc() 的包装器,用于尚未禁用抢占的上下文。通过重新获取每 CPU 区域指针来补偿可能的 cpu 更改。
 */
static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
			  unsigned long addr, struct kmem_cache_cpu *c, unsigned int orig_size)
{
	void *p;

#ifdef CONFIG_PREEMPT_COUNT
	/*
	 * 在禁用抢占之前,我们可能已被抢占并重新安排在不同的 CPU 上。需要重新加载 cpu 区域指针。
	 * 禁用抢占以避免在获取 cpu_slab 时被抢占。
	 */
	c = slub_get_cpu_ptr(s->cpu_slab);
#endif

	p = ___slab_alloc(s, gfpflags, node, addr, c, orig_size);
#ifdef CONFIG_PREEMPT_COUNT
	//启用抢占
	slub_put_cpu_ptr(s->cpu_slab);
#endif
	return p;
}

__slab_alloc_node 内存分配函数

static __always_inline void *__slab_alloc_node(struct kmem_cache *s,
		gfp_t gfpflags, int node, unsigned long addr, size_t orig_size)
{
	struct kmem_cache_cpu *c;
	struct slab *slab;
	unsigned long tid;
	void *object;

redo:
	/*
	 * 必须通过此 cpu ptr 读取 kmem_cache CPU 数据。已启用抢占。我们可以在从一个 cpu 区域读取数据时在 cpu 之间来回切换。只要我们在执行 cmpxchg 时再次使用原始 cpu,这并不重要。
	 *
	 * 我们必须保证 tid 和 kmem_cache_cpu 在同一个 cpu 上检索。我们首先读取 kmem_cache_cpu 指针,然后使用它来读取 tid。如果我们在两次读取之间被抢占并切换到另一个 cpu,那没关系,因为两者仍然与同一个 cpu 相关联,cmpxchg 稍后将验证 cpu。
	 */
	c = raw_cpu_ptr(s->cpu_slab);
	tid = READ_ONCE(c->tid);

	/*
	 * 这里使用的 Irqless 对象 alloc/free 算法取决于获取 cpu_slab 数据的顺序。tid 应该在 C 上的任何内容之前获取,以保证与前一个 tid 关联的对象和 slab 不会与当前 tid 一起使用。如果我们先获取 tid,object 和 slab 可能是与下一个 tid 关联的,我们的 alloc/free 请求将失败。在这种情况下,我们将重试。所以,没问题。
	 */
	barrier();

	/*
	 * 事务 ID 对于每个 CPU 和每个 CPU 队列上的每个作都是全局唯一的。
	 * 因此,他们可以保证cmpxchg_double发生在正确的处理器上,并且两者之间没有对链表的作。
	 */
	object = c->freelist;
	slab = c->slab;

	if (!USE_LOCKLESS_FAST_PATH() ||
	    unlikely(!object || !slab || !node_match(slab, node))) {
		//慢速分配
		object = __slab_alloc(s, gfpflags, node, addr, c, orig_size);
	} else {
		void *next_object = get_freepointer_safe(s, object);	//获取空闲列表中的下一个对象指针
		/*
		 * cmpxchg 仅在没有其他作且我们使用的是正确的处理器时才会匹配。
		 *
		 * cmpxchg 以原子方式执行以下作(没有锁语义!
		 * 1.将第一个指针重新定位到当前每 CPU 区域。
		 * 2.验证 tid 和 freelist 是否未更改
		 * 3.如果未更改,请替换 tid 和 freelist
		 *
		 * 由于这是没有锁语义的,因此保护仅针对在此 CPU 上执行的代码 * 而不是* 其他 CPU 的访问。
		 */
		if (unlikely(!__update_cpu_freelist_fast(s, object, next_object, tid))) {	// 尝试原子更新空闲列表(通过 cmpxchg 实现)
			//如果更新失败(如由于并发操作导致的冲突),记录失败信息并重试分配(跳转到 redo 标签)
			note_cmpxchg_failure("slab_alloc", s, tid);
			goto redo;
		}
		//预取下一个空闲对象的指针,优化后续分配的性能
		prefetch_freepointer(s, next_object);
		//更新分配统计信息,记录快速路径的使用情况
		stat(s, ALLOC_FASTPATH);
	}

	return object;
}

slab_alloc_node 分配内存

/*
 * 内联 fastpath,以便分配函数 (kmalloc, kmem_cache_alloc) 将 fastpath 折叠到其函数中。
 * 因此,对于可以在 fastpath 上满足的请求,没有函数调用开销。
 *
 * 快速路径首先尝试从无锁空闲列表(lockless freelist)中分配对象。如果失败,则回退到慢速路径(__slab_alloc_node)
 *
 * 否则,我们可以简单地从无锁空闲列表中选择下一个对象。
 */
static __fastpath_inline void *slab_alloc_node(struct kmem_cache *s, struct list_lru *lru,
		gfp_t gfpflags, int node, unsigned long addr, size_t orig_size)
{
	void *object;
	bool init = false;

	s = slab_pre_alloc_hook(s, gfpflags);	//nothing
	if (unlikely(!s))
		return NULL;

	object = kfence_alloc(s, orig_size, gfpflags);	//NULL
	if (unlikely(object))
		goto out;

	object = __slab_alloc_node(s, gfpflags, node, addr, orig_size);

out:
	/*
	 * 当 init 等于 'true' 时,就像 kzalloc() 系列一样,只有 @orig_size 个字节可能被归零,而不是 s->object_size如果由于 memcg_slab_post_alloc_hook() 而失败,object 将被设置为 NULL
	 */
	slab_post_alloc_hook(s, lru, gfpflags, 1, &object, init, orig_size);

	return object;
}

__kmalloc_cache_noprof 分配内存

void *__kmalloc_cache_noprof(struct kmem_cache *s, gfp_t gfpflags, size_t size)
{
	void *ret = slab_alloc_node(s, NULL, gfpflags, NUMA_NO_NODE,
					    _RET_IP_, size);

	trace_kmalloc(_RET_IP_, ret, size, s->size, gfpflags, NUMA_NO_NODE);

	ret = kasan_kmalloc(s, ret, size, gfpflags);
	return ret;
}
EXPORT_SYMBOL(__kmalloc_cache_noprof);

kmem_cache_alloc_lru_noprof 分配LRU内存

void *kmem_cache_alloc_lru_noprof(struct kmem_cache *s, struct list_lru *lru,
			   gfp_t gfpflags)
{
	void *ret = slab_alloc_node(s, lru, gfpflags, NUMA_NO_NODE, _RET_IP_,
				    s->object_size);

	trace_kmem_cache_alloc(_RET_IP_, ret, s, gfpflags, NUMA_NO_NODE);

	return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_lru_noprof);

__kmalloc_large_node_noprof 分配大内存块

/*
 * 为避免不必要的开销,我们直接将大型分配请求传递给页面分配器。
 * 我们使用 __GFP_COMP,因为我们需要知道分配顺序以便在 kfree 中正确释放页面。
 */
static void *___kmalloc_large_node(size_t size, gfp_t flags, int node)
{
	struct folio *folio;
	void *ptr = NULL;
	unsigned int order = get_order(size);

	if (unlikely(flags & GFP_SLAB_BUG_MASK))
		flags = kmalloc_fix_flags(flags);

	/* 添加 __GFP_COMP 标志,确保分配的页面可以正确地被释放(需要知道分配阶数) */
	flags |= __GFP_COMP;
	/* 分配页面 */
	folio = (struct folio *)alloc_pages_node_noprof(node, flags, order);
	if (folio) {
		/* 获取页面的起始地址 */
		ptr = folio_address(folio);
		/* 更新页面的统计信息(lruvec_stat_mod_folio),标记为不可回收的 slab 内存 */
		lruvec_stat_mod_folio(folio, NR_SLAB_UNRECLAIMABLE_B,
				      PAGE_SIZE << order);
		/* 指示它是通过 kmalloc 分配的大块内存(__folio_set_large_kmalloc) */
		__folio_set_large_kmalloc(folio);
	}

	ptr = kasan_kmalloc_large(ptr, size, flags);
	/* As ptr might get tagged, call kmemleak hook after KASAN. */
	kmemleak_alloc(ptr, size, 1, flags);
	kmsan_kmalloc_large(ptr, size, flags);

	return ptr;
}

void *__kmalloc_large_node_noprof(size_t size, gfp_t flags, int node)
{
	void *ret = ___kmalloc_large_node(size, flags, node);

	trace_kmalloc(_RET_IP_, ret, size, PAGE_SIZE << get_order(size),
		      flags, node);
	return ret;
}
EXPORT_SYMBOL(__kmalloc_large_node_noprof);

__do_kmalloc_node 分配内存

static __always_inline
void *__do_kmalloc_node(size_t size, kmem_buckets *b, gfp_t flags, int node,
			unsigned long caller)
{
	struct kmem_cache *s;
	void *ret;

	if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) {
		ret = __kmalloc_large_node_noprof(size, flags, node);
		trace_kmalloc(caller, ret, size,
			      PAGE_SIZE << get_order(size), flags, node);
		return ret;
	}

	if (unlikely(!size))
		return ZERO_SIZE_PTR;

	s = kmalloc_slab(size, b, flags, caller);
	
	ret = slab_alloc_node(s, NULL, flags, node, caller, size);
	ret = kasan_kmalloc(s, ret, size, flags);
	trace_kmalloc(caller, ret, size, s->size, flags, node);
	return ret;
}

__kvmalloc_node 尝试分配物理上连续的内存块。如果分配失败,则会回退到非连续内存分配(通过 vmalloc)

/**
 * __kvmalloc_node - 尝试分配物理连续内存,但在失败时回退到非连续 (vmalloc) 分配。
 * @size: 请求的大小。
 * @b: 要从中分配的 kmalloc 桶集合。
 * @flags: 分配的 gfp 掩码 - 必须与 GFP_KERNEL 兼容(超集)。
 * @node: 要分配的 numa 节点。
 *
 * 使用 kmalloc 获取内存,但如果分配失败,则回退到 vmalloc 分配器。使用 kvfree 释放内存。
 *
 * 不支持 GFP_NOWAIT 和 GFP_ATOMIC,也不支持 __GFP_NORETRY 修饰符。
 * 支持 __GFP_RETRY_MAYFAIL,且仅在 kmalloc 优于 vmalloc 回退时使用,因为回退可能会导致明显的性能缺陷。
 *
 * 返回值: 指向分配内存的指针,失败时返回 %NULL。
 */
void *__kvmalloc_node_noprof(DECL_BUCKET_PARAMS(size, b), gfp_t flags, int node)
{
	void *ret;

	/*
	 * 对于子页面请求,回退到 vmalloc 实际上没有意义
	 */
	ret = __do_kmalloc_node(size, PASS_BUCKET_PARAM(b),
				kmalloc_gfp_adjust(flags, size),
				node, _RET_IP_);
	/* 如果请求的内存大小小于或等于页面大小(PAGE_SIZE),即使 kmalloc 失败,
	 * 也不会回退到 vmalloc,因为对于小内存块,使用 vmalloc 不合理。 */
	if (ret || size <= PAGE_SIZE)
		return ret;

	/* 不支持通过 vmalloc 进行非睡眠分配 */
	if (!gfpflags_allow_blocking(flags))
		return NULL;

	/* 如果请求的内存大小超过 INT_MAX,认为这是一个不合理的请求 */
	if (unlikely(size > INT_MAX)) {
		WARN_ON_ONCE(!(flags & __GFP_NOWARN));
		return NULL;
	}

	/*
	* kvmalloc() 始终可以使用 VM_ALLOW_HUGE_VMAP,
	* 因为调用者已经无法对结果指针做任何假设,
	* 也无法进行保护操作。
	*/
	return __vmalloc_node_range_noprof(size, 1, VMALLOC_START, VMALLOC_END,
			flags, PAGE_KERNEL, VM_ALLOW_HUGE_VMAP,
			node, __builtin_return_address(0));
}
EXPORT_SYMBOL(__kvmalloc_node_noprof);

--------------------内存释放相关函数------------------

__slab_free 释放内存

/*
 * 路径处理速度慢。这仍然可以频繁调用,因为在大多数处理负载中,对象的生命周期比 cpu slab 长。
 *
 * 因此,我们仍然尝试减少缓存行的使用。只需拿起石板锁并释放该物品。如果不需要额外的部分板处理,那么我们可以立即返回。
 */
static void __slab_free(struct kmem_cache *s, struct slab *slab,
			void *head, void *tail, int cnt,
			unsigned long addr)

{
	void *prior;
	int was_frozen;
	struct slab new;
	unsigned long counters;
	struct kmem_cache_node *n = NULL;
	unsigned long flags;
	bool on_node_partial;

	stat(s, FREE_SLOWPATH);

	if (IS_ENABLED(CONFIG_SLUB_TINY) || kmem_cache_debug(s)) {
		free_to_partial_list(s, slab, head, tail, cnt, addr);
		return;
	}
	//释放对象并更新 slab 状态
	do {
		if (unlikely(n)) {
			spin_unlock_irqrestore(&n->list_lock, flags);
			n = NULL;
		}
		prior = slab->freelist;
		counters = slab->counters;
		set_freepointer(s, tail, prior);
		new.counters = counters;
		was_frozen = new.frozen;
		new.inuse -= cnt;
		if ((!new.inuse || !prior) && !was_frozen) {
			/* Needs to be taken off a list */
			if (!kmem_cache_has_cpu_partial(s) || prior) {

				n = get_node(s, slab_nid(slab));
				/*
				 * 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);

				on_node_partial = slab_test_node_partial(slab);
			}
		}

	} while (!slab_update_freelist(s, slab,
		prior, counters,
		head, new.counters,
		"__slab_free"));
	//处理未加锁的情况
	if (likely(!n)) {

		if (likely(was_frozen)) {
			/*
			 * The list lock was not taken therefore no list
			 * activity can be necessary.
			 */
			stat(s, FREE_FROZEN);
		} else if (kmem_cache_has_cpu_partial(s) && !prior) {
			/*
			 * If we started with a full slab then put it onto the
			 * per cpu partial list.
			 */
			put_cpu_partial(s, slab, 1);
			stat(s, CPU_PARTIAL_FREE);
		}

		return;
	}
	//处理部分 slab 列表
	/*
	 * This slab was partially empty but not on the per-node partial list,
	 * in which case we shouldn't manipulate its list, just return.
	 */
	if (prior && !on_node_partial) {
		spin_unlock_irqrestore(&n->list_lock, flags);
		return;
	}

	if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
		goto slab_empty;

	/*
	 * 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)) {
		add_partial(n, slab, DEACTIVATE_TO_TAIL);
		stat(s, FREE_ADD_PARTIAL);
	}
	spin_unlock_irqrestore(&n->list_lock, flags);
	return;

slab_empty:
	if (prior) {
		/*
		 * Slab on the partial list.
		 */
		remove_partial(n, slab);
		stat(s, FREE_REMOVE_PARTIAL);
	}

	spin_unlock_irqrestore(&n->list_lock, flags);
	stat(s, FREE_SLAB);
	discard_slab(s, slab);
}

do_slab_free 释放内存

/*
 * 具有强制内联的 Fastpath 以生成 kfree 和 kmem_cache_free,无需额外的函数调用即可执行 fastpath 释放。
 *
 * 只有当我们释放到该处理器的当前 cpu slab 时,快速路径才可用。如果我们之前刚刚分配了 item,则通常会出现这种情况。
 *
 * 如果 fastpath 不可用,则回退到 __slab_free 我们处理各种特殊处理。
 *
 * 通过指定 head 和 tail ptr 以及对象计数 (cnt),可以批量释放具有多个对象(所有对象都指向同一个 slab)的空闲列表。由正在设置的尾部指针指示批量释放。
 */
static __always_inline void do_slab_free(struct kmem_cache *s,
				struct slab *slab, void *head, void *tail,
				int cnt, unsigned long addr)
{
	struct kmem_cache_cpu *c;
	unsigned long tid;
	void **freelist;

redo:
	/*
	 * 确定当前每个 CPU 的 cpus slab。之后 cpu 可能会发生变化。但是,这并不重要,因为数据是通过此指针检索的。如果我们在 cmpxchg 期间位于同一个 cpu 上,那么 free 将成功。
	 */
	c = raw_cpu_ptr(s->cpu_slab);
	tid = READ_ONCE(c->tid);

	/* 与 __slab_alloc_node() 中 barrier() 的注释相同 */
	barrier();

	if (unlikely(slab != c->slab)) {
		//目标 slab 与当前 CPU 的 slab 不匹配,调用慢速路径函数 __slab_free 处理释放
		__slab_free(s, slab, head, tail, cnt, addr);
		return;
	}

	if (USE_LOCKLESS_FAST_PATH()) {
		freelist = READ_ONCE(c->freelist);

		set_freepointer(s, tail, freelist);	//将需要释放的对象链表拼接到空闲链表上
		//使用 __update_cpu_freelist_fast 尝试原子更新空闲链表
		if (unlikely(!__update_cpu_freelist_fast(s, freelist, head, tid))) {
			note_cmpxchg_failure("slab_free", s, tid);
			goto redo;	//更新失败(如由于并发操作导致的冲突),记录失败信息并重试
		}
	} else {	//慢速路径的回退
		/* 在本地锁下更新免费列表*/
		local_lock(&s->cpu_slab->lock);
		c = this_cpu_ptr(s->cpu_slab);
		if (unlikely(slab != c->slab)) {
			local_unlock(&s->cpu_slab->lock);
			goto redo;
		}
		tid = c->tid;
		freelist = c->freelist;

		set_freepointer(s, tail, freelist);
		c->freelist = head;
		c->tid = next_tid(tid);

		local_unlock(&s->cpu_slab->lock);
	}
	stat_add(s, FREE_FASTPATH, cnt);
}

slab_free 释放内存

static __fastpath_inline
void slab_free(struct kmem_cache *s, struct slab *slab, void *object,
	       unsigned long addr)
{
	memcg_slab_free_hook(s, slab, &object, 1);
	alloc_tagging_slab_free_hook(s, slab, &object, 1);

	if (likely(slab_free_hook(s, object, slab_want_init_on_free(s), false)))
		do_slab_free(s, slab, object, object, 1, addr);
}

kfree 释放内存

/**
 * kfree - free previously allocated memory
 * @object: pointer returned by kmalloc() or kmem_cache_alloc()
 *
 * If @object is NULL, no operation is performed.
 */
void kfree(const void *object)
{
	struct folio *folio;
	struct slab *slab;
	struct kmem_cache *s;
	void *x = (void *)object;

	trace_kfree(_RET_IP_, object);

	if (unlikely(ZERO_OR_NULL_PTR(object)))
		return;

	folio = virt_to_folio(object);
	if (unlikely(!folio_test_slab(folio))) {
		free_large_kmalloc(folio, (void *)object);
		return;
	}

	slab = folio_slab(folio);
	s = slab->slab_cache;
	slab_free(s, slab, x, _RET_IP_);
}
EXPORT_SYMBOL(kfree);

网站公告

今日签到

点亮在社区的每一天
去签到