ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

slub node_nr_slabs

slub node_nr_slabs

node_nr_slabs()是 SLUB 调试里用于读取一个 NUMA 节点上该缓存的 slab 页总数的访问器,封装了atomic_long_read()。

一、函数签名

static inline unsigned long node_nr_slabs(struct kmem_cache_node *n) { return atomic_long_read(&n->nr_slabs); }
参数含义
nper-NUMA-node 的struct kmem_cache_node

返回:该节点上该缓存的 slab 页总数(unsigned long)。

二、n->nr_slabs回顾

struct kmem_cache_node:

struct kmem_cache_node { spinlock_t list_lock; 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 };
  • 仅在CONFIG_SLUB_DEBUG下存在。

  • 记录该节点上该缓存的所有 slab 页数量(partial + full + 其他)。

  • 用atomic_long_t而非普通unsigned long,因为可能在不加list_lock的路径被读取或更新(如统计输出、validate_slab_cache),需要原子性。

三、为什么用atomic_long_read()

return atomic_long_read(&n->nr_slabs);
  • atomic_long_read():原子读取,保证不会读到撕裂值(如 64 位变量在 32 位系统上被部分更新)。

  • 虽然写入通常也在锁内,但读取可能发生在:

    • sysfs/debugfs 统计输出。

    • kmem_cache_shrink()计算。

    • 内存热插拔回调。

  • 这些路径不一定持有list_lock,所以用原子读保证一致性。

四、配套的写操作

nr_slabs的更新通常用原子加减:

// slab 加入 static inline void inc_slabs_node(struct kmem_cache *s, int node, int objects) { struct kmem_cache_node *n = get_node(s, node); atomic_long_inc(&n->nr_slabs); atomic_long_add(objects, &n->total_objects); } // slab 移除 static inline void dec_slabs_node(struct kmem_cache *s, int node, int objects) { struct kmem_cache_node *n = get_node(s, node); atomic_long_dec(&n->nr_slabs); atomic_long_sub(objects, &n->total_objects); }

调用时机:

  • allocate_slab():新 slab 从伙伴系统分配后inc_slabs_node()。

  • discard_slab():slab 归还伙伴系统时dec_slabs_node()。

五、与total_objects的配套

static inline unsigned long node_nr_objs(struct kmem_cache_node *n) { return atomic_long_read(&n->total_objects); }

node_nr_slabs()和node_nr_objs()成对,分别读:

函数字段含义
node_nr_slabsnr_slabsslab 页数
node_nr_objstotal_objects对象总数(nr_slabs × objects_per_slab)

用于计算使用率、空闲率、内存占用等。

六、使用场景

node_nr_slabs()在以下路径被调用:

1. sysfs / debugfs 统计

/sys/kernel/slab/<cache>/slabs

输出该缓存所有节点的nr_slabs之和。

2.kmem_cache_shrink()

static unsigned long count_partial(struct kmem_cache_node *n, ...) { ... return node_nr_slabs(n); // 或遍历 partial 计算 }

判断是否需要收缩。

3.validate_slab_cache()

static void validate_slab_cache(struct kmem_cache *s) { int node; struct kmem_cache_node *n; for_each_kmem_cache_node(s, node, n) { pr_info("Node %d, slabs %lu, objects %lu\n", node, node_nr_slabs(n), node_nr_objs(n)); } }

调试时汇总每个节点的 slab 和对象数。

4. 内存热插拔

节点上线/下线时更新和检查这些计数。

七、知识呼应

相关函数这里体现
struct kmem_cache_noden->nr_slabs字段
atomic_long_t原子读写,避免锁外撕裂
inc_slabs_node/dec_slabs_node写侧操作
get_node()获取n的方式
for_each_kmem_cache_node遍历所有节点读取计数
CONFIG_SLUB_DEBUG字段和函数的存在条件

八、总结

node_nr_slabs(n)用atomic_long_read(&n->nr_slabs)原子读取一个 NUMA 节点上该缓存的 slab 页总数,仅在CONFIG_SLUB_DEBUG下存在。它与node_nr_objs()配套,用于 sysfs 统计、kmem_cache_shrink()判断和validate_slab_cache()汇总,写侧由inc_slabs_node()/dec_slabs_node()维护。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进