c - 为什么我们需要读写屏障?
问题描述
为什么我们需要定义两种具有相同实现的屏障?
#if defined(__x86_64) || defined(__i386__)
#define read_barrier() __asm__ __volatile__("":::"memory")
#define write_barrier() __asm__ __volatile__("":::"memory")
#else
解决方案
真正的答案是:因为 x86 的内存模型已经足够强大,阻塞编译时重新排序对于加载或存储排序就足够了;运行时重新排序已被硬件阻止。
这些只是通过一段内联程序集制作的通用编译时障碍,如果使用,它会阻止 GCC 重新排序指令。这在另一篇文章中解释得很好。使用这个“技巧”可以实现的目标通常也可以使用 Cvolatile
限定符来实现。
请注意,Linux 内核不会在代码中的任何地方使用这些特定的宏,它们只是为io_uring
用户空间测试工具定义的两个宏。它肯定会asm volatile ("" ::: "memory")
在需要的地方使用,但使用不同的名称(例如smp_rmb()
,smp_wmb()
)。
x86 的内存模型使得CPUsfence
之间lfence
的通信完全无用;阻塞编译时重新排序就足够了:请参阅英特尔内存模型是否使 SFENCE 和 LFENCE 变得多余?
smp_mb()
是一个完整的障碍,确实需要一个实际的 asm 指令,以及阻止编译时重新排序。
x86 确实有一些用于只读和只写“真实”(运行时)内存屏障的内存屏障 asm 指令。它们是sfence
(存储栅栏)、lfence
(加载栅栏)和mfence
(内存栅栏=完整栅栏)。
mfence
序列化读取和写入(完全屏障),而其他仅序列化两者之一(读取或写入,即加载或存储)。关于内存排序的维基百科页面在解释这些含义方面做得不错。lfence
实际上阻止 LoadStore 重新排序,而不仅仅是 LoadLoad,用于movntdqa
来自 WC 内存的弱排序加载。已经不允许对来自其他内存类型的其他类型的负载进行重新排序,因此几乎没有任何理由实际lfence
用于内存排序,而不是阻止乱序执行的其他效果。
例如,内核将这些实际的 asm 指令用于 I/O 代码中的内存屏障mb()
,rmb()
并wmb()
精确地扩展为mfence
、lfence
、sfence
和其他(例如)。
sfence
并且lfence
在大多数情况下可能是矫枉过正,例如围绕 MMIO 到强排序的 UC 内存。写入 WC 内存实际上可能需要一个 sfence。但与 I/O 相比,它们并不算太慢,而且在某些情况下可能会出现问题,因此 Linux 采用了安全的方法。
除此之外,x86 具有不同类型的读/写屏障,它们也可能更快(例如我上面链接的那个)。mfence
有关使用 ed 指令或伪ed 指令的完整障碍(C11 称为顺序一致性)的更多信息,请参阅以下答案lock
:
推荐阅读
- python - 比较不同 pandas 数据集中的 2 列,如果值存在于第二个数据集中,则替换值
- javascript - 如何与 momentjs 约会?
- c++ - 插入 unordered_map 时的 bad_alloc
- automated-tests - 如何使用绝对路径而不是类路径来读取文件
- c# - 在 Jpath 中转义单引号
- python-3.x - 无法从命令行运行 tensorboard 来评估我的模型的效率
- python - Python - 高效的数据分组
- javascript - 如何从javascript中的嵌套对象数组中获取对象
- wordpress - WordPress 如何从 thumbnail_id 获取 post_id?
- r - 在 R flexdashboard:runtime:shiny 中嵌入本地 PDF 文件