Linux内核Slab分配器深度实战:从SLAB到SLUB全链路剖析与性能调优

引言

在Linux内核中,内存分配是操作系统最核心的功能之一。伙伴系统(Buddy System)以页(4KB)为单位管理物理内存,但内核中大量场景需要远小于4KB的对象分配——如task_struct、inode、dentry、文件描述符等。若直接由伙伴系统分配整页,内存浪费将极其严重。Slab分配器应运而生,它以对象(Object)为单位缓存、管理和复用内核结构体,极大提升了小对象分配的效率和内存利用率。

自2.6.23内核起,SLUB取代SLAB成为默认分配器;而在嵌入式等资源受限场景中,SLOB依然有一席之地。本文将深入剖析三种变体的实现原理、数据结构、分配/释放全链路、调试手段和性能调优策略。

1. Slab分配器设计思想

1.1 核心原理

Slab分配器的核心思想是对象缓存与复用:将相同类型的结构体预先分配好并保存在一个高速缓存(cache)中,每次分配时直接从cache取出已初始化的对象,释放时归还而非销毁,避免频繁的构造/析构开销。

工作流程:

  1. 为特定类型的对象创建kmem_cache,指定对象大小、对齐方式和构造/析构函数
  2. kmem_cache向伙伴系统申请一个或多个page作为slab,将slab内存按对象大小切分为slots
  3. 分配时从partial/full slabs链表中取出空闲slot,返回对象指针
  4. 释放时标记slot为空闲,若slab全空则有机会归还给伙伴系统

1.2 Slab的三种状态

  • Full slab:所有slot都已分配,无空闲对象
  • Partial slab:部分分配、部分空闲,优先从这里分配
  • Empty slab:所有slot均空闲,在系统内存紧张时会被回收

2. SLAB分配器实现剖析

2.1 核心数据结构

struct kmem_cache {
    struct kmem_cache_node **node;  // NUMA节点数组
    struct array_cache __percpu *cpu_cache; // per-CPU批处理缓存
    unsigned long flags;
    unsigned int object_size;       // 实际对象大小
    unsigned int size;          // 对齐后大小
    unsigned int align;
    const char *name;
    void (*ctor)(void *obj);        // 构造函数
    struct list_head list;          // 全局cache链表
    unsigned long num;          // 每slab对象数
    gfp_t alloc_gfp;               // 分配标志
};

struct kmem_cache_node {
    struct list_head slabs_full;
    struct list_head slabs_partial;
    struct list_head slabs_free;
    unsigned long free_objects;  // 空闲对象计数
    unsigned int free_limit;
};

struct slab {
    union {
        struct {
            struct list_head slab_list; // 链入full/partial/free
            void *s_mem;        // slab中第一个对象地址
            unsigned int inuse; // 已使用对象数
            unsigned int free;  // 第一个空闲对象索引
        };
        struct kmem_cache *slab_cache; // SLUB复用此union
    };
};

2.2 SLAB对象布局(kmem_cache slab obj)

经典SLAB中,每个slab的管理结构(struct slab)位于slab内存区域的末尾,开头是freelist数组(记录空闲对象索引),中间是对象内存区域。coloring机制通过在slab开头添加不同大小的填充,缓解Cache Line冲突。

3. SLUB分配器——现代默认方案

3.1 设计改进

SLUB(Unqueued Slab Allocator)针对SLAB的复杂元数据进行了简化:

  • 将slab管理结构直接嵌入page结构体,无需外部冗余存储
  • 每个CPU维护单节点缓存,摒弃了SLAB的多队列设计
  • per-CPU partial slab支持无锁批量操作
  • freelist使用链表而非索引数组,CPU cache-friendly

3.2 SLUB核心数据结构

// page结构体中与slab相关的字段(SLUB嵌入)
struct page {
    ...
    union {
        struct {
            unsigned long flags;
            ...
            struct {
                union {
                    struct list_head lru;
                    struct {
                        union {
                            struct page *next; // partial链表指针
                        };
                        int pages;     // compound order
                        int pobjects;  // partial中空闲对象数
                    };
                };
                struct kmem_cache *slab_cache; // 指向所属cache
                void *freelist;        // 指向第一个空闲对象
                ...
            };
        };
    };
};

struct kmem_cache {
    struct kmem_cache_cpu *cpu_slab;    // per-CPU
    struct kmem_cache_node *node[MAX_NUMNODES];
    unsigned long flags;
    unsigned int offset;        // 空闲指针偏移(Red Zone检测用)
    unsigned int object_size;
    unsigned int inuse;
    unsigned int size;
    unsigned int align;
    unsigned long min_partial;      // node中最小partial数
    unsigned int cpu_partial;       // per-CPU partial批处理数
    const char *name;
    struct list_head list;
    ...
};

3.3 SLUB分配全链路

kmem_cache_alloc(cache, flags)
  └─ __kmem_cache_alloc(cache, flags)
      │
      ├─ CPU空闲对象(freelist)存在?
      │   ├─ 是 → 直接pop,快速路径(无锁,2-3条指令)
      │   └─ 否 → new_slab()
      │       ├─ partial slab存在? → 提升为CPU slab
      │       └─ 无 → 向伙伴系统申请新page
      │           └─ page_address()初始化对象布局
      │               └─ 构建freelist(空闲对象链表)
      │
      └─ 返回对象指针

3.4 SLUB释放全链路

kmem_cache_free(cache, obj)
  └─ __slab_free(cache, obj)
      │
      ├─ 对象属于当前CPU slab?
      │   ├─ 是 → push到CPU freelist,快速路径
      │   └─ 否 → 判断对象所在slab状态
      │       ├─ 原slab full → 移入partial
      │       └─ 原slab partial → 若超过free_limit则归还
      │
      └─ (若部分空闲数超过cpu_partial阈值,补充给node partial链表)

4. SLOB——超轻量嵌入式方案

SLOB(Simple List Of Blocks)是为内存受限系统设计的极简分配器,匹配kmalloc请求到最佳空闲块(最佳适应算法),代码量极少(约600行),但存在严重外部碎片问题,不推荐用于通用系统。

5. 三种分配器对比

特性SLABSLUBSLOB
引入版本Linux 2.1Linux 2.6.23Linux 2.6
默认内核Solaris风格Linux2.6.23 嵌入式
元数据结构独立struct slab嵌入page结构页内位图
NUMA支持多节点队列node cpu无
调试能力slub_debug(P/F/U/Z/R/A)有限
内存开销较高低最低
性能良好最优一般
适用场景服务器桌面/服务器/云嵌入式

6. Slab调试与监控

6.1 /proc/slabinfo

$ cat /proc/slabinfo
slabinfo - version: 2.1
# name                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部