记忆障碍如何运作?

时间:2013-01-28 22:03:55

标签: c++ c assembly execution instructions

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

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 

3 个答案:

答案 0 :(得分:8)

_ReadBarrier_WriteBarrier_ReadWriteBarrierintrinsics that affect how the compiler can reorder code;它们与CPU内存屏障完全无关,仅对特定类型的内存有效(请参阅“受影响的内存”here)。

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

答案 1 :(得分:4)

现代处理器能够在实际“完成”指令之前执行相当长的指令,因此当涉及某些类型的存储器操作时,使用内存屏障来阻止它运行到远处。需要严格的排序 - 对于大多数事情,如果你在变量b之前写入变量a,或者在变量之前写入b,则实际上并不重要。但有时它确实如此。

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

如果您正在实现信号量,互斥量或类似指令,这很重要,因为在继续读取其他数据之前,存储值“我已锁定信号量”非常重要。换句话说,事情可能会出错。

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

有几次,我需要让编译器不要传播代码,你可以用“无操作函数”来做,显然现在甚至还有特殊的内部函数来做。

答案 2 :(得分:0)

有几点需要考虑。也许是 首先,障碍只对多线程产生影响 代码,大多数编译器需要一个特殊的选项来生成 多线程代码。像_ReadBarrier这样的东西差不多 当然是编译器内置函数,应该什么都不做,除非 你已经给出了多线程代码的选项。

第二个是硬件需要的东西,即使在一个 多线程的上下文,各不相同。在大多数机器上我都是 工作(大约四十年),机器永远不需要 任何事情;只有机器才有障碍 复杂的读写流水线。 (大多数早期的机器 甚至没有围栏或障碍指令,所以产生了 代码必须为空。)