4

在 Windows 下,有 3 个编译器内在函数来实现内存屏障:

1. _ReadBarrier;

2. _WriteBarrier;

3. _ReadWriteBarrier;

但是,我发现了一个奇怪的问题:_ReadBarrier 似乎是一个什么都不做的虚拟函数!以下是我用 VC++ 2012 生成的汇编代码。

我的问题是:如何在汇编指令中实现内存屏障功能?

int main()
{   
013EEE10  push        ebp  
013EEE11  mov         ebp,esp  
013EEE13  sub         esp,0CCh  
013EEE19  push        ebx  
013EEE1A  push        esi  
013EEE1B  push        edi  
013EEE1C  lea         edi,[ebp-0CCh]  
013EEE22  mov         ecx,33h  
013EEE27  mov         eax,0CCCCCCCCh  
013EEE2C  rep stos    dword ptr es:[edi]  
    int n = 0;
013EEE2E  mov         dword ptr [n],0  
    n = n + 1;
013EEE35  mov         eax,dword ptr [n]  
013EEE38  add         eax,1  
013EEE3B  mov         dword ptr [n],eax  
    _ReadBarrier();
    n = n + 1;
013EEE3E  mov         eax,dword ptr [n]  
013EEE41  add         eax,1  
013EEE44  mov         dword ptr [n],eax 
}
013EEE56  xor         eax,eax  
013EEE58  pop         edi  
013EEE59  pop         esi  
013EEE5A  pop         ebx  
013EEE5B  add         esp,0CCh  
013EEE61  cmp         ebp,esp  
013EEE63  call        __RTC_CheckEsp (013EC3B0h)  
013EEE68  mov         esp,ebp  
013EEE6A  pop         ebp  
013EEE6B  ret 
4

3 回答 3

9

_ReadBarrier, _WriteBarrier, 和_ReadWriteBarrier影响编译器如何重新排序代码的内在函数;它们与 CPU 内存屏障完全无关,仅对特定类型的内存有效(请参阅此处的“受影响的内存” )。

MemoryBarrier()是您用来强制 CPU 内存屏障的内在函数。但是,Microsoft 的建议是使用std::atomic<T>VC++。

于 2013-01-28T22:26:21.563 回答
4

现代处理器能够比实际“完成”指令的位置提前很长的距离执行指令,因此当涉及某些类型的内存操作时,使用内存屏障来防止它运行到很远的位置,其中严格的顺序是必需 - 对于大多数事情,实际上是在变量 b 之前写入变量 a 还是在 a 之前写入 b 并不重要。但有时确实如此。

x86 指令集有lfencesfencefence,它们分别是“隔离”加载、存储和所有内存操作的指令。关于“栅栏”或“屏障”指令的要点是确保屏障指令之前的所有指令在屏障之后的下一条指令可以继续之前完成它们的加载、存储或两者。

如果您正在实现例如信号量、互斥锁或类似指令,这一点很重要,因为在继续读取其他数据之前存储“我已锁定信号量”的值很重要。否则事情可能会出错,让我们说。

请注意,除非您真的知道自己在使用内存屏障做什么,否则最好不要使用它们——并依靠解决相同问题的现有代码——std::atomic是为此类代码提供资金的一个地方。我写了很多“棘手”的代码,但只有一两次我需要在我的代码中设置内存屏障。

有几次,我需要让编译器不散布代码,你可以用“无操作函数”来做到这一点,显然现在甚至有特殊的内在函数可以做到这一点。

于 2013-01-28T23:25:58.073 回答
0

有几个要点需要考虑。第一个可能是屏障只在多线程代码中起作用,并且大多数编译器需要一个特殊的选项来生成多线程代码。类似的东西_ReadBarrier几乎可以肯定是编译器内置的,除非您提供了多线程代码的选项,否则应该什么都不做。

第二个是硬件要求,即使在多线程环境中,也是不同的。在我工作过的大多数机器上(大约四十年),机器从来不需要任何东西。只有当机器具有复杂的读写管道时,障碍才会变得相关。(大多数早期的机器甚至没有栅栏或屏障指令,所以生成的代码必须是空的。)

于 2013-01-28T22:21:33.020 回答