[Linux Kernel] atomic 原子操作 官方文档

一、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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值