土法炼钢兴趣小组的算法知识备份

【操作系统百科】percpu_refcount 与 SRCU

文章导航

分类入口
os
标签入口
#percpu-refcount#srcu#kref#reference-counting#cache-bounce

目录

引用计数是内核对象生命周期管理的基础。但原子 refcount_inc/dec 在高频路径上 → cache line bouncing → 性能崩溃。

一、先看图

flowchart TD
    subgraph 活跃模式
        CPU0[CPU 0<br/>local_count++]
        CPU1[CPU 1<br/>local_count++]
        CPU2[CPU 2<br/>local_count++]
    end
    subgraph 关闭模式
        KILL[percpu_ref_kill] --> CONVERGE[汇总 per-CPU 到 atomic]
        CONVERGE --> ATOMIC[atomic_long_t count<br/>正常 dec]
        ATOMIC --> ZERO[count == 0<br/>→ 释放]
    end

    classDef active fill:#3fb95022,stroke:#3fb950,color:#adbac7;
    classDef shutdown fill:#f0883e22,stroke:#f0883e,color:#adbac7;
    class CPU0,CPU1,CPU2 active
    class KILL,CONVERGE,ATOMIC,ZERO shutdown

二、问题

atomic_inc(&ref);  // I/O 提交
// ... 使用对象 ...
atomic_dec(&ref);  // I/O 完成

NVMe 驱动每秒百万次 I/O → atomic 操作 → 同一 cacheline 被所有核争抢。

三、percpu_refcount 设计

3.1 活跃阶段

percpu_ref_get(&ref);  // → __this_cpu_inc(ref->data->count)

每 CPU 独立计数器 → 零竞争

3.2 关闭阶段(kill)

percpu_ref_kill(&ref);
  1. 切换到原子模式
  2. 汇总所有 per-CPU 计数到全局 atomic_long_t
  3. 后续 get/put 走原子路径
  4. 计数归零 → 调用 release 回调

3.3 两阶段的原因

活跃时不能知道全局 refcount 是否为零(分散在各 CPU)→ 只在关闭路径收敛。

四、API

struct percpu_ref ref;
percpu_ref_init(&ref, release_fn, 0, GFP_KERNEL);

percpu_ref_get(&ref);      // 活跃时 per-CPU,关闭后 atomic
percpu_ref_put(&ref);

percpu_ref_kill(&ref);     // 切换到关闭模式
// release_fn 在 count → 0 时被调用

五、使用场景

六、SRCU(Sleepable RCU)

RCU 读侧不能睡眠。SRCU 解决:

int idx = srcu_read_lock(&my_srcu);
// 可以睡眠的读临界区
srcu_read_unlock(&my_srcu, idx);

synchronize_srcu(&my_srcu);  // 等待读者退出

6.1 实现

SRCU 内部使用 per-CPU 计数器(类似 percpu_refcount 思路)。

6.2 代价

比 RCU 慢:

6.3 使用场景

七、kref

struct kref ref;
kref_init(&ref);
kref_get(&ref);
kref_put(&ref, release_fn);

kref 是 refcount_t 的简单包装:

八、正确关闭顺序

// 1. 停止新请求
percpu_ref_kill(&ref);

// 2. 等待 in-flight 请求完成
wait_for_completion(&done);  // release_fn 里 complete()

// 3. 释放资源
cleanup();

错误顺序 → use-after-free 或永远等不到释放。

九、观察

# per-CPU refcount 状态
bpftrace -e 'kprobe:percpu_ref_kill { printf("%s %s\n", comm, kstack(3)); }'

# SRCU grace period
cat /sys/kernel/debug/rcu/rcu_preempt/rcudata

十、小结


参考文献

工具


上一篇seqlock 下一篇futex

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-05-28 · os

【操作系统百科】RCU 深度

RCU 是 Linux 内核使用最广泛的同步机制——读侧零开销。本文讲经典 RCU、Tree RCU、Tasks RCU、SRCU、grace period、rcu_dereference、call_rcu、lazy RCU、nocb CPU。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。


By .