【发布时间】:2018-02-08 18:21:25
【问题描述】:
也许只有我一个人,但man 2 页面中membarrier 的示例似乎毫无意义。
基本上,membarrier() 是一个异步内存屏障,给定两个协调的代码段(让我们调用 fast path 和 slow 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 上实现。
【问题讨论】:
标签: linux x86 memory-barriers memory-model