ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

slub kmem_cache_node

slub kmem_cache_node

SLUB 的per-NUMA-node 结构struct kmem_cache_node,每个缓存对每个 NUMA 节点都有一份。它管理该节点上所有 slab 页的全局列表,是快速路径耗尽后的慢路径后端。

一、整体定位

SLUB 每个缓存有三层结构:

struct kmem_cache ├─ struct kmem_cache_cpu (per-CPU) ← 快速路径 └─ struct kmem_cache_node (per-NUMA-node) ← 慢路径 / 全局列表
  • kmem_cache_cpu:本 CPU 的私有 freelist 和当前 slab,无锁快路径。

  • kmem_cache_node:本 NUMA 节点上该缓存的所有 slab 页,用锁保护,供慢路径取用。

当 CPU 的 freelist 用完、当前 slab 也空了,就到这里来拿新的 partial slab;释放时若对象不属于当前 CPU slab,也会落到这里。

二、字段逐个解析

1.spinlock_t list_lock;

spinlock_t list_lock;
  • 保护这个 node 结构里所有列表和计数的锁。

  • 慢路径(get_partial、__slab_free、deactivate、shrink)都要拿它。

  • 与kmem_cache_cpu->lock的区别:

    • cpu->lock是 per-CPU 本地锁,开销极小。

    • node->list_lock是真正的跨 CPU 竞争锁,多个 CPU 可能同时访问同一个 node。

  • 快速路径不碰它,所以不产生竞争;一旦进入慢路径就要付加锁代价。这也是 SLUB 统计里ALLOC_SLOWPATH、FREE_SLOWPATH关注的成本来源。

2.unsigned long nr_partial;

unsigned long nr_partial;
  • 记录当前 node 上partial slab 的数量(即partial列表长度)。

  • 用于和阈值比较:

    • MIN_PARTIAL:少于这个数时,空的 slab 也保留在 partial 列表,不立即归还伙伴系统。

    • MAX_PARTIAL:超过这个数时,kmem_cache_shrink()会按inuse排序并回收空闲 slab。

  • 判断逻辑大致:

if (n->nr_partial < MIN_PARTIAL) // 保留空 slab if (n->nr_partial > MAX_PARTIAL) // 触发排序/收缩

3.struct list_head partial;

struct list_head partial;
  • 部分空闲 slab 的链表头(链表节点是struct slab的slab_list字段)。

  • “partial” = 该 slab 中既有已分配对象,也有空闲对象。

  • 分配时慢路径从这里取一个有空闲对象的 slab 补充给 CPU:

___slab_alloc → get_partial → 从 n->partial 取 slab → 设为 c->slab
  • 释放时,若某个 slab 由满变部分空闲,可能被加回这个列表。

  • 这是 SLUB 慢路径最核心的列表。

三、调试专用字段

#ifdef CONFIG_SLUB_DEBUG atomic_long_t nr_slabs; atomic_long_t total_objects; struct list_head full; #endif

这三个只在编译CONFIG_SLUB_DEBUG时存在,用于调试、统计和一致性检查。

4.atomic_long_t nr_slabs;

  • 当前 node 上该缓存的 slab 页总数。

  • 为什么用atomic_long_t而不是普通unsigned long:

    • 虽然list_lock保护列表操作,但这个计数可能被不加锁的路径读取或更新(如统计输出、校验、per-node 汇总),需要原子性。

    • atomic_long_t保证读写原子,避免锁外读取到撕裂值。

  • 用途:/sys/kernel/slab/<cache>/slabs、一致性校验、内存统计。

5.atomic_long_t total_objects;

  • 当前 node 上该缓存的对象总数(nr_slabs × objects_per_slab,大致)。

  • 用途:

    • 计算使用率、空闲率。

    • kmem_cache_shrink判断回收价值。

    • sysfs/debugfs 统计输出。

  • 同样用原子类型,理由与nr_slabs相同。

6.struct list_head full;

  • 完全占满的 slab 链表头(所有对象都已分配,无空闲对象)。

  • 为什么调试时才需要:

    • 正常运行时,full slab 不需要被慢路径扫描——它们没有空闲对象可用,留在 partial 列表没意义,可以完全不追踪,节省内存和操作。

    • 调试时,为了能完整校验和统计所有 slab(检查红区、poison、一致性、统计对象总数),必须把所有 slab 都挂起来,包括 full 的。所以单独维护一个full列表。

    • 典型用途:validate_slab_cache()、/sys/kernel/slab/<cache>/下的详细统计。

这个字段很好地体现了 SLUB 的取舍:正常路径只维护“有用的”数据结构,调试时才维护完整视图,以降低运行时开销。

四、partial与full的关系

列表何时使用内容
partial正常 + 调试部分空闲的 slab,可被分配复用
full仅调试全满的 slab,正常时不追踪
(无列表)正常全空的 slab 可能直接被归还伙伴系统

一个 slab 在生命周期里可能:

新分配 → (满) → 释放一个对象 → partial → 全部释放 → free → 归还伙伴系统

正常运行时 SLUB 主要关心partial;full只在调试时需要完整枚举。

五、与阈值宏的呼应

#define MIN_PARTIAL 5 #define MAX_PARTIAL 10

就是作用在nr_partial上的:

// 释放路径中,slab 变空时 if (n->nr_partial >= MIN_PARTIAL) discard_slab(s, slab); // 归还伙伴系统 else add_partial(n, slab); // 保留在 partial 列表 // shrink 时 if (n->nr_partial > MAX_PARTIAL) // 按 inuse 排序,回收空闲 slab

六、在慢路径中的角色

分配慢路径

slab_alloc_node 快速路径失败 └─ ___slab_alloc └─ get_partial(s, node, ...) ├─ 遍历 n->partial 找有空闲对象的 slab └─ 找不到 → new_slab() → 伙伴系统 └─ 成功后加到 n->partial 或直接设为 CPU slab

释放慢路径

slab_free 快速路径失败(对象不属于当前 CPU slab) └─ __slab_free ├─ 加 n->list_lock ├─ 对象放回 slab freelist ├─ slab 由满变 partial → add_partial ├─ slab 全空 → 可能 discard_slab 归还伙伴系统 └─ slab 由 partial 变满 → (调试时) 加入 full

跨节点

分配时若指定 node,SLUB 优先用该 node 的kmem_cache_node;ALLOC_NODE_MISMATCH统计的就是请求节点与实际 slab 节点不一致、需要切换的情况。

七、与 per-CPU 结构的分工

维度kmem_cache_cpukmem_cache_node
粒度per-CPUper-NUMA-node
锁local_lock(本地)spinlock_t list_lock(跨 CPU)
数据freelist、当前 slab、CPU partialpartial 列表、full 列表(调试)、计数
路径快速路径慢路径
目标零锁、低延迟全局池、跨 CPU 复用、回收

八、总结

struct kmem_cache_node是 SLUB 的per-NUMA-node 全局池:list_lock保护慢路径并发,nr_partial+partial维护部分空闲 slab 列表供分配复用,并在超过MAX_PARTIAL时触发收缩;调试配置下额外用nr_slabs、total_objects、full维护完整视图以支持校验和统计。它是快速路径耗尽后的后端,也是跨 CPU 复用和内存回收的关键。

返回列表