[TOC]
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。
SLAB :这是最初的、经典的Slab分配器实现。它的设计非常精巧,为每个NUMA节点和每个CPU维护了三类Slab链表:完全满的(full)、部分满的(partial)和完全空的(empty)。这种设计能很好地回收部分使用的slab,但其内部逻辑非常复杂,管理这些链表本身也带来了不可忽略的性能和内存开销。
SLOB (Simple List Of Blocks) :这是一个极其简单的分配器,专为内存占用是首要考虑因素的嵌入式系统设计。它使用首次适应算法(first-fit)在页面中分配内存,内存开销极小,但性能和扩展性较差,且容易产生碎片。
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 )分割成多个固定大小的对象 ,并高效地管理这些对象的分配与释放。
kmem_cache
:代表一类特定对象的缓存。通过kmem_cache_create()
创建,它定义了对象的大小、对齐方式、构造函数等属性。
Slab :从伙伴系统(Buddy System)分配的一个或多个连续的物理页。一个slab被格式化后,专门用于存放一种kmem_cache
的对象。
Per-CPU缓存 :这是SLUB高性能的关键。每个CPU核心都有一个私有的、当前活动的slab 。当一个CPU上的代码需要分配对象时,它会首先尝试从这个私有slab中获取。因为访问的是CPU私有数据,所以这个过程完全无锁 ,速度极快。
简单的Freelist管理 :当一个对象被释放时,它会被放回当前CPU的活动slab的freelist中。SLUB的freelist设计非常巧妙和简单:它将指向下一个空闲对象的指针直接存储在前一个空闲对象的内存空间中 。这几乎消除了所有额外的元数据开销。
Slab的流转 :
分配路径 :当一个CPU的活动slab用完时,它会去一个per-NUMA节点的部分空闲slab链表 中获取一个新的slab。如果这个链表也为空,它才会向伙伴系统申请新的页来创建一个全新的slab。
释放路径 :当一个CPU释放对象,导致其活动slab变满(所有对象都空闲)时,这个slab可能会被放回到per-NUMA节点的链表中,供其他CPU使用。
调试特性 :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)。这种设计的缺陷是灾难性的:
API混乱 :函数需要传递多个参数,如(struct page *page, unsigned int order)
,增加了复杂性。
逻辑脆弱且易错 :代码中到处都需要检查一个给定的struct page
指针是否是尾页。如果是,就必须调用compound_head(page)
来找到真正的头页才能获取正确信息。忘记这个检查是内核bug的一个常见来源。
概念不清 :一个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
基础设施集成,以实现平滑过渡。
数据结构 :struct folio
并非一个全新的、独立分配的结构。为了节省内存,它被巧妙地嵌入(通过union
)到复合页的头页 的struct page
结构中。因此,一个Folio本质上是头页的一个“更结构化的视图”,它没有带来额外的内存开销。
明确的大小 :一个struct folio
内部直接包含了它所代表的内存块的大小(或阶数order
)。因此,一个struct folio*
指针就足以描述整个内存块,不再需要额外的size
或order
参数。
API的演进 :
旧API:void function(struct page *page, unsigned int order);
新API:void function(struct folio *folio);
新API更简洁,并且类型安全。
关系与转换 :内核提供了一套完整的函数来在folio
和page
之间转换:
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 page
和order
。这是因为它就是构建所有上层抽象(包括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 计算给定大小的内存块所需的页数 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 static __always_inline __attribute_const__ int get_order (unsigned long size) { if (__builtin_constant_p(size)) { if (!size) return BITS_PER_LONG - PAGE_SHIFT; if (size < (1UL << PAGE_SHIFT)) 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 结构体 1 2 3 4 5 6 7 8 9 10 11 #define folio_slab(folio) (_Generic((folio), \ const struct folio *: (const struct slab *)(folio), \ struct folio *: (struct slab *)(folio)))
slab folio 结构体 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 struct slab { unsigned long __page_flags; struct kmem_cache *slab_cache ; union { struct { struct list_head slab_list ; void *freelist; }; struct rcu_head rcu_head ; }; unsigned int __page_type; atomic_t __page_refcount; }; struct folio { struct page page ; struct page __page_1 ; struct page __page_2 ; struct page __page_3 ; };
kmem_cache_args 结构体
在 Linux 内核中,kmem_cache_create 提供了一个构造函数(ctor),用于在分配内存时对对象进行自定义初始化。然而,内核并没有直接提供析构函数的机制。这是因为内核的内存管理模型与用户态的对象生命周期管理不同,析构操作通常是由调用者显式完成的。 原因分析
内存管理的简单性: 内核的内存管理尽量保持简单高效,避免增加额外的复杂性。构造函数的主要作用是确保分配的内存对象在被使用之前处于已知的初始状态,而析构函数则需要在释放内存时执行清理逻辑,这会增加额外的开销。
释放内存的不可预测性: 在内核中,内存释放操作通常是由调用者决定的,内核并不知道对象的具体用途或释放时需要进行什么操作。因此,析构逻辑更适合由调用者在释放前手动完成。
RCU 和延迟释放机制的影响: 在某些情况下,内核使用 RCU(Read-Copy-Update)或其他延迟释放机制来管理内存。对象在被释放之前可能会经历一段延迟期,在这期间执行析构函数可能会导致不一致性或额外的复杂性。
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_args { unsigned int align; unsigned int useroffset; unsigned int usersize; unsigned int freeptr_offset; bool use_freeptr_offset; void (*ctor)(void *); };
kmalloc_index 计算 kmalloc 的索引 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 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(); return -1 ; } static_assert (PAGE_SHIFT <= 20 );#define kmalloc_index(s) __kmalloc_index(s, true)
kmalloc_type 计算 kmalloc 的类型 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 static __always_inline enum kmalloc_cache_type kmalloc_type (gfp_t flags, unsigned long caller) { if (likely((flags & KMALLOC_NOT_NORMAL_BITS) == 0 )) #ifdef CONFIG_RANDOM_KMALLOC_CACHES return KMALLOC_RANDOM_START + hash_64(caller ^ random_kmalloc_seed, ilog2(RANDOM_KMALLOC_CACHES_NR + 1 )); #else return KMALLOC_NORMAL; #endif 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 分配内核内存 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 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 分配并清零内核内存 1 2 3 4 5 6 7 8 9 10 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 分配数组并清零 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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__)) #define kcalloc(n, size, flags) kmalloc_array(n, size, (flags) | __GFP_ZERO)
kmem_cache_zalloc 分配 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 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; } 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__)) #define kmem_cache_zalloc(_k, _flags) kmem_cache_alloc(_k, (_flags)|__GFP_ZERO)
kmem_cache_create_usercopy 创建用户拷贝缓存
useroffset(用户复制区域偏移量): useroffset 表示用户可复制区域相对于内存块起始地址的偏移量。 通过指定偏移量,可以确保用户只能访问内存块中的特定区域,而不会影响其他敏感数据。
usersize(用户复制区域大小): usersize 定义了用户可复制区域的大小。 这确保用户只能操作指定大小的内存数据,从而避免越界访问或数据损坏。
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 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 缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 = {}; if (WARN_ON_ONCE(args)) return ERR_PTR(-EINVAL); return __kmem_cache_create_args(name, size, &kmem_default_args, flags); }
__kmem_cache_create 创建 slab 缓存 传入构造函数与对齐值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 缓存 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 #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 缓存 1 2 3 4 5 6 7 8 9 10 #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 缓存中的对象 1 #define kmem_cache_alloc_lru(...) alloc_hooks(kmem_cache_alloc_lru_noprof(__VA_ARGS__))
kvmalloc_array 分配大数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #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 缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 在指定节点上分配对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define kmem_cache_alloc_node(...) alloc_hooks(kmem_cache_alloc_node_noprof(__VA_ARGS__)) 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 分配虚拟内存 1 2 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 计算对齐要求 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 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 缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 = {}; if (flags & 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); if (err) panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n" , name, size, err); s->refcount = -1 ; }
slab_unmergeable 检查 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 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 if (s->refcount < 0 ) return 1 ; return 0 ; }
setup_kmalloc_cache_index_table 设置 kmalloc 缓存索引表 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 u8 kmalloc_size_index[24 ] __ro_after_init = { 3 , 4 , 5 , 5 , 6 , 6 , 6 , 6 , 1 , 1 , 1 , 1 , 7 , 7 , 7 , 7 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 }; void __init setup_kmalloc_cache_index_table (void ) { unsigned int i; 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); if (elem >= ARRAY_SIZE(kmalloc_size_index)) break ; kmalloc_size_index[elem] = KMALLOC_SHIFT_LOW; } if (KMALLOC_MIN_SIZE >= 64 ) { for (i = 64 + 8 ; i <= 96 ; i += 8 ) kmalloc_size_index[size_index_elem(i)] = 7 ; } if (KMALLOC_MIN_SIZE >= 128 ) { for (i = 128 + 8 ; i <= 192 ; i += 8 ) kmalloc_size_index[size_index_elem(i)] = 8 ; } }
new_kmalloc_cache 创建新的 kmalloc 缓存 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 56 57 58 59 60 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 __initnew_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 (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 缓存 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 void __init create_kmalloc_caches (void ) { int i; enum kmalloc_cache_type type ; for (type = KMALLOC_NORMAL; type < NR_KMALLOC_TYPES; type++) { if (KMALLOC_MIN_SIZE <= 32 ) new_kmalloc_cache(1 , type); if (KMALLOC_MIN_SIZE <= 64 ) new_kmalloc_cache(2 , type); 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 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 缓存 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 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; 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 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 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 (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; } 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 中可以容纳的对象数量 1 2 3 4 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) 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 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; for (order = min_order; order <= max_order; order++) { unsigned int slab_size = (unsigned int )PAGE_SIZE << order; unsigned int rem; rem = slab_size % size; if (rem <= slab_size / fract_leftover) break ; } return order; }
calculate_order 计算 slab 的阶数(order) 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 56 57 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) { unsigned int nr_cpus = num_present_cpus(); if (nr_cpus <= 1 ) nr_cpus = nr_cpu_ids; min_objects = 4 * (fls(nr_cpus) + 1 ); } 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 ; 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 的分配配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct kmem_cache_order_objects { unsigned int x; }; static inline struct kmem_cache_order_objects oo_make (unsigned int order, unsigned int size) { struct kmem_cache_order_objects x = { (order << OO_SHIFT) + order_objects(order, size) }; return x; }
oo_objects 获取对象数量 1 2 3 4 5 6 7 #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 缓存中对象的布局 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 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) || (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); size = ALIGN(size, s->align); s->size = size; s->reciprocal_size = reciprocal_value(size); order = calculate_order(size); 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; s->oo = oo_make(order, size); s->min = oo_make(get_order(size), size); return !!oo_objects(s->oo); }
alloc_slab_page 分配一个新的 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 static inline struct slab *alloc_slab_page (gfp_t flags, int node, struct kmem_cache_order_objects oo) { struct folio *folio ; struct slab *slab ; unsigned int order = oo_order(oo); if (node == NUMA_NO_NODE) folio = (struct folio *)alloc_frozen_pages(flags, order); else folio = (struct folio *)__alloc_frozen_pages(flags, order, node, NULL ); if (!folio) return NULL ; slab = folio_slab(folio); __folio_set_slab(folio); if (folio_is_pfmemalloc(folio)) slab_set_pfmemalloc(slab); return slab; }
set_freepointer 设置空闲指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 初始化对象 1 2 3 4 5 6 7 8 9 10 11 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 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 56 57 58 59 60 61 62 63 64 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; 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)) { oo = s->min; alloc_gfp = flags; slab = alloc_slab_page(alloc_gfp, node, oo); if (unlikely(!slab)) return NULL ; stat(s, ORDER_FALLBACK); } slab->objects = oo_objects(oo); slab->inuse = 0 ; slab->frozen = 0 ; account_slab(slab, oo_order(oo), s, flags); slab->slab_cache = s; start = slab_address(slab); shuffle = shuffle_freelist(s, slab); if (!shuffle) { slab->freelist = start; start = setup_object(s, start); for (idx = 0 , p = start; idx < slab->objects - 1 ; idx++) { next = p + s->size; next = setup_object(s, next); set_freepointer(s, p, next); p = next; } set_freepointer(s, p, NULL ); } return slab; }
new_slab 为指定的 kmem_cache 分配一个新的 slab 1 2 3 4 5 6 7 8 9 10 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); 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 结构体 1 2 3 4 5 6 7 8 9 10 11 12 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 到部分空闲链表 1 2 3 4 5 6 7 8 9 10 11 12 13 static inline void __add_partial(struct kmem_cache_node *n, struct slab *slab, int tail) { n->nr_partial++; 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); }
early_kmem_cache_node_alloc 早期分配 kmem_cache_node 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 static struct kmem_cache *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; init_kmem_cache_node(n); __add_partial(n, slab, DEACTIVATE_TO_HEAD); }
init_kmem_cache_nodes 初始化 NUMA 节点相关数据 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 #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; 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 缓存数据 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 static void init_kmem_cache_cpus (struct kmem_cache *s) { int cpu; struct kmem_cache_cpu *c ; for_each_possible_cpu(cpu) { c = per_cpu_ptr(s->cpu_slab, cpu); local_lock_init(&c->lock); 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)); s->cpu_slab = __alloc_percpu(sizeof (struct kmem_cache_cpu), 2 * sizeof (void *)); if (!s->cpu_slab) return 0 ; init_kmem_cache_cpus(s); return 1 ; }
sysfs_slab_add 将 slab 缓存添加到 sysfs 文件系统中
slab 缓存对象暴露到 sysfs 文件系统中,以便用户或开发者可以通过文件系统查看和调试 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 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++ = ':' ; 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) { 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; 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) { 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 缓存 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 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) 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 56 57 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)) goto out; 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); if (slab_state >= UP) { if (init_cache_random_seq(s)) goto out; } if (!init_kmem_cache_nodes(s)) goto out; if (!alloc_kmem_cache_cpus(s)) goto out; err = 0 ; if (slab_state <= UP) goto out; if (sysfs_slab_add(s)) pr_err("SLUB: Unable to add cache %s to sysfs\n" , s->name); if (s->flags & SLAB_STORE_USER) debugfs_slab_add(s); out: if (err) __kmem_cache_release(s); return err; }
bootstrap 初始化早期 kmem_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 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); __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 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 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; for_each_node_state(node, N_NORMAL_MEMORY) node_set(node, slab_nodes); 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; 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 ); kmem_cache = bootstrap(&boot_kmem_cache); kmem_cache_node = bootstrap(&boot_kmem_cache_node); setup_kmalloc_cache_index_table(); create_kmalloc_caches(); init_freelist_randomization(); cpuhp_setup_state_nocalls(CPUHP_SLUB_DEAD, "slub:dead" , NULL , slub_cpu_dead); 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 缓存初始化延迟 1 2 3 4 5 6 7 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 1 2 3 4 5 6 7 8 9 10 11 12 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 的空闲列表 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 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; } 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 ; } 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 的空闲列表 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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 ; 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; } 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 的去激活 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 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; } 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; } do { old.freelist = READ_ONCE(slab->freelist); old.counters = READ_ONCE(slab->counters); VM_BUG_ON(!old.frozen); 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" )); 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 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 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) { if (unlikely(node != NUMA_NO_NODE && !node_isset(node, slab_nodes))) node = NUMA_NO_NODE; goto new_slab; } if (unlikely(!node_match(slab, node))) { if (!node_isset(node, slab_nodes)) { node = NUMA_NO_NODE; } else { stat(s, ALLOC_NODE_MISMATCH); goto deactivate_slab; } } if (unlikely(!pfmemalloc_match(slab, gfpflags))) goto deactivate_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)); 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; 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); if (slab) { freelist = freeze_slab(s, slab); goto retry_load_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); 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; } 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 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 内存分配函数 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 56 57 58 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: c = raw_cpu_ptr(s->cpu_slab); tid = READ_ONCE(c->tid); barrier(); 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); if (unlikely(!__update_cpu_freelist_fast(s, object, next_object, tid))) { note_cmpxchg_failure("slab_alloc" , s, tid); goto redo; } prefetch_freepointer(s, next_object); stat(s, ALLOC_FASTPATH); } return object; }
slab_alloc_node 分配内存 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 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); if (unlikely(!s)) return NULL ; object = kfence_alloc(s, orig_size, gfpflags); if (unlikely(object)) goto out; object = __slab_alloc_node(s, gfpflags, node, addr, orig_size); out: slab_post_alloc_hook(s, lru, gfpflags, 1 , &object, init, orig_size); return object; }
__kmalloc_cache_noprof 分配内存 1 2 3 4 5 6 7 8 9 10 11 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内存 1 2 3 4 5 6 7 8 9 10 11 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 分配大内存块 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 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); flags |= __GFP_COMP; folio = (struct folio *)alloc_pages_node_noprof(node, flags, order); if (folio) { ptr = folio_address(folio); lruvec_stat_mod_folio(folio, NR_SLAB_UNRECLAIMABLE_B, PAGE_SIZE << order); __folio_set_large_kmalloc(folio); } ptr = kasan_kmalloc_large(ptr, size, flags); 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 分配内存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static __always_inlinevoid *__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) 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 void *__kvmalloc_node_noprof(DECL_BUCKET_PARAMS(size, b), gfp_t flags, int node){ void *ret; ret = __do_kmalloc_node(size, PASS_BUCKET_PARAM(b), kmalloc_gfp_adjust(flags, size), node, _RET_IP_); if (ret || size <= PAGE_SIZE) return ret; if (!gfpflags_allow_blocking(flags)) return NULL ; if (unlikely(size > INT_MAX)) { WARN_ON_ONCE(!(flags & __GFP_NOWARN)); return NULL ; } 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 释放内存 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 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 ; } 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) { if (!kmem_cache_has_cpu_partial(s) || prior) { n = get_node(s, slab_nid(slab)); 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)) { stat(s, FREE_FROZEN); } else if (kmem_cache_has_cpu_partial(s) && !prior) { put_cpu_partial(s, slab, 1 ); stat(s, CPU_PARTIAL_FREE); } 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; 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) { 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 释放内存 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 56 57 58 59 60 61 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: c = raw_cpu_ptr(s->cpu_slab); tid = READ_ONCE(c->tid); barrier(); if (unlikely(slab != c->slab)) { __slab_free(s, slab, head, tail, cnt, addr); return ; } if (USE_LOCKLESS_FAST_PATH()) { freelist = READ_ONCE(c->freelist); set_freepointer(s, tail, freelist); 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 释放内存 1 2 3 4 5 6 7 8 9 10 static __fastpath_inlinevoid 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 释放内存 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 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);