找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1863

积分

0

好友

249

主题
发表于 5 小时前 | 查看: 4| 回复: 0

从 Linux 内核内存管理框架图可知:slab/slub/slob 都是构建在伙伴系统之上的小内存块管理机制。

Linux内核内存管理架构示意图,含用户空间、内核空间与硬件层三层结构

伙伴系统以 page(通常为 4KB)为单位进行物理内存分配。但在大量场景中——如频繁创建销毁 struct inodetask_structsk_buff 等固定尺寸内核对象——申请整页内存既低效又易造成内部碎片。slab 分配器正是为此而生:它复用伙伴系统分配的连续页面,在其上实现细粒度、无锁化、缓存友好的对象池管理。

本章将围绕以下主线展开:

  • 静态认知:从 struct kmem_cache 等核心数据结构出发,理解 slab 的组织逻辑;
  • 生命周期:完整梳理 kmem_cache_createkmem_cache_allockmem_cache_freekmem_cache_destroy 四阶段行为;
  • 底层联动:解析 kmalloc 如何基于 slab 实现字节级通用分配,并揭示其与 slab_caches 全局链表的映射关系。

需要强调的是:slab 分配器本身不直接管理物理页,而是依赖伙伴系统提供底层页面支撑。它所做的,是在这些连续物理页之上构建一套高效、可追踪、支持调试的“对象工厂”。


slab 分配器相关核心函数

struct kmem_cache *kmem_cache_create(const char *, size_t, size_t,
                                     unsigned long, void (*)(void *));
// 创建 slab 描述符 kmem_cache,此时并未真正分配物理内存
void *kmem_cache_alloc(struct kmem_cache *, gfp_t flags);
// 从指定 slab 缓存中分配一个对象
void kmem_cache_free(struct kmem_cache *, void *);
// 将对象归还至对应 slab 缓存
void kmem_cache_destroy(struct kmem_cache *);
// 销毁 slab 描述符(需确保所有对象已释放)

Slab Cache 结构示意图:Cache → Slab → Object 层级关系


关键问题解析

Q:每个 slab 由多少个页面组成?
A:每个 slab 至少占用 1 个物理页面,最多不超过 2^MAX_ORDER(通常为 2^10 = 1024 页,即 4MB)。具体页数由 gfporder 字段决定,该值在 kmem_cache_create 时通过 calculate_slab_order() 动态计算得出。

Q:slab 所需物理内存何时分配?
A:kmem_cache_create() 仅初始化描述符,不分配页;首次调用 kmem_cache_alloc() 且本地 CPU 缓存、共享缓存及 slabs_partial/slabs_free 链表均无空闲对象时,才触发 cache_grow(),向伙伴系统申请 2^gfporder 个页面,并挂入 slabs_free 链表。

Q:slab 中空闲对象过多,是否回收?如何回收?
A:是,有两种主动回收路径:

  1. 按需回收:当本地缓冲池 ac->avail > ac->limit 时,kmem_cache_free() 会主动批量释放 batchcount 个对象;若某 slab 中所有对象均空闲且无活跃引用,内核可将其整体销毁并归还页面;
  2. 定时扫描:内核注册了 delayed_work 任务 cache_reap(),周期性遍历所有 kmem_cache,对长期闲置的 slab 进行清理。

Q:cache colouring(着色)的作用是什么?
A:使不同 slab 上相同偏移位置的对象起始地址在 L1 Cache Line 中错开,避免多个对象映射到同一 cache line 导致的伪共享(false sharing),从而提升多核并发访问性能。

另一个典型应用是 Per-CPU 本地对象缓冲池,其优势在于:

  • 对象尽可能在创建它的 CPU 上被复用(提高 cache locality);
  • 访问无需加锁(lock-free),显著降低竞争开销。

一、slab 相关核心数据结构

slab 分配器核心数据结构关系图:kmem_cache → kmem_cache_cpu → kmem_cache_node → slab 链表

struct kmem_cache:slab 描述符主干

这是整个 slab 缓存的元数据容器,定义于 include/linux/slub_def.h

struct kmem_cache {
    struct array_cache __percpu *cpu_cache;
    /* 1) Cache tunables. Protected by slab_mutex */
    unsigned int batchcount;     // CPU 本地缓存为空时,从 shared/partial/free 链表批量填充的对象数
    unsigned int limit;          // 本地缓存空闲对象上限,超限则触发释放
    unsigned int shared;

    unsigned int size;           // 对齐后的对象大小(bytes)
    struct reciprocal_value reciprocal_buffer_size;

    /* 2) touched by every alloc & free from the backend */
    unsigned int flags;          // 分配掩码(如 SLAB_HWCACHE_ALIGN)
    unsigned int num;            // 每个 slab 中的对象数量

    /* 3) cache_grow/shrink */
    unsigned int gfporder;       // 占用 2^gfporder 个页面
    gfp_t allocflags;            // 页面分配标志(如 GFP_DMA)

    size_t colour;               // cache colouring 可选值个数
    unsigned int colour_off;     // cache line 大小(通常为 64B)
    struct kmem_cache *freelist_cache;
    unsigned int freelist_size;

    void (*ctor)(void *obj);     // 对象构造函数(可选)

    /* 4) cache creation/removal */
    const char *name;            // 缓存名称(如 "kmalloc-64"),可见于 /proc/slabinfo
    struct list_head list;      // 全局 slab_caches 链表节点
    int refcount;                // 引用计数(用于安全销毁)
    int object_size;             // 用户请求的对象原始大小
    int align;                   // 显式对齐要求

    /* 5) statistics */
#ifdef CONFIG_DEBUG_SLAB
    unsigned long num_active;
    unsigned long num_allocations;
    unsigned long high_mark;
    unsigned long grown;
    unsigned long reaped;
    unsigned long errors;
    unsigned long max_freeable;
    unsigned long node_allocs;
    unsigned long node_frees;
    unsigned long node_overflow;
    atomic_t allochit;
    atomic_t allocmiss;
    atomic_t freehit;
    atomic_t freemiss;
    int obj_offset;
#endif
#ifdef CONFIG_MEMCG_KMEM
    struct memcg_cache_params memcg_params;
#endif

    struct kmem_cache_node *node[MAX_NUMNODES]; // NUMA 节点对应的 slab 管理结构
};

✅ 提示:slab 是 Linux 内核 内存管理 的关键子系统之一,其设计深刻体现了对局部性原理与并发效率的权衡。


struct array_cache:Per-CPU 本地对象缓冲池

每个 CPU 拥有一个独立的 array_cache,用于暂存近期分配/释放的对象,避免频繁访问全局链表:

struct array_cache {
    unsigned int avail;   // 当前可用对象数
    unsigned int limit;   // 最大允许缓存数
    unsigned int batchcount;
    unsigned int touched; // 标记是否被访问过(影响回收策略)
    void *entry[];        // 对象指针数组(柔性数组)
};

struct kmem_cache_node:NUMA 节点级 slab 管理

针对 NUMA 架构,每个内存节点维护独立的 slab 链表,避免跨节点访问延迟:

struct kmem_cache_node {
    spinlock_t list_lock;

#ifdef CONFIG_SLAB
    struct list_head slabs_partial; // 部分对象已分配
    struct list_head slabs_full;    // 所有对象均已分配
    struct list_head slabs_free;    // 所有对象均空闲
    unsigned long free_objects;     // 三链表中空闲对象总数
    unsigned int free_limit;        // 允许的最大空闲对象数
    unsigned int colour_next;       // per-node cache coloring 索引
    struct array_cache *shared;     // 多 CPU 共享的缓冲池
    struct alien_cache **alien;     // 其他节点的缓存副本
    unsigned long next_reap;
    int free_touched;
#endif

#ifdef CONFIG_SLUB
    unsigned long nr_partial;
    struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
    atomic_long_t nr_slabs;
    atomic_long_t total_objects;
    struct list_head full;
#endif
#endif
};

SLAB 常用标志位(flags)

#define SLAB_DEBUG_FREE        0x00000100UL    /* DEBUG: Perform (expensive) checks on free */
#define SLAB_RED_ZONE          0x00000400UL    /* DEBUG: Red zone objs in a cache */
#define SLAB_POISON            0x00000800UL    /* DEBUG: Poison objects */
#define SLAB_HWCACHE_ALIGN     0x00002000UL    /* Align objs on cache lines */
#define SLAB_CACHE_DMA         0x00004000UL    /* Use GFP_DMA memory */
#define SLAB_STORE_USER        0x00010000UL    /* DEBUG: Store the last owner for bug hunting */
#define SLAB_PANIC             0x00040000UL    /* Panic if kmem_cache_create() fails */

二、创建 slab 描述符

kmem_cache_create() 是 slab 生命周期的起点,其核心职责是填充 struct kmem_cache 并将其加入全局 slab_caches 链表。

主要参数说明

参数 含义
name 缓存名称(如 "my_slab_cache"),将出现在 /proc/slabinfo
size 对象大小(bytes)
align 对齐要求(0 表示默认对齐)
flags 分配标志(如 SLAB_HWCACHE_ALIGN
ctor 构造函数指针(可为 NULL

函数调用流程概览

kmem_cache_create
└── do_kmem_cache_create
    └── __kmem_cache_create
        ├── calculate_slab_order     ← 计算所需 page 数与每 slab 对象数
        └── setup_cpu_cache          ← 初始化 CPU 本地缓存参数(limit/batchcount)

关键源码片段(精简)

struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
                  unsigned long flags, void (*ctor)(void *))
{
    struct kmem_cache *s;
    // 检查是否存在同名/同规格缓存(避免重复创建)
    s = __kmem_cache_alias(name, size, align, flags, ctor);
    if (s)
        goto out_unlock;

    s = do_kmem_cache_create(cache_name, size, size,
                             calculate_alignment(flags, align, size),
                             flags, ctor, NULL, NULL);
    return s;
}
static struct kmem_cache *
do_kmem_cache_create(const char *name, size_t object_size, size_t size,
                     size_t align, unsigned long flags, void (*ctor)(void *),
                     struct mem_cgroup *memcg, struct kmem_cache *root_cache)
{
    struct kmem_cache *s;
    s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL); // 分配 kmem_cache 结构体
    // 填充字段...
    s->name = name;
    s->object_size = object_size;
    s->size = size;
    s->align = align;
    s->ctor = ctor;

    err = __kmem_cache_create(s, flags); // 核心初始化逻辑
    list_add(&s->list, &slab_caches);  // 加入全局链表
    return s;
}

__kmem_cache_create 核心逻辑

该函数完成对齐计算、着色配置、gfporder 推导、CPU 缓存初始化等关键步骤:

  • 对齐处理:依次考虑 BYTES_PER_WORDREDZONE_ALIGN、用户传入 align,取最大值作为最终对齐单位;
  • off-slab 判定:若对象尺寸较大(≥ PAGE_SIZE >> 5),则将 slab 管理结构(freelist)单独存放于额外页面,以节省 slab 内部空间;
  • cache colouring:根据 cache_line_size()(通常为 64B)计算 colour_offcolour,确保对象起始地址在 cache line 中错开;
  • CPU 缓存参数设置:调用 setup_cpu_cache() 设置 limitbatchcount,并分配 array_cache

slab_state:slab 初始化状态机

内核使用枚举类型标识 slab 子系统的启动阶段:

enum slab_state {
    DOWN,           /* 尚未启用 slab 功能 */
    PARTIAL,        /* SLUB:kmem_cache_node 已就绪 */
    PARTIAL_NODE,   /* SLAB:kmalloc node 结构可用 */
    UP,             /* slab 缓存已可用,但部分高级特性未启用 */
    FULL            /* 完全初始化完成 */
};

calculate_slab_order:确定 slab 规模

该函数从 gfporder = 0(4KB)开始尝试,逐级增大,直到满足:

  • 每 slab 至少容纳 1 个对象;
  • 对象总数不超过 SLAB_OBJ_MAX_NUM(255);
  • 剩余空间(left_over)足够支持 cache colouring。
static size_t calculate_slab_order(struct kmem_cache *cachep,
                                   size_t size, size_t align, unsigned long flags)
{
    for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
        unsigned int num;
        size_t remainder;
        cache_estimate(gfporder, size, align, flags, &remainder, &num);
        if (!num) continue; // 不满足最小对象数,继续尝试更大页
        if (num > SLAB_OBJ_MAX_NUM) break;
        // ... 满足条件则记录
        cachep->num = num;
        cachep->gfporder = gfporder;
        left_over = remainder;
        if (left_over * 8 <= (PAGE_SIZE << gfporder)) break; // 着色空间充足
    }
    return left_over;
}

cache_estimate:估算单 slab 容量

计算给定 2^gfporder 页面下,能容纳多少个 size 大小的对象,以及剩余可用于着色的空间:

static void cache_estimate(unsigned long gfporder, size_t buffer_size,
                           size_t align, int flags, size_t *left_over,
                           unsigned int *num)
{
    size_t slab_size = PAGE_SIZE << gfporder;
    int nr_objs;

    if (flags & CFLGS_OFF_SLAB) {
        nr_objs = slab_size / buffer_size;
        *num = nr_objs;
        *left_over = slab_size - nr_objs * buffer_size;
    } else {
        nr_objs = calculate_nr_objs(slab_size, buffer_size,
                                   sizeof(freelist_idx_t), align);
        size_t mgmt_size = calculate_freelist_size(nr_objs, align);
        *num = nr_objs;
        *left_over = slab_size - nr_objs * buffer_size - mgmt_size;
    }
}

三、分配 slab 对象

kmem_cache_alloc() 是用户获取 slab 对象的入口,其执行全程关闭本地中断,保证原子性。

slab 分配流程图:应用程序 → 每 CPU 缓存 → Node 缓存 → SLAB 页面 → 伙伴系统

分配路径优先级(自上而下)

  1. 本地 CPU 缓存 (ac->entry[]):最快路径,无锁;
  2. 共享缓存 (n->shared):跨 CPU 共享池,需加锁;
  3. partial 链表:已有部分对象被分配的 slab;
  4. free 链表:全部对象空闲的 slab;
  5. 全新分配 (cache_grow):向伙伴系统申请新页,初始化 slab 并加入 slabs_free

关键源码逻辑(简化)

static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
    struct array_cache *ac = cpu_cache_get(cachep);
    if (likely(ac->avail)) {
        ac->touched = 1;
        objp = ac_get_obj(cachep, ac, flags, false);
        if (objp) return objp;
    }
    return cache_alloc_refill(cachep, flags, force_refill);
}
static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags,
                                bool force_refill)
{
    struct kmem_cache_node *n;
    struct array_cache *ac;
    int node = numa_mem_id();
    ac = cpu_cache_get(cachep);
    n = get_node(cachep, node);
    spin_lock(&n->list_lock);

    // 1. 尝试从 shared 缓存迁移
    if (n->shared && transfer_objects(ac, n->shared, batchcount))
        goto alloc_done;

    // 2. 尝试从 partial/free 链表分配
    while (batchcount > 0) {
        struct list_head *entry = n->slabs_partial.next;
        if (entry == &n->slabs_partial) {
            entry = n->slabs_free.next;
            if (entry == &n->slabs_free)
                goto must_grow; // 链表全空,需新分配
        }
        struct page *page = list_entry(entry, struct page, lru);
        // 从 page 中取出对象,填入 ac->entry[]
        ...
    }

must_grow:
    // 3. 触发 cache_grow()
    x = cache_grow(cachep, flags | GFP_THISNODE, node, NULL);
    ...
}

四、释放 slab 对象

kmem_cache_free() 是对象归还的终点,同样全程关闭本地中断。

slab 释放结构示意图:slab0/slab1/slab2 对应不同尺寸对象池及 kmalloc-xxx 缓存

释放流程要点

  • 定位 slab 描述符:通过 cache_from_obj() 从对象虚拟地址反查所属 kmem_cache
  • 归还至本地缓存:调用 ac_put_obj() 将对象插入 ac->entry[]
  • 触发回收:若 ac->avail >= ac->limit,则调用 cache_flusharray() 批量归还对象至 slabs_partialslabs_free
  • NUMA 优化:非 NUMA 系统跳过 cache_free_alien(),避免不必要的跨节点访问。

关键源码片段

void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{
    unsigned long flags;
    cachep = cache_from_obj(cachep, objp); // 通过对象地址找到所属缓存
    local_irq_save(flags);
    __cache_free(cachep, objp, _RET_IP_);
    local_irq_restore(flags);
}

static inline void __cache_free(struct kmem_cache *cachep, void *objp,
                                unsigned long caller)
{
    struct array_cache *ac = cpu_cache_get(cachep);

    if (ac->avail < ac->limit) {
        STATS_INC_FREEHIT(cachep);
    } else {
        STATS_INC_FREEMISS(cachep);
        cache_flusharray(cachep, ac); // 归还至全局链表
    }
    ac_put_obj(cachep, ac, objp); // 插入本地缓存
}
static inline struct kmem_cache *cache_from_obj(struct kmem_cache *s, void *x)
{
    struct page *page = virt_to_head_page(x);
    struct kmem_cache *cachep = page->slab_cache;
    if (slab_equal_or_root(cachep, s))
        return cachep;
    return s;
}

五、kmalloc 分配函数原理

kmalloc() 是内核中最常用的通用内存分配接口,其底层完全基于 slab 分配器实现。

设计原则

  • 按需映射:不同尺寸请求映射到预创建的 kmalloc-xx 缓存;
  • 对齐保障:所有分配均对齐至 L1_CACHE_BYTES(通常 64B),避免 cache 伪共享;
  • 快速路径:关键函数(如 slab_alloc)标记为 __always_inline,减少调用开销。

kmalloc 尺寸分级策略

尺寸范围 映射方式 示例缓存
≤ 192B 查表 size_index[] kmalloc-64, kmalloc-128, kmalloc-192
192B ~ 8KB fls(size - 1) 计算 order kmalloc-256, kmalloc-512, ..., kmalloc-8192
> 8KB kmalloc_large() → 直接调用伙伴系统

其中 KMALLOC_SHIFT_LOW = 6(64B)、KMALLOC_SHIFT_HIGH = 13(8KB)、KMALLOC_SHIFT_MAX = 23(8MB)。

size_index 映射表(重映射后)

size_index[0]=6  /* 8 */     → kmalloc-64
size_index[1]=6  /* 16 */    → kmalloc-64
...
size_index[7]=6  /* 64 */    → kmalloc-64
size_index[8]=7  /* 72 */    → kmalloc-128
...
size_index[15]=7  /* 128 */  → kmalloc-128
size_index[16]=2  /* 136 */  → kmalloc-192
...
size_index[23]=2  /* 192 */ → kmalloc-192

/proc/slabinfo 输出解读(节选)

kmalloc-8192         12     12   8192    4    8 : tunables    0    0    0 : slabdata      3      3      0
kmalloc-4096         61     88   4096    8    8 : tunables    0    0    0 : slabdata     11     11      0
kmalloc-192         441    441    192   21    1 : tunables    0    0    0 : slabdata     21     21      0
kmalloc-128        1280   1280    128   32    1 : tunables    0    0    0 : slabdata     40     40      0
kmalloc-64         4416   4416     64   64    1 : tunables    0    0    0 : slabdata     69     69      0

字段含义(从左至右):

  • 缓存名;
  • active_objs:当前已分配对象数;
  • num_objs:总对象数;
  • objsize:对象大小(bytes);
  • objperslab:每 slab 对象数;
  • pagesperslab:每 slab 占用页数;
  • tunables 后:limitbatchcountsharedfactor
  • slabdata 后:active_slabsnum_slabssharedavail

🔍 /proc/slabinfo 是诊断内核内存泄漏与碎片化的第一手资料,建议结合 CONFIG_DEBUG_SLAB 使用。


六、slab 描述符创建实验

通过编写内核模块,可直观验证 slab 生命周期。

6.1 实验代码 slab_demo.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>

/* 自定义一个用于 slab 分配的对象结构 */
struct my_object {
    int id;
    char name[32];
};

/* 全局的 slab 描述符指针 */
static struct kmem_cache *my_cachep;

/* 可选:对象的构造函数,在创建 slab 时自动调用 */
static void my_obj_ctor(void *obj)
{
    struct my_object *my_obj = (struct my_object *)obj;
    my_obj->id = 0;
    memset(my_obj->name, 0, sizeof(my_obj->name));
    pr_info("slab_demo: Object constructed\n");
}

/* 模块初始化函数 */
static int __init slab_demo_init(void)
{
    struct my_object *obj1, *obj2;

    pr_info("slab_demo: Module init\n");

    /* 1. 创建 slab 描述符 */
    my_cachep = kmem_cache_create("my_slab_cache",
                                  sizeof(struct my_object),
                                  0,
                                  SLAB_HWCACHE_ALIGN,
                                  my_obj_ctor);
    if (!my_cachep) {
        pr_err("slab_demo: Failed to create slab cache\n");
        return -ENOMEM;
    }

    pr_info("slab_demo: Slab cache created successfully\n");

    /* 2. 分配两个 slab 对象 */
    obj1 = kmem_cache_alloc(my_cachep, GFP_KERNEL);
    if (!obj1) {
        pr_err("slab_demo: Failed to allocate obj1\n");
        kmem_cache_destroy(my_cachep);
        return -ENOMEM;
    }

    obj2 = kmem_cache_alloc(my_cachep, GFP_KERNEL);
    if (!obj2) {
        pr_err("slab_demo: Failed to allocate obj2\n");
        kmem_cache_free(my_cachep, obj1);
        kmem_cache_destroy(my_cachep);
        return -ENOMEM;
    }

    /* 使用对象 */
    obj1->id = 1;
    strncpy(obj1->name, "Object 1", sizeof(obj1->name) - 1);
    pr_info("slab_demo: Obj1: id=%d, name=%s\n", obj1->id, obj1->name);

    obj2->id = 2;
    strncpy(obj2->name, "Object 2", sizeof(obj2->name) - 1);
    pr_info("slab_demo: Obj2: id=%d, name=%s\n", obj2->id, obj2->name);

    /* 3. 释放对象 */
    kmem_cache_free(my_cachep, obj1);
    kmem_cache_free(my_cachep, obj2);
    pr_info("slab_demo: Objects freed\n");

    return 0;
}

/* 模块退出函数 */
static void __exit slab_demo_exit(void)
{
    pr_info("slab_demo: Module exit\n");

    /* 4. 销毁 slab 描述符 */
    if (my_cachep) {
        kmem_cache_destroy(my_cachep);
        pr_info("slab_demo: Slab cache destroyed\n");
    }
}

module_init(slab_demo_init);
module_exit(slab_demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Slab Demo");
MODULE_DESCRIPTION("A simple demo for kmem_cache creation");

6.2 Makefile

obj-m += slab_demo.o

# 内核源码路径,这里使用当前运行内核的源码
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
    make -C $(KDIR) M=$(PWD) modules

clean:
    make -C $(KDIR) M=$(PWD) clean

6.3 操作步骤

  1. 编译模块

    make

    成功后生成 slab_demo.ko

  2. 加载模块

    sudo insmod slab_demo.ko
  3. 查看日志

    dmesg | tail

    输出应包含:

    slab_demo: Module init
    slab_demo: Object constructed
    slab_demo: Object constructed
    slab_demo: Slab cache created successfully
    slab_demo: Obj1: id=1, name=Object 1
    slab_demo: Obj2: id=2, name=Object 2
    slab_demo: Objects freed
  4. 检查 /proc/slabinfo

    cat /proc/slabinfo | grep my_slab_cache

    示例输出:

    my_slab_cache         2      2     64   64    1 : tunables    0    0    0 : slabdata      1      1      0
  5. 卸载模块

    sudo rmmod slab_demo

    日志中将出现:

    slab_demo: Module exit
    slab_demo: Slab cache destroyed

    此时 my_slab_cache 条目从 /proc/slabinfo 中消失。


七、slab 分配器调试接口

/proc/slabinfo 详解(开启 CONFIG_DEBUG_SLAB

slabinfo - version: 2.1 (statistics)
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>

各字段含义:

  • active_objs:当前已分配对象数;
  • num_objs:该缓存总对象数;
  • objsize:对象实际大小(不含管理开销);
  • objperslab:每个 slab 中的对象数量;
  • pagesperslab:每个 slab 占用物理页数;
  • tunableslimit(本地缓存上限)、batchcount(批量迁移数)、sharedfactor(共享因子);
  • slabdataactive_slabs(部分对象已分配的 slab 数)、num_slabs(总 slab 数)、sharedavail(共享缓存中可用对象数)。

八、kmem 相关 Tracepoint

内核提供了丰富的 tracepoint 用于监控内存分配行为,位于 include/trace/events/kmem.h

8.1 kmalloc/kfree 跟踪事件

kfree
kmalloc
kmalloc_node

典型 trace 输出:

sh-647  [001] ....  843.814154: kmalloc: call_site=c012fbec ptr=ee042600 bytes_req=200 bytes_alloc=256 gfp_flags=GFP_KERNEL|GFP_ZERO
sh-647  [001] ....  843.816029: kfree: call_site=c0176744 ptr=(null)
sh-647  [001] ....  843.816143: kfree: call_site=c012ebdc ptr=ee042600

💡 ptr 是关联 kmallockfree 的关键线索;若存在 kmalloc 无对应 kfree,即为潜在内存泄漏。

可通过 patch 改造 call_site 显示函数名(%pf)提升可读性:

- TP_printk("call_site=%lx ptr=%p ...", __entry->call_site, ...)
+ TP_printk("call_site=%pf ptr=%p ...", __entry->call_site, ...)

改造后输出:

sh-646  [001] .... 102.511304: kmalloc: call_site=do_execveat_common ptr=ee0dd400 ...
sh-646  [001] .... 102.513996: kfree: call_site=do_execveat_common ptr=ee0dd400

8.2 kmem_cache_alloc/kmem_cache_free 事件

kmem_cache_alloc
kmem_cache_alloc_node
kmem_cache_free

用途与 kmalloc 类似,适用于自定义 slab 缓存的跟踪。

8.3 Page 级分配事件

mm_page_alloc
mm_page_alloc_zone_locked
mm_page_free
mm_page_free_batched

8.4 Per-CPU 分配器事件

mm_page_alloc_zone_locked
mm_page_pcpu_drain

8.5 外部碎片事件

mm_page_alloc_extfrag

📚 更多细节请查阅内核文档:Documentation/trace/events-kmem.txt




上一篇:开源多Agent框架OpenAkita:集成飞书钉钉,AI助理一站式简化工作流
下一篇:基于NSA安全建议:企业云环境部署的十大核心安全策略与最佳实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-5 19:13 , Processed in 0.407024 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表