[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 路径发放新引用 */
}

percpu_count_ptr percpu_ref_init __percpu_ref_exit percpu_ref_exit 初始化与退出资源管理

作用与实现要点

  • 指针低位复用标志位percpu_count_ptr() 负责把 percpu_count_ptr 字段中的 ATOMIC/DEAD 低位标志剥离,得到真实 percpu 指针,避免把“带标志的值”当地址使用。
  • 初始化三态percpu_ref_init() 根据 flags 决定初始处于 percpu 还是 atomic、以及是否从一开始就是死亡态;在 percpu 起步时给全局计数加偏置,并建立初始引用为 1。
  • 失败路径配平:初始化中任一步分配失败都会回收已分配资源并把指针清零,避免悬挂与重复释放。
  • 退出时保留计数快照percpu_ref_exit() 在释放 data 之前,把最终 count 快照移入 percpu_count_ptr 的高位,供 percpu_ref_is_zero()data==NULL 时仍可读取。

percpu_count_ptr 提取真实percpu计数指针

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @brief 从带标志位的 percpu_count_ptr 字段中提取真实 percpu 指针
* @param ref 目标 percpu_ref
* @return 去除 ATOMIC/DEAD 标志后的 percpu 指针
*
* 返回值为 NULL 表示 percpu 区已不存在或已被置为死亡态常量。
*/
static unsigned long __percpu *percpu_count_ptr(struct percpu_ref *ref)
{
return (unsigned long __percpu *)
(ref->percpu_count_ptr & ~__PERCPU_REF_ATOMIC_DEAD); /* 剥离低位标志,避免把标志位当地址位 */
}

percpu_ref_init 初始化percpu引用计数对象

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* @brief 初始化 percpu_ref 引用计数对象
* @param ref 目标 percpu_ref
* @param release 计数归零时调用的释放回调
* @param flags PERCPU_REF_INIT_* 初始化标志
* @param gfp 分配掩码
* @return 0 成功;-ENOMEM 资源不足
*
* 初始化后默认进入 percpu 模式并建立初始引用为 1;若指定 ATOMIC/DEAD 则以 atomic 形态起步。
*/
int percpu_ref_init(struct percpu_ref *ref, percpu_ref_func_t *release,
unsigned int flags, gfp_t gfp)
{
size_t align = max_t(size_t, 1 << __PERCPU_REF_FLAG_BITS,
__alignof__(unsigned long));
unsigned long start_count = 0;
struct percpu_ref_data *data;

ref->percpu_count_ptr = (unsigned long)
__alloc_percpu_gfp(sizeof(unsigned long), align, gfp);
if (!ref->percpu_count_ptr)
return -ENOMEM; /* 分配失败直接返回,避免后续使用半初始化对象 */

data = kzalloc(sizeof(*ref->data), gfp);
if (!data) {
free_percpu((void __percpu *)ref->percpu_count_ptr); /* 回收已分配的 percpu 区,避免泄漏 */
ref->percpu_count_ptr = 0; /* 清零避免后续重复释放或被误判为有效 */
return -ENOMEM;
}

data->force_atomic = flags & PERCPU_REF_INIT_ATOMIC; /* 记录是否粘性保持 atomic */
data->allow_reinit = flags & PERCPU_REF_ALLOW_REINIT; /* 记录是否允许 atomic->percpu 的切回 */

if (flags & (PERCPU_REF_INIT_ATOMIC | PERCPU_REF_INIT_DEAD)) {
ref->percpu_count_ptr |= __PERCPU_REF_ATOMIC; /* 从 atomic 起步,快路径将走全局原子计数 */
data->allow_reinit = true; /* ATOMIC/DEAD 起步隐含允许重初始化 */
} else {
start_count += PERCPU_COUNT_BIAS; /* percpu 起步先加偏置,避免汇总前全局计数意外到 0 */
}

if (flags & PERCPU_REF_INIT_DEAD)
ref->percpu_count_ptr |= __PERCPU_REF_DEAD; /* 起步即死亡态,tryget_live 将拒绝发放活体引用 */
else
start_count++; /* 建立初始引用为 1,保证 kill 前不会触发归零释放 */

atomic_long_set(&data->count, start_count); /* 写入全局计数起点,偏置与初始引用语义都依赖此值 */

data->release = release;
data->confirm_switch = NULL;
data->ref = ref;
ref->data = data;
return 0;
}
EXPORT_SYMBOL_GPL(percpu_ref_init);

__percpu_ref_exit 内部释放percpu区域并固定为atomic死亡态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @brief 内部退出路径:释放 percpu 计数区域并把状态固定为 atomic+dead
* @param ref 目标 percpu_ref
*
* 用于最终销毁或禁止重初始化的场景,避免后续仍被当作 percpu 模式访问。
*/
static void __percpu_ref_exit(struct percpu_ref *ref)
{
unsigned long __percpu *percpu_count = percpu_count_ptr(ref);

if (percpu_count) {
WARN_ON_ONCE(ref->data && ref->data->confirm_switch); /* 切换未完成就释放会破坏汇总一致性与等待条件 */
free_percpu(percpu_count); /* 释放每CPU计数存储,后续不得再解引用 */
ref->percpu_count_ptr = __PERCPU_REF_ATOMIC_DEAD; /* 固定为死亡态常量,阻断 percpu 路径判定 */
}
}

percpu_ref_exit 对外退出接口 释放data并保存最终计数快照

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
/**
* @brief 退出 percpu_ref,撤销 percpu_ref_init 分配的资源
* @param ref 目标 percpu_ref
*
* 常见调用点是 release 回调或初始化失败回滚路径;调用者需要保证不再有并发访问。
*/
void percpu_ref_exit(struct percpu_ref *ref)
{
struct percpu_ref_data *data = ref->data;
unsigned long flags;

__percpu_ref_exit(ref);

if (!data)
return;

spin_lock_irqsave(&percpu_ref_switch_lock, flags);
ref->percpu_count_ptr |= atomic_long_read(&ref->data->count) <<
__PERCPU_REF_FLAG_BITS; /* 把最终 count 快照塞进高位,供 data 被释放后仍可读取 */
ref->data = NULL; /* 断开 data 指针,防止并发路径继续通过 data 访问 */
spin_unlock_irqrestore(&percpu_ref_switch_lock, flags);

kfree(data); /* 释放元数据 */
}
EXPORT_SYMBOL_GPL(percpu_ref_exit);

percpu_ref_call_confirm_rcu percpu_ref_switch_to_atomic_rcu percpu_ref_noop_confirm_switch __percpu_ref_switch_to_atomic __percpu_ref_switch_to_percpu __percpu_ref_switch_mode percpu_ref_switch_to_atomic percpu_ref_switch_to_atomic_sync percpu_ref_switch_to_percpu 模式切换与RCU确认收敛

作用与实现要点

  • 切换串行化:所有模式切换都在 percpu_ref_switch_lock 下进行,且在进入实际切换前会等待 confirm_switch 清空,避免多次汇总/多次清零叠加导致计数错乱。
  • RCU回调完成汇总与确认:从 percpu 切到 atomic 的关键动作(跨CPU求和并并入全局)在 RCU 回调里执行,结束后调用确认回调并唤醒等待者,形成“切换完成”的统一收敛点。
  • confirm_switch 的双重含义:既是回调函数指针,也是“切换进行中”的标志;非 NULL 期间,其他切换请求会等待,防止并发切换打破顺序。
  • percpu恢复的发布语义:从 atomic 切回 percpu 时先给全局加偏置、清零所有每CPU计数,然后用 smp_store_release() 清除 ATOMIC 位,让快路径在看到 ATOMIC 清除后一定能看到清零结果。

percpu_ref_call_confirm_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 rcu RCU 头
*
* 完成确认后会唤醒等待切换完成的线程;若禁止重初始化则顺带释放 percpu 计数区;
* 最后会 put 掉切换发起时额外 get 的引用,配平切换期间的存活保护。
*/
static void percpu_ref_call_confirm_rcu(struct rcu_head *rcu)
{
struct percpu_ref_data *data = container_of(rcu,
struct percpu_ref_data, rcu);
struct percpu_ref *ref = data->ref;

data->confirm_switch(ref); /* 通知切换完成,调用者可在此点之后建立稳定边界 */
data->confirm_switch = NULL; /* 清除“切换进行中”标志,使后续切换请求不再等待 */
wake_up_all(&percpu_ref_switch_waitq); /* 唤醒等待 confirm_switch 清空的切换请求 */

if (!data->allow_reinit)
__percpu_ref_exit(ref); /* 不允许重初始化时释放 percpu 计数区,防止后续再切回 percpu */

percpu_ref_put(ref); /* 配平 __percpu_ref_switch_to_atomic() 的额外 get,避免引用泄漏 */
}

percpu_ref_switch_to_atomic_rcu 汇总每CPU计数并并入全局计数

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
28
29
30
31
32
33
34
35
/**
* @brief 在 RCU 回调中汇总各CPU计数,并把结果并入全局 atomic 计数
* @param rcu RCU 头
*
* 先求所有CPU之和再并入全局,避免跨CPU get/put 交错导致中间态归零;并入时同时去除偏置。
* 完成后进入统一确认路径。
*/
static void percpu_ref_switch_to_atomic_rcu(struct rcu_head *rcu)
{
struct percpu_ref_data *data = container_of(rcu,
struct percpu_ref_data, rcu);
struct percpu_ref *ref = data->ref;
unsigned long __percpu *percpu_count = percpu_count_ptr(ref);
static atomic_t underflows;
unsigned long count = 0;
int cpu;

for_each_possible_cpu(cpu)
count += *per_cpu_ptr(percpu_count, cpu); /* 汇总所有CPU计数,确保一次性得到一致的总和 */

pr_debug("global %lu percpu %lu\n",
atomic_long_read(&data->count), count);

atomic_long_add((long)count - PERCPU_COUNT_BIAS, &data->count); /* 把总和并入全局并去偏置,使全局计数回到可归零检测语义 */

if (WARN_ONCE(atomic_long_read(&data->count) <= 0,
"percpu ref (%ps) <= 0 (%ld) after switching to atomic",
data->release, atomic_long_read(&data->count)) &&
atomic_inc_return(&underflows) < 4) {
pr_err("%s(): percpu_ref underflow", __func__);
mem_dump_obj(data);
}

percpu_ref_call_confirm_rcu(rcu); /* 进入确认路径:清除切换标志、唤醒等待者、配平引用 */
}

percpu_ref_noop_confirm_switch 缺省确认回调

1
2
3
4
5
6
7
8
9
/**
* @brief 缺省确认回调
* @param ref 目标 percpu_ref
*
* 用于未提供确认回调时,仍以非 NULL 指针标记切换进行中,保证等待逻辑一致。
*/
static void percpu_ref_noop_confirm_switch(struct percpu_ref *ref)
{
}

__percpu_ref_switch_to_atomic 发起切到atomic并注册RCU汇总

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
/**
* @brief 发起从 percpu 切到 atomic 的切换
* @param ref 目标 percpu_ref
* @param confirm_switch 可选确认回调
*
* 置位 ATOMIC 后,快路径将不再走 percpu 指针;随后设置 confirm_switch 标记切换进行中并注册 RCU 回调做汇总。
*/
static void __percpu_ref_switch_to_atomic(struct percpu_ref *ref,
percpu_ref_func_t *confirm_switch)
{
if (ref->percpu_count_ptr & __PERCPU_REF_ATOMIC) {
if (confirm_switch)
confirm_switch(ref); /* 已是 atomic 时直接确认,避免重复切换 */
return;
}

ref->percpu_count_ptr |= __PERCPU_REF_ATOMIC; /* 先切断 percpu 快路径,后续 get/put 走全局计数 */

ref->data->confirm_switch = confirm_switch ?:
percpu_ref_noop_confirm_switch; /* 非 NULL 表示切换进行中,供等待逻辑与确认回调使用 */

percpu_ref_get(ref); /* 切换期间保护对象存活,防止 RCU 回调执行时 ref/data 已被释放 */
call_rcu_hurry(&ref->data->rcu,
percpu_ref_switch_to_atomic_rcu);
}

__percpu_ref_switch_to_percpu 从atomic恢复到percpu并发布清零结果

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 从 atomic 切回 percpu
* @param ref 目标 percpu_ref
*
* 先恢复偏置,再清零所有每CPU计数,最后以 release-store 清除 ATOMIC 位,保证快路径看到 ATOMIC 清除时必然能看到清零结果。
*/
static void __percpu_ref_switch_to_percpu(struct percpu_ref *ref)
{
unsigned long __percpu *percpu_count = percpu_count_ptr(ref);
int cpu;

BUG_ON(!percpu_count);

if (!(ref->percpu_count_ptr & __PERCPU_REF_ATOMIC))
return;

if (WARN_ON_ONCE(!ref->data->allow_reinit))
return;

atomic_long_add(PERCPU_COUNT_BIAS, &ref->data->count); /* 恢复偏置,使 percpu 阶段不在 put 中做归零检测 */

for_each_possible_cpu(cpu)
*per_cpu_ptr(percpu_count, cpu) = 0; /* 清零每CPU计数,避免沿用上一次生命周期残留 */

smp_store_release(&ref->percpu_count_ptr,
ref->percpu_count_ptr & ~__PERCPU_REF_ATOMIC); /* 发布“清零已完成”,并允许快路径重新使用 percpu 指针 */
}

__percpu_ref_switch_mode 在锁内等待并选择切换方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @brief 在切换锁内等待前次切换完成并选择切换方向
* @param ref 目标 percpu_ref
* @param confirm_switch 需要切到 atomic 时使用的确认回调
*
* 若 force_atomic 置位或已进入死亡态,则走切到 atomic;否则尝试切回 percpu。
*/
static void __percpu_ref_switch_mode(struct percpu_ref *ref,
percpu_ref_func_t *confirm_switch)
{
struct percpu_ref_data *data = ref->data;

lockdep_assert_held(&percpu_ref_switch_lock);

wait_event_lock_irq(percpu_ref_switch_waitq,
!data->confirm_switch, /* confirm_switch 非 NULL 表示仍在切换中,必须等待收敛 */
percpu_ref_switch_lock);

if (data->force_atomic || percpu_ref_is_dying(ref))
__percpu_ref_switch_to_atomic(ref, confirm_switch);
else
__percpu_ref_switch_to_percpu(ref);
}

percpu_ref_switch_to_atomic 对外接口 强制切到atomic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 对外接口:请求切到 atomic 模式
* @param ref 目标 percpu_ref
* @param confirm_switch 可选确认回调
*
* force_atomic 会被置位,使该对象保持粘性 atomic,直到显式调用 percpu_ref_switch_to_percpu()。
*/
void percpu_ref_switch_to_atomic(struct percpu_ref *ref,
percpu_ref_func_t *confirm_switch)
{
unsigned long flags;

spin_lock_irqsave(&percpu_ref_switch_lock, flags);

ref->data->force_atomic = true; /* 设置粘性策略,后续模式选择优先/强制走 atomic */
__percpu_ref_switch_mode(ref, confirm_switch);

spin_unlock_irqrestore(&percpu_ref_switch_lock, flags);
}
EXPORT_SYMBOL_GPL(percpu_ref_switch_to_atomic);

percpu_ref_switch_to_atomic_sync 切到atomic并等待切换完成

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @brief 对外接口:切到 atomic 并同步等待切换完成
* @param ref 目标 percpu_ref
*
* 返回时 confirm_switch 已清空,表示汇总与确认回调路径已完成。
*/
void percpu_ref_switch_to_atomic_sync(struct percpu_ref *ref)
{
percpu_ref_switch_to_atomic(ref, NULL);
wait_event(percpu_ref_switch_waitq, !ref->data->confirm_switch); /* 等待切换收敛,避免后续依赖全局计数时看到未汇总状态 */
}
EXPORT_SYMBOL_GPL(percpu_ref_switch_to_atomic_sync);

percpu_ref_switch_to_percpu 对外接口 解除粘性atomic并尝试切回percpu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @brief 对外接口:请求切回 percpu 模式
* @param ref 目标 percpu_ref
*
* 若对象处于死亡态或禁止重初始化,则不会实际切回 percpu,以避免 teardown 期间重新走快路径导致边界失效。
*/
void percpu_ref_switch_to_percpu(struct percpu_ref *ref)
{
unsigned long flags;

spin_lock_irqsave(&percpu_ref_switch_lock, flags);

ref->data->force_atomic = false; /* 解除粘性 atomic,使非死亡态时可恢复 percpu 快路径 */
__percpu_ref_switch_mode(ref, NULL);

spin_unlock_irqrestore(&percpu_ref_switch_lock, flags);
}
EXPORT_SYMBOL_GPL(percpu_ref_switch_to_percpu);

percpu_ref_kill_and_confirm percpu_ref_is_zero percpu_ref_reinit percpu_ref_resurrect 两阶段销毁确认与归零复用

作用与实现要点

  • 死亡态标记先行kill_and_confirm() 先置 DEAD,再走统一的“切到 atomic 并汇总”路径,使后续 tryget_live 在 atomic 分支上不再发放新引用。
  • 丢弃初始引用kill_and_confirm() 在锁内最后 percpu_ref_put(ref),把使用者持有的初始引用放掉,让对象真正进入“可归零释放”的阶段。
  • 归零判定避开已释放datais_zero() 在 data 仍存在时读 data->count,data 已被 exit 释放时改读 percpu_count_ptr 高位快照,避免读取已释放内存。
  • 复活与重初始化reinit() 只做“必须已归零”的入口检查并转调 resurrect()resurrect()DEAD、额外 get 保护切换过程、再按策略选择切回 percpu 或保持 atomic。

percpu_ref_kill_and_confirm 标记死亡态 切到atomic汇总 丢弃初始引用 可选确认回调

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
/**
* @brief 进入死亡态并丢弃初始引用,可选提供“全CPU已看到死亡态”的确认回调
* @param ref 目标 percpu_ref
* @param confirm_kill 可选确认回调
*
* 该接口用于两阶段销毁:先阻断新引用发放与快路径模式,再允许计数归零触发 release。
*/
void percpu_ref_kill_and_confirm(struct percpu_ref *ref,
percpu_ref_func_t *confirm_kill)
{
unsigned long flags;

spin_lock_irqsave(&percpu_ref_switch_lock, flags);

WARN_ONCE(percpu_ref_is_dying(ref),
"%s called more than once on %ps!", __func__,
ref->data->release); /* 重复进入销毁会破坏上层 teardown 同步边界,提示调用方必须串行化 */

ref->percpu_count_ptr |= __PERCPU_REF_DEAD; /* 置死亡态,tryget_live 在 atomic 分支上将拒绝发放新引用 */
__percpu_ref_switch_mode(ref, confirm_kill); /* 进入统一切换逻辑:死亡态下会切到 atomic 并汇总,确认回调在 RCU 收敛点执行 */
percpu_ref_put(ref); /* 丢弃初始引用,使对象真正进入“可归零释放”的阶段 */

spin_unlock_irqrestore(&percpu_ref_switch_lock, flags);
}
EXPORT_SYMBOL_GPL(percpu_ref_kill_and_confirm);

percpu_ref_is_zero 在atomic路径判断是否归零 并避免data已释放时的悬空读取

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
/**
* @brief 判断引用计数是否已归零
* @param ref 目标 percpu_ref
* @return true 已归零;false 未归零或仍处于 percpu 模式
*
* percpu 模式阶段不提供归零判定;进入 atomic 后才允许稳定地检测 0。
*/
bool percpu_ref_is_zero(struct percpu_ref *ref)
{
unsigned long __percpu *percpu_count;
unsigned long count, flags;

if (__ref_is_percpu(ref, &percpu_count))
return false; /* percpu 模式下不做归零检测,避免在快路径引入全局同步语义 */

spin_lock_irqsave(&percpu_ref_switch_lock, flags);
if (ref->data)
count = atomic_long_read(&ref->data->count); /* data 存在时读真实全局计数 */
else
count = ref->percpu_count_ptr >> __PERCPU_REF_FLAG_BITS; /* data 已释放时读 exit 保存的高位快照 */
spin_unlock_irqrestore(&percpu_ref_switch_lock, flags);

return count == 0;
}
EXPORT_SYMBOL_GPL(percpu_ref_is_zero);

percpu_ref_reinit 要求已归零后重新初始化为可用态

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @brief 在引用计数归零后重新初始化到可用状态
* @param ref 目标 percpu_ref
*
* 该接口用于复用对象:对象已到 0 但未 exit,可通过 resurrect 清除死亡态并恢复模式。
*/
void percpu_ref_reinit(struct percpu_ref *ref)
{
WARN_ON_ONCE(!percpu_ref_is_zero(ref)); /* 未归零就 reinit 意味着生命周期边界被破坏,提示上层使用错误 */
percpu_ref_resurrect(ref);
}
EXPORT_SYMBOL_GPL(percpu_ref_reinit);

percpu_ref_resurrect 从死亡态恢复为活体 并按策略切回percpu或保持atomic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @brief 将引用计数从死亡态恢复为活体状态
* @param ref 目标 percpu_ref
*
* 清除 DEAD 后允许 tryget_live 再次发放引用;额外 get 用于防止切换过程中被 put 到 0 触发 release。
*/
void percpu_ref_resurrect(struct percpu_ref *ref)
{
unsigned long __percpu *percpu_count;
unsigned long flags;

spin_lock_irqsave(&percpu_ref_switch_lock, flags);

WARN_ON_ONCE(!percpu_ref_is_dying(ref)); /* 不是死亡态却 resurrect,说明上层状态机使用顺序不一致 */
WARN_ON_ONCE(__ref_is_percpu(ref, &percpu_count)); /* 死亡态通常要求已切到 atomic;仍是 percpu 表示切换顺序被破坏 */

ref->percpu_count_ptr &= ~__PERCPU_REF_DEAD; /* 清除死亡态,tryget_live 的 atomic 分支不再因 DEAD 拒绝发放 */
percpu_ref_get(ref); /* 保护切换过程不被并发 put 归零触发 release,避免 resurrect 与释放并发 */
__percpu_ref_switch_mode(ref, NULL); /* 非死亡态下按 force_atomic/allow_reinit 决策是否切回 percpu */

spin_unlock_irqrestore(&percpu_ref_switch_lock, flags);
}
EXPORT_SYMBOL_GPL(percpu_ref_resurrect);