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); }| 参数 | 含义 |
|---|---|
n | per-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_slabs | nr_slabs | slab 页数 |
node_nr_objs | total_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_node | n->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()维护。