长文103: 内存管理-内核篇:Slab 分配器深度设计 —— 对比 Linux 实现

“Buddy 系统擅长分配大块内存,但内核频繁申请 32 字节的 task_struct 怎么办? 本文将深入 Slab 分配器的设计哲学,对比 Linux 的三种实现(SLAB/SLUB/SLOB), 并构建一个高性能、低碎片的工业级 Slab 系统。”

引言:小对象分配的困境

在操作系统内核中, 小对象 (Small Objects)的分配极其频繁:

进程创建


task_struct
(约 1.5KB)
文件操作


file
(约 200 字节)、
dentry
(约 192 字节)
网络栈


sk_buff
(约 256 字节)
VFS 层


inode
(约 600 字节)

如果直接使用 Buddy 系统分配这些对象:

内部碎片高达 75%+

:分配 4KB 页仅用 200 字节
初始化开销巨大

:每次
memset
清零整个页
缓存局部性差

:对象分散在不同页,CPU 缓存命中率低

Slab 分配器 正是为解决这些问题而生!它通过 对象缓存 + 空闲链表 ,实现:

零初始化开销

:对象复用,构造函数按需调用
零内部碎片

:精确分配对象大小
高缓存命中率

:同类对象紧凑存储

本文将系统性地剖析 Slab 分配器的设计,并 深度对比 Linux 的三种实现 (SLAB/SLUB/SLOB),最后提供一个可运行的工业级框架。


第一章:Slab 理论模型与核心思想

1.1 为什么需要 Slab?

Buddy 系统的小对象分配问题:

问题 后果 Slab 解决方案
内部碎片 分配 4KB 页仅用 200 字节,浪费 95% 精确分配对象大小
初始化开销 每次分配都 memset 清零 对象复用,按需构造
缓存局部性 对象分散在不同页 同类对象紧凑存储
TLB 压力 频繁分配/释放导致 TLB 刷新 减少页分配次数

Slab 的核心思想:

“预分配一批同类型对象,用时即取,废时即还”

Slab

:一个或多个连续物理页,划分为固定大小的对象
Cache

:管理同类型对象的 Slab 池(如
task_cache

对象复用

:释放的对象不归还 Buddy,而是放入空闲链表

1.2 Slab 三级架构

Slab 系统采用三级层次结构:

关键设计决策:

  1. 空闲指针内嵌

    :空闲对象的指针直接存放在对象内存开头,
    零额外内存开销

  2. 构造函数延迟调用

    :仅在对象首次分配时调用
    ctor
    ,复用时不调用

  3. Slab 按需创建

    :无空闲对象时才向 Buddy 申请新页

1.3 Slab 操作流程

分配对象(kmem_cache_alloc):

  1. 优先从
    partial slab
    分配(有空闲对象)
  2. 其次从
    free slab
    分配(全空闲)
  3. 若无可用 slab,
    新建 slab
    (向 Buddy 申请页)
  4. 从 slab 取第一个空闲对象
  5. 调用构造函数

    (如果存在且是首次使用)

释放对象(kmem_cache_free):

  1. 调用析构函数

    (如果存在)

  2. 将对象放回 slab 的空闲链表
  3. 调整 slab 状态(full → partial,partial → free)
  4. 若 slab 全空闲且系统内存紧张,
    延迟释放
    (避免频繁 Buddy 调用)

第二章:Linux Slab 实现演进史

Linux 内核历史上存在 三种 Slab 实现 ,反映了不同设计哲学的演进:

2.1 SLAB(经典实现,1990s-2008)

设计特点:

复杂但功能完整

:支持对象着色、硬件缓存对齐
内存开销大

:每个 slab 需额外元数据(
struct slab

锁粒度粗

:每个 cache 一个全局锁

核心数据结构:

优点:

对象着色

(Slab Coloring):避免不同 cache 的对象映射到同一 CPU 缓存行
硬件优化

:L1 缓存行对齐

缺点:

内存开销大

:元数据占 10-20% 内存
复杂度高

:代码超过 10,000 行

2.2 SLUB(现代默认,2008-至今)

设计哲学:

“简化、去中心化、每 CPU 优化”

关键改进:

  1. 移除 slab 结构

    :元数据直接嵌入页描述符(
    struct page

  2. 每 CPU 缓存无锁

    :分配/释放完全无锁

  3. 简化链表管理

    :partial 链表全局共享

  4. 动态调试支持

    :运行时启用红区(Redzone)、对象跟踪

核心数据结构:

优势:

内存开销极小

:元数据仅 16 字节/页
性能卓越

:99% 分配路径无锁
调试友好


slabinfo

/sys/kernel/slab/
提供详细统计

2.3 SLOB(嵌入式简化版)

设计目标:

内存极度受限

(<64MB RAM)
代码最小化

(<1000 行)

实现原理:

简单 first-fit 分配器
无 cache 概念

:所有对象共享一个堆
使用 Buddy 页作为堆

适用场景:

嵌入式 Linux(路由器、IoT 设备)
内核配置
CONFIG_SLOB=y

2.4 三种实现对比

特性 SLAB SLUB SLOB
代码复杂度 高(10k+ 行) 中(5k 行) 低(1k 行)
内存开销 高(10-20%) 低(<5%) 极低
性能
调试支持 有限 丰富
适用场景 旧版内核 现代默认 嵌入式

💡 Linux 5.15+ 默认使用 SLUB ,因其在性能、内存、调试三者间取得最佳平衡。


第三章:工业级 Slab 设计框架

3.1 设计目标与约束

核心目标:

  1. 高性能

    :分配/释放 O(1) 时间,多核无锁

  2. 低内存开销

    :元数据 < 5% 总内存

  3. 高缓存命中率

    :同类对象紧凑存储

  4. 调试友好

    :支持红区、对象跟踪

约束条件:

32 位系统

:指针对齐 4 字节
4KB 页大小

:slab 大小为页的整数倍
无动态重定位

:对象地址固定

3.2 数据结构设计

缓存描述符(kmem_cache)

每 CPU 缓存(kmem_cache_cpu)

元数据嵌入页描述符

3.3 关键算法实现

创建缓存(kmem_cache_create)

分配对象(kmem_cache_alloc)

释放对象(kmem_cache_free)

3.4 slab 创建与销毁

分配新 slab

释放 slab


第四章:kmalloc 接口与多级缓存

4.1 kmalloc 的设计挑战

kmalloc 需要支持任意大小(8-8192 字节)的分配,但:

不能为每个大小创建 cache

(缓存爆炸)
需要快速映射大小到 cache

解决方案: 多级大小类 (Size Classes)

Linux 将分配大小分为 离散的大小类 ,每个类对应一个 cache:

大小范围 Cache 名称 实际分配大小
8-16 kmalloc-16 16
17-32 kmalloc-32 32
33-64 kmalloc-64 64
4097-8192 kmalloc-8k 8192

大小类计算:

4.2 kmalloc 实现框架

初始化 kmalloc caches

kmalloc/kfree 接口

4.3 地址反查 cache 机制

问题:如何从对象地址找到所属 cache?

SLAB

:通过
obj_to_index
计算 cache
SLUB


元数据嵌入页描述符
,直接访问

SLUB 方案(工业级):

✅ SLUB 的页描述符嵌入方案是性能关键 !


第五章:高级优化与调试特性

5.1 对象着色(Slab Coloring)

问题:不同 cache 的对象映射到同一 CPU 缓存行

后果

:缓存颠簸(Cache Thrashing)
解决方案


着色
(Coloring)

实现原理:

每个 slab 的
起始偏移
不同
偏移量 =
color L1_CACHE_BYTES
确保对象起始地址在不同缓存行

代码框架:

💡 SLUB 移除了显式着色,依赖硬件预取优化

5.2 红区(Redzone)与调试支持

红区设计:

在对象前后添加
保护区
(通常 8-16 字节)
填充特定模式(如
0x5a5a5a5a

释放时检查是否被覆盖

SLUB 调试框架:

5.3 内存泄漏检测

对象跟踪(Object Tracking):

记录每个对象的分配/释放轨迹
通过
/sys/kernel/slab//alloc_calls
查看

实现:


结论:Slab 设计的工程权衡

Slab 分配器的设计体现了操作系统内核开发的核心哲学: 在性能、内存、复杂度之间寻找最优平衡 。

SLAB

选择了
功能完整
,但牺牲了内存效率
SLUB

选择了
简化与性能
,成为现代默认
SLOB

选择了
极致精简
,适用于嵌入式

对于我们的实现, SLUB 风格是最佳选择 :

元数据嵌入页描述符

:内存开销最小
每 CPU 无锁缓存

:多核性能卓越
动态调试支持

:开发友好

理解 Slab 系统不仅有助于内核开发,更能培养 缓存友好编程 (Cache-Friendly Programming)的意识——这是高性能系统软件的基石。


附录:关键数据结构与函数速查

核心数据结构

结构 作用 文件
struct kmem_cache 缓存描述符 slab.h
struct kmem_cache_cpu 每 CPU 缓存 slab.h
struct page (slab) slab 元数据 mm_types.h

关键函数

函数 功能 文件
kmem_cache_create 创建缓存 slab_common.c
kmem_cache_alloc 分配对象 slub.c
kmem_cache_free 释放对象 slub.c
kmalloc / kfree 通用接口 slab_common.c

调试接口

接口 用途
/sys/kernel/slab/ 查看所有 cache 信息
slabinfo 命令行工具
echo 1 > /sys/kernel/slab//trace 启用轨迹跟踪
最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。