slub get_node
SLUB 里访问per-NUMA-node 结构的最基础辅助函数,一行代码,但背后涉及 SLUB 的节点组织和编译配置。
一、函数本身
static inline struct kmem_cache_node *get_node(struct kmem_cache *s, int node) { return s->node[node]; }功能
给定缓存s和 NUMA 节点号node,返回该缓存在这个节点上的struct kmem_cache_node。
调用形式:
struct kmem_cache_node *n = get_node(s, node);
get_partial()、__slab_free()、add_partial()、discard_slab()等慢路径函数,第一步几乎都是拿n,然后在n->list_lock保护下操作n->partial等。
二、s->node是什么
在struct kmem_cache里有:
struct kmem_cache_node *node[MAX_NUMNODES];
这是一个按节点号索引的指针数组,每个元素指向该缓存的对应节点的
kmem_cache_node。MAX_NUMNODES由CONFIG_NODES_SHIFT决定:非 NUMA 系统通常为 1,NUMA 系统可达 64、1024 等。
所以get_node(s, node)本质就是数组下标访问,O(1)、无锁、无分支。
三、为什么用static inline
极高频调用:慢路径每一步都要用,而且常作为参数传递。
极简单:只有一次数组访问,函数调用开销相对过大。
内联后编译器可把它折叠进调用点,甚至把
s->node[node]直接编进后续代码,消除中间变量。
这是内核里“简单访问器一律内联”的典型做法。
四、与 NUMA 配置的关系
SLUB 的node数组组织随 NUMA 配置变化:
| 配置 | MAX_NUMNODES | s->node[] |
|---|---|---|
非 NUMA(CONFIG_NUMA关) | 1 | 只有一个元素,node恒为 0 |
NUMA(CONFIG_NUMA开) | 多 | 每个节点一项 |
因此get_node(s, node)在非 NUMA 下等价于s->node[0],编译器可能把整个节点参数优化掉。SLUB 代码不需要为此写两套,靠这个访问器屏蔽差异。
五、与strict_numa的呼应
#ifdef CONFIG_NUMA static DEFINE_STATIC_KEY_FALSE(strict_numa); #endif
默认(
strict_numa关):get_node()拿到的节点可能与请求节点不同,SLUB 允许跨节点取 slab,优先保证分配成功。开启
strict_numa:分配时严格要求从请求节点的kmem_cache_node取,get_node(s, node)里的node必须是目标节点,不匹配就可能失败或走特殊路径。
ALLOC_NODE_MISMATCH统计的就是“实际 slab 所在节点 ≠ 请求节点”的情况,判断依据正是通过get_node()拿到的 node 信息。
六、对比this_cpu_ptr
SLUB 的两类后端访问方式形成对称:
| 访问 | 粒度 | 方式 | 路径 |
|---|---|---|---|
this_cpu_ptr(s->cpu_slab) | per-CPU | 隐式(当前 CPU) | 快速路径 |
get_node(s, node) | per-node | 显式传node | 慢路径 |
per-CPU 用
this_cpu_ptr,因为“当前 CPU”是隐式的,且要配合preempt_disable()/migrate_disable()。per-node 用
get_node(s, node),因为节点号是显式参数(可能来自numa_node_id()、请求参数、对象所在页等)。
七、使用示例(慢路径骨架)
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node, ...) { struct kmem_cache_node *n; ... n = get_node(s, node); // 拿到目标节点的 node 结构 spin_lock(&n->list_lock); slab = get_partial(s, n, ...); // 从 n->partial 找 spin_unlock(&n->list_lock); ... }static void __slab_free(struct kmem_cache *s, struct slab *slab, void *object, ...) { struct kmem_cache_node *n; ... n = get_node(s, slab_nid(slab)); // 用 slab 实际所在节点 spin_lock(&n->list_lock); ... spin_unlock(&n->list_lock); }注意两者传的node来源不同:
分配:
node是请求节点(可能来自kmalloc_node参数或numa_node_id())。释放:
node是对象所在 slab 的实际节点(slab_nid(slab)),因为释放必须回到对象真正的归属节点。
八、一句话总结
get_node(s, node)是 SLUB 访问per-NUMA-node 结构的访问器:s->node[node]一次数组下标,static inline保证零开销。它屏蔽了 NUMA 与非 NUMA 的差异,是慢路径拿到list_lock和partial列表的入口。