2. slab分配器核心概念:对象缓存、slab、着色与硬件缓存对齐

slab分配器是Linux内核中用于管理小对象(通常小于一页)的高效内存分配机制。它通过预分配和缓存常用数据结构,显著减少了频繁分配/释放带来的碎片和性能开销。本章深入剖析其四大核心概念:对象缓存(Object Cache)、slab、着色(Color)与硬件缓存对齐。

2.1 对象缓存(Object Cache)

对象缓存是slab分配器对外暴露的抽象接口,每个缓存负责管理一种特定类型或大小的对象。内核中通过 kmem_cache_create() 创建缓存,通过 kmem_cache_alloc() / kmem_cache_free() 分配/释放对象。

关键设计思想: 将频繁使用的内核数据结构(如 task_struct、inode、dentry)预先分配好,避免每次使用时都从伙伴系统申请零散页面,从而消除外部碎片并加速分配路径。

2.1.1 缓存结构

每个 struct kmem_cache 包含以下核心字段:

字段 类型 说明
name const char * 缓存名称,用于 /proc/slabinfo 标识
object_size unsigned int 每个对象的大小(字节)
align unsigned int 对象对齐要求
flags slab_flags_t 缓存标志(如 SLAB_HWCACHE_ALIGN)
num unsigned int 每个slab中可容纳的对象数量
gfporder unsigned int 每个slab占用的页面阶数(2^gfporder 页)
cpu_cache struct array_cache * 每CPU对象缓存(快速路径)
slabs_full struct list_head 完全分配完的slab链表
slabs_partial struct list_head 部分分配的slab链表
slabs_free struct list_head 完全空闲的slab链表

2.1.2 缓存层次与分配路径

分配路径遵循三级缓存策略,从最快到最慢:

  1. 每CPU缓存(array_cache):无锁访问,直接返回已缓存的对象指针。
  2. slab链表:从 slabs_partial 或 slabs_free 中取出对象。
  3. 伙伴系统:当所有slab都满时,从伙伴系统分配新页面创建新slab。
性能指标: 在理想情况下(每CPU缓存命中),一次 kmem_cache_alloc() 仅需几十条指令,远低于伙伴系统的页面分配开销。

2.2 slab:内存管理的物理单元

slab 是对象缓存下的实际内存载体,由一个或多个连续物理页面组成。每个slab被划分为多个等长的对象槽位,并包含管理数据。

2.2.1 slab布局

一个典型的slab内存布局如下:

+--------------------------------------------------+
|  slab管理结构 (struct slab)  |  着色区 (可选)      |
+--------------------------------------------------+
|  对象 0  |  对象 1  |  ...  |  对象 N-1           |
+--------------------------------------------------+
|  空闲对象链表 (freelist)  |  填充 (对齐用)        |
+--------------------------------------------------+
  • 管理结构:记录slab所属缓存、对象数量、空闲链表指针等。
  • 着色区:用于硬件缓存对齐(见2.3节)。
  • 对象数组:连续排列的固定大小对象。
  • freelist:以数组或链表形式记录空闲对象索引。

2.2.2 slab状态与链表

每个缓存维护三个slab链表,对应slab的不同使用状态:

链表 状态 分配行为
slabs_full 所有对象已分配 不可分配,等待释放
slabs_partial 部分对象空闲 优先从此链表分配
slabs_free 所有对象空闲 可回收或重新使用
注意: 当 slabs_free 链表中的slab数量超过阈值时,内核会触发slab回收,将空闲页面归还给伙伴系统,避免内存浪费。

2.3 着色(Color)与硬件缓存对齐

着色 是slab分配器为减少CPU硬件缓存(Cache)冲突而引入的优化技术。当多个slab中的对象映射到相同的缓存行时,会导致频繁的缓存失效,降低性能。

2.3.1 缓存冲突问题

假设每个slab大小为4KB,对象大小为64字节(与L1缓存行大小相同)。如果所有slab的起始地址都是4KB对齐,那么每个slab中第0个对象的地址在缓存中映射到同一组(set),导致严重的缓存颠簸(Cache Thrashing)。

2.3.2 着色原理

着色通过在slab起始处插入可变大小的偏移量(着色区),使不同slab中相同索引的对象映射到不同的缓存行。着色区的大小在 [0, color_max] 范围内循环变化。

着色(Color)原理示意图 Slab 0(未着色):对象0 ~ 对象N-1 Slab 1(未着色):对象0 ~ 对象N-1 对象0映射到同一缓存行 → 冲突 着色区 Slab 2(着色后):对象0 ~ 对象N-1 着色区(更大) Slab 3(着色后):对象0 ~ 对象N-1 对象0映射到不同缓存行 → 无冲突 着色区(偏移量) 对象数据

2.3.3 着色参数计算

着色相关的关键参数由 calculate_slab_order() 和 cache_init_objs() 计算:

/* 伪代码:着色参数计算 */
color_max = cachep->gfporder * PAGE_SIZE;          /* slab总大小 */
color_max -= cachep->num * cachep->object_size;    /* 减去对象占用 */
color_max -= sizeof(struct slab);                  /* 减去管理结构 */
color_off = cachep->align;                         /* 对齐粒度 */

/* 每次创建新slab时,着色偏移循环递增 */
cachep->colour_next = (cachep->colour_next + 1) % cachep->colour_count;
offset = cachep->colour_next * color_off;
  • colour_count:可用的着色偏移数量,等于 color_max / color_off。
  • colour_off:每次偏移的步长,通常等于 L1_CACHE_BYTES(64字节)。
  • colour_next:下一个slab使用的着色索引,循环递增。

2.4 硬件缓存对齐(Hardware Cache Alignment)

除了着色,slab分配器还通过 SLAB_HWCACHE_ALIGN 标志提供更直接的缓存对齐保证。当创建缓存时指定此标志,内核会确保:

  • 每个对象的起始地址对齐到硬件缓存行大小(通常为64字节或128字节)。
  • 对象之间不会跨缓存行,避免伪共享(False Sharing)问题。
伪共享示例: 两个CPU分别修改同一缓存行中的不同对象。虽然逻辑上不共享数据,但硬件缓存一致性协议会强制缓存行在CPU间频繁失效,导致性能骤降。缓存对齐可彻底避免此问题。

2.4.1 对齐策略对比

策略 机制 内存开销 适用场景
着色(Color) slab间偏移 低(每个slab少量填充) 通用小对象缓存
硬件缓存对齐 对象起始地址对齐到缓存行 中(每个对象可能有填充) 高频访问、多CPU共享的数据结构
两者结合 着色 + 对齐 较高 性能敏感型内核子系统(如网络、文件系统)

2.5 核心概念关系总览

下图展示了对象缓存、slab、着色与硬件缓存对齐之间的整体架构关系:

slab分配器核心概念架构图 对象缓存 (kmem_cache) 管理特定类型对象的分配与释放 slab 链表管理 slabs_full slabs_partial slabs_free 单个 slab 内部结构 着色区 对象0 对象1 对象2 ... freelist 硬件缓存对齐:对象起始地址对齐到缓存行边界

2.6 关键数据结构与API

以下是与本章核心概念直接相关的关键数据结构和API:

/* 内核源码:include/linux/slab.h 和 mm/slab.c */

/* 创建对象缓存 */
struct kmem_cache *kmem_cache_create(const char *name,
                                      unsigned int object_size,
                                      unsigned int align,
                                      slab_flags_t flags,
                                      void (*ctor)(void *));

/* 从缓存分配对象 */
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

/* 释放对象回缓存 */
void kmem_cache_free(struct kmem_cache *cachep, void *objp);

/* 销毁缓存 */
void kmem_cache_destroy(struct kmem_cache *cachep);

/* slab管理结构(简化版) */
struct slab {
    struct list_head slab_list;    /* 挂入 slabs_full/partial/free 链表 */
    struct kmem_cache *cachep;     /* 所属缓存 */
    unsigned int inuse;            /* 已分配对象数量 */
    unsigned int free;             /* 空闲对象索引(freelist头) */
    void *s_mem;                   /* 对象数据区起始地址 */
    unsigned int active;           /* 活跃对象计数 */
};
调试技巧: 通过 /proc/slabinfo 可以查看系统中所有slab缓存的实时状态,包括对象总数、活跃数、每个slab的对象数等。使用 slabtop 命令可动态监控slab使用情况。

2.7 本章小结

  • 对象缓存 是slab分配器的顶层抽象,每个缓存管理一种固定大小的对象,通过三级缓存路径加速分配。
  • slab 是实际的内存载体,由连续物理页面组成,内部划分为对象槽位和freelist,通过三个链表管理状态。
  • 着色 通过在slab起始处插入可变偏移量,使不同slab中相同索引的对象映射到不同缓存行,减少缓存冲突。
  • 硬件缓存对齐 确保对象起始地址与缓存行边界对齐,避免多核环境下的伪共享问题。
  • 着色与硬件缓存对齐可以组合使用,在性能敏感的内核子系统中达到最佳缓存利用率。