22

我试图准确地理解什么是内存屏障。根据我目前所知道的,内存屏障(例如:)mfence用于防止指令从内存屏障之前到之后以及从之后到之前的重新排序。

这是使用中的内存屏障的示例:

instruction 1
instruction 2
instruction 3
mfence
instruction 4
instruction 5
instruction 6

现在我的问题是:mfence指令只是一个标记,告诉 CPU 以什么顺序执行指令?或者它是 CPU 实际执行的指令,就像它执行其他指令一样(例如:)mov

4

4 回答 4

24

CPU 在其代码中遇到的每个字节序列都是 CPU 执行的指令。没有其他类型的指令。

您可以在Intel 指令集参考和mfence的特定页面中清楚地看到这一点。

MFENCE
对在 MFENCE 指令之前发出的所有从内存加载和存储到内存指令执行序列化操作。这种序列化操作保证了在程序顺序中位于 MFENCE 指令之前的每个加载和存储指令在 MFENCE 指令之后的任何加载或存储指令之前变得全局可见。

MFENCE 指令相对于所有加载和存储指令、其他 MFENCE 指令、任何 LFENCE 和 SFENCE 指令以及任何序列化指令(例如 CPUID 指令)进行排序。MFENCE 不序列化指令流。弱排序内存类型可用于通过乱序发布、推测读取、写入组合和写入折叠等技术来实现更高的处理器性能。数据消费者识别或知道数据弱排序的程度因应用程序而异,并且可能不为该数据的生产者所知。MFENCE 指令提供了一种高效的方式来确保在产生弱排序结果的例程和使用该数据的例程之间进行加载和存储排序。

处理器可以自由地从使用 WB、WC 和 WT 内存类型的系统内存区域推测性地获取和缓存数据。这种推测性的取指可以随时发生,并且与指令执行无关。因此,对于 MFENCE 指令的执行,它没有顺序;数据可以推测性地在 MFENCE 指令执行之前、期间或之后被带入缓存。

正如您从摘录中看到的那样,该MFence指令做了很多工作,而不仅仅是某种标记。

于 2017-03-10T19:43:29.460 回答
19

我将解释对mfence管道流动的影响。以Skylake管道为例。考虑以下指令序列:

inst1
store1
inst2
load1
inst3
mfence
inst4
store2
load2
inst5

指令以相同的程序顺序被解码为一系列微指令。然后所有的微指令都按顺序传递给调度程序。通常,如果没有围栏,所有 uops 都会乱序执行。但是,当调度器接收到mfence微指令时,它需要确保没有内存微指令下游mfence执行直到所有上游内存 uop 全局可见(这意味着存储已退休并且加载至少已完成)。这适用于所有内存访问,无论被访问区域的内存类型如何。这可以通过让调度程序分别不发出任何下游存储或加载微指令到存储或加载缓冲区,直到缓冲区耗尽或通过发出下游存储或加载微指令并标记它们以便它们可以与缓冲区中所有现有的内存微指令。围栏上方或下方的所有非内存微指令仍然可以乱序执行。在示例中,一旦store1退出并load1完成(通过接收数据并将其保存在某个内部寄存器中),mfence指令被认为已完成执行。我认为这mfence可能会或可能不会占用后端(ROB 或 RS)中的任何资源,并且可能会被转换为多个微指令。

英特尔在 1999 年提交了一项描述其工作原理的专利mfence。由于这是一项非常古老的专利,因此实施可能已经改变,或者在不同的处理器中可能会有所不同。我将在这里总结专利。mfence被解码为三个微指令。不幸的是,尚不清楚这些微指令的确切用途。然后从保留站分配条目以保存微指令,并且还从加载和存储缓冲区分配条目。这意味着加载缓冲区可以保存真实加载请求或栅栏(基本上是虚假加载请求)的条目。类似地,存储缓冲区可以保存真正的存储请求和栅栏的条目。这mfence直到所有较早的加载或存储 uop(在各自的缓冲区中)都已退役后,才会调度 uop。发生这种情况时,mfenceuop 本身会作为内存请求发送到 L1 缓存控制器。控制器检查是否所有先前的请求都已完成。在这种情况下,它将被简单地视为 NOP,并且 uop 将从缓冲区中删除。否则,缓存控制器拒绝mfenceuop。

于 2018-05-11T21:08:16.517 回答
4

mfence是一个指令。

在 Linux 上获取它:

1/ 写一个文件 mfence.c

#include <stdio.h>

int main(){
    printf("Disass me\n");
    asm volatile ("mfence" ::: "memory");
    return 0;
}

2/ 编译

gcc mfence.c mfence

3/ 拆卸

objdump -d mfence | grep -A 10 "<main>:"

000000000000063a <main>:
 63a:   55                      push   %rbp
 63b:   48 89 e5                mov    %rsp,%rbp
 63e:   48 8d 3d 9f 00 00 00    lea    0x9f(%rip),%rdi        # 6e4 <_IO_stdin_used+0x4>
 645:   e8 c6 fe ff ff          callq  510 <puts@plt>
 64a:   0f ae f0                mfence 
 64d:   b8 00 00 00 00          mov    $0x0,%eax
 652:   5d                      pop    %rbp
 653:   c3                      retq   
 654:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 65b:   00 00 00 

4/ 观察到第 64a 行mfence是(3 位)指令(0f ae f0)

所以这是一条 cpu 指令(如mov):处理器需要在获取之前解码先前的指令,否则它无法猜测它是对齐的。

例如0f ae f0,可能出现在地址中,因此 cpu 无法将其用作制造商。

最后,它只是一条老派指令,在其流水线中的执行点,它将在执行下一条指令之前进一步同步流水线中的内存访问。


注意:在 Windows 上使用宏_ReadWriteBarrierin 来生成 mfence

于 2018-05-12T18:05:29.117 回答
3

你的问题有错误的假设。MFENCE 不会阻止指令的重新排序(参见突出显示的引用)。例如,如果有 1000 条指令流只对寄存器进行操作,而 MFENCE 指令放在中间,那么它不会影响 CPU 如何重新排序这些指令。

MFENCE 指令相对于所有加载和存储指令、其他 MFENCE 指令、任何 LFENCE 和 SFENCE 指令以及任何序列化指令(例如 CPUID 指令)进行排序。MFENCE 不序列化指令流。

相反,MFENCE 指令防止对高速缓存和主存储器的加载和存储重新排序。

于 2018-05-18T09:56:09.993 回答