add_full()是 SLUB 调试中用于把完全占满的 slab 页加入 per-node 的full链表的函数。这个函数只在CONFIG_SLUB_DEBUG下存在,因为正常运行时 SLUB 不追踪满 slab。
一、函数签名
static void add_full(struct kmem_cache *s, struct kmem_cache_node *n, struct slab *slab)
| 参数 | 含义 |
|---|---|
s | 缓存 |
n | 对象所在 NUMA 节点的kmem_cache_node |
slab | 要加入的 slab 页 |
无返回值。
二、典型实现
static void add_full(struct kmem_cache *s, struct kmem_cache_node *n, struct slab *slab) { if (!(s->flags & SLAB_STORE_USER)) return; lockdep_assert_held(&n->list_lock); list_add(&slab->slab_list, &n->full); }逻辑:
门控:未开
SLAB_STORE_USER直接返回。锁断言:
lockdep_assert_held(&n->list_lock)确认调用者已持有 node 的list_lock。头插链表:
list_add(&slab->slab_list, &n->full)把 slab 加入full列表头部。
三、full列表回顾
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 };partial:部分空闲的 slab,正常运行时也维护。full:完全占满的 slab,仅调试时维护。
为什么正常运行时不需要full:
满 slab 没有空闲对象,分配路径不会用到。
释放时若某个满 slab 变部分空闲,需要知道它在哪——正常运行时它根本不在任何列表上,靠对象指针反查 slab 即可处理。
只有调试时需要枚举所有 slab(校验、统计),才需要
full列表把满 slab 也挂起来。
四、调用时机
add_full()在 slab由非满变满时调用:
// 分配路径中,对象从 slab 取出,slab 变满 if (slab->inuse == slab->objects) { // 从 partial 移除,加入 full remove_partial(n, slab); add_full(s, n, slab); }对应地,由满变非满时调remove_full():
static void remove_full(struct kmem_cache *s, struct kmem_cache_node *n, struct slab *slab) { if (!(s->flags & SLAB_STORE_USER)) return; lockdep_assert_held(&n->list_lock); list_del(&slab->slab_list); }| 状态变化 | 操作 |
|---|---|
| partial → full | remove_partial+add_full |
| full → partial | remove_full+add_partial |
| full → free | remove_full+ 归还伙伴系统 |
五、与其他列表操作的关系
SLUB 里 slab 列表操作的完整集合:
| 函数 | 操作 |
|---|---|
add_partial(n, slab, tail) | 加入 partial 列表(头或尾) |
remove_partial(n, slab) | 从 partial 移除 |
add_full(s, n, slab) | 加入 full 列表(仅调试) |
remove_full(s, n, slab) | 从 full 移除(仅调试) |
这些操作都在n->list_lock保护下进行,且通常成对出现。
六、锁与并发
lockdep_assert_held(&n->list_lock);
lockdep_assert_held():调试断言,确认调用者持有指定锁。不改变运行时行为(未开 lockdep 时是空宏),但能在开发阶段抓出“忘了加锁”的 bug。
所有列表操作都必须在
list_lock保护下,因为n->partial/n->full是多个 CPU 共享的 per-node 结构。
调用者通常:
spin_lock(&n->list_lock); // ... 操作 slab 状态 add_full(s, n, slab); spin_unlock(&n->list_lock);
七、slab_list字段
list_add(&slab->slab_list, &n->full);
slab->slab_list:struct slab里的struct list_head成员,用于挂在各种列表上。同一个
slab_list字段在不同状态下挂不同列表:在 partial 列表上 → 挂在
n->partial。在 full 列表上 → 挂在
n->full。在 CPU partial 上 → 挂在
c->partial。
因为一个 slab 同一时刻只能处于一种列表状态,复用同一个
list_head节省空间。
八、与前面知识的呼应
| 前面讲过 | 这里体现 |
|---|---|
struct kmem_cache_node | n->full、n->list_lock |
SLAB_STORE_USER | 门控条件 |
list_lock | 保护列表操作 |
add_partial/remove_partial | 对称的 partial 操作 |
struct slab | slab->slab_list字段 |
slab->inuse/slab->objects | 判断满/非满 |
九、总结
add_full(s, n, slab)在开启SLAB_STORE_USER时,把由非满变满的 slab 页加入 per-node 的full链表,要求在n->list_lock保护下调用。它只在调试时存在,用于维护完整视图以便校验和统计;正常运行时满 slab 不被追踪,以节省开销。