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 MOV 指令迷惑

什么时候应该在 x86 汇编中使用 MOVS 而不是 MOV?

x86 简单 mov 指令

GNU 汇编器 x86 指令后缀(如“mov.s”中的“.s”)如何工作?

Android 逆向x86 汇编 ( call 子函数调用指令 | jmp 跳转指令 | lea 加载指令 | mov 数据传送指令 )