2. Slab分配器核心概念:对象缓存、Slab、Cache、着色(Coloring)原理

Slab分配器是Linux内核中用于管理小对象(通常小于一页)的高效内存分配机制。它通过引入对象缓存、Slab、Cache和着色(Coloring)四个核心概念,解决了传统伙伴系统(Buddy System)在小对象分配时产生的内部碎片和性能瓶颈问题。本章将逐一剖析这些概念,并揭示它们如何协同工作以提升系统性能。

2.1 核心概念总览

在深入细节之前,我们先通过一张架构图建立整体认知。Slab分配器位于伙伴系统之上,为内核中频繁创建和销毁的对象(如task_struct、inode)提供高速缓存。

关键设计思想: Slab分配器将内存视为对象的集合,而不是字节的连续区域。它预先分配大块内存(Slab),并将其分割成多个相同大小的对象,从而避免频繁调用伙伴系统进行页分配。
Slab分配器核心概念架构图 Slab分配器核心概念架构 Cache (缓存描述符) Slab链表 (partial / full / empty) Slab 1 状态: partial 对象: 3/8 已用 Slab 2 状态: full 对象: 8/8 已用 Slab 3 状态: empty 对象: 0/8 已用 对象布局 [obj][obj][obj]... 着色偏移: 32B 对象布局 [obj][obj][obj]... 着色偏移: 64B 对象布局 [obj][obj][obj]... 着色偏移: 96B

2.2 对象缓存(Object Cache)

对象缓存是Slab分配器对外暴露的接口。内核中每种需要频繁分配的数据结构(如struct file、struct dentry)都对应一个独立的缓存。缓存负责管理该类型对象的分配、回收和复用。

  • 缓存创建: 通过 kmem_cache_create() 创建,指定对象大小、对齐方式、构造函数等。
  • 对象分配: 通过 kmem_cache_alloc() 从缓存中获取一个空闲对象。
  • 对象释放: 通过 kmem_cache_free() 将对象归还给缓存,而不是直接释放给伙伴系统。
  • 缓存销毁: 通过 kmem_cache_destroy() 销毁缓存,并释放所有Slab占用的物理页。
性能优势: 对象缓存避免了每次分配都进行页表操作和TLB刷新。对于频繁创建/销毁的对象(如网络连接描述符),缓存命中率可达90%以上,分配延迟从微秒级降至纳秒级。

2.3 Slab

Slab 是Slab分配器中最基本的物理内存单元。一个Slab由一个或多个连续的物理页组成,这些页被划分为多个相同大小的对象槽位。Slab是对象缓存和物理内存之间的桥梁。

2.3.1 Slab的三种状态

每个Slab在生命周期中会处于以下三种状态之一,并由Cache中的链表管理:

状态 描述 管理链表 分配策略
Full Slab中所有对象均已被分配,无空闲对象。 slabs_full 不参与分配,仅用于回收。
Partial Slab中部分对象已被分配,仍有空闲对象。 slabs_partial 优先从Partial Slab中分配对象。
Empty Slab中所有对象均为空闲,未被使用。 slabs_empty 当内存紧张时,Empty Slab可被回收给伙伴系统。

2.3.2 Slab的内部结构

一个典型的Slab包含以下部分:

  1. Slab头部(Slab Header): 存储管理信息,如对象数量、空闲对象链表指针、着色偏移量等。
  2. 对象数组: 连续排列的固定大小对象。
  3. 着色区域(Coloring Area): 用于调整对象起始地址的偏移量,以优化CPU缓存行对齐。
Slab内部结构图 Slab内部结构(以4KB页为例) Slab头部 管理信息 空闲链表指针 着色偏移 对象计数 着色区域 对象0 对象1 对象2 对象3 总大小 = 4KB (1页) | 对象大小 = 128B | 对象数量 = 4 (示例)

2.4 Cache(缓存描述符)

Cache 是Slab分配器的顶层管理结构,它描述了一类对象的分配行为。每个Cache包含以下关键信息:

  • 对象大小(obj_size): 缓存管理的对象字节数。
  • Slab大小(slab_size): 每个Slab占用的物理页数。
  • 三个Slab链表: slabs_full、slabs_partial、slabs_empty。
  • 着色参数: 着色偏移量、着色范围、着色方向。
  • 构造函数/析构函数: 用于对象初始化和清理的回调函数。
  • 统计信息: 分配次数、释放次数、活跃对象数等。
Cache与Slab的关系: 一个Cache管理多个Slab。当Cache中的Partial Slab耗尽时,它会从伙伴系统申请新的物理页,创建新的Empty Slab并加入slabs_partial链表。当内存回收时,Empty Slab会被销毁,物理页归还给伙伴系统。

2.5 着色(Coloring)原理

着色(Coloring) 是Slab分配器中最精妙的设计之一,其核心目标是优化CPU缓存(Cache Line)利用率,减少因对象起始地址对齐导致的缓存行冲突(Cache Line Conflict)。

2.5.1 问题背景

在默认情况下,Slab中的对象是连续排列的。如果所有Slab中的对象都从相同的页内偏移开始,那么不同Slab中相同索引的对象将映射到CPU缓存的同一组(Cache Set)中。当CPU频繁访问这些对象时,会导致缓存行颠簸(Cache Thrashing),即频繁的缓存行替换,降低性能。

2.5.2 着色机制

着色通过在Slab头部和第一个对象之间插入一个可变的偏移量(着色偏移),使得不同Slab中对象的起始地址在页内偏移不同。这个偏移量就是“颜色”。

  1. 着色范围: 从0到 (L1缓存行大小 - 1) 的整数倍。通常L1缓存行大小为64字节。
  2. 着色方向: 偏移量可以向前(在Slab头部后增加填充)或向后(减少Slab头部大小)。
  3. 着色分配: 每个新创建的Slab会从着色范围中选取一个尚未使用的颜色值,作为该Slab的着色偏移。

2.5.3 着色效果对比

场景 对象起始地址 CPU缓存映射 性能影响
未着色 所有Slab中对象0均位于页内偏移0x80 映射到同一缓存组 高冲突,缓存命中率低
已着色 Slab1对象0偏移0x80;Slab2对象0偏移0xC0 分散到不同缓存组 低冲突,缓存命中率高
着色原理对比图 着色(Coloring)原理:缓存行冲突缓解 未着色(冲突严重) Hdr Obj0 Obj1 Obj2 所有Slab中Obj0偏移相同 → 缓存组冲突 已着色(冲突缓解) Hdr 色 Obj0 Obj1 Obj2 着色偏移使Obj0分散到不同缓存组 CPU缓存组映射 缓存组 0 缓存组 1 缓存组 2 缓存组 3

2.6 着色参数计算示例

假设系统配置如下:

  • L1缓存行大小:64字节
  • 对象大小:128字节
  • Slab大小:4096字节(1页)
  • Slab头部大小:64字节

着色计算过程:

  1. 可用对象空间: 4096 - 64 (头部) = 4032字节
  2. 最大对象数量: floor(4032 / 128) = 31个
  3. 着色范围: 0 到 (64 - 1) = 63字节
  4. 着色步长: 通常为缓存行大小,即64字节
  5. 可用颜色数量: floor(4032 - 31*128) / 64 = floor(64 / 64) = 1种颜色
注意: 当对象大小恰好是缓存行大小的整数倍时,着色效果有限。此时需要结合对象对齐和Slab内部分组进一步优化。实际内核中,着色算法会动态调整,以平衡缓存利用率和内存开销。

2.7 核心概念总结

下表总结了Slab分配器四个核心概念的作用和相互关系:

概念 作用 管理粒度 关键属性
对象缓存 对外接口,管理一类对象的分配与回收 对象类型 对象大小、构造函数、统计信息
Slab 物理内存单元,承载具体对象 物理页 状态(full/partial/empty)、着色偏移
Cache 组织和管理多个Slab Slab集合 三个Slab链表、着色参数
着色 优化CPU缓存行利用率,减少冲突 字节偏移 着色范围、着色步长、颜色值