[TOC]

lib/percpu-refcount.c percpu_ref(percpu 引用计数) 为高并发对象提供按 CPU 本地计数并可切换到原子模式的引用计数机制

介绍

percpu_ref 是内核通用引用计数基础设施,主体实现位于 lib/percpu-refcount.c,对外 API 与数据结构主要在 include/linux/percpu-refcount.h。它提供 struct percpu_ref 抽象:热路径用每 CPU 本地计数(this_cpu_add()/this_cpu_sub())降低争用,进入销毁阶段后切换到全局原子计数并在归零时调用释放回调。内核态的接入点通常是“对象查找/获取引用”与“对象释放/销毁”两端:运行期用 percpu_ref_get()/percpu_ref_put()percpu_ref_tryget_live(),销毁期用 percpu_ref_kill() / percpu_ref_kill_and_confirm()。用户态不会直接调用 percpu_ref,但常通过系统调用触发其生命周期(例如 fs/aio.cio_setup()/io_destroy() 路径驱动 struct kioctx 的创建与销毁)。典型入口函数是对象创建时的 percpu_ref_init(),以及 teardown 时的 percpu_ref_kill(),而热路径则集中在 percpu_ref_get_many()/percpu_ref_put_many()


历史与背景

为了解决什么特定问题而诞生?

  • 问题 1:原子引用计数在高并发下争用严重
    传统 atomic_inc()/atomic_dec_and_test()(或 refcount_t/kref 背后的原子操作)会在多核上造成同一 cacheline 抖动;percpu_ref 将热路径拆到每 CPU 本地计数,核心热路径函数是 percpu_ref_get_many()/percpu_ref_put_many(),分别走 this_cpu_add()/this_cpu_sub() 分支以避开全局原子更新。

  • 问题 2:需要统一的“两阶段销毁”抽象,避免销毁窗口发放新引用
    对象销毁时,必须先阻止新的引用获取,再等待存量引用归零。percpu_ref 用 percpu_ref_kill_and_confirm() 设置 __PERCPU_REF_DEAD 并触发模式切换;对“查找即取引用”的路径提供 percpu_ref_tryget_live()/percpu_ref_tryget_live_rcu(),在 __PERCPU_REF_DEAD 后拒绝新引用(并可用 confirm 回调建立“全 CPU 可见”的时序点)。

  • 问题 3:并发边界与可维护性:切换/释放必须可被严格约束
    模式切换依赖 RCU:__percpu_ref_switch_to_atomic() 通过 call_rcu_hurry() 触发 percpu_ref_switch_to_atomic_rcu() 汇总 per-cpu 计数;确认回调走 percpu_ref_call_confirm_rcu(),并用 wait_event_lock_irq() + percpu_ref_switch_lock/percpu_ref_switch_waitq 序列化切换。由于释放回调可能在 RCU 回调上下文触发,percpu_ref_init() 明确要求 ref->data->release 不得睡眠;confirm 回调同样要求不阻塞。

发展经历了哪些重要的里程碑或版本迭代?

  • 里程碑 1:对象模型与两种计数模式确立
    struct percpu_ref(指针低位编码 __PERCPU_REF_ATOMIC/__PERCPU_REF_DEAD)+ percpu_ref_init() 初始化 per-cpu 计数与全局 atomic_long_t count(在 struct percpu_ref_data),“percpu 模式热路径 + atomic 模式可归零释放”成为基本模型。

  • 里程碑 2:能力扩展:kill/confirm、可重用与显式切换
    percpu_ref_kill_and_confirm() 提供 kill 语义与确认点;percpu_ref_resurrect()/percpu_ref_reinit() 支持“死而复生”(受 PERCPU_REF_ALLOW_REINIT/allow_reinit 控制);percpu_ref_switch_to_atomic()/percpu_ref_switch_to_percpu() 支持非 kill 场景下的模式管理(由 force_atomic__percpu_ref_switch_mode() 统一裁决)。

  • 里程碑 3:可维护性增强:更清晰的切换状态机与诊断
    切换进行中用 ref->data->confirm_switch != NULL 表示,__percpu_ref_switch_mode() 通过 wait_event_lock_irq() 等待上一次切换完成;切换汇总阶段在 percpu_ref_switch_to_atomic_rcu() 中对 underflow 做 WARN_ONCE()mem_dump_obj()pr_err() 诊断,减少静默错误。

  • 里程碑 4:后续完善:内存占用与 fast path 结构布局优化
    将非热字段移出 struct percpu_ref,集中到 struct percpu_ref_data(注释明确“仅 fast path 需要 percpu_count_ptr”),以降低嵌入对象的常驻大小;同时在 percpu_ref_exit()/__percpu_ref_exit() 中细化退出与允许 reinit 时的资源保留/释放策略。

目前该技术的社区活跃度和主流应用情况如何?

  • 主线长期维护:代码位于 lib/include/linux/,接口以 EXPORT_SYMBOL_GPL() 形式提供,属于通用基础设施;切换逻辑依赖 RCU、waitqueue、spinlock 等核心原语,维护通常随内核并发模型演进而持续调整。
  • 主流应用:典型是“RCU 保护查找 + 高频 get/put”的内核对象,例如 fs/aio.cstruct kioctx,查找路径用 percpu_ref_tryget_live() 确保对象未进入 kill,系统调用结束时用 percpu_ref_put() 归还引用,销毁路径用 percpu_ref_kill() 进入两阶段 teardown。
  • 变化趋势:围绕“切换成本可控、并发语义可证明、嵌入对象占用更低”持续演进;受影响的关键点集中在 __ref_is_percpu()(READ_ONCE + release/acquire 配对)、__percpu_ref_switch_mode()(等待与串行化)、percpu_ref_switch_to_atomic_rcu()(汇总与健壮性检查)等路径。

核心原理与设计

它的核心工作原理是什么?

  1. 组件/层次划分:

    • 框架层/核心层lib/percpu-refcount.c 负责模式切换、汇总与 kill/reinit;关键入口包括 percpu_ref_init()percpu_ref_exit()percpu_ref_kill_and_confirm()percpu_ref_switch_to_atomic()percpu_ref_switch_to_percpu(),以及切换回调 percpu_ref_switch_to_atomic_rcu()/percpu_ref_call_confirm_rcu()
    • 驱动/实现层:嵌入对象在自身结构体中包含 struct percpu_ref,并提供 percpu_ref_func_t release(如 fs/aio.cfree_ioctx_users()free_ioctx_reqs());对象 teardown 时由业务逻辑调用 percpu_ref_kill()(或 _and_confirm())进入销毁阶段。
    • 用户态接口层(间接):用户态通过系统调用驱动对象生命周期;例如 AIO 路径中 io_setup() 创建对象后通过 percpu_ref_get() 持有初始引用,后续 syscall 查找对象时通过 percpu_ref_tryget_live() 成功才可继续,结束时 percpu_ref_put();销毁由 io_destroy()/进程退出路径触发 percpu_ref_kill()
  2. 关键数据结构:

    • struct percpu_ref

      • unsigned long percpu_count_ptr:指向 per-cpu 计数的指针,低 __PERCPU_REF_FLAG_BITS 位复用为标志(__PERCPU_REF_ATOMIC/__PERCPU_REF_DEAD);__ref_is_percpu()READ_ONCE() 取值并判定是否走 per-cpu 快路径。
      • struct percpu_ref_data *data:指向慢路径元数据与全局计数(可能在 percpu_ref_exit() 中被置 NULL 并转存 count 到 percpu_count_ptr 高位)。
    • struct percpu_ref_data

      • atomic_long_t count:atomic 模式下的全局计数;切换到 atomic 时在 percpu_ref_switch_to_atomic_rcu() 汇总 per-cpu 后用 atomic_long_add() 合并,并用 PERCPU_COUNT_BIAS 防止过早归零。
      • percpu_ref_func_t *release:计数归零时由 percpu_ref_put_many() 触发调用(atomic 分支 atomic_long_sub_and_test() 为真则调用)。
      • percpu_ref_func_t *confirm_switch:非 NULL 表示“切换进行中”;percpu_ref_call_confirm_rcu() 调用后清空并 wake_up_all(&percpu_ref_switch_waitq)
      • bool force_atomic/bool allow_reinit:由 percpu_ref_switch_to_atomic()percpu_ref_switch_to_percpu()percpu_ref_init() flags(PERCPU_REF_INIT_ATOMICPERCPU_REF_ALLOW_REINIT)驱动,最终在 __percpu_ref_switch_mode() 决策走向。
      • struct rcu_head rcu:承载 call_rcu_hurry() 回调。
      • struct percpu_ref *ref:反向指针,供 RCU 回调中取回对象。
    • per-cpu 计数存储

      • 分配:percpu_ref_init() 中用 __alloc_percpu_gfp(sizeof(unsigned long), align, gfp) 分配。
      • 访问:percpu_count_ptr(ref)(在 lib/percpu-refcount.c 中使用)+ per_cpu_ptr()/this_cpu_*()
      • 释放:__percpu_ref_exit()percpu_ref_exit() 负责释放,且在切换确认阶段 percpu_ref_call_confirm_rcu() 可按 allow_reinit 决定是否释放。
    • 并发与同步承载体

      • percpu_ref_switch_lock:串行化切换/exit/部分状态读取(如 percpu_ref_is_zero() 读取 data->count 也会短暂持锁防并发销毁)。
      • percpu_ref_switch_waitq:等待 confirm_switch 清空,避免重入切换。
  3. 关键流程:

    • 初始化:
      percpu_ref_init()__alloc_percpu_gfp() 分配 per-cpu 计数 → kzalloc(sizeof(struct percpu_ref_data)) → 设置 data->force_atomic/data->allow_reinit 与初始 atomic_long_set(&data->count, start_count)(percpu 起始会加入 PERCPU_COUNT_BIAS 并再 +1 初始引用)→ ref->data = data

    • 运行时:

      • 热路径(percpu 模式)
        percpu_ref_get_many()rcu_read_lock()__ref_is_percpu() 成功 → this_cpu_add(*percpu_count, nr)rcu_read_unlock()
        percpu_ref_put_many()rcu_read_lock()__ref_is_percpu() 成功 → this_cpu_sub(*percpu_count, nr)(不检查 0)→ rcu_read_unlock()
      • 慢路径(atomic 或 kill 后)
        percpu_ref_get_many()/percpu_ref_put_many()__ref_is_percpu() 失败时走 atomic:atomic_long_add()atomic_long_sub_and_test();后者为真则 ref->data->release(ref)
        获取新引用但需要防止已 kill:percpu_ref_tryget_live()rcu_read_lock()percpu_ref_tryget_live_rcu():若非 percpu 且 ref->percpu_count_ptr & __PERCPU_REF_DEAD 为真则拒绝,否则 atomic_long_inc_not_zero()
    • 销毁/卸载:

      • teardown 触发:percpu_ref_kill()(inline)→ percpu_ref_kill_and_confirm(ref, NULL)
      • kill 主体:percpu_ref_kill_and_confirm() → 持 percpu_ref_switch_lock → 设置 ref->percpu_count_ptr |= __PERCPU_REF_DEAD__percpu_ref_switch_mode()(必要时 wait_event_lock_irq() 等待前次切换完成)→ __percpu_ref_switch_to_atomic():设置 __PERCPU_REF_ATOMIC、设置 data->confirm_switchpercpu_ref_get()(为回调期间保活)→ call_rcu_hurry(..., percpu_ref_switch_to_atomic_rcu)percpu_ref_put(ref)(丢弃“初始引用”)
      • 汇总与确认:RCU 回调 percpu_ref_switch_to_atomic_rcu() → 遍历 for_each_possible_cpu(cpu) 汇总 per-cpu 计数 → atomic_long_add(count - PERCPU_COUNT_BIAS, &data->count)percpu_ref_call_confirm_rcu():调用 confirm(若有)→ 清空 confirm_switch + 唤醒等待者 → 按 allow_reinit 决定是否 __percpu_ref_exit() 释放 per-cpu 区 → percpu_ref_put(ref)(对 __percpu_ref_switch_to_atomic()get 的对称释放)。
      • 资源释放收尾:对象 release 回调中通常调用 percpu_ref_exit() 释放 percpu_ref 自身资源;percpu_ref_exit()__percpu_ref_exit() 释放 per-cpu 区,并在持 percpu_ref_switch_lock 下将 data->count 迁移到 percpu_count_ptr 高位并 kfree(data)

它的主要优势体现在哪些方面?

  • 优势 1:一致性与通用抽象
    struct percpu_ref + percpu_ref_init()/percpu_ref_exit() 提供统一的引用计数生命周期接口;percpu_ref_kill() 将“进入销毁阶段”的语义显式化,配合 percpu_ref_tryget_live() 把“是否还能发放新引用”固化为可复用接口。

  • 优势 2:性能与可扩展性
    热路径 percpu_ref_get_many()/percpu_ref_put_many() 在 percpu 模式下仅做 __ref_is_percpu() + this_cpu_add/sub(),避免全局 atomic_long_* 争用;切换仅在 teardown 或显式切换时触发,且通过 call_rcu_hurry() 将“全 CPU 视角一致化”外包给 RCU 回调 percpu_ref_switch_to_atomic_rcu()

  • 优势 3:资源管理/安全边界/可维护性
    kill/release 分离:percpu_ref_kill_and_confirm() 先把系统带到“不会再发放新引用”的状态,再由 percpu_ref_put_many() 的 atomic 分支在归零时触发 data->release,减少 use-after-free 窗口;切换状态由 data->confirm_switch 显式表示并可用 percpu_ref_switch_waitq 等待;underflow 检测集中在 percpu_ref_switch_to_atomic_rcu(),便于定位错误使用。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 局限 1:切换与汇总开销集中且不可忽略
    percpu_ref_switch_to_atomic_rcu() 需要遍历 for_each_possible_cpu() 汇总 per-cpu 计数,并做一次 atomic_long_add() 合并;在 CPU 数很多或 teardown 频繁的场景,切换成本会显著。

  • 局限 2:上下文限制严格
    percpu_ref_init() 要求 release 不得睡眠(可能在 RCU 回调链路触发);percpu_ref_kill_and_confirm()/percpu_ref_switch_to_atomic() 的 confirm 回调也要求不阻塞;__percpu_ref_switch_mode() 可能通过 wait_event_lock_irq() 阻塞等待前次切换完成,因此调用者若无法保证“无并行切换”就需要接受潜在睡眠点。

  • 局限 3:语义更复杂,误用风险更高
    需要遵循“两阶段销毁”:先 percpu_ref_kill() 再依赖存量引用归零触发释放;若仅用 percpu_ref_put() 试图完成 teardown,在 percpu 模式下不会检查 0,无法触发 release。同时 percpu_ref 本身不提供 RCU 宽限期语义,若对象指针通过 RCU 发表,则仍需像 fs/aio.c 那样显式 call_rcu()/rcu_work 同步。

  • 局限 4:可重用(reinit/resurrect)带来额外状态机复杂度
    percpu_ref_resurrect()/percpu_ref_reinit() 依赖 __PERCPU_REF_DEADallow_reinit 与切换状态组合;若 release 可能释放包含 percpu_ref 的外层对象,调用方必须自行保证 resurrect 期间不会并发触发 releasepercpu_ref_resurrect() 文档要求调用者保证这一点)。


使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

  • 场景 1:RCU 保护查找表中的高频对象引用获取
    典型模式是“查找返回对象指针时立刻取引用”;查找路径用 percpu_ref_tryget_live()(或 percpu_ref_tryget_live_rcu())确保对象未进入 kill,成功后调用方持有引用,结束时 percpu_ref_put();teardown 端先让查找路径不可再成功(percpu_ref_kill_and_confirm() 设置 __PERCPU_REF_DEAD 并在确认后保证不再发放新引用)。

  • 场景 2:AIO 场景下的 struct kioctx 生命周期管理(fs/aio.c)

    • 创建:io_setup() 路径中 percpu_ref_init(&ctx->users, free_ioctx_users, 0, GFP_KERNEL)percpu_ref_init(&ctx->reqs, free_ioctx_reqs, 0, GFP_KERNEL),并用 percpu_ref_get() 提前持有引用用于跨阶段初始化。
    • 运行:查找 kioctx 时用 percpu_ref_tryget_live(&ctx->users) 成功才返回;多个 syscall 结束点(如提交/取消/获取事件等)统一用 percpu_ref_put(&ctx->users) 归还引用。
    • 销毁:kill_ioctx()percpu_ref_kill(&ctx->users) 进入 teardown;当 ctx->users 归零后触发 free_ioctx_users(),其内部再对 ctx->reqs 执行 percpu_ref_kill(&ctx->reqs) + percpu_ref_put(&ctx->reqs),形成级联释放链路。
  • 场景 3:需要“保留 per-cpu 热路径但允许显式降级为 atomic”的对象
    对象在某些阶段需要严格归零检测或更强的全局一致性时,可用 percpu_ref_switch_to_atomic()/percpu_ref_switch_to_atomic_sync() 进入 atomic 模式;若允许恢复 per-cpu(PERCPU_REF_ALLOW_REINIT/allow_reinit),可在安全点调用 percpu_ref_switch_to_percpu() 重新启用 per-cpu 快路径(内部 __percpu_ref_switch_to_percpu() 会清零各 CPU 计数并用 smp_store_release() 清除 __PERCPU_REF_ATOMIC)。

是否有不推荐使用该技术的场景?为什么?

  • 不推荐场景 1:引用操作不频繁或并发度低
    若热路径 get/put 频率不高,直接用 refcount_t/kref 的原子计数更简单;percpu_ref 的 percpu_ref_kill() 两阶段 teardown 与切换链路(call_rcu_hurry()percpu_ref_switch_to_atomic_rcu())会引入额外复杂度与切换成本。

  • 不推荐场景 2:释放回调必须睡眠或需要复杂阻塞收尾
    percpu_ref_put_many() 在 atomic 归零时直接调用 ref->data->release(ref),而 percpu_ref_init() 明确 release 可能在 RCU 回调上下文触发,不能睡眠;若必须做可睡眠的销毁工作,应改为在 release 中仅做唤醒/调度,把重活迁移到 workqueue 等可睡眠上下文。


对比分析

请将其 与 其他相似技术 进行详细对比。

(选择对象:refcount_t/krefpercpu_counter、纯 RCU 生命周期管理)

维度 本技术/文件 相似技术 A(refcount_t/kref) 相似技术 B(percpu_counter) 相似技术 C(纯 RCU 生命周期)
实现方式 struct percpu_ref + struct percpu_ref_data;热路径 percpu_ref_get_many()/percpu_ref_put_many();切换 percpu_ref_kill_and_confirm()call_rcu_hurry()percpu_ref_switch_to_atomic_rcu() 单一全局原子计数;热路径原子 RMW;释放通常在 refcount_dec_and_test()/kref_put() 触发 每 CPU 计数为主,聚合用 percpu_counter_sum() 等;通常用于统计而非对象生命周期释放 依赖 kfree_rcu()/call_rcu() 等宽限期;不提供“引用计数归零触发释放”语义
性能开销 percpu 模式下 this_cpu_add/sub,切换阶段一次性汇总;kill 后退化为 atomic_long_* 始终原子 RMW,争用随并发上升 热路径 per-cpu 更新,读取/汇总开销不小;缺少 kill/归零释放语义 读侧几乎零开销(RCU 读锁),但写侧需要宽限期等待;不适合需要显式引用计数的场景
资源占用 每对象一个 per-cpu unsigned long 区 + struct percpu_ref_data;通过拆分 percpu_ref_data降低嵌入对象大小 每对象一个原子计数(通常 4/8 字节) 每对象 per-cpu 存储,通常比 percpu_ref 更偏“统计容器” 需要 RCU 发表指针与释放路径;不需要 per-cpu 计数存储
隔离级别 明确 kill 边界:__PERCPU_REF_DEAD + percpu_ref_tryget_live();确认点 confirm_kill 仅靠原子计数归零,难表达“禁止新引用发放”的阶段性语义 无生命周期隔离语义 以宽限期隔离“旧读者”,但无法表达“持引用直到完成”的计数语义
启动速度 percpu_ref_init()__alloc_percpu_gfp() + kzalloc();比单原子更重 初始化最轻 初始化依赖 per-cpu 分配,成本较高 取决于对象发表方式;通常不需要 per-cpu 分配

总结

关键特性总结

  • percpu 热路径引用计数percpu_ref_get_many()/percpu_ref_put_many()__ref_is_percpu() 成功时走 this_cpu_add/sub,把高频引用操作从全局原子争用中剥离到每 CPU。
  • 两阶段 teardown 与“禁止新引用”语义percpu_ref_kill()(→percpu_ref_kill_and_confirm())设置 __PERCPU_REF_DEAD 并切换到 atomic,配合 percpu_ref_tryget_live() 在 kill 后拒绝新引用发放。
  • RCU 驱动的模式切换与确认点__percpu_ref_switch_to_atomic() 通过 call_rcu_hurry() 触发 percpu_ref_switch_to_atomic_rcu() 汇总并合并到 atomic_long_t count,再由 percpu_ref_call_confirm_rcu() 执行 confirm 回调并唤醒 percpu_ref_switch_waitq 等待者。

percpu_ref_kill __ref_is_percpu percpu_ref_get_many percpu_ref_get percpu_ref_tryget_many percpu_ref_tryget percpu_ref_tryget_live_rcu percpu_ref_tryget_live percpu_ref_put_many percpu_ref_put percpu_ref_is_dying 快路径引用获取与死亡态门控

作用与实现要点

  • 快路径分流:通过 percpu_count_ptr 低位标志判断当前是 percpu 模式还是 atomic/死亡态模式,快路径尽量只做本地 CPU 计数增减。
  • RCU 包裹读侧:get/put/tryget 的模式判定与指针使用放在 rcu_read_lock() 内,保证并发切换模式时不会把已变化的值当作稳定指针使用。
  • 死亡态门控DEAD 置位后,tryget_live 在 atomic 路径上拒绝再发放新引用;需要更强边界时依赖 kill 的确认回调。
  • 释放触发位置:percpu 模式不在 put 里做归零检测;只有切到 atomic 后才允许 put 在减到 0 时调用 release()

percpu_ref_kill 进入死亡态并触发切到atomic

1
2
3
4
5
6
7
8
9
10
/**
* @brief 标记引用计数进入死亡态并触发切到 atomic 汇总
* @param ref 目标 percpu_ref
*
* 需要在 teardown 开始阶段调用,以阻断后续的“活体获取”路径,并让计数回到可归零检测的 atomic 形态。
*/
static inline void percpu_ref_kill(struct percpu_ref *ref)
{
percpu_ref_kill_and_confirm(ref, NULL); /* 进入死亡态并切到 atomic,后续 put 才可能触发 release */
}

__ref_is_percpu 判定是否可走percpu计数路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief 判断当前是否处于 percpu 模式,并返回每CPU计数指针
* @param ref 目标 percpu_ref
* @param percpu_countp 输出的每CPU计数指针
* @return true 表示 percpu 模式;false 表示 atomic 或死亡态
*
* 返回 true 时,输出指针只能在 RCU 读侧临界区内使用,避免并发切换导致指针语义变化。
*/
static inline bool __ref_is_percpu(struct percpu_ref *ref,
unsigned long __percpu **percpu_countp)
{
unsigned long percpu_ptr;

percpu_ptr = READ_ONCE(ref->percpu_count_ptr); /* 只读一次,避免并发置位标志后再次取值把“带标志的值”当指针用 */

if (unlikely(percpu_ptr & __PERCPU_REF_ATOMIC_DEAD)) /* 已切到 atomic 或已进入死亡态,此时不能走 percpu 快路径 */
return false;

*percpu_countp = (unsigned long __percpu *)percpu_ptr; /* 输出 percpu 计数地址,后续解引用需保持在 RCU 读侧 */
return true;
}

percpu_ref_get_many 增加多个引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 增加引用计数(一次增加 nr)
* @param ref 目标 percpu_ref
* @param nr 增加的引用数量
*
* percpu 模式走本地CPU计数以降低共享写竞争;atomic 模式走全局原子计数以保持归零检测语义。
*/
static inline void percpu_ref_get_many(struct percpu_ref *ref, unsigned long nr)
{
unsigned long __percpu *percpu_count;

rcu_read_lock(); /* 保护模式判定与 percpu 指针使用的时间窗口 */

if (__ref_is_percpu(ref, &percpu_count))
this_cpu_add(*percpu_count, nr); /* 本CPU累计,避免全局原子竞争 */
else
atomic_long_add(nr, &ref->data->count); /* atomic/死亡态下统一走全局计数 */

rcu_read_unlock();
}

percpu_ref_get 增加单个引用

1
2
3
4
5
6
7
8
/**
* @brief 增加单个引用计数
* @param ref 目标 percpu_ref
*/
static inline void percpu_ref_get(struct percpu_ref *ref)
{
percpu_ref_get_many(ref, 1);
}

percpu_ref_tryget_many 在未归零时尝试增加多个引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @brief 当引用计数未到 0 时尝试增加 nr 个引用
* @param ref 目标 percpu_ref
* @param nr 增加的引用数量
* @return true 表示成功发放引用;false 表示计数已为 0
*
* percpu 模式阶段不做归零判定,直接增加并返回成功;atomic 模式通过 “不在 0 上增加” 避免已释放对象被重新拿到。
*/
static inline bool percpu_ref_tryget_many(struct percpu_ref *ref,
unsigned long nr)
{
unsigned long __percpu *percpu_count;
bool ret;

rcu_read_lock(); /* 保护模式判定与 percpu 指针使用 */

if (__ref_is_percpu(ref, &percpu_count)) {
this_cpu_add(*percpu_count, nr); /* percpu 阶段直接累加,避免额外同步开销 */
ret = true;
} else {
ret = atomic_long_add_unless(&ref->data->count, nr, 0); /* 计数为 0 时拒绝发放新引用 */
}

rcu_read_unlock();

return ret;
}

percpu_ref_tryget 在未归零时尝试增加单个引用

1
2
3
4
5
6
7
8
9
/**
* @brief 当引用计数未到 0 时尝试增加单个引用
* @param ref 目标 percpu_ref
* @return true 成功;false 失败
*/
static inline bool percpu_ref_tryget(struct percpu_ref *ref)
{
return percpu_ref_tryget_many(ref, 1);
}

percpu_ref_tryget_live_rcu 在已持有RCU时尝试获取未死亡引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @brief 在调用者已进入 RCU 读侧临界区时尝试获取“未死亡”的引用
* @param ref 目标 percpu_ref
* @return true 成功;false 表示进入死亡态或计数已为 0
*
* percpu 模式直接增加本地计数;atomic 模式下只有在未进入死亡态时才允许尝试增加,并且计数为 0 时失败。
*/
static inline bool percpu_ref_tryget_live_rcu(struct percpu_ref *ref)
{
unsigned long __percpu *percpu_count;
bool ret = false;

WARN_ON_ONCE(!rcu_read_lock_held()); /* 调用者必须已持有 RCU,避免并发切换时出现悬空指针解引用 */

if (likely(__ref_is_percpu(ref, &percpu_count))) {
this_cpu_inc(*percpu_count); /* percpu 快路径增持 */
ret = true;
} else if (!(ref->percpu_count_ptr & __PERCPU_REF_DEAD)) /* 已进入死亡态则不再发放“活体引用”,避免 teardown 边界继续增长 */
ret = atomic_long_inc_not_zero(&ref->data->count); /* 计数为 0 时失败,防止把生命周期已结束的对象重新拿到 */

return ret;
}

percpu_ref_tryget_live 自动持有RCU后尝试获取未死亡引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @brief 尝试获取“未死亡”的引用(内部负责 RCU 读侧保护)
* @param ref 目标 percpu_ref
* @return true 成功;false 失败
*/
static inline bool percpu_ref_tryget_live(struct percpu_ref *ref)
{
bool ret = false;

rcu_read_lock(); /* 覆盖 tryget_live_rcu 的读侧要求 */
ret = percpu_ref_tryget_live_rcu(ref);
rcu_read_unlock();
return ret;
}

percpu_ref_put_many 减少多个引用并在atomic模式下可能触发释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 减少引用计数(一次减少 nr),必要时触发 release
* @param ref 目标 percpu_ref
* @param nr 减少的引用数量
*
* percpu 模式只改本地计数,不在此处做归零释放;atomic 模式减到 0 时调用 release,完成对象生命周期结束的动作。
*/
static inline void percpu_ref_put_many(struct percpu_ref *ref, unsigned long nr)
{
unsigned long __percpu *percpu_count;

rcu_read_lock(); /* 保护模式判定与 percpu 指针使用 */

if (__ref_is_percpu(ref, &percpu_count))
this_cpu_sub(*percpu_count, nr); /* percpu 阶段只减本地计数,不做归零检测 */
else if (unlikely(atomic_long_sub_and_test(nr, &ref->data->count))) /* atomic 阶段减到 0,说明所有引用已释放完毕 */
ref->data->release(ref);

rcu_read_unlock();
}

percpu_ref_put 减少单个引用并在atomic模式下可能触发释放

1
2
3
4
5
6
7
8
/**
* @brief 减少单个引用计数,必要时触发 release
* @param ref 目标 percpu_ref
*/
static inline void percpu_ref_put(struct percpu_ref *ref)
{
percpu_ref_put_many(ref, 1);
}

percpu_ref_is_dying 查询死亡态标志位

1
2
3
4
5
6
7
8
9
10
11
/**
* @brief 判断是否已进入死亡态或正在销毁
* @param ref 目标 percpu_ref
* @return true 表示死亡态已置位
*
* 该返回值是标志位快照;若需要稳定边界,应结合切换锁或 kill 的确认回调使用。
*/
static inline bool percpu_ref_is_dying(struct percpu_ref *ref)
{
return ref->percpu_count_ptr & __PERCPU_REF_DEAD; /* DEAD 置位后 tryget_live 将拒绝在 atomic 路径发放新引用 */
}