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

Bee*_*ope 6 linux x86 memory-model memory-barriers

也许这只是我,但man 2页面中的例子membarrier似乎毫无意义.

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

现在,这是手册页中给出的示例转换:

原始代码

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;
}
Run Code Online (Sandbox Code Playgroud)

转换代码

(省略了一些syscall和init样板)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

实际上有意义的一个例子,IMO应该有一个存储,后跟一个加载,由快速路径中的一个屏障隔开.

我错过了什么吗?


1编译器屏障防止编译器在其上移动加载或存储(并且取决于实现可能会强制某些寄存器值到内存),但不会发出任何类型的原子操作或内存栅栏,因此请避免经常命令 -这些指令中固有的幅度减慢.

2当然,在可能发生负载重新排序的较弱平台上,示例可能有意义,但示例显式为x86,membarrier()并且仅在x86上实现.

Iwi*_*ist 6

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

英特尔SDM卷 3§8.2.3.2载荷也不存储与类似操作的重新排序 在此输入图像描述


小智 5

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

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

感谢您的反馈!

马蒂厄

  • 感谢您花时间解决此问题! (2认同)