c - 下面的程序集是原子的,如果不是,为什么?

标签 c assembly atomic

addl, $9, _x(%rip)

_x 是一个全局变量。基本上我不确定在这种情况下如何添加到全局变量,以及多处理器系统中这条线是否存在固有的竞争条件。

最佳答案

正如 duskwuff 所指出的,您需要一个 lock 前缀。

原因是:

addl $9,_x(%rip)

实际上是从内存系统的角度来看的三个“微操作”[这里的%eax只是为了说明——从未真正使用过]:

mov     _x(%rip),%eax
addl    $9,%eax
mov     %eax,_x(%rip)

这是一个有效的事件序列。这是由 lock 前缀保证的。最后,_x 为 18:

# this is a valid sequence

# cpu 1                         # cpu 2
mov     _x(%rip),%eax
addl    $9,%eax
mov     %eax,_x(%rip)
                                mov     _x(%rip),%eax
                                addl    $9,%eax
                                mov     %eax,_x(%rip)

但是,如果没有,我们可以得到:

# this is an invalid sequence

# cpu 1                         # cpu 2
mov     _x(%rip),%eax
                                mov     _x(%rip),%eax
addl    $9,%eax                 addl    $9,%eax
mov     %eax,_x(%rip)
                                mov     %eax,_x(%rip)

最后,_x 将是 9。序列的进一步困惑可能会产生 18。因此,根据两个 CPU 上微操作之间的确切顺序,我们可以得到 9 或 18。

我们可以让它变得更糟。如果 CPU 2 添加 8 而不是 9,则序列 without lock 可以产生以下任何一个:8、9 或 17


更新:

基于一些评论,只是为了澄清一下术语。

当我说微操作时……它是用引号引起来的,所以我是为了本文的讨论而创造了一个术语。它意味着直接转换为 x86 处理器文献中定义的 x86 微指令。我本可以[也许应该]说步骤

同样,虽然使用 x86 asm 表达步骤似乎最简单、最清晰,但我本来可以更抽象:

(1) FETCH_MEM_TO_MREG _x
(2) ADD_TO_MREG 9
(3) STORE_MREG_TO_MEM _x

不幸的是,这些步骤纯粹是在硬件逻辑中执行的(即程序无法看到它们或使用调试器逐步执行它们)。内存系统(例如高速缓存逻辑、DRAM Controller 等)会注意到(并且必须响应)步骤 (1) 和 (3)。 CPU 的 ALU 将执行步骤 (2),这对内存逻辑是不可见的。

请注意,一些 RISC CPU arches 没有在内存上工作的 add 指令,也没有锁前缀。见下文。

除了阅读一些文献之外,检查效果的一种实用方法是创建一个使用多线程(通过 pthreads)并使用一些 C 原子操作和/或 pthread_mutex_lock 的 C 程序

此外,此页 Atomically increment two integers with CAS有一个我给出的答案,还有一个指向另一个人在 cppcon 上给出的视频谈话的链接(关于“无锁”实现)

在这个更通用的模型中,它还可以说明在没有正确记录锁定的数据库中会发生什么。

如何实现 lock 的实际机制可能是特定于 x86 模型的。

并且,可能,目标指令特定(例如,如果目标指令是 [say] addl vs xchg,则 lock 的工作方式不同)因为处理器可能能够使用更高效/特殊类型的内存周期(例如类似于原子“读-修改-写”的东西)。

在其他情况下(例如,数据对于单个周期来说太宽或跨越缓存行边界),它可能必须锁定整个内存总线(例如,获取全局锁并强制完全序列化),进行多次读取、进行更改、进行多次写入,然后然后解锁内存总线。这种模式类似于将某些东西包装在互斥锁锁定/解锁配对中的方式,仅在内存总线逻辑级别的硬件中完成

关于 ARM [一个 RISC cpu] 的注释。 ARM 只支持ldr r1,memory_addressstr r1,memory_address add r1,memory_address。它只允许 add r1,r2,r3 [即它是“三元”] 或者可能是 add r1,r2,#immed。为实现锁定,ARM 有两个特殊指令:ldrexstrex 必须 配对。在上面的抽象模型中,它看起来像:

ldrex r1,_x
add r1,r1,#9
strex r1,_x
// must be tested for success and loop back if failed ...

关于c - 下面的程序集是原子的,如果不是,为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35332057/

相关文章:

c - 从函数返回字符串时遇到问题

c - 在C中的最后一个位置反转一个带有0的数字

c - 与 '%rbx' 后缀一起使用的寄存器 0x​​104567910 不正确

java - 使用 AtomicReference 对多个预期值进行比较和设置

c++ - 使用 OpenMP atomic 并行更新矩阵列

c - 使用 Contiki 测量时间(以时钟滴答为单位)

c - Mongo c driver find查询错误处理

assembly - 外部符号的目标文件链接如何工作?

assembly - 汇编语言有多少种数据类型?

c - C中的引用计数