slab 页的标志位定义和per-CPU 访问/快速路径的编译期开关。
一、enum slab_flags
enum slab_flags { SL_locked = PG_locked, SL_partial = PG_workingset, /* Historical reasons for this bit */ SL_pfmemalloc = PG_active, /* Historical reasons for this bit */ };背景:slab 页复用了struct page的 flags
SLUB 早期直接用struct page的flags字段来标记 slab 页的状态。为了不额外占用空间,它借用了page->flags中几个当前 slab 场景下用不到的位:
| 枚举 | 借用 page flag | 含义 |
|---|---|---|
SL_locked | PG_locked | slab 页被锁住,正在被操作 |
SL_partial | PG_workingset | 该 slab 处于 partial 状态(部分对象空闲) |
SL_pfmemalloc | PG_active | 该 slab 来自PFMEMALLOC预留内存,用于内存极度紧张时避免死锁 |
为什么是这几个位
PG_locked在普通页缓存里表示页锁,slab 页本来就不参与页缓存回写,所以借来当 slab 自己的锁标志很合适。PG_workingset属于页回收的工作集统计,slab 页不需要,借来标记 partial。PG_active属于 LRU 活跃标志,slab 页不在 LRU 上,借来标记 pfmemalloc。
注释里的"Historical reasons for this bit"就是在说明:这些绑定是历史沿用下来的,不是语义上的自然对应,不要误以为 slab 和 workingset/active 有真实关系。
后续演进
较新内核(6.x 之后)引入了独立的struct slab,不再直接复用struct page,这些SL_* = PG_*的借位写法逐步被struct slab自己的字段取代。
二、per-CPU 指针与快速路径开关
#ifndef CONFIG_PREEMPT_RT #define slub_get_cpu_ptr(var) get_cpu_ptr(var) #define slub_put_cpu_ptr(var) put_cpu_ptr(var) #define USE_LOCKLESS_FAST_PATH() (true) #else ... #define USE_LOCKLESS_FAST_PATH() (false) #endif
目的
SLUB 的快速路径依赖per-CPU 数据:kmem_cache_cpu里的freelist和当前 slab 页。要安全访问它,必须保证当前代码不会被迁移到别的 CPU,否则this_cpu_ptr()拿到的可能是错的数据。
普通内核(非 PREEMPT_RT)
#define slub_get_cpu_ptr(var) get_cpu_ptr(var) #define slub_put_cpu_ptr(var) put_cpu_ptr(var) #define USE_LOCKLESS_FAST_PATH() (true)
get_cpu_ptr()=preempt_disable()+this_cpu_ptr()。put_cpu_ptr()=preempt_enable()。非 RT 内核里
preempt_disable()是内联的、只是改一个 per-thread 计数器,开销极小,所以宏直接展开成函数调用即可。USE_LOCKLESS_FAST_PATH() == true:允许走无锁快速路径。因为preempt_disable()同时阻止了抢占和 CPU 迁移,当前 CPU 的 freelist 不会被本地抢占打断,也不会被迁走,于是可以直接无锁操作。
PREEMPT_RT 内核
#define slub_get_cpu_ptr(var) \ ({ \ migrate_disable(); \ this_cpu_ptr(var); \ }) #define slub_put_cpu_ptr(var) \ do { \ (void)(var); \ migrate_enable(); \ } while (0) #define USE_LOCKLESS_FAST_PATH() (false)RT 内核里spinlock_t变成可睡眠的rt_mutex,preempt_disable()不能像普通内核那样随便用(会破坏实时性,且语义不同)。因此改用migrate_disable()/migrate_enable():
只禁止 CPU 迁移,但允许抢占。
这样在操作 per-CPU freelist 的过程中,当前任务可能被抢占,别的任务在同一 CPU 上也可能来动这个 freelist →不再安全。
所以
USE_LOCKLESS_FAST_PATH() == false,快速路径必须退化成加锁方式(走 spinlock 保护的慢路径)。
slub_put_cpu_ptr里的(void)(var);
RT 分支里this_cpu_ptr(var)的结果在put时不用,为了消除未使用参数/变量的编译警告,加一句(void)(var);。同时保证宏在语法上像一个完整语句。
三、这两段合起来说明了什么
SLUB 的性能核心是per-CPU 无锁快速路径,而它的正确性依赖两个前提:
访问期间不能被迁移到别的 CPU—— 两个分支都保证。
访问期间该 CPU 的 freelist 不被并发修改—— 普通内核靠
preempt_disable()(关抢占)实现,所以可以无锁;RT 内核只关迁移不关抢占,做不到,于是主动放弃无锁快速路径,改为加锁。
因此USE_LOCKLESS_FAST_PATH()就是 SLUB 在“普通内核走无锁快路”和“RT 内核走加锁慢路”之间的编译期选择开关,后续代码里会大量出现:
if (USE_LOCKLESS_FAST_PATH()) { // 无锁快速路径 } else { // 加锁路径 }四、一句话总结
enum slab_flags:SLUB 历史上借用struct page的几个无关 flag来标记 slab 页的 locked/partial/pfmemalloc 状态,注释强调是历史沿革。宏:为per-CPU 快速路径提供 CPU 绑定保护——普通内核用
preempt_disable()实现真正的无锁;PREEMPT_RT 只能用migrate_disable(),无法阻止抢占,于是把USE_LOCKLESS_FAST_PATH()关掉,强制走加锁路径。