内存栅栏和内存屏障是一样的吗?

Kar*_*yan 5 c++ processor atomic rust

在这里,我对术语“记忆栅栏”(Rust 中的栅栏功能)感到困惑。我可以清楚地理解原子方面的内存屏障是什么,但我无法弄清楚什么是内存栅栏。

内存栅栏和内存屏障是一样的吗?如果不是,有什么区别以及何时使用内存栅栏而不是内存屏障?

小智 11

在这种情况下,“栅栏”是一种记忆屏障。这个区别很重要。为了讨论的目的,我将非正式地区分三种野兽:

  • 原子栅栏:控制观察者可以看到原子内存操作效果的顺序。(这就是你问的问题。)
  • 更一般的内存屏障:控制针对内存或内存映射 I/O 的实际操作的顺序。这通常是一个更大的锤子,可以达到与原子栅栏类似的效果,但成本更高。(取决于架构。)
  • 编译器栅栏:控制处理器接收指令的顺序这不是你问的问题,但人们经常不小心用它来代替真正的障碍,这让他们事后感到难过。

什么是栅栏

Ruststd::sync::atomic::fence提供了原子栅栏操作,它提供了其他原子栅栏和原子内存操作之间的同步。人们用来描述各种原子条件的术语一开始可能有点令人畏惧,但它们在文档中定义得很好,尽管在撰写本文时存在一些遗漏。如果您想了解更多信息,我建议您阅读以下文档。

首先, Rust 的类型文档Ordering。这是对不同操作如何交互的很好的描述Ordering,与该领域的许多参考文献(原子内存排序)相比,使用了更少的术语。然而,在撰写本文时,它对您的具体问题具有误导性,因为它说的是

此排序仅适用于可以执行存储的操作。

它忽略了 的存在fence

文档提供fence了一些修复该问题的方法。IMO 这个领域的文档需要一些爱。

但是,如果您希望精确地布置所有交互,恐怕您必须寻找不同的来源:等效的 C++ 文档。我知道,我们不是在编写 C++,但是 Rust 从 LLVM 继承了很多这种行为,并且 LLVM 在这里尝试遵循 C++ 标准。C++ 文档的行话要多得多,但如果你慢慢阅读,它实际上并不比 Rust 文档更复杂——只是行话。C++ 文档的好处是它们讨论了加载/存储/fence 和加载/存储/fence 之间的每个交互案例。

栅栏不是什么

我使用内存屏障最常见的地方是推断低级代码(例如驱动程序)中内存映射 I/O 的写入完成情况。(这是因为我倾向于在堆栈中较低的位置工作,因此这可能不适用于您的情况。)在这种情况下,您可能正在执行volatile内存访问,并且您需要比提供的屏障更强的fence屏障。

特别是,fence它可以帮助您推断哪些原子内存操作对于哪些其他原子内存操作是可见的——它不能帮助您推断特定的存储值是否已通过内存层次结构并到达特定的级别。公共汽车。例如。对于这样的情况,您需要一种不同类型的内存屏障。

Linux 内核的内存屏障文档对这些屏障进行了相当详细的描述。

为了回应这个问题的另一个答案,即平淡地指出栅栏和屏障是等效的,我在 Rust 不安全代码指南问题跟踪器上提出了这个案例,并得到了一些澄清。

Ordering特别是,您可能会注意到和的文档fence没有提及它们如何与volatile内存访问交互,那是因为它们没有提及。或者至少,它们不能保证——在某些架构上,需要生成的指令是相同的(ARM),而在其他情况下,它们不是(PowerPC)。

Rust 目前提供了一个可移植的原子栅栏(您已找到),但不提供任何其他类型的内存屏障的可移植版本,例如 Linux 内核中提供的内存屏障。如果您需要推理(例如)volatile内存访问的完成,您将需要不可移植的asm!或最终生成它的函数/宏。

旁白:编译器围栏

当我做出像上面所说的那样的陈述时,有人不可避免地会跳入(GCC语法)

asm("" :::: memory);
Run Code Online (Sandbox Code Playgroud)

这既不是原子栅栏,也不是内存屏障:它大致相当于 Rust 的compiler_fence,因为它阻止编译器在生成的代码中跨该点重新排序内存访问。它对机器启动或完成指令的顺序没有影响。


Lig*_*ica 2

没有区别。

在这种情况下,“栅栏”和“屏障”的含义相同。