membarrier 手册页中的示例在 x86 中毫无意义吗?

Is the example in the membarrier man page pointless in x86?

也许只有我一个人是这样,但是 membarrierman 2 页面中的示例似乎毫无意义。

基本上,membarrier() 是一个异步内存屏障,给定两个协调的代码片段(让我们调用 fast pathslow path) 允许您将屏障的所有硬件成本移动到 慢速路径,而只留下 快速路径编译器障碍1。有几种不同的方法可以实现 membarrier 行为,例如向每个涉及的处理器发送 IPI 或等待每个处理器上的代码 运行 取消调度 - 但具体的实现细节是这里很重要。

现在,这是 man page 中给出的示例转换:

原代码

static volatile int a, b;

static void
fast_path(void)
{
   int read_a, read_b;

   read_b = b;
   asm volatile ("mfence" : : : "memory");
   read_a = a;

   /* read_b == 1 implies read_a == 1. */

   if (read_b == 1 && read_a == 0)
       abort();
}

static void
slow_path(void)
{
   a = 1;
   asm volatile ("mfence" : : : "memory");
   b = 1;
}

转换后的代码

(省略了一些系统调用和初始化样板文件)

static volatile int a, b;

static void
fast_path(void)
{
   int read_a, read_b;

   read_b = b;
   asm volatile ("" : : : "memory");
   read_a = a;

   /* read_b == 1 implies read_a == 1. */

   if (read_b == 1 && read_a == 0)
       abort();
}

static void
slow_path(void)
{
   a = 1;
   membarrier(MEMBARRIER_CMD_SHARED, 0);
   b = 1;
}

这里 slow_path 正在执行两次写入(a,然后是 b),由屏障分隔,fast_path 正在执行两次读取(b, 然后 a) 也被屏障隔开。

但是,x86 内存模型不允许加载-加载或存储-存储重新排序!据我所知,在这种情况下根本不需要 membarrier() 并且原始代码中也不需要 mfence 。似乎简单的编译器障碍在两个地方就足够了2.

一个实际有意义的例子,IMO,应该有一个存储后跟一个加载,在 快速路径.

中由障碍分隔

我是不是漏掉了什么?


1 编译器屏障阻止编译器在其上移动加载或存储(并且根据实现可能会强制将某些寄存器值存入内存),但不会发出任何类型的原子操作或内存栅栏,因此请避免这些指令中固有的通常数量级的减速。

2 当然,在较弱的平台上,可能会发生加载-加载重新排序,该示例可能有意义,但该示例明确为 x86,而 membarrier() 仅在 x86 上实现。

你是对的。 在 x86 上,membarrier() 的这种特殊用法完全没有用。事实上,这个确切的例子第一个在Intel SDM中给出的用来说明x86内存排序规则的例子:

Intel SDM Vol. 3 §8.2.3.2 Neither Loads Nor Stores Are Reordered with Like Operations

我正在提交对此联机帮助页的修复以改为使用 Dekker 示例。参见 https://lkml.org/lkml/2017/9/18/779

顺便说一下,membarrier 系统调用不是特定于 x86 的,而是现在在大多数 Linux 架构上实现。

感谢反馈!

马修