SLUB 统计计数的递增辅助函数,配合enum stat_item和kmem_cache_cpu.stat[]使用。核心设计点在于“per-CPU 无锁、容忍轻微不精确、避免关中断开销”。
一、stat():单次递增
static inline void stat(const struct kmem_cache *s, enum stat_item si) { #ifdef CONFIG_SLUB_STATS /* * The rmw is racy on a preemptible kernel but this is acceptable, so * avoid this_cpu_add()'s irq-disable overhead. */ raw_cpu_inc(s->cpu_slab->stat[si]); #endif }功能
把缓存s的本 CPU 统计数组里第si项加 1。
调用形式:
stat(s, ALLOC_FASTPATH); stat(s, FREE_SLOWPATH); stat(s, CPU_PARTIAL_DRAIN);
关键:raw_cpu_inc而非this_cpu_inc
这是注释的核心。区别:
| 操作 | 是否关中断/抢占 | 是否原子 | 开销 |
|---|---|---|---|
this_cpu_inc() | 关中断(或至少保证不迁移) | 单 CPU 上是原子的 | 较大 |
raw_cpu_inc() | 不关中断、不关抢占 | 非原子,可能出现 RMW 竞争 | 极小 |
this_cpu_*系列会保证操作期间不被中断/抢占打断,防止read-modify-write(RMW,读-改-写)被并发打断,从而在单 CPU 视角下是原子的。raw_cpu_*系列不做任何保护,就是一条inc指令(读内存、加 1、写回)。在可抢占内核上,这条指令可能被抢占打断,别的代码可能同时改同一个计数器,导致丢失更新。
注释明确说:“The rmw is racy on a preemptible kernel but this is acceptable”——在可抢占内核上这个 RMW 有竞争,但可以接受。原因:
统计值用于性能分析,不是正确性关键数据,偶尔少算几次无所谓。
关中断/关抢占的代价(尤其在高频分配路径上)比统计精度更重要。
计数器是per-CPU的,跨 CPU 不会冲突,只有本 CPU 被抢占时才可能丢更新,概率低、影响小。
所以这里刻意选择raw_cpu_inc,用统计精度换分配/释放路径的极致开销。
条件编译
#ifdef CONFIG_SLUB_STATS ... #endif
未开启CONFIG_SLUB_STATS时,函数体为空,stat()调用完全被优化掉,零开销。配合kmem_cache_cpu.stat[]字段也是条件编译的,两者一致。
二、stat_add():批量/带值增加
static inline void stat_add(const struct kmem_cache *s, enum stat_item si, int v) { #ifdef CONFIG_SLUB_STATS raw_cpu_add(s->cpu_slab->stat[si], v); #endif }功能
把第si项加v(v可以是任意整数,不只 1)。
用途举例:
// 一次处理 n 个对象,批量计数 stat_add(s, FREE_SLAB, 1); // 或某些场景一次补多个 stat_add(s, ALLOC_REFILL, nr);
同样用raw_cpu_add
raw_cpu_add是raw_cpu_inc的带值版本,同样不关中断、不关抢占、非原子。注释理由与
stat()相同:可接受轻微竞争,换取低开销。未开
CONFIG_SLUB_STATS时同样编译为空。
为什么需要stat_add而不只有stat
有些事件一次涉及多个对象或多次操作,逐个stat()调用不如一次stat_add(..., v)高效,也避免循环里的函数调用。典型如:
一次补充 freelist 时补了
nr个对象 →ALLOC_REFILL += nr一次 drain 多个 slab →
CPU_PARTIAL_DRAIN += n
三、两者对比
stat() | stat_add() | |
|---|---|---|
| 增量 | 固定 +1 | +v |
| 底层操作 | raw_cpu_inc | raw_cpu_add |
| 场景 | 单次事件计数 | 批量/带值计数 |
| 保护 | 无(raw) | 无(raw) |
| 条件编译 | CONFIG_SLUB_STATS | CONFIG_SLUB_STATS |
| 精度 | 可抢占下可能丢失更新 | 同左 |
四、相关的呼应
| 呼应 | 这里体现 |
|---|---|
enum stat_item | si参数就是它的枚举值 |
kmem_cache_cpu.stat[NR_SLUB_STAT_ITEMS] | s->cpu_slab->stat[si]访问的就是它 |
CONFIG_SLUB_STATS | 控制整个统计机制是否编译进来 |
| per-CPU 无锁设计 | 统计也放 per-CPU,避免全局原子竞争 |
USE_LOCKLESS_FAST_PATH()等开关 | 同样体现“正常路径追求零开销,精度可妥协” |
一个有意思的对比:前面的USE_LOCKLESS_FAST_PATH()在 RT 下放弃无锁以保证正确性;而这里的统计在可抢占内核下接受竞争以保住性能。两者取舍方向相反,但都是基于“该数据是否影响正确性”来判断:
freelist 操作错误 → 内存损坏,必须保证正确 → RT 下加锁。
统计计数偏差 → 只影响分析数字,可接受 → 用 raw 操作省开销。
五、raw_cpu_*与this_cpu_*的选用原则(内核通用)
| 场景 | 选择 |
|---|---|
| 数据是正确性关键,不能被并发打断 | this_cpu_*(隐含关中断/抢占) |
| 数据是统计/近似值,允许竞争 | raw_cpu_*(省开销) |
| 已在关抢占/关中断上下文中 | raw_cpu_*也可安全用 |