引言
内存管理是 Linux 内核最为核心的子系统之一。在操作系统运行过程中,内核需要频繁地分配和释放大量小对象(如 task_struct、inode、dentry 等),这些对象的大小通常只有几百字节,远小于内存页大小(4KB)。如果直接基于页分配器(Buddy System)来管理这些小对象,必然会导致严重的内部碎片问题。
为了解决这个问题,Linux 内核引入了 Slab 分配器——一种专门用于管理小对象内存的高效分配机制。Slab 分配器不仅减少了内存碎片,还通过对象复用、CPU 缓存着色、per-CPU 缓存等手段,极大地提升了内存分配和释放的性能。
本文将从 Slab 分配器的设计动机出发,深入剖析其内部数据结构、工作原理、实现细节以及在实际系统调优中的应用。
1. 从 Buddy System 到 Slab:问题与设计动机
1.1 页级分配器的局限性
Linux 内存管理的最高层是页分配器(Buddy System),它负责管理物理页框(通常 4KB),分配和释放的基本单位是页。Buddy System 解决了外部碎片问题,但它至少分配一个完整页面。当一个内核对象只有 200 字节却占用 4096 字节时,将近 95% 的内存被浪费——这就是内部碎片。
1.2 小对象分配的痛点
内核运行中频繁创建/销毁的对象:
task_struct(进程描述符):约 7KB,但在 fork 时频繁分配inode和dentry(文件系统缓存):几百字节file结构体:打开文件时分配- 网络相关的
sk_buff:每个数据包都需要 - 各种驱动私有数据结构
除了内存浪费,直接基于页分配还有两个性能问题:
- 初始化开销:每个对象都涉及复杂的初始化工作(如锁、链表头),频繁分配/释放带来巨大开销
- 缓存抖动:反复分配释放导致 CPU 缓存频繁失效,严重影响性能
1.3 Slab 分配器的设计目标
Slab 分配器的核心设计目标可以归纳为:
- 减少内部碎片:精确按对象大小分配,几乎零浪费
- 对象复用:释放的对象回到缓存中,避免反复构造/析构
- 硬件缓存友好:通过缓存着色(Cache Coloring)减少 Cache 冲突
- per-CPU 缓存:每个 CPU 有独立缓存,避免锁竞争
- NUMA 感知:支持按节点分配,减少远端内存访问
2. Slab 分配器的三层架构
Linux 内核中的小型对象内存分配采用了一个三层架构:
-------------------------------------------------------
| 用户接口层 |
| kmalloc() / kfree() / kmem_cache_alloc() / free() |
-------------------------------------------------------
↓
-------------------------------------------------------
| Slab 分配器(通用层) |
| kmem_cache 管理对象缓存池(slabs) |
-------------------------------------------------------
↓
-------------------------------------------------------
| 页分配器(Buddy System) |
| 伙伴系统分配物理页 |
-------------------------------------------------------
2.1 顶层:kmalloc — 通用内存分配
kmalloc() 是最常用的内核内存分配接口,它类似于用户态的 malloc()。kmalloc 通过预定义的一组不同大小的 kmem_cache(称为 "size cache")来分配内存:
// Linux 内核中 kmalloc 的实现(简化版)
static __always_inline void *__do_kmalloc(size_t size, gfp_t flags)
{
struct kmem_cache *cachep;
void *ret;
if (size

发表评论 取消回复