MOV x86 指令是不是实现了 C++11 memory_order_release 原子存储?
Posted
技术标签:
【中文标题】MOV x86 指令是不是实现了 C++11 memory_order_release 原子存储?【英文标题】:Does the MOV x86 instruction implement a C++11 memory_order_release atomic store?MOV x86 指令是否实现了 C++11 memory_order_release 原子存储? 【发布时间】:2015-07-07 12:00:18 【问题描述】:据此https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html,
已发布的存储在 x86(包括 x86-64)上实现为 MOV
(进入内存)。
根据他的http://en.cppreference.com/w/cpp/atomic/memory_order
memory_order_release:
具有此内存顺序的存储操作执行释放 操作:当前线程中没有内存访问可以重新排序 在这家店之后。这确保了当前线程中的所有写入 在获取或相同原子变量的其他线程中可见 并且将依赖项携带到原子变量中的写入变为 在消耗相同原子的其他线程中可见。
我了解当使用 memory_order_release 时,之前完成的所有内存存储都应该在此之前完成。
int a;
a = 10;
std::atomic<int> b;
b.store(50, std::memory_order_release); // i can be sure that 'a' is already 10, so processor can't reorder the stores to 'a' and 'b'
问题: 一个简单的MOV
指令(没有显式内存栅栏)怎么可能足以应对这种行为? MOV
如何告诉处理器完成之前的所有存储?
【问题讨论】:
你忘了提到“on x86” @cubbi:对,很重要,完成 x86 没有单独的发布和获取障碍。 该 cppreference 页面的底部有一个指向 x86-TSO 论文的链接,其中包含您将需要的更多详细信息 "我可以确定 'a' 已经是 10,因此处理器无法将存储重新排序为 'a' 和 'b" 为清楚起见,在标准中没有全局概念“ 'a' 已经是 10”,所以更准确地说:“我可以确定另一个线程加载存储在 'b' 中的 50 且内存顺序至少为 memory_order_acquire 的另一个线程也会观察到 'a' 为 10。”人们普遍认为,该版本使以前的写入在其他线程中神奇地可见——该标准仅规定来自一个线程的写入应该在“合理的时间内”在其他线程中可见。 【参考方案1】:在运行时进行内存重新排序(由 CPU 完成),在编译时进行内存重新排序。请阅读Jeff Preshing's article on compile-time reordering(以及该博客上的许多其他好文章)以获取更多信息。
memory_order_release
防止编译器重新排序对数据的访问,以及发出任何必要的隔离或特殊指令。在 x86 asm 中,普通的加载和存储已经具有获取/释放语义,因此阻塞编译时重新排序对于 acq_rel 就足够了,但对于 seq_cst 则不行。
【讨论】:
“memory_order_release 防止编译器重新排序对数据的访问”定义包括 CPU 行为 @curiousguy:是的,这个答案之前解释得很糟糕。但我没有投票反对,而是决定修复它。【参考方案2】:这似乎是映射,至少在使用英特尔编译器编译的代码中,我看到:
0000000000401100 <_Z5storeRSt6atomicIiE>:
401100: 48 89 fa mov %rdi,%rdx
401103: b8 32 00 00 00 mov $0x32,%eax
401108: 89 02 mov %eax,(%rdx)
40110a: c3 retq
40110b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000401110 <_Z4loadRSt6atomicIiE>:
401110: 48 89 f8 mov %rdi,%rax
401113: 8b 00 mov (%rax),%eax
401115: c3 retq
401116: 0f 1f 00 nopl (%rax)
401119: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
代码:
#include <atomic>
#include <stdio.h>
void store( std::atomic<int> & b ) ;
int load( std::atomic<int> & b ) ;
int main()
std::atomic<int> b ;
store( b ) ;
printf("%d\n", load( b ) ) ;
return 0 ;
void store( std::atomic<int> & b )
b.store(50, std::memory_order_release ) ;
int load( std::atomic<int> & b )
int v = b.load( std::memory_order_acquire ) ;
return v ;
当前的Intel architecture documents,第 3 卷(系统编程指南)很好地解释了这一点。见:
8.2.2 P6 和更新的处理器系列中的内存排序
读取不会与其他读取重新排序。 写入不会与旧读取一起重新排序。 对内存的写入不会与其他写入一起重新排序,但以下情况除外:...这里解释了完整的内存模型。我假设英特尔和 C++ 标准人员已经详细合作,为每个可能的内存顺序操作确定了最佳映射,该映射符合第 3 卷中描述的内存模型,并且已经确定了普通存储和加载在这些情况下就足够了。
请注意,仅仅因为 x86-64 上的这个有序存储不需要特殊说明,并不意味着这将是普遍适用的。对于 powerpc,我希望在 store 中看到类似 lwsync 指令的东西,而在 hpux (ia64) 上,编译器应该使用 st4.rel 指令。
【讨论】:
就在您引用英特尔文档之前指出“在单处理器系统中”,其中“处理器”可以表示“核心”。我们肯定在讨论多核系统中的重新排序吗?该文档继续说“在多处理器系统中,适用以下排序原则:” 除非是这部分“单个处理器使用与单处理器系统相同的排序原则。” ??以上是关于MOV x86 指令是不是实现了 C++11 memory_order_release 原子存储?的主要内容,如果未能解决你的问题,请参考以下文章
X86中mov和movl指令的区别?我在阅读汇编时遇到了一些麻烦[重复]
什么时候应该在 x86 汇编中使用 MOVS 而不是 MOV?
GNU 汇编器 x86 指令后缀(如“mov.s”中的“.s”)如何工作?
Android 逆向x86 汇编 ( call 子函数调用指令 | jmp 跳转指令 | lea 加载指令 | mov 数据传送指令 )