Linux 内核 SLAB/SLUB/SLOB 分配器深度实战:从原理架构到生产调优

内核内存分配器是 Linux 系统性能与稳定性的基石。SLAB、SLUB、SLOB 三种分配器各有其设计哲学与适用场景,本文从数据结构源码级剖析其实现机制,并给出生产环境中 OOM、碎片化、性能瓶颈的完整排查与调优方案。

一、为什么需要专用的内核内存分配器

用户态的 malloc() 基于 brk()/mmap() 系统调用获取整页内存(通常 4KB),而内核中大量对象远小于一页——task_struct 约 1.7KB、inode 约 600B、dentry 约 192B。若直接以页为单位分配,内存利用率将极其低下。此外,内核对分配延迟有严格约束,频繁的页分配/释放还会加剧 TLB 抖动。

内核内存分配器的核心设计目标:

  • 减少内部碎片:将同类型对象聚集在同一个 SLAB 页框中,最大化空间利用率
  • 降低分配延迟:利用对象缓存(empty/partial/full 三链表)实现 O(1) 级别的快速分配
  • 对象复用与缓存着色:保留已初始化对象,避免重复构造/析构开销;通过 cache coloring 优化 CPU 缓存行利用
  • NUMA 感知:在 NUMA 系统上优先从本地节点分配,降低跨节点延迟
  • 硬件缓存对齐:确保对象起始地址按 cache line 对齐,避免 false sharing

二、SLAB 分配器:经典设计的完整剖析

2.1 历史背景与核心数据结构

SLAB 由 Sun 的 Jeff Bonwick 于 1994 年为 Solaris 设计,Linux 2.1/2.2 中由 Ingo Molnár 引入并成为默认分配器。其核心思想是对象存储池化:相同类型和大小的内存在逻辑上被组织为 kmem_cache,每个 cache 管理多个 slab(物理页框组)。

SLAB 的核心数据结构关系:

// include/linux/slab.h (简化)
struct kmem_cache {
    struct array_cache __percpu *cpu_cache;    // per-CPU 快速缓存(SLAB 特有)
    unsigned int limit;                         // cpu_cache 中最大对象数
    unsigned int colour;                        // cache colouring 范围
    unsigned int colour_off;                    // 颜色偏移增量
    struct kmem_cache *slabp_cache;             // 用于分配 kmem_cache_node 的 cache
    unsigned int slab_size;                     // 单个 slab 总大小
    unsigned int object_size;                   // 用户请求的对象大小
    unsigned int align;                         // 对齐要求
    unsigned long flags;                        // SLAB_* 标志位
    unsigned int num;                           // 每 slab 中对象数量
    const char *name;                           // cache 名称(/proc/slabinfo 可见)
    
    struct list_head list;                      // 全局 cache 链表
    struct kmem_cache_node **node;              // NUMA 节点数组
    struct array_cache __percpu *array[1];      // flexible array of per-cpu caches
};

struct kmem_cache_node {
    struct list_head slabs_partial;             // 部分使用 slab 链表
    struct list_head slabs_full;                // 已满 slab 链表
    struct list_head slabs_free;                // 空闲 slab 链表
    unsigned long free_objects;                 // 节点中空闲对象总数
    unsigned int free_limit;                    // 空闲对象上限
    spinlock_t list_lock;                       // 节点链表锁
};

struct slab {
    union {
        struct list_head list;                  // 链入 slabs_partial/full/free
        struct slab_rcu slab_rcu;              // RCU 释放路径
    };
    void *s_mem;                                // slab 中第一个对象地址
    unsigned inuse;                             // 已使用对象计数器
    unsigned free;                              // 下一个空闲对象索引
    unsigned short nodeid;                      // 所属 NUMA 节点
};

2.2 SLAB 描述符的内存布局

SLAB 有两种布局模式,由 slab 大小与页框容量的比例决定:

  • 常规布局 (Normal Layout):对象较小,slab 结构体(struct slab)放在 slab 页框外部(即使用单独的内存存放元数据),对象在页框内部紧密排列。
  • 大型布局 / Off-slab:对象较大(> 页框容量的 1/8),slab 元数据嵌入页框末尾,s_mem 指向页框起始,末尾空间存放 kmem_bufctl_t 数组。

kmem_bufctl_t 是一个 unsigned integer 数组,用作空闲对象链表。每个 entry 存储下一个空闲对象的索引,最后一个空闲对象标记为 BUFCTL_END (0xffff)。这种设计将 freelist 与对象本身分离,避免了传统嵌入式链表导致对象被修改的问题。

2.3 Cache Coloring 机制

Cache coloring(缓存着色)是 SLAB 中一个精巧的设计:不同 slab 中第一个对象的起始地址在页内有不同的偏移量(colour_off 的倍数),目的是让不同 slab 中同序号的对象映射到 CPU 缓存的不同 set 中,降低 cache line 冲突。

// mm/slab.c: cache_Colour 计算
static void cache_Colour(struct kmem_cache *cachep, struct slab *slabp, unsigned long colour)
{
    slabp->s_mem = page_address(slabp->page) + colour * cachep->colour_off;
}

colour_off 通常等于 L1 cache line 大小(32B 或 64B)。当一个 cache 的 colour 范围耗尽时,colour 回绕到 0。实践表明,在频繁分配释放小块对象的场景下,cache coloring 可以带来 5%-15% 的指令缓存命中率提升。

2.4 SLAB 分配流程

SLAB 的 kmem_cache_alloc() 走三级路径(从快到慢):

kmem_cache_alloc(cachep, flags)
│
├── Level 1: cpu_cache (array_cache)
│   └── avail > 0 ? entry[--avail] : cache_alloc_refill()
│
├── Level 2: node->slabs_partial
│   └── 取 first_partial slab → obj = slab→s_mem + (free * size)
│        slab→inuse++; slab→free = bufctl[free];
│        if slab满: 从 partial 摘下,挂入 full
│
├── Level 3: node->slabs_free 或新分配
│   └── alloc_slabmgmt() 分配一个新 slab
│       alloc_slabmgmt() → alloc_pages_node()
│       slab→s_mem = page_address(page)
│       初始化 bufctl  freelist
│       node→slabs_partial 加入

第一级 CPU 缓存无需加锁,是纯 per-CPU 操作;第二级和第三级需要获取 kmem_cache_node.list_lock。这种设计使得单线程分配几乎无竞争。

2.5 SLAB 释放流程

释放时按逆向路径归还:

kmem_cache_free(cachep, objp)
│
├── 计算 slabp = virt_to_slab(objp)
├── cpu_cache.avail < limit ? cpu_cache->avail[cpu] = objp : cache_flusharray()
│
├── slab→inuse--
├── if slab→inuse == 0:
│   └── 从 partial 摘下 → slabs_free 或 free_slab() 归还页框
│       若 node→free_objects > free_limit → 强制释放 free slabs
│
└── if slab→inuse == num-1 (刚由 full 变 partial):
    └── 从 full 摘下 → slabs_partial 加入

关键细节:释放后检查是否需要让出空闲 slab 给全局伙伴系统——这保证了多 cache 之间的内存平衡。

2.6 SLAB 对象构造与析构

SLAB 支持 SLAB_CTOR_CONSTRUCTOR 标志位,缓存一个对象的构造函数指针(cachep->ctor)。当 slab 中对象首次被分配前调用 ctor 初始化,释放时不立即析构,而是保持构造状态。这种对象复用策略减少了重复的初始化开销,对task_struct、inode、signal_struct 等高频构造/析构的内核对象尤其有效。

然而,这也带来了一个潜在问题:若构造函数持有外部资源(如锁、引用计数),在对象被借用时可能导致状态泄漏。因此只有"纯数据初始化"的构造函数才应放在 ctor 中。

三、SLUB 分配器:现代 Linux 的选择

3.1 为什么需要替代 SLAB

SLAB 虽然经典,但随着硬件发展暴露出以下问题:

  • 元数据膨胀严重:每个 slab page 对应一个 struct slab + 完整的 kmem_bufctl_t[] 数组,加上 per-CPU 的 array_cache,小对象场景下元数据开销接近有效载荷的 3%-5%。
  • 扩展性瓶颈:早期 SLAB 的全局链表 cache_cache 和多层 shrink 导致大规模系统(>128 核)下锁竞争严重。
  • 代码复杂性过高:slab.c 巅峰期超过 5000 行,大量条件分支难以维护,漏洞频发。
  • Off-slab 模型反直觉:大型 slab 的元数据嵌入方式使得地址计算复杂,调试困难。

SLUB 由 Christoph Lameter 于 2007 年合入 Linux 2.6.23,目标是简化数据结构、提升扩展性、保持 SLAB 兼容接口,现已成为现代发行版(RHEL 6+、Ubuntu 12.04+、Android)的默认分配器。

3.2 SLUB 数据结构:极简主义

SLUB 最核心的设计变化是元数据内嵌到 page 结构体:

// include/linux/mm_types.h (简化)
struct page {
    ...
    union {
        struct {
            void *freelist;         // 指向 page 中第一个空闲对象
            union {
                unsigned inuse:16;  //  slab 中已使用对象数
                struct {
                    unsigned objects:15;
                    unsigned frozen:1; // 是否在 per-CPU 部分缓存中(frozen=1 时不回 budy)
                };
            };
        };
        
        struct {
            struct page *next;      // 在 node 的 partial 链表中的下一个 page
            int pages;              // 剩余 page 数(compound page 尾部)
            int pobjects;           // 空闲对象数(用于 partial 排序)
        };
    };
    
    struct kmem_cache *slab_cache;   // 指向所属 cache
    ...
};

关键设计决策:SLUB 复用 struct page 中的 union 字段存放 slab 元数据,完全消除了独立的 struct slab 结构。每个物理 page 的 struct page(64 字节)即为其 slab 描述符,甚至连 kmem_bufctl_t 数组都被嵌入式空闲链表替代(每个空闲对象的第一个 8 字节被用作 next 指针)。

3.3 kmem_cache 与 node 结构
// mm/slub.c
struct kmem_cache {
    struct kmem_cache_cpu __percpu *cpu_slab;    // per-CPU 快速路径
    unsigned long flags;
    unsigned int min_objects;       // 每 slab 最小对象数(计算值)
    unsigned int size;              // 对象实际大小(含对齐)
    unsigned int object_size;       // 用户请求大小
    struct reciprocal_value reciprocal_buffer_size; // 用于 reciprocal division 优化
    unsigned int offset;            // 空闲链表指针在对象内的偏移
    unsigned int cpu_partial;       // cpu slab partial 链表最大页数
    ...
    struct kmem_cache_node *node[MAX_NUMNODES]; // NUMA 节点数组
    const char *name;
};

struct kmem_cache_cpu {
    void **freelist;        // per-CPU freelist(指向下一个空闲对象)
    unsigned long tid;      // 全局唯一事务 ID(解决 ABA 问题)
    struct page *page;      // per-CPU 主 slab
    struct page *partial;   // per-CPU partial slab 链表
#ifdef CONFIG_SLUB_CPU_PARTIAL
    static_key stateful_call; // 调试用
#endif
};

struct kmem_cache_node {
    spinlock_t list_lock;
#ifdef CONFIG_SLUB
    unsigned long nr_partial;       // partial slab 数量
    struct list_head partial;       // partial slab 链表
#endif
};

SLUB 将 freelist 直接嵌入空闲对象内部——分配时返回 freelist 指向地址,并将 *freelist 作为下一个空闲指针读取。释放时,将 obj 的起始 8 字节写入 freelist,然后 freelist = obj。这种侵入式链表彻底消除了外部元数据数组。

3.4 SLUB 分配路径(New SLUB Path)

___slab_alloc(node, gfpflags, addr, s)
│
├── fast_path:
│   └── cpu_slab->freelist != NULL
│       && (!node || !in_interrupt() || !(gfpflags & __GFP_THISNODE))
│       && !irqs_disabled()
│       └── 直接返回 freelist,freelist = *freelist
│            tid 递增,O(1) 无锁操作
│
├── slow_path:
│   ├── new_slab:
│   │   ├── get_partial(node) 
│   │   │   └── node->partial 链表取一页
│   │   ├── new_slab_objects():
│   │   │   └── new_slab() 从 buddy 获取一页或 compound page
│   │   │       → 初始化 page->freelist、page->objects
│   │   │       → 根据 colour 偏移(slub 简化为 randomized offset)
│   │   │       → 返回第一个对象
│   │   └── cpu_slab->page = new_page
│   └── redo:
│       └── freelist 不为空,正常分配

SLUB 的快速路径极其精简,仅两条指针解引用操作(读取 freelist、修改 freelist),无任何函数调用。这也是其相对于 SLAB array_cache 路径的优势——SLAB 还需要遍历 avail 数组和可能的 cache_alloc_refill() 调用。

3.5 释放路径与 Frozen Page 优化

__slab_free(s, page, x, addr)
│
├── 将 x 插入 cpu_slab->freelist 头部
├── page→inuse--
│
├── 若 page 为空但未 frozen:
│   └── put_cpu_partial(): 将 page 从 cpu_slab 摘下,挂入 cpu_slab->partial
│
├── 若 page→inuse==0 且 partial 链表已满:
│   └── unfreeze_partials(): 将 page 转移至 node->partial 或释放给 buddy
│       若 node→nr_partial < min_partial → 保留在 node 的 partial 中
│       min_partial 由 kmem_cache 创建时根据 size 自动计算

SLUB 的 frozen 标志是一个巧妙的优化:当 page 被固定在某 CPU 的 cpu_slab 上时(page->frozen=1),即使空闲也不回收入 buddy 系统,保证该 CPU 下次分配无需再从 node 取页,避免缓存行 bouncing。

3.6 SLUB 对象布局化调试(slabinfo)

SLUB 提供丰富且低开销的调试机制:

  • Red zoning:在对象边界写入 0x12345678 魔数,溢出时触发 BUG()
  • Poisoning 释放时填充 0x5a,分配时填充 0xa5(不同阶段),检测 use-after-free 与 uninitialized read
  • Tracking:记录每个对象的分配/释放调用栈,/proc/slabinfo 中可查看 active_num vs num_active 判断泄漏
  • Cpu partial/node partial 平衡:通过 /sys/kernel/slab/<cache>/cpu_partial 和 min_partial 控制回廊策略

四、SLOB 分配器:极致精简方案

4.1 适用场景

SLOB (Simple List Of Blocks) 是为内存极度受限的嵌入式系统设计的分配器。它没有对象缓存机制,本质上维护一个简单的空闲块链表,分配时遍历链表找满足大小要求的块(first-fit)。三个分配器的核心对比:

特性SLABSLUBSLOB
代码复杂度~5000 行~1500 行~400 行
元数据结构struct slab + bufctl[]struct page 复用无独立元数据,块头嵌入
per-CPU 缓存array_cache[]cpu_slab + frozen无
NUMA 支持完善完善(最佳)基本(单一 node)
碎片化控制cache coloringCPU partial 链表无
最坏外部碎片低(对象等大小)低(对象等大小)高(best-fit 失败时)
默认启用场景历史兼容桌面/服务器/移动端嵌入式(CONFIG_EMBEDDED)

4.2 SLOB 实现机制

// mm/slob.c
static void *slob_alloc(size_t size, gfp_t gfp, int align)
{
    struct slob_list *prev, *cur, **p;
    slobidx_t offset;
    
    list_for_each_entry(sp, slob_list, list) {
        // 遍历空闲链表寻找满足大小的块
        cur = sp->freelist;
        while (cur) {
            if (cur->units >= SLOB_UNITS(size)) {
                // 找到合适的块,分割
                offset = cur->units - SLOB_UNITS(size);
                if (offset >= SLOB_UNITS(SLAB_SIZE)) {
                    // 分割块,剩余部分链回 freelist
                    split_block(cur, size);
                }
                return cur;  // 返回分配块指针
            }
            cur = cur->next;
        }
    }
    // 无可用块,调用 __alloc_pages()
    return slob_new_page(gfp);
}

SLOB 中每个内存块有 8 字节的 slob_t 头(包含 units 计数和块指针),剩余空间供用户使用。由于是 first-fit 而非 buddy-system,长期运行后会产生严重的外部碎片——典型场景下 SLOB 系统的空闲内存总量可能足够,但找不到连续满足要求的块。

五、三种分配器的切换与运行时选择

5.1 编译期选择

通过 Kconfig 选定默认分配器:

5.2 运行时动态创建与销毁 cache

// 创建专用 cache(对象池化)
struct kmem_cache *kmem_cache_create(const char *name, unsigned int size,
                                     unsigned int align, slab_flags_t flags,
                                     void (*ctor)(void *));

// 分配/释放
void *obj = kmem_cache_alloc(cache, GFP_KERNEL);
kmem_cache_free(cache, obj);

// 销毁(确保无活跃对象)
kmem_cache_destroy(cache);

// 或全局的 kmalloc 系列(使用预置大小 cache: 8, 16, 32, ... 128K)
void *p = kmalloc(size, GFP_KERNEL);
kfree(p);

kmalloc() 并非独立实现,而是通过一套预创建的 kmalloc_caches[] 数组(SLAB/SLUB/SLOB 共享相同接口)实现。每个 cache 覆盖几何分布的大小级(8/16/32/64/96/128/192/256/512/1024/2048/4096/8192 等),请求时选择能满足的最小 cache,内部碎片浪费不超过请求大小的 12.5%。

六、生产环境性能诊断与调优

6.1 /proc/slabinfo 与 slabtop 详解

$ cat /proc/slabinfo
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
dentry            2154524  2218833    192   21    1 : tunables  0    0    0 : slabdata  105658  105658      0
inode_cache        543210   567890    648    5    2 : tunables  0    0    0 : slabdata  113578  113578      0
task_struct         89234   102400   1696    4    2 : tunables  0    0    0 : slabdata   25600   25600      0
vm_area_struct     456789   500000    208   19    1 : tunables  0    0    0 : slabdata   26316   26316      0
kmalloc-32        1234567  2000000     32  128    1 : tunables  0    0    0 : slabdata   15625   15625      0

关键字段解读: active_objs < num_objs 表示有对象处于空闲态。若两者差值巨大(如 kmalloc-32 差 765,433),说明存在大量对象未释放,可能存在内存泄漏。objsize 是分配器实际分配大小(含对齐),objperslab 是每个 slab 页框的对象密度。

slabtop 实时观察:

$ slabtop -s c   # 按Cache size排序
 Active / Total Objects (% used)    : 2345678 / 3456789 (67.8%)
 Active / Total Slabs (% used)      : 45678 / 56789 (80.4%)
 Active / Total Caches (% used)     : 180 / 220 (81.8%)
 Active / Total Size (used)         : 456.78 MB / 678.90 MB (67.3%)
 Minimum / Average / Maximum Object : 0.01K / 0.20K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME             
2200000 1500000  68%    0.03K  17188       128     67.0MB kmalloc-32
 500000  400000  80%    0.64K  100000        5     40.0MB inode_cache
 200000  180000  90%    0.20K   10240       19      8.0MB vm_area_struct

6.2 SLUB 碎片化排查

内存碎片化是 slab 分配器的常见问题,SLUB 通过 CPU partial 链表缓解但并非消除。排查命令:

$ sudo cat /sys/kernel/slab/kmalloc-1024/alloc_from_cpu_partial
45690    # CPU partial 列表中成功分配的次数(越高越好)

$ sudo cat /sys/kernel/slab/kmalloc-1024/cpu_partial  
2        # 每个 CPU 的 partial slab 页数上限

$ sudo cat /sys/kernel/slab/kmalloc-1024/min_partial
5        # 节点保留 partial slab 的最小值

# 排查 node partial 链表长度
$ sudo cat /sys/kernel/slab/kmalloc-1024/node_partial

若 alloc_from_cpu_partial 保持为 0 且 alloc_calls 频繁上升,说明 slab_cache 长时间处于高负载且无有效 partial 缓冲,应考虑调高 cpu_partial 值。

6.3 对象泄漏排查实战

当 active_objs 持续增长且不释放时,第一步是确认是否为真正的泄漏:

# 1. 开启 SLUB 泄漏追踪
echo 1 > /sys/kernel/slab/<cache_name>/trace

# 2. 触发一次内存回收后查看泄漏报告
echo 1 > /sys/kernel/slab/<cache_name>/.leak追踪
cat /sys/kernel/slab/dentry/.leak追踪

# 3. 或使用 systemtap 精确追踪分配/释放不匹配点
$ stap -e 'probe kernel.function("kmem_cache_alloc") {
    if ($s->name == "dentry") 
        printf("%s %d kmem_cache_alloc %s\n", execname(), pid(), $s->name)
}'
 
# 4. 利用 slabinfo 的 /proc/slabinfo 对比两次采样
$ cat /proc/slabinfo > /tmp/slab1.txt
$ sleep 60
$ cat /proc/slabinfo > /tmp/slab2.txt
$ diff /tmp/slab1.txt /tmp/slab2.txt | grep active_objs

6.4 高负载下 SLUB 参数调优

针对高频分配场景(如 NFV 数据面、Web 前端服务器、容器宿主机),推荐调节参数:

# 放大 per-CPU partial 缓存,减少 slub 全局链表竞争
echo 50 > /sys/kernel/slab/kmalloc-*/cpu_partial

# 提高 min_partial,保证节点层有更多 spare slab
echo 20 > /sys/kernel/slab/dentry/min_partial

# 关闭 slub merge(排查问题时定位具体 cache)
echo 0 > /sys/kernel/slab/kmalloc-*/cache_dma

# 降低 slaborder 减少单 slab 大小,减少大页浪费
echo 0 > /sys/kernel/slab/kmalloc-1024/order

注意:order(slaborder)增加可以提升 buddy 利用率(更大连续页),但会增加分配延迟;对延迟敏感场景保持默认值即可。

6.5 OOM 与 slab 回收的边界

当系统内存紧张时,内核通过 shrink_slab() 通知各 cache 释放空闲对象。SLUB 的回收策略比 SLAB 更激进——它直接释放 empty slab page 回 buddy 系统,且优先释放 node partial 而非 cpu partial(cpu partial 访问需要锁)。

OOM 排查要点:

$ dmesg | grep -i "slub\|oom"
[284734.294] SLUB: Unable to allocate memory on node 0 (gfp=0x20)
[284734.294]   cache: kmalloc-256, object size 256, buffer total: 4096, kernel addr: 00000000fe32f7e2
[284734.294]   node 0: slabs: 2000, partial: 50, free: 20000 (cpu 0: 0/0 cpu 1: 0/0 cpu 2: 0/0 ...)

OOM 发生时,node 0: slabs: 2000, partial: 5 表示 2000 个 slab 中 partial 仅 5 个,说明 cache 处于高负载且 buddy 系统内存不足。这种情况需通过 slab_order 或 slab ratio 调整。

七、分配器选型决策与最新演进

7.1 决策树

  • 嵌入式设备(<64MB RAM) → SLOB(编译进内核,零元数据开销)
  • 通用服务器/桌面/移动(≥256MB RAM) → SLUB(默认选择)
  • 旧系统或特殊 CPU(SPARC/M68K/Alpha) → SLAB(兼容驱动)
  • 实时性要求极高且对象等大小 → 直接使用 kmem_cache_create() 创建专用 cache + SLUB 后端

7.2 SLUB 的最新演进方向

Linux 6.x 内核中 SLUB 持续优化,值得关注的新特性:

  • Tiny RCU 与可睡眠 RCU:SLUB 的释放路径已适配可抢占上下文,RT 内核受益
  • Memoryfolios:针对 folio(多页复合对象)的 slab 分层次布局,减少大页 slab 的元数据开销
  • 回收侧扩展性增强:6.5+ 内核引入 per-CPU 统计与无锁 node 遍历,减少 shrink 路径的锁竞争

八、总结

SLAB 开创了对象池化分配思想,SLUB 继承了这一哲学并通过元数据内嵌和 per-CPU 优化将其推向极致,SLOB 则服务于极致轻量场景。三者共享 kmem_cache_alloc()/kmem_cache_free() 接口,向上层内核子系统(文件系统、网络栈、驱动)提供统一的内存抽象。

掌握三种分配器的内部结构、分配路径与参数调节手段,是每一位 Linux 内核开发与运维工程师的必修课。在实际生产中,应养成通过 /proc/slabinfo 和 slabtop 定期巡检的习惯,对 active_objs 异常增长保持警惕,以构建稳定高效的内核内存环境。

参考资料

  • Jeff Bonwick, "The Slab Allocator: An Object-Caching Kernel Memory Allocator", USENIX 1994
  • Christoph Lameter, "SLUB: The unqueued slab allocator", Linux Symposium 2007
  • Linux 内核源码:mm/slab.c、mm/slub.c、mm/slob.c
  • Documentation/vm/slub.rst
点赞(0) 打赏

评论列表 共有 0 条评论

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

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部