引言

操作系统内核的内存管理是计算机科学中最精细的工程领域之一。与用户态程序不同,内核需要在极端受限的条件下运行:不能睡眠、不能有内存碎片、必须保证实时响应。在这种场景下,简单的 malloc/free 远远不够。

Linux 内核引入的 Slab 分配器(由 Jeff Bonwick 在 Solaris 中首创,后被 Linux 采纳并演进)正是解决这一难题的核心机制。它不仅管理内存,更通过对象复用、着色对齐和 per-CPU 缓存,将内存分配从"通用问题"转化为"专用路径",实现了微秒级的分配延迟。

本文将从设计理念、核心数据结构、分配路径、着色策略、SLUB 演进,以及实际调优案例,全方位解析 Slab 分配器的工程智慧。

一、为什么需要 Slab? 页面分配器的局限

1.1 以页为粒度的物理内存管理

Linux 内存管理的最底层是 Buddy System(伙伴系统),它以 页(Page,通常 4KB) 为最小单位管理物理内存。伙伴系统将页面组织为 11 个链表(order 0~10),分别管理 2^n 个连续页面的内存块。

Order 0:   4KB  页面Order 1:   8KB  连续 2 页Order 2:   16KB 连续 4 页...Order 10:  4MB  连续 1024 页

伙伴系统的优势是高效处理大块连续内存的分配与回收,消除了外部碎片。但它的致命问题是:粒度太粗。

1.2 内核对象的现实需求

内核中大量对象的尺寸远小于一个页面:

  • struct task_struct:~1.8KB(x86_64)
  • struct inode:~580B
  • struct dentry:~192B
  • struct file:~256B
  • 各种网络数据结构:几百字节到几KB

如果每个对象都从伙伴系统分配 4KB 页面,内部碎片率可达 50%~95%。对于每秒创建/销毁数千个对象的服务器场景,这是巨大的内存浪费。

1.3 malloc 在内核空间不可用

用户态的 glibc malloc 依赖 brk/mmap 系统调用扩展堆空间,而内核态无法调用系统调用。此外,内核分配器还需要满足:

  • 不可睡眠:中断上下文中的分配必须使用 GFP_ATOMIC,不能等待页面回收。
  • 对齐要求:DMA 设备需要特定对齐的内存块。
  • 缓存亲和性:NUMA 架构中,内存分配应靠近请求者的 CPU 节点。
  • 调试能力:检测越界写入、释放后使用(use-after-free)等内存错误。

这些约束催生了 Slab 分配器。

二、Slab 的核心设计理念

Slab 分配器的设计建立在三个关键洞察之上:

2.1 对象复用(Object Reuse)

对象创建和销毁的开销不仅在于内存分配,还在于构造函数/析构函数的调用。当对象被释放时,Slab 并不立即销毁它,而是将其放回空闲链表。下次分配同一类型对象时,直接从空闲链表取出,跳过初始化。

分配新对象 = 分配内存   构造初始化  -                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部