内存屏障是CPU执行的指令,还是仅仅是一个标记?

时间:2017-03-10 09:20:21

标签: assembly x86 cpu-architecture memory-barriers memory-fences

我正在努力了解什么是内存障碍。 根据我目前所知,内存屏障(例如:mfence)用于防止在内存屏障之前和之后以及之后重新排序指令。

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

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

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

4 个答案:

答案 0 :(得分:23)

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

您可以在Intel instruction set reference和特定页面for mfence中清楚地看到这一点。

  

MFENCE
  对所有内存加载执行序列化操作   以前发布的存储到存储器指令   MFENCE指令。此序列化操作保证了之前的每个加载和存储指令   程序顺序中的MFENCE指令在随后的任何加载或存储指令之前变为全局可见   MFENCE指令。

     

MFENCE指令是针对所有加载和存储指令而订购的   MFENCE指令,任何LFENCE和SFENCE指令,以及任何序列化指令(例如CPUID   指令)。 MFENCE不会序列化指令流。   弱序存储器类型可用于通过诸如此类技术实现更高的处理器性能   无序问题,推测性读取,写入组合,   并写入崩溃。消费者的程度   数据识别或知道数据的微弱排序因应用程序而异,并且可能是未知的   这个数据的制作者。 MFENCE指令提供   确保加载和存储的高性能方法   在产生弱顺序的例程之间排序   ed结果和使用该数据的例程。

     

处理器可以自由地以推测方式获取和缓存数据   来自使用WB,WC和的系统内存区域   WT内存类型。这种推测性提取可以在任何时间发生,并且与指令执行无关。因此,它   没有关于执行MFENCE的命令   指令;在执行MFENCE指令之前,期间或之后,可以推测性地将数据引入高速缓存。

从摘录中可以看出MFence指令做了很多工作,而不仅仅是某种标记。

答案 1 :(得分:13)

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

inst1
store1
inst2
load1
inst3
mfence
inst4
store2
load2
inst5

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

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

答案 2 :(得分: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上使用宏_ReadWriteBarrier来生成mfence

答案 3 :(得分:2)

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

  

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

相反,MFENCE指令阻止加载和存储重新排序到缓存和主存储器。