确定障碍物(围栏)的使用位置

use*_*986 4 concurrency kernel memory-management linux-kernel memory-barriers

x86指令lfence/sfence/mfence用于实现Linux内核中的rmb()/wmb()/mb()机制。很容易理解,它们用于串行化内存访问。然而,在遇到运行时行为中的错误之前,在编写代码时确定何时何地使用它们要困难得多。

我很想知道在编写/审查代码时是否可以检查已知的警告,这可以帮助我们确定必须在哪里插入障碍。我知道这太复杂了,但是是否有经验法则或清单可以帮助我们识别需要这些的代码位置?

Arc*_*son 6

我的经验(不是在 Linux 内核中)是两种模式满足了防护的绝大多数需求。

模式“发送/接收”:线程 1 向线程 2 发送数据,并且有一个内存位置以某种方式指示“数据已准备好”。线程 1 在数据存储和“数据已准备好”存储之间至少需要一个栅栏。线程 2 需要在表示“数据已就绪”的数据加载和数据加载之间建立一个 lfence。

如果传输中仅涉及常规(不是非临时、DMA 设备等)加载/存储,则仅需要编译器栅栏。此外,带有 LOCK 前缀的指令意味着栅栏。例如,有时“数据已准备好”位置不仅仅是一个标志,而是一个原子计数器,并且用于操纵它的以 LOCK 为前缀的增量/减量可以充当栅栏。

此模式还涵盖自旋锁。释放锁就是“发送”。获取锁就是“接收”。

模式“共识”:两个线程必须就某件事达成共识。必须有一个 mfence(或由 LOCK 前缀指令隐含的一个)。栅栏必须位于“我发布了我的投票”和“我阅读了其他线程的投票”之间。 Dekker 的协议就是一个例子。困难的部分是发现这种模式。我们曾经错过了 TBB 内部深处的一个共识问题:“是否抛出了异常?” 最终我们意识到这是一个共识问题,因此需要一个 mfence。

上面的两种模式是经验法则,并不能涵盖所有情况,但我发现它们涵盖了 99% 的情况。