首页 > 解决方案 > 为什么我们需要读写屏障?

问题描述

为什么我们需要定义两种具有相同实现的屏障?

例如,来自io_uringLinux 的这段代码:

#if defined(__x86_64) || defined(__i386__)
#define read_barrier()  __asm__ __volatile__("":::"memory")
#define write_barrier() __asm__ __volatile__("":::"memory")
#else

标签: cgcclinux-kernelx86memory-barriers

解决方案


真正的答案是:因为 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() 精确地扩展为mfencelfencesfence和其他(例如)。

sfence并且lfence在大多数情况下可能是矫枉过正,例如围绕 MMIO 到强排序的 UC 内存。写入 WC 内存实际上可能需要一个 sfence。但与 I/O 相比,它们并不算太慢,而且在某些情况下可能会出现问题,因此 Linux 采用了安全的方法。

除此之外,x86 具有不同类型的读/写屏障,它们也可能更快(例如我上面链接的那个)。mfence有关使用 ed 指令或伪ed 指令的完整障碍(C11 称为顺序一致性)的更多信息,请参阅以下答案lock


推荐阅读