文章目录
一、atomic types
- atomic_t
- atomic64_t
- atomic_long_t
原子类型提供了一个接口,用于在CPU之间执行原子 RMW 操作(不支持MMIO上的原子操作,并且可能导致某些平台上出现致命陷阱)。
二、API
The ‘full’ API consists of (为简洁起见省略了 atomic64_ 和 atomic_long_ 前缀)
Non-RMW ops
- atomic_read()
- atomic_set()
- atomic_read_acquire()
- atomic_set_release()
RMW atomic operations
Arithmetic 算术:
- atomic_{add,sub,inc,dec}()
- atomic_{add,sub,inc,dec}_return{,_relaxed,_acquire,_release}()
- atomic_fetch_{add,sub,inc,dec}{,_relaxed,_acquire,_release}()
Bitwise 位运算:
- atomic_{and,or,xor,andnot}()
- atomic_fetch_{and,or,xor,andnot}{,_relaxed,_acquire,_release}()
Swap 交换:
- atomic_xchg{,_relaxed,_acquire,_release}()
- atomic_cmpxchg{,_relaxed,_acquire,_release}()
- atomic_try_cmpxchg{,_relaxed,_acquire,_release}()
Reference count 引用计数:
请参考 refcount_t 。
- atomic_add_unless(), atomic_inc_not_zero()
- atomic_sub_and_test(), atomic_dec_and_test()
Misc 杂项:
- atomic_inc_and_test(), atomic_add_negative()
- atomic_dec_unless_positive(), atomic_inc_unless_negative()
Barriers 内存屏蔽:
- smp_mb__{before,after}_atomic()
三、TYPES (signed vs unsigned)
当atomic_t、atomic_long_t 和 atomic64_t 分别使用int、long 和 s64 (for hysterical raisins) 时,内核使用 -fno-strict-overflow
(which implies -fwrapv) 并定义有符号溢出,其行为类似于2s补码。
因此,原子操作的显式无符号变体是完全不必要的,我们可以简单地进行转换,不存在 UB 。
在GCC-8之前的 UBSAN 中有一个bug,它会为签名类型生成UB警告。
我们也符合 C/C++ 原子行为,如 P1236R1 。
四、SEMANTICS 语义
Non-RMW ops
Non-RMW 操作(通常)是常规加载和存储,分别使用 READ_ONCE() 、WRITE_ONCE() 、smp_load_acquire() 和 smp_store_release() 规范实现。因此,如果您发现自己只使用原子的非RMW操作,那么实际上您根本不需要原子,而是在做错事。
atomic_set{}() 的一个微妙的细节是它应该可以被RMW操作人员观察到。那就是:
C atomic-set
{
atomic_set(v, 1);
}
P1(atomic_t *v)
{
atomic_add_unless(v, 1, 0);
}
P2(atomic_t *v)
{
atomic_set(v, 0);
}
exists
(v=2)
在这个案例中,我们希望 CPU1 中的 atomic_set() 发生在 atomic_add_excel() 之前,在这种情况下,后者将不执行操作,在这种情况下,我们将覆盖它的结果。在任何情况下,“2” 都不是有效的结果。
这在“普通”平台上通常是正确的,普通的竞争“存储”将使LL/SC失效或使 CMPXCHG 失败。
显然,当我们需要用锁实现原子操作时,情况并非如此:
CPU0 | CPU1
|
atomic_add_unless(v, 1, 0); |
lock(); |
ret = READ_ONCE(v->counter); /*== 1*/ |
| atomic_set(v, 0);
if (ret != u) | WRITE_ONCE(v->counter, 0);
WRITE_ONCE(v->counter, ret + 1); |
unlock(); |
典型的解决方案是使用 atomic_xchg() 实现 atomic_set{}() 。
RMW ops
它们有多种形式:
-
无返回值的普通操作:atomic_{} ()
-
返回修改后值的操作:atomic_{}_return()
这些操作仅限于算术运算,因为它们是可逆的。位操作是不可逆的,因此修改后的值具有不确定的实用性。
-
返回原始值的操作:atomic_fetch_{} ()
-
交换操作(swap operations):xchg(), cmpxchg() and try_cmpxchg()
-
杂项 misc:在给定接口的情况下,通常使用 (try_)cmpxchg 循环来实现常用的特殊用途操作,但这些操作是时间关键的,并且可以(通常)在LL/SC体系结构上更有效地实现。
所有这些操作都是SMP-atomic;也就是说,操作(对于单个原子变量)可以完全有序,没有中间状态丢失或可见。
五、ORDERING 顺序
请先阅读 memory-barriers.txt 。
经验法则:
- non-RMW 操作是无序的
- 没有返回值的 RMW 操作是无序的
- 具有返回值的 RMW 操作是完全有序的
- 有条件的 RMW 操作在失败时是无序的,否则将应用上述规则。
当然,当一个操作具有如下显式顺序时除外:
- {}_relaxed: unordered
- {}_acquire: the R of the RMW (or atomic_read) is an ACQUIRE
- {}_release: the W of the RMW (or atomic_set) is a RELEASE
其中 “unordered” 与其他内存位置相反。地址依赖关系不会失败。
完全有序的原语是针对之前的一切和之后的一切进行排序的。因此,一个完全有序的原语就像在原语之前有一个 smp_mb() ,在原语之后有一个 smp_mb() 。
The barriers
- smp_mb__{before,after}_atomic()
仅适用于 RMW 原子操作,可用于(增加/升级)操作固有的顺序。这些屏障的作用类似于完整的 smp_mb() :smp_mb__before_atomic() orders all earlier accesses against the RMW op itself and all accesses following it, and smp_mb__after_atomic() orders all later accesses against the RMW op and all accesses preceding it. However, accesses between the smp_mb__{before,after}_atomic() and the RMW op are not ordered, so it is advisable to place the barrier right next to the RMW atomic op whenever possible.
这些辅助屏障的存在是因为体系结构在其 SMP 原子原语上具有不同的隐式顺序。例如,我们的 TSO 体系结构提供了全有序原子,而这些屏障是无操作的。
注意: 当原子RmW操作是完全有序的时,它们也应该意味着一个编译器屏障。
因此,该函数:
atomic_fetch_add();
等价于下面一系列函数:
smp_mb__before_atomic();
atomic_fetch_add_relaxed();
smp_mb__after_atomic();
不过,atomic_fetch_add() 的实现可能更有效率。
更进一步地说,就像:
smp_mb__before_atomic();
atomic_dec(&X);
is a ‘typical’ RELEASE pattern, the barrier is strictly stronger than a RELEASE because it orders preceding instructions against both the read and write parts of the atomic_dec(), and against all following instructions as well. Similarly, something like:
atomic_inc(&X);
smp_mb__after_atomic();
is an ACQUIRE pattern (though very much not typical), but again the barrier is strictly stronger than ACQUIRE. As illustrated:
C strong-acquire
{
}
P1(int *x, atomic_t *y)
{
r0 = READ_ONCE(*x);
smp_rmb();
r1 = atomic_read(y);
}
P2(int *x, atomic_t *y)
{
atomic_inc(y);
smp_mb__after_atomic();
WRITE_ONCE(*x, 1);
}
exists
(r0=1 /\ r1=0)
This should not happen; but a hypothetical atomic_inc_acquire() – (void)atomic_fetch_inc_acquire() for instance – would allow the outcome, because it would not order the W part of the RMW against the following WRITE_ONCE. Thus:
P1 | P2
|
| t = LL.acq *y (0)
| t++;
| *x = 1;
r0 = *x (1) |
RMB |
r1 = *y (0) |
| SC *y, t;
is allowed.
参考
- linux-5.4.37/Documentation/atomic_t.txt
- linux-5.4.37/Documentation/atomic_bitops.txt
- linux-5.4.37/Documentation/core-api/atomic_ops.rst
- linux-5.4.37/Documentation/core-api/refcount-vs-atomic.rst