我正在努力了解什么是内存障碍。
根据我目前所知,内存屏障(例如:mfence
)用于防止在内存屏障之前和之后以及之后重新排序指令。
这是使用内存屏障的一个示例:
instruction 1
instruction 2
instruction 3
mfence
instruction 4
instruction 5
instruction 6
现在我的问题是:mfence
指令只是一个标记告诉CPU执行指令的顺序是什么?或者它是CPU执行的指令,就像执行其他指令一样(例如:mov
)。
答案 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指令阻止加载和存储重新排序到缓存和主存储器。