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 不被追踪,以节省开销。



