内核里到处是小对象:task_struct、inode、dentry、sk_buff、bio……它们几十到几百字节,如果每次都向 buddy 要一整页太浪费。slab 层把一页切成多个”对象槽”,复用已分配的页。
一、先看图
flowchart TD
KMALLOC[kmalloc 64 字节] --> CACHE[kmem_cache<br/>size=64]
CACHE --> PCPU{per-CPU<br/>freelist?}
PCPU -->|有| OBJ[返回 object]
PCPU -->|无| PARTIAL[partial slab<br/>页]
PARTIAL -->|有空槽| OBJ
PARTIAL -->|无| BUDDY[buddy alloc<br/>新 slab 页]
BUDDY --> INIT[切成 N 个<br/>64 字节 slot]
INIT --> OBJ
classDef fast fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef slow fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class PCPU,OBJ fast
class BUDDY,INIT slow
二、slab → SLUB → SLOB 简史
2.1 slab(1994)
Solaris 启发:per-cache 的 full / partial / empty 三链表 + coloring。复杂、管理结构占用大。
2.2 SLUB(2.6.22,2007)
Christoph Lameter 重写:
- 无 full/empty 链表——只跟踪 partial
- 对象元数据存在对象自身(空闲时 freelist 指针)
- per-CPU freelist 替代 per-CPU slab
- 代码量少一半、内存开销更低
6.x 唯一主力。
2.3 SLOB(已删除 6.4)
极简分配器,面向 <64KB RAM 嵌入式。6.4 后删除。
三、kmem_cache
每种对象类型一个 kmem_cache:
struct kmem_cache *task_struct_cache;
task_struct_cache = kmem_cache_create("task_struct",
sizeof(struct task_struct), __alignof__(struct task_struct),
SLAB_PANIC|SLAB_ACCOUNT, NULL);
// 分配
struct task_struct *p = kmem_cache_alloc(task_struct_cache, GFP_KERNEL);
// 释放
kmem_cache_free(task_struct_cache, p);kmalloc
通用分配器——底层是一组按 size class 的 kmem_cache:
kmalloc-8, kmalloc-16, kmalloc-32, ..., kmalloc-8192
void *p = kmalloc(100, GFP_KERNEL); // 实际从 kmalloc-128 分配
kfree(p);四、SLUB 内部
4.1 slab page
一个 slab 页(通常 order-0 或 order-1)被切成若干对象:
[ obj0 | obj1 | obj2 | ... | objN ]
空闲对象串成 freelist(在对象内部存 next 指针)。
4.2 per-CPU freelist
每 CPU 缓存一个”当前 slab”+ freelist。分配只需读 freelist
头——无锁(this_cpu_cmpxchg)。
4.3 partial 链表
CPU freelist 空了 → 从 node 的 partial 链表取一个 slab。SLUB_CPU_PARTIAL 控制缓存多少 partial。
4.4 冻结
partial slab 从 node 链表移到 CPU 本地时被”冻结”——其他 CPU 不能碰。减少争抢。
五、对齐与着色
- 对象按
ARCH_SLAB_MINALIGN(通常 8/16 字节)对齐 - KASAN 要求额外 redzone(对象前后的守卫区)
- 缓存行着色(cache line coloring):同类 slab 的起始偏移不同,分散 cache 冲突
六、KASAN 集成
KASAN(Kernel Address SANitizer)在 SLUB 级别检测 UAF 和 OOB:
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y # 基于 shadow memory
CONFIG_KASAN_SW_TAGS=y # arm64 tag-based
CONFIG_KASAN_HW_TAGS=y # arm64 MTE
SLUB 分配时 poison 对象,释放后再 poison → 访问已释放内存触发 KASAN report。
七、SLUB_DEBUG
编译期 CONFIG_SLUB_DEBUG=y + 启动参数
slub_debug=FZPU:
- F:sanity check(double free、对象损坏)
- Z:red zone(对象前后填充守卫值)
- P:poison(释放后填充 0x6b/0xa5)
- U:user tracking(记录分配调用栈)
生产一般不开(性能惩罚 2-10x)。调试时通过
slub_debug=FZP,kmalloc-64 只对特定 cache
开。
八、memcg 感知
cgroup v2 下 SLUB 支持 SLAB_ACCOUNT flag →
内核对象计入 memcg。
cat /sys/fs/cgroup/myapp/memory.stat | grep slab
# slab 12345678
# slab_reclaimable 8765432
# slab_unreclaimable 3580246九、观察
# 所有 slab cache
cat /proc/slabinfo | head
# 或更友好的
slabtop -o
# 特定 cache
cat /sys/kernel/slab/kmalloc-256/objs_per_slab
cat /sys/kernel/slab/kmalloc-256/cpu_partial
cat /sys/kernel/slab/kmalloc-256/total_objects
# slab 总内存
cat /proc/meminfo | grep Slab
# Slab: 456000 kB
# SReclaimable: 300000 kB
# SUnreclaim: 156000 kB十、常见问题
A:Slab 占用很高 检查 slabtop 看哪个
cache 大——常见:dentry_cache、inode_cache(文件系统
cache)。echo 2 > /proc/sys/vm/drop_caches
可回收,但不推荐生产常用。
B:slab leak kmemleak 工具扫描 slab 中没有引用指向的对象 → 可能泄漏。
C:KASAN report UAF in slab 看 report 的 alloc/free stack trace,定位生命周期管理 bug。
十一、小结
- SLUB 是 buddy 之上的对象缓存层
- per-CPU freelist 让分配几乎无锁
- kmalloc 按 size class 走预置的 kmem_cache
- KASAN + SLUB_DEBUG 是内核内存 bug 的主力检测
- memcg 感知让容器可以限制内核 slab 使用
参考文献
- Bonwick, J. “The Slab Allocator: An Object-Caching Kernel Memory Allocator.” USENIX 1994
- Christoph Lameter, “SLUB: The Unqueued Slab Allocator.” 2007
mm/slub.cDocumentation/mm/slub.rstinclude/linux/slab.h
工具
/proc/slabinfo、slabtop/sys/kernel/slab/slub_debug=boot paramkmemleakperf kmem
延伸阅读
上一篇:Buddy 系统 下一篇:vmalloc/kmap/ioremap
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内核内存调试
内核内存 bug 是最难追的:UAF、OOB、double free、leak 都可能沉默数月。本文讲 KASAN 三种模式、KFENCE 生产采样、kmemleak、SLUB_DEBUG、UBSAN/KCSAN 联动。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。