1. 课程导论:内存分配器在操作系统中的角色,slab与kmalloc的定位与关系

在Linux内核中,内存管理是系统性能的基石。内核需要为各种内核对象(如进程描述符、文件对象、网络缓冲区)分配和释放内存。如果直接使用底层页分配器(如伙伴系统)来管理这些频繁创建和销毁的小对象,将导致严重的性能问题和内存碎片。因此,内核引入了更高效的内存分配器——slab分配器和kmalloc,它们共同构成了内核对象缓存与通用内存分配的核心机制。

1.1 内存分配器的分层角色

Linux内核的内存分配体系呈现清晰的层次结构,每一层解决不同粒度与频率的分配需求:

层次 分配粒度 典型接口 适用场景
伙伴系统 (Buddy) 物理页框 (4KB ~ 4MB) alloc_pages() 大块连续物理内存,页缓存、进程页表
slab分配器 对象级 (32B ~ 8KB) kmem_cache_alloc() 内核数据结构缓存,如 task_struct、inode
kmalloc 通用字节级 (8B ~ 4MB) kmalloc() 驱动临时缓冲区、动态大小内存请求
vmalloc 非连续虚拟地址 vmalloc() 需要大块虚拟连续但物理不连续的内存
核心观点:slab分配器是kmalloc的底层实现基础。kmalloc本质上是slab分配器对外提供的通用接口,两者共享同一套对象缓存机制。

1.2 slab分配器的定位:对象缓存工厂

slab分配器专为内核对象的生命周期管理而设计。它通过预分配固定大小的对象缓存(称为slab缓存),避免了频繁的页分配和释放操作。其核心设计思想包括:

  • 对象复用:对象释放后不归还给伙伴系统,而是留在缓存中供后续分配直接重用。
  • 着色(Coloring):通过偏移调整使不同对象在CPU缓存行中对齐,减少缓存行冲突。
  • 每CPU缓存:为每个CPU维护一个本地对象池,减少锁竞争和缓存一致性开销。
slab分配器内部结构图 slab分配器内部结构 kmem_cache (slab缓存) slab (满) 对象已全部分配 slab (部分) 部分对象空闲 slab (空) 所有对象空闲 CPU0 本地对象池 CPU1 本地对象池 CPU2 本地对象池 说明:slab分配器通过“满/部分/空”三级链表管理对象, 每CPU缓存减少锁竞争,着色技术优化CPU缓存行利用率。

1.3 kmalloc的定位:通用内存分配接口

kmalloc是内核中最常用的通用内存分配函数,其行为类似于用户空间的malloc。它隐藏了底层slab缓存的复杂性,为驱动和内核模块提供简洁的分配接口:

  • 基于slab实现:kmalloc内部维护一组预定义的slab缓存(如kmalloc-8、kmalloc-16、…、kmalloc-4M),根据请求大小选择最合适的缓存。
  • GFP标志:通过GFP(Get Free Pages)标志控制分配行为,如GFP_KERNEL(可睡眠)、GFP_ATOMIC(不可睡眠)。
  • 大小对齐:返回的地址至少按8字节或16字节对齐,满足大多数数据结构的要求。
实践提示:当需要频繁分配同一大小的对象时,应使用kmem_cache_create()创建专用slab缓存,而非直接使用kmalloc。专用缓存可避免内部碎片,并支持对象构造函数和析构函数。

1.4 slab与kmalloc的关系:一体两面

slab分配器和kmalloc并非两个独立的分配器,而是同一体系的不同抽象层次:

对比维度 slab分配器 kmalloc
抽象层次 底层对象缓存管理 上层通用分配接口
缓存粒度 自定义对象大小 预定义2的幂次大小
适用场景 高频固定大小对象 通用动态大小请求
内部碎片 极低(精确匹配对象大小) 较低(向上取整到2的幂次)
性能 极高(专用缓存+每CPU池) 高(通用缓存+每CPU池)
slab与kmalloc关系架构图 slab与kmalloc关系架构图 内核对象 / 驱动模块 kmalloc() / kfree() 通用接口 kmem_cache_alloc() / free() 专用缓存接口 slab缓存池 (kmem_cache) kmalloc-8 | kmalloc-16 | ... | kmalloc-4M | task_struct_cache | inode_cache | ... 伙伴系统 (Buddy Allocator) — 物理页框 kmalloc底层依赖slab缓存池,slab缓存池从伙伴系统获取物理页框

1.5 为什么需要区分slab和kmalloc?

理解两者的区别对于性能优化至关重要。以下场景展示了何时应选择专用slab缓存而非kmalloc:

  1. 高频分配/释放:如网络协议栈的sk_buff结构,每秒可能分配释放数百万次。专用slab缓存可避免kmalloc的通用缓存竞争。
  2. 对象初始化开销大:slab支持构造函数,对象分配时自动初始化,减少重复初始化开销。
  3. 内存碎片敏感:专用缓存精确匹配对象大小,消除内部碎片;kmalloc向上取整到2的幂次可能浪费30%-50%空间。
  4. 调试与统计:slab缓存可通过/proc/slabinfo监控每个缓存的使用情况,便于定位内存泄漏。
性能陷阱:不要对小于1KB的临时缓冲区使用kmalloc再频繁释放。每次分配都会触发slab缓存查找和可能的锁操作。对于临时小对象,优先考虑栈分配或每CPU变量。

1.6 本章小结