ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

slub stat

slub stat

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 有竞争,但可以接受。原因:

  1. 统计值用于性能分析,不是正确性关键数据,偶尔少算几次无所谓。

  2. 关中断/关抢占的代价(尤其在高频分配路径上)比统计精度更重要。

  3. 计数器是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_incraw_cpu_add
场景单次事件计数批量/带值计数
保护无(raw)无(raw)
条件编译CONFIG_SLUB_STATSCONFIG_SLUB_STATS
精度可抢占下可能丢失更新同左

四、相关的呼应

呼应这里体现
enum stat_itemsi参数就是它的枚举值
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_*也可安全用
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进