内存屏障,可以保证在此之前的代码全部执行完才开始执行在此之后的代码
参考wikipedia的定义:
Memory barrier, also known as membar or memory fence or fence instruction, is a type of barrier and a class of instruction which causes a central processing unit (CPU) or compiler to enforce an ordering constraint on memory operations issued before and after the barrier instruction.
http://en.wikipedia.org/wiki/Memory_barrier
一个例子:
Processor #1:
x = f = 0
loop:
load the value in location f, if it is 0 goto loop
print the value in location x
Processor #2:
store the value 42 into location x
store the value 1 into location f
上面的例子中,变量 x f 初始化值都是0。我们期望输出“42”。但是结果并不总是这样。
如果 Processor #2 的执行顺序是乱序的(out-of-order execution),也就是说,对f赋值的语句先于对x赋值的语句,那么就有可能输出“0”
对于大多数的程序来说,这种特例情况是不能容忍的。
如果将内存屏障置于对f赋值的语句之前,那么就能保证 Processor #2 先对x赋值,然后才对f赋值。这样就能得到我们期望的结果,输出“42”
--------------------------------------------------------------------------------------------------------------------------------------------------------------
本文摘录自:
http://lxr.linux.no/linux+v2.6.27.8/Documentation/memory-barriers.txt
抽象内存访问模型
CPU在执行程序的时候会产生很多内存访问操作。然而,CPU在访问内存操作的顺序是很随意的,它以任意自己喜欢的方式来执行程序提供的访存指令。除此之外,编译器同样会对访存指令按照它认定的方式进行修改。
例(1)
假定CPU 1和CPU 2 执行一下操作:
CPU 1 CPU 2
=============== ===============
{ A == 1; B == 2 }
A = 3; x = A;
B = 4; y = B;
对于这样的一些内存操作,可能出现的顺序有:
1) STORE A=3, STORE B=4, x=LOAD A->3, y=LOAD B->4
2) STORE A=3, STORE B=4, y=LOAD B->4, x=LOAD A->3
3) STORE A=3, x=LOAD A->3, STORE B=4, y=LOAD B->4
4) STORE A=3, x=LOAD A->3, y=LOAD B->2, STORE B=4
5) STORE A=3, y=LOAD B->2, STORE B=4, x=LOAD A->3
6) STORE A=3, y=LOAD B->2, x=LOAD A->3, STORE B=4
7) STORE B=4, STORE A=3, x=LOAD A->3, y=LOAD B->4
可能出现的结果有:
x == 1, y == 2
x == 1, y == 4
x == 3, y == 2
x == 3, y == 4
例(2)
CPU 1 CPU 2
=============== ===============
{ A == 1, B == 2, C = 3, P == &A, Q == &C }
B = 4; Q = P;
P = &B D = *Q;
在这个操作序列中,有明显的数据依赖关系。存储在D中的值依赖于CPU 2通过P取得的地址。可能的结果有:
(Q == &A) and (D == 1)
(Q == &B) and (D == 2)
(Q == &B) and (D == 4)
设备操作
对于有些设备,它们的寄存器映射在内存中。当访问某些控制寄存器来进行设备的控制的时候,访问顺序就相当关键。比如,访问一个以太网卡的内部寄存器时,必须通过一个地址端口寄存器A和数据端口寄存器D来进行,如果要访问内部寄存器5,可能会使用如下代码:
*A = 5
x = *D
但实际执行的序列可能是:
STORE *A = 5, x = LOAD *D
x = LOAD *D, STORE *A = 5
这里的第二个执行序列肯定会导致错误,因为它在访问地址端口寄存器A前访问了数据端口寄存器D。
CPU能够保证的顺序
1) 对任何给定的CPU,相互依赖的内存访问将会按照顺序执行,比如:
Q = P; D = *Q;
CPU执行的顺序始终是:
Q = LOAD P, D = LOAD *Q
2) 相重叠的加载、存储操作将会按照顺序执行,比如:
a = *X; *X = b;
CPU执行的顺序始终是:
a = LOAD *X, STORE *X = b
比如:
*X = c; d = *X;
CPU执行的顺序是:
STORE *X = c, d = LOAD *X
不能被假定保证顺序
1)不能假定独立的加载、存储将会按照顺序执行,比如:
X = *A; Y = *B; *D = Z;
其中可能出现的操作序列有:
X = LOAD *A, Y = LOAD *B, STORE *D = Z
X = LOAD *A, STORE *D = Z, Y = LOAD *B
Y = LOAD *B, X = LOAD *A, STORE *D = Z
Y = LOAD *B, STORE *D = Z, X = LOAD *A
STORE *D = Z, X = LOAD *A, Y = LOAD *B
STORE *D = Z, Y = LOAD *B, X = LOAD *A
2) 有些重叠的内存访问可能合并或者丢弃,比如:
X = *A; Y = *(A + 4);
可能的执行顺序有:
X = LOAD *A; Y = LOAD *(A + 4);
Y = LOAD *(A + 4); X = LOAD *A;
{X, Y} = LOAD {*A, *(A + 4) };
比如:
*A = X; Y = *A;
可能执行的顺序有:
STORE *A = X; Y = LOAD *A;
STORE *A = Y = X;
根据以上分析,对于独立的内存操作以随机的方式进行,它是相当的有效的,但与这也给CPU之间的交互和IO操作来说带来了问题。这个时候必须使用Memory Barrier来保证各种操作按照顺序执行。