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 主要关心partialfull只在调试时需要完整枚举。五、与阈值宏的呼应#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跨节点分配时若指定 nodeSLUB 优先用该 node 的kmem_cache_nodeALLOC_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_partialpartial维护部分空闲 slab 列表供分配复用并在超过MAX_PARTIAL时触发收缩调试配置下额外用nr_slabs、total_objects、full维护完整视图以支持校验和统计。它是快速路径耗尽后的后端也是跨 CPU 复用和内存回收的关键。