为什么我们在两个内核之间的邮箱数据通信中需要两个内存屏障?

时间:2016-02-07 00:13:23

标签: c gcc memory arm

这里我们有一个邮箱代码代码,用于两个ARM内核之间的数据通信(直接来自ARM Cortex A Series Programming Guide)。

核心A:

STR R0, [Msg] @ write some new data into postbox
STR R1, [Flag] @ new data is ready to read

核心B:

Poll_loop:
LDR R1, [Flag]
CMP R1,#0 @ is the flag set yet?
BEQ Poll_loop
LDR R0, [Msg] @ read new data.

为了强制执行依赖,文档说我们需要在代码中插入两个但不是两个内存障碍DMB。

核心A:

STR R0, [Msg] @ write some new data into postbox
DMB
STR R1, [Flag] @ new data is ready to read

核心B:

Poll_loop:
LDR R1, [Flag]
CMP R1,#0 @ is the flag set yet?
BEQ Poll_loop
DMB
LDR R0, [Msg] @ read new data.

我理解核心A中的第一个DMB:它阻止了编译重新排序,并且系统也会观察到对[Msg]变量的内存访问。以下是同一文档中DMB的定义。

  
    

数据记忆障碍(DMB)
    该指令确保所有内存     在观察屏障之前按程序顺序访问     在程序中出现的任何显式内存访问之前的系统     在障碍之后命令。它不会影响任何其他的排序     在核心或指令提取上执行的指令。

  

但是,我不确定为什么使用Core B中的DMB。在文件中它说:

  

核心B需要在LDR R0之前使用DMB,[Msg]才能确保   在设置标志之前不会读取消息。

如果核心A中的DMB使得[Msg]的存储被观察到系统,那么我们不应该在第二核心中需要DMB。我的猜测是,编译器可能会重新排序读取Core B中的[Flag]和[Msg](虽然我不明白为什么它应该这样做,因为[Msg]上的读取依赖于[Flag])。 / p>

如果是这种情况,编译屏障(asm volatile(“”:::“内存)而不是DMB就足够了。我在这里想念一下吗?

2 个答案:

答案 0 :(得分:4)

首先,您可能会混淆编译器障碍和内存障碍。编译器障碍阻止编译器在最终程序集中跨越该障碍移动指令。 OTOH,内存屏障指示硬件遵守某种顺序。由于您已经提交了汇编代码,因此您的问题实际上是硬件内存障碍,此处不涉及编译器。

你需要在核心B中使用(读取)内存屏障的原因是核心可能会在任何需要的地方重新排序消息读取指令,因为 不,在读取标志和阅读消息之间没有数据依赖性,至少在上面的代码中没有:阅读Msg所需的唯一信息是它的地址,这在每个点都是已知的。时间。您可能想要争辩说存在控件依赖性。但是,控件依赖性不会对内存读取施加任何排序约束。

答案 1 :(得分:4)

这两个障碍都是必要的,并且确实需要dmb - 这仍然是硬件内存模型,与编译器重新排序无关。

让我们首先看一下核心A的作者:

STR R0, [Msg] @ write some new data into postbox
STR R1, [Flag] @ new data is ready to read

由于这些是两个独立存储到不同地址而它们之间没有依赖关系,因此没有什么可以强制核心A以程序顺序实际发布存储。例如,Msg的商店可以留在一个部分填充的写缓冲区中,而Flag的商店超过它并直接进入内存系统。因此,除核心A之外的任何观察者都可以看到Flag的新值,但尚未看到Msg的新值。

STR R0, [Msg] @ write some new data into postbox
DMB
STR R1, [Flag] @ new data is ready to read

现在,通过屏障,商店前Flag不允许显示商店Msg,因为这样就需要一个或其他商店出现穿越障碍物。因此,任何外部观察者都可以看到旧值,新Msg但旧Flag,或两个新值。 Flag但旧Msg的情况不再发生。

好的,所以第一道屏障按照正确的顺序处理的内容,但还有 read 的问题。核心B ......

Poll_loop:
LDR R1, [Flag]
CMP R1,#0 @ is the flag set yet?
BEQ Poll_loop
LDR R0, [Msg] @ read new data.

请注意,Poll_loop的分支不会在两个负载之间形成控制依赖关系;如果你考虑程序顺序,Msg的加载是无条件的,Flag的值不影响它是否被执行,只是执行是否进展到程序的那一部分。因此,代码可以等效地编写:

Poll_loop:
LDR R1, [Flag]
LDR R0, [Msg] @ read data, just in case.
CMP R1,#0 @ is the flag set yet?
BEQ Poll_loop @ no? OK, throw away that data and read everything again.
... @ do stuff with R0, because Flag was set so it must be good data, right?

开始看问题?即使使用原始代码,核心B一旦到达Msg就可以自由地推测性地加载Poll_loop,所以即使来自核心A的商店在程序顺序中变得可见,事情仍然会像这样发挥出来:

  core A   |  core B
-----------+-----------
           | load Msg
store Msg  |
store Flag |
           | load Flag
           | conclude that old Msg is valid

因此你需要一个障碍:

...
BEQ Poll_loop
DMB
LDR R0, [Msg] @ read new data.

或者可能是假地址依赖:

...
BEQ Poll_loop
EOR R1, R1, R1
LDR R0, [Msg, R1] @ read new data.

将两个负载相互对齐。