引言

Linux 内核的内存管理是一个精密的分层系统。在用户态,我们通过 malloc/free 分配堆内存,底层由 ptmalloc、jemalloc 或 tcmalloc 等分配器管理。但在内核态,情况完全不同——内核不能依赖用户态的分配器,必须自行管理物理内存。Slab 分配器正是为此而生。

从 Linux 2.1.23 引入 Slab 分配器解决内核对象频繁创建/销毁的性能问题,到如今 Slub 作为默认分配器统治内核内存分配,这一子系统经历了三次架构演进。本文将深入剖析 Slab/Slob/Slub 三大分配器的设计哲学、核心数据结构、性能特征以及在真实内核模块开发中的应用实战。

一、为什么内核需要专用分配器

1.1 页分配器的局限

Linux 内存管理的基础是Buddy System(伙伴系统),它以物理页(通常 4KB)为最小单位进行分配。然而,内核中绝大多数内存需求远小于一页:

  • task_struct(进程描述符)约 8KB
  • inode(索引节点)约 700 字节
  • dentry(目录项缓存)约 200 字节
  • file(文件对象)约 300 字节

如果直接用 Buddy 分配这些对象,内部碎片将浪费大量内存。更致命的是,内核对象的创建/销毁频率极高(每次 open() 分配 file 对象,每次 fork() 复制 task_struct),频繁的页分配/释放会导致:

  1. TLB 抖动:频繁的页表操作冲刷 TLB 缓存
  2. Cache 失效:新分配的页面不携带任何热缓存数据
  3. 锁竞争:mm_struct->mmap_lock 成为多线程瓶颈

1.2 对象复用的红利

内核对象有一个关键特征:类型相同、生命周期规律、可复用。一个 file 对象被释放后,其内存布局与下一个需要的 file 对象完全一致。Slab 分配器正是利用这一特征,实现了:

  • 避免内部碎片:按对象实际大小精准分配,无页内浪费
  • 避免初始化开销:通过构造函数/析构函数延迟初始化,对象复用时不重复清零
  • 硬件 Cache 友好:相同类型的对象集中排列,提升缓存命中率

二、Slab 分配器:经典架构

2.1 核心数据结构

Slab 分配器的三层架构如下:

┌──────────────────────────────────────────────────────┐
│                    slab_cache (Cache)                 │
│  ┌─────────────────────────────────────────────────┐ │
│  │ name = "dentry_cache"                            │ │
│  │ object_size = 200                                │ │
│  │ align = 8                                        │ │
│  │ lists.slabs_full    ──→ [slab1] → [slab2]       │ │
│  │ lists.slabs_partial ──→ [slab3] → [slab4]       │ │
│  │ lists.slabs_free    ──→ [slab5]                 │ │
│  │ constructor = dentry_ctor                        │ │
│  │ destructor  = dentry_dtor                        │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

一个 slab(slab_t):
┌──────────────────────────────────────────────────────┐
│  mem (首地址,指向页内存起始)                          │
│  inuse (已使用对象数量)                                │
│  free  (第一个空闲对象索引)                            │
│  s_mem 数组: [obj0|obj1|obj2|...|objN]               │
│  freelist: obj5 → obj2 → obj7 → NULL                 │
└──────────────────────────────────────────────────────┘

2.2 Slab 描述符

每个 slab 由一个 slab 描述符管理,包含以下关键字段:

struct slab {
    struct list_head;        /* 链接到 cache 的三个链表之一 */
    unsigned long s_mem;     /* 第一个对象的虚拟地址 */
    unsigned int inuse;      /* 已分配对象计数 */
    unsigned int free;       /* 下一个空闲对象的索引 */
};

为了提升性能,现代 Slab 实现将 slab 描述符与对象数据物理分离:对象数据存放在页面内存中,而 slab 描述符存放在独立的内存区域。这使得对象数据页面完全用于数据,避免了描述符嵌入导致的缓存行浪费。

2.3 着色(Coloring)机制

Slab 分配器中一个精妙的设计是Cache Coloring。同一类型的 slab,其对象相对于页面起始的偏移量可以不同:

Slab A: 页面起始 → |  obj0 |  obj1 |  obj2 | ... | color=0
Slab B: 页面起始 → | pad |  obj0 |  obj1 |  obj2 | ... | color=16
Slab C: 页面起始 → | pad | pad |  obj0 |  obj1 |  obj2 | ... | color=32

这样做的目的是让不同 slab 中的相同索引对象映射到 CPU Cache 的不同行,减少 Cache Line 冲突。可用颜色数量由 cache_ colour 决定(通常为 0~255 字节的对齐偏移)。

三、Slub 分配器:现代默认方案

3.1 Slab 的问题

经典 Slab 分配器在大型多核系统中暴露出严重问题:

  • 元数据开销大:每个 slab 一个描述符,每个 CPU 维护三个链表,在拥有数千 CPU 的系统中,一个 cache 的 CPU 槽位可能占用数 MB 内存
  • 缓存管理复杂:Full/Partial/Free 三个链表在不同 slab 间频繁移动对象,锁竞争激烈
  • 调试困难:Red Zone、Poison 等调试机制添加大量不可见元数据

3.2 Slub 的革命性简化

Slub(Unqueued Slab)由 Christoph Lameter 设计,其核心理念是将元数据嵌入对象页面本身:

Slub 页面布局:
┌──────────────────────────────────────────────────────┐
│  Page Header ( struct page )                          │
│  ├─ freelist: 指向第一个空闲对象                      │
│  ├─ inuse: 已使用数量                                │
│  ├─ objects: 对象总数                                 │
│  ├─ frozen: 是否被 CPU 独占                          │
│  └─ next: 下一个 slab 页面                            │
├──────────────────────────────────────────────────────┤
│  对象区域:                                             │
│  [obj0 | obj1 | obj2 | ... | objN]                   │
│  (空闲对象的前 8/16 字节存储 next 指针)                 │
└──────────────────────────────────────────────────────┘

关键创新是" freelist 复用对象内存 ":当一个对象被释放,其内存前几个字节被用来存储 freelist 指针。这意味着元数据存储零额外开销。

3.3 CPU-Cache 与 Node-Cache

Slub 设计了三级缓存策略:

分配路径(快速路径):
1. CPU kmem_cache_cpu-                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部