refcount_t API 与 atomic_t 的比较¶
简介¶
refcount_t API 的目标是为实现对象的引用计数器提供一个最小的 API。虽然 lib/refcount.c 中的通用架构无关实现使用了底层的原子操作,但在一些 refcount_*()
和 atomic_*()
函数中,关于内存排序保证存在一些差异。本文档概述了这些差异,并提供了相应的示例,以帮助维护人员根据这些内存排序保证的变化验证其代码。
本文档中使用的术语尽量遵循 tools/memory-model/Documentation/explanation.txt 中定义的正式 LKMM。
memory-barriers.txt 和 atomic_t.txt 提供了关于内存排序的一般背景知识以及原子操作的详细信息。
相关的内存排序类型¶
注意
以下部分仅涵盖对原子操作和引用计数器相关的,且本文档中用到的一些内存排序类型。要了解更全面的内容,请查阅 memory-barriers.txt 文档。
在没有任何内存排序保证(即完全无序)的情况下,原子操作和引用计数器仅提供原子性和程序顺序(po)关系(在同一 CPU 上)。它保证每个 atomic_*()
和 refcount_*()
操作都是原子的,并且指令在单个 CPU 上按照程序顺序执行。这是通过使用 READ_ONCE()/WRITE_ONCE() 和比较并交换原语来实现的。
强(完整)内存排序保证在同一 CPU 上执行任何 po-后指令之前,必须完成同一 CPU 上所有先前的加载和存储(所有 po-更早的指令)。它还保证在原始 CPU 上执行任何 po-后指令之前,同一 CPU 上所有 po-更早的存储和来自其他 CPU 的所有传播的存储都必须传播到所有其他 CPU(A 累积属性)。这是使用 smp_mb() 实现的。
RELEASE 内存排序保证在操作之前,必须完成同一 CPU 上所有先前的加载和存储(所有 po-更早的指令)。它还保证在 release 操作之前,同一 CPU 上所有 po-更早的存储和来自其他 CPU 的所有传播的存储都必须传播到所有其他 CPU(A 累积属性)。这是使用 smp_store_release() 实现的。
ACQUIRE 内存排序保证在 acquire 操作之后,必须完成同一 CPU 上所有 post 加载和存储(所有 po-后指令)。它还保证在执行 acquire 操作之后,同一 CPU 上所有 po-后存储都必须传播到所有其他 CPU。这是使用 smp_acquire__after_ctrl_dep() 实现的。
引用计数器的控制依赖性(成功时)保证如果成功获取了对象的引用(发生引用计数器递增或加法,函数返回 true),则进一步的存储会根据此操作进行排序。存储上的控制依赖性不是使用任何显式屏障实现的,而是依赖于 CPU 不会推测存储。这只是一个单 CPU 关系,不为其他 CPU 提供任何保证。
函数比较¶
情况 1) - 非“读/修改/写”(RMW)操作¶
函数更改
内存排序保证更改
无(两者都完全无序)
情况 2) - 不返回值的基于递增的操作¶
函数更改
内存排序保证更改
无(两者都完全无序)
情况 3) - 不返回值的基于递减的 RMW 操作¶
函数更改
内存排序保证更改
完全无序 --> RELEASE 排序
情况 4) - 返回值的基于递增的 RMW 操作¶
函数更改
没有原子对应物 -->
refcount_add_not_zero()
内存排序保证更改
完全有序 --> 成功时对存储的控制依赖性
注意
我们这里实际上假设必要的排序是通过获取指向对象的指针来提供的!
情况 5) - 返回值的通用 dec/sub 基于递减的 RMW 操作¶
函数更改
内存排序保证更改
完全有序 --> RELEASE 排序 + 成功时的 ACQUIRE 排序
情况 6) - 其他返回值的基于递减的 RMW 操作¶
函数更改
没有原子对应物 -->
refcount_dec_if_one()
atomic_add_unless(&var, -1, 1)
-->refcount_dec_not_one(&var)
内存排序保证更改
完全有序 --> RELEASE 排序 + 控制依赖性
注意
atomic_add_unless()
仅在成功时提供完全顺序。
情况 7) - 基于锁的 RMW¶
函数更改
atomic_dec_and_lock() -->
refcount_dec_and_lock()
atomic_dec_and_mutex_lock()
-->refcount_dec_and_mutex_lock()
内存排序保证更改
完全有序 --> RELEASE 排序 + 控制依赖性 + 成功时保持 spin_lock()