Memory ordering
Memory ordering用来描述系统中的processor对内存的操作如何对其它processor可见(可见的定义见前面的描述)。同时需要说明的是,大多数文献都采用reorder这个表达方式,是从执行等价的角度来描述的:比如P1上执行两个写操作WRITE(A)和WRITE(B),如果对于观察者P2来说P1|WRITE(B)先于P2|WRITE(A)可见,那么就可以认为P1的写操作发生了reorder。对读操作也是类似的。
影响memoryordering的因素很多,包括:
-
体系结构,X86和ARM的memory ordering就截然不同;
-
内存的类型,体系结构一般都会定义若干种内存类型,不同的内存类型有不同的memoryordering,比如X86分为Strong Uncacheable (UC)、Uncacheable (UC-)、Write Combining (WC)、Write Through (WT)、Write Back (WB)和Write Protected (WP),而ARM的内存类型用memory type和memory attribute来描述,不同的内存类型通常对应了不同的用途(具体情况见对应的厂商文档)。本文描述的是一般情况,也就是不做特殊处理,直接通过内存分配接口分配到的内存,也就是X86的WB类型,ARM的Shareable Normal memory。
-
具体的指令,比如INTEL的REP MOVSB和REP STOSB的memory ordering就和一般的mov指令不一样。除非特别说明,而本文描述的是普通的内存访问指令,通常是C语言的赋值语句对应的汇编指令。
和前面描述SC的时候一样,X、Y、Z表示变量,初始值都为0,P1 P2表示processor,R(X)=1表示从X中读到了值1,W(X)=1表示向X中写入了值1。P1|R(X)=1表示P1执行R(X)=1。符号A->B表示动作A先于动作B发生。
X86的memory ordering
X86的memory ordering属于strong order,其与SC的要求接近,在大多数典型场景,没有必要使用memory barrier指令。即使是和外设的DMA操作共享的内存,X86也能通过bus snoop完成强顺序保证(查看linux内核分配一致性内存的接口dma_alloc_coherent,你会发现尼玛就是分配内存咯,并没有设置页表的PWT/PCD标识,也没有设置Memory type range registers(MTRRs))。
普通内存操作
X86实现的memory ordering比较接近SC的要求,其违反SC的场景是:读操作可能和按照program order中在前面的对不同地址的写操作发生reorder,也就是读操作先于program order在其前面的对其他地址的写操作对外生效。注意:这里仅限于对不同地址的读和写。
也就是以下执行在X86上是可以发生的,但是并不满足SC的要求:
P1: W(X)=1,R(Y)=0
P2: W(Y)=1,R(X)=0
本例子明显不符合SC的要求,因为如果P1|R(Y)=0,那么必然有P1|R(Y)=0 –> P2|W(Y)=1
结合SC按照program order生效的要求,很容易得到P1|W(X)=1 -> P2|R(X)=0的悖论。
而X86允许读操作先于按照program order在其前面的写操作对外生效,P1|W(X)=1 -> P1|R(Y)=0不一定成立,使得上面的序列在X86上变得合法。
以上执行序列可以看成是X86的write buffer对程序员的体现,write buffer是CPU上的一个部件,当一个写操作由于种种原因不能立即放到cache/内存中的时候,CPU可以先把它放到本CPU的write buffer中,后续再刷新到内存中,这个write buffer只对本CPU可见。这就造成对一个地址的写操作在本 CPU看来已经完成,但是对其它CPU还不可见,而CPU继续执行后续的读操作,在其它CPU看起来,就造成了读和先前的写发生了reorder。
Memory barrier指令
X86的memory barrier指令包括lfence sfence mfence,这些指令通常在使用内存模型(比如Write Combining的操作),特殊的指令(REP MOVSB 和REP STOSB)才需要关注。
-
lfence
lfence确保program order在其前面的读不会和program order在其后面的读和写发生reorder,也就是lfence前面的读操作总是比lfence后面的读操作和写操作先生效。
-
sfence
sfence确保program order在其前面的写不会和program order在其后面的写发生reorder,也就是sfence前面的写操作总是比sfence后面的写操作先生效
-
mfence
mfence确保program order在其前面的写和写不会和program order在其后面的读和写发生reorder,也就是sfence前面的读操作和写操作总是比sfence后面的读操作和写操作先生效