本页目录
Slab 与 kmalloc
覆盖: SLUB allocator → kmem_cache → kmalloc/kfree → slab merging → 调试 (KASAN/SLUB_DEBUG) → SLOB 退役 (6.4) 内核版本: 2.6 ~ 6.x
概述
Buddy allocator 最小分配单位是 1 页 (4KB)。但内核中绝大多数分配远小于 4KB——task_struct (~4KB)、inode (~600B)、dentry (~200B)。如果每个小对象都占用一整页,浪费将极其严重。
SLUB allocator 解决了这个问题:将一整页(或连续几页)切成多个相同大小的小对象,用 freelist 管理空闲和已分配的对象。SLUB 是当前默认的 slab 实现(6.4 移除了旧的 SLOB),源自 SLAB (Solaris → Linux 2.2) → SLUB (Linux 2.6.23, C. Lameter)。
SLUB 架构
kmem_cache
// include/linux/slub_def.h
;
Per-CPU 热 slab
flowchart TD
START["SLUB 分配/释放请求"]
START --> OP{"操作类型?"}
OP -->|"分配"| ALLOC["从 freelist 头取对象<br/>(原子: this_cpu_cmpxchg)"]
ALLOC --> FULL{"当前 slab<br/>满了?"}
FULL -->|"否"| FAST_ALLOC["✅ 返回对象<br/>(无锁, 极快)"]
FULL -->|"是"| PART{"有 partial<br/>slab?"}
PART -->|"是"| SWITCH["换下一个 partial slab"]
SWITCH --> FAST_ALLOC
PART -->|"否"| BUDDY_ALLOC["从 buddy 拿新 slab"]
BUDDY_ALLOC --> FAST_ALLOC
OP -->|"释放"| FREE["对象归还到 freelist 头"]
FREE --> EMPTY{"slab 变全空?"}
EMPTY -->|"是"| RETURN["可能归还回 buddy"]
EMPTY -->|"否"| FAST_FREE["✅ 释放完成<br/>(无锁, 极快)"]
classDef fast fill:#e8f5e9,stroke:#2e7d32
classDef slow fill:#ffebee,stroke:#c62828
classDef decision fill:#fff3e0,stroke:#ef6c00
class START,ALLOC,FREE fast
class FULL,PART,EMPTY decision
class SWITCH,BUDDY_ALLOC,RETURN slow
class FAST_ALLOC,FAST_FREE fast
分配与释放路径
// mm/slub.c
// 分配 (fast path → slow path):
→ →
├─ fast path: 从 cpu_slab 的 freelist 取
│ object = ;
│ if
│ // 更新 freelist 指针 (cmpxchg)
│ return object;
│
└─ slow path:
├─ 尝试 CPU partial list 中的 slab
├─ 尝试 node partial list 中的 slab
├─ 都失败 → →
│ → → buddy allocator
│ → 构造 freelist
└─ 返回第一个对象
// 释放 (fast path):
→
├─ fast path: 对象放回 cpu_slab 的 freelist
│ ;
│
└─ slow path:
├─ slab 变全空闲? → 如果 per-node partial 不多 → 转为 partial
│ → 否则 → 释放回 buddy allocator
└─ 更新 node 统计
kmalloc: 通用分配接口
// include/linux/slab.h
// kmalloc 实际上是预定义的 kmem_cache 的包装
// 内核中预定义的通用 cache:
// kmalloc-8, kmalloc-16, kmalloc-32, kmalloc-64,
// kmalloc-96, kmalloc-128, kmalloc-192, kmalloc-256,
// kmalloc-512, kmalloc-1k, kmalloc-2k, kmalloc-4k, kmalloc-8k
// kmalloc(size, flags):
// 1. 根据 size 查到对应的 kmem_cache (通过 kmalloc_caches 表)
// 2. 调用 kmem_cache_alloc(s, flags)
// 3. 返回对象指针
// kfree(ptr):
// 1. 从对象 metadata 中找到它属于哪个 kmem_cache
// → 通过 page->slab_cache (struct page 中的字段)
// 2. kmem_cache_free(cache, ptr)
Slab Merging
// mm/slab_common.c
// 为了节省内存,尺寸相同或相近的 kmem_cache 可以合并:
// "dentry" (256B) 和 "inode_cache" (592B)
// → 都分配自 kmalloc-512? 不合并: 它们不同 size
// → 但如果两个 cache 的 object_size 相近 (都接近 256B)
// → 可能共享同一个底层 slab
//
// /sys/kernel/slab/ 下可以看到哪些 cache 被合并
SLOB 退役 (6.4)
历史上 Linux 有三种 slab 实现:
SLAB: 原始实现 (Solaris 移植 → Linux 2.2), 最复杂
SLUB: 简化实现 (2.6.23), 当前默认
SLOB: 极小实现, 用于嵌入式系统 (< 16MB RAM)
6.4 移除了 SLOB:
- SLUB 经过多年优化, 代码体积已经足够小
- 嵌入式系统可以用 SLUB (CONFIG_SLUB_TINY)
- 维持三个 allocator 的维护负担太大
SLOB 的设计思路值得一提:
- 没有 per-CPU slab
- 所有分配从单一空闲链表
- first-fit 分配 (找到第一块够大的)
- 极致简单, 但碎片化和性能差
kmemleak: 内存泄漏检测
// mm/kmemleak.c
// 类似用户态 valgrind 的 memcheck
// 周期性扫描所有已分配的内核对象,找到无引用的 → 报告泄漏
// 启用:
// CONFIG_DEBUG_KMEMLEAK=y
// boot: kmemleak=on
// 手动触发:
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak # 泄漏报告
KASAN: 越界/UAF 检测
// mm/kasan/
// Kernel Address Sanitizer — 编译时 instrumentation
// 检测 slab out-of-bounds, use-after-free, double-free
// 三种模式:
// Generic KASAN: 影子内存 (每 8 bytes 用 1 byte 标记) → 1/8 内存开销
// Software Tag-Based KASAN: ARM64 使用 TBI (Top Byte Ignore) → 低开销
// Hardware Tag-Based KASAN: ARM64 MTE → 硬件检查 → 用于生产环境
调试与观测
# slab 使用情况
# 列: name, active_objs, num_objs, objsize, objperslab, pagesperslab
# 更好的人类可读版本
# 特定 cache 的详细信息
# 查看谁在用最多 slab (kmem trace)
|
# 内存泄漏检测
|
参考与延伸
- 内核文档:
Documentation/mm/slub.rst,Documentation/dev-tools/kmemleak.rst,Documentation/dev-tools/kasan.rst - LWN:
- "The SLUB allocator" (lwn.net/Articles/229984/)
- "Removing SLOB" (lwn.net/Articles/967178/)
- 源码文件:
mm/slub.c— SLUB 实现mm/slab_common.c— slab merging, sysfs 接口include/linux/slub_def.h— struct kmem_cachemm/kmemleak.c— 泄漏检测mm/kasan/— KASAN
关键词: SLUB, kmem_cache, kmalloc, per-CPU slab, freelist, slab merging, SLOB退役, kmemleak, KASAN