Linux SLUB 分配器:内核对象分配的核心引擎

在内核编程中,频繁的内存分配与碎片化管理是性能瓶颈的主要来源。SLUB(the unqueued slab allocator)作为 Linux 内核默认的 slab 分配器,自 2.6.23 版本取代 SLAB 分配器以来,已成为内核对象分配的核心引擎。本文深入剖析 SLUB 的设计哲学、内部机制、调试接口以及与性能剖析工具的集成。

为什么要需要 Slab 分配器?

传统伙伴系统(Buddy System)以 2^n 页为粒度分配内存,若内核频繁请求几十个字节的小对象(如 task_struct、inode、dentry),将导致严重的内部碎片。此外,对象初始化(构造函数)的开销在高频场景下不可接受。

Slab 分配器通过「对象缓存池」模式解决了这两个核心问题:

  • 对象复用:预分配的 slab 页中的对象在被释放后回到缓存,下次分配时无需重新初始化。
  • 减少碎片:相同大小的对象在同一个 slab 中紧凑排列,彻底消除内部碎片。
  • 硬件缓存友好:slab 着色(Slab Coloring)利用不同的起始偏移对象,使不同 slab 的对象映射到不同的 cache line,减少冲突未命中。

SLUB 架构总览

SLUB 将每个缓存(kmem_cache)分配到一组连续的页面,并切分成固定大小的槽位(slots)。其核心数据结构包括:

struct kmem_cache {
    struct kmem_cache_cpu *cpu_slab;       // 每个 CPU 的快速路径缓存
    struct kmem_cache_node *node[MAX_NUMNODES];  // 每个 NUMA 节点的 partial/full 链表
    unsigned int size;                      // 对象实际大小
    unsigned int object_size;              // 用户请求的大小
    unsigned int offset;                    // 下一个空闲对象的偏移量
    struct kmem_cache_order_objects oo;     // 每次分配的页面阶数
    // ...
};

// Per-CPU 快速路径
struct kmem_cache_cpu {
    void **freelist;    // 空闲对象链表
    unsigned long tid;  // 全局事务 ID(无锁顺序保证)
    struct page *page;  // 当前正在使用的 slab 页
    struct page *partial;  // CPU 本地 partial 链表(CONFIG_SLUB_CPU_PARTIAL)
};

// 每个 NUMA 节点
struct kmem_cache_node {
    struct list_head partial;   // 部分空闲的 slab 链表
    unsigned long nr_partial;   // partial 数量
    struct list_head full;      // 完全满盈的 slab 链表(部分实现版本)
    spinlock_t list_lock;
};

分配路径:从部分空闲到直接内存

SLUB 的分配路径严格按照快速路径优先的原则设计:

// 简化伪代码:SLUB 分配流程
void *slub_alloc(kmem_cache *cachep, gfp_t flags)
{
    // ① 尝试 Per-CPU fastpath(无锁)
    cpu_slab = raw_cpu_ptr(cachep->cpu_slab);
    
    if (likely(cpu_slab->freelist                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部