1. 内存管理概述
1.1 Linux内核内存管理层次结构
Linux内核的内存管理是一个分层架构,从物理页帧管理到虚拟地址空间映射,每一层都解决特定的抽象问题。理解这个层次结构是掌握slab/slub分配器的基础。
核心观点: Linux内存管理自底向上分为四个主要层次——物理页管理(伙伴系统)、通用对象缓存(slab/slub)、连续内存分配器(CMA/VMALLOC)和虚拟地址空间(VMA/页表)。slab/slub位于第二层,是连接物理页与内核对象的关键桥梁。
下图展示了Linux内核内存管理的完整层次结构及各层之间的调用关系:
层次关系说明: 伙伴系统管理物理页帧(最小单位4KB),slab/slub在伙伴系统之上将物理页切分为更小的对象缓存,供内核频繁创建和销毁的小对象(如inode、task_struct)使用。vmalloc和CMA则处理大块或非连续映射的需求。
1.2 伙伴系统与slab/slub的关系
伙伴系统(Buddy System)是Linux物理内存管理的基石,而slab/slub分配器是建立在伙伴系统之上的对象缓存层。两者之间是服务者与消费者的关系。
| 对比维度 | 伙伴系统 (Buddy System) | Slab/Slub 分配器 |
|---|---|---|
| 管理粒度 | 物理页帧(4KB ~ 4MB 连续块) | 内核对象(几十字节 ~ 几KB) |
| 分配单位 | 2^order 个连续物理页 | 单个内核对象(如 inode、dentry) |
| 主要接口 | alloc_pages() / __get_free_pages() | kmem_cache_alloc() / kmalloc() |
| 碎片问题 | 外部碎片(连续页块分裂) | 内部碎片(对象对齐填充) |
| 缓存特性 | 无对象缓存,每次分配清零 | 对象复用,构造函数/析构函数 |
| 性能特征 | 分配/释放较慢(页级操作) | 分配/释放极快(对象级操作) |
下图展示了伙伴系统与slab/slub之间的协作流程:
重要理解: slab/slub并不替代伙伴系统,而是寄生在伙伴系统之上。当slab/slub需要更多内存时,通过调用
alloc_pages() 从伙伴系统获取整页;释放时则将整页归还给伙伴系统。这种分层设计使得内核既能高效管理大块物理内存,又能快速分配小对象。
1.3 Slab分配器的历史背景
Slab分配器的演进是Linux内核内存管理不断优化的缩影。从最初的Slab到Slub,再到近年来的Slab Kmalloc重构,每一次变革都针对特定的性能瓶颈和可扩展性问题。
| 时间 | 内核版本 | 事件 | 核心改进 |
|---|---|---|---|
| 1994年 | Linux 1.0 | SunOS 4.1 引入Slab分配器 | 对象缓存、构造函数/析构函数 |
| 1996年 | Linux 2.0 | Linux内核首次移植Slab | 由Jeff Bonwick实现,替代旧式kmalloc |
| 2001年 | Linux 2.4 | Slab成为默认分配器 | 支持NUMA、CPU本地缓存 |
| 2007年 | Linux 2.6.22 | Slub分配器合入主线 | 简化slab结构、减少元数据开销、提高可扩展性 |
| 2008年 | Linux 2.6.28 | Slub成为默认分配器 | 嵌入式和大规模SMP场景全面优化 |
| 2015年 | Linux 4.0 | Slab Kmalloc重构 | 统一kmalloc缓存、减少内部碎片 |
| 2020年至今 | Linux 5.x / 6.x | Slub持续优化 | 批量分配、CPU本地缓存深度优化、KASAN支持 |
下图展示了Slab分配器从诞生到成为Linux内核标配的演进路线:
关键洞察: Slab分配器的设计哲学是“对象复用”——内核中大量对象(如inode、dentry、task_struct)被频繁创建和销毁,如果每次都通过伙伴系统分配和释放物理页,性能将极其低下。Slab通过维护一个已初始化对象的缓存池,实现了接近O(1)的分配和释放速度。Slub在此基础上进一步简化了数据结构,减少了元数据开销,使得在数百个CPU核心的系统上仍能保持高性能。
1.4 本章小结
- 内存管理层次: 伙伴系统(物理页)→ Slab/Slub(对象缓存)→ vmalloc/CMA(大块/非连续)→ VMA(虚拟地址空间)
- 伙伴系统与slab的关系: slab是伙伴系统的消费者,通过
alloc_pages()获取物理页,切分为对象后缓存复用 - 历史演进: SunOS Slab (1994) → Linux Slab (1996) → Slub (2007) → 默认Slub (2008) → 持续优化至今
- 核心优势: 对象复用减少分配延迟、CPU本地缓存避免锁竞争、构造函数/析构函数保持对象状态