11

我正在阅读 Paul E. McKenney 的 Memory Barriers http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.07.23a.pdf 一切都得到了详细的解释,当我看到一切都很清楚时我遇到一句话,让一切变得晦涩难懂,让我觉得我什么都不懂。让我举个例子

void foo(void)
{
   a = 1; #1
   b = 1; #2
}

void bar(void)
{
   while (b == 0) continue; #3
   assert(a == 1); #4
}

假设这两个函数在不同的处理器上运行。现在可能发生的情况是,在存储到 b #2 之后,第二个处理器可以看到存储到 a #1,因为第一个处理器将存储到“a”并继续存储 b 指令。好的,没关系,我们在 #1 和 #2 之间的行中添加了一个写栅栏,但是这段代码仍然可能失败,因为第二个处理器可能会将无效消息排队,所以我们在其中添加了一个内存栅栏(这次是读栅栏) #4 和 #4 之间的线。

void foo(void)
{
   a = 1; #1
   write_memory_barrier();
   b = 1; #2
}

void bar(void)
{
   while (b == 0) continue; #3
   read_memory_barrier();
   assert(a == 1); #4
}

这会强制第二个处理器处理所有排队的消息(使 a 无效)并通过在 #4 上向第一个处理器发送读取的 MESI 消息来再次读取它。好的。接下来文章说

因此,许多 CPU 架构提供了较弱的内存屏障指令,它们只执行这两者中的一个或另一个。粗略地说,“读内存屏障”仅标记无效队列,“写内存屏障”仅标记存储缓冲区。而成熟的内存屏障两者兼而有之。

很好,很清楚,但在那之后我看到了

这样做的效果是,读内存屏障命令仅在执行它的 CPU 上加载,因此读内存屏障之前的所有加载似乎都在读内存屏障之后的任何加载之前完成。类似地,写内存屏障只命令存储,再次在执行它的 CPU 上,并且再次在写内存屏障之前的所有存储将看起来在写内存屏障之后的任何存储之前完成。

所以

读内存屏障之前的所有加载似乎都在读内存屏障之后的任何加载之前完成

这混淆了之前解释的所有内容。这是什么意思?函数“bar”中的哪个加载必须在加载“a”#4 之前完成?我知道断言可能会在没有内存屏障的情况下失败,因为处理器可能会读取旧值,因为它仍然没有设法使其缓存线无效,对象“a”所在的位置。

详细解释真的很有帮助,我整天都在努力理解它。

首先十分感谢。

4

1 回答 1

10

这是什么意思?

这意味着如果您有:

read
read
read
READ BARRIER
read
read
read

然后读取屏障充当“连接点”,将这些读取分成两批。读屏障之前的所有读取都将在读屏障之后的任何读取开始之前完成。

在(#4)bar()的加载开始之前必须完成哪些加载?a

(#3) 的所有读取b都强制在a(#4) 的任何读取之前。这意味着a直到之后才读取b不再为 0。因为foo()使用写屏障来确保在更改的时间(#2)a已经更改为(#1 )。因此,这两个障碍一起工作以确保断言语句将始终成功。1b

于 2010-10-22T18:20:51.540 回答