您的位置:首页 > 其它

关于memory barrier/memory ordering的笔记和总结(1)

2016-01-19 22:28 441 查看


本文是ARM和INTEL官方手册、LINUX内核文档的笔记。

             谁需要关心memory
ordering

以下情况下,不需要关心memoryordering:

如果不涉及内存的并发访问;

代码完全利用信号量、自旋锁等内核原语或者高级语言提供互斥手段完成并发访问控制。

典型的需要关心memoryordering的场景:

免锁算法的实现;

互斥原语的实现;

不使用内核的writel/readl系列接口,直接访问外设。

             理想场景

站在程序员的角度,理想中的多线程编程/执行环境是所写即所得:

高级语言代码和编译生成的汇编语言(机器指令)是一一对应的,语句的顺序和对应汇编指令顺序是一致的,访问内存的地方就访问内存。

对内存的读写操作满足SC(Sequential Consistency)。

         什么是SC(SequentialConsistency):

                       定义和解读:

‘A multiprocessor is sequentially consistent ifthe result of any execution is the same as if the operations of all theprocessors were executed
in some sequential order, and the operations of eachindividual processor appear in this sequence in the order specified by itsprogram.’– Leslie Lamport (1979)

其中的the orderspecified by its program通常被称为program
order,是编译后生成的汇编代码(机器指令)出现的顺序。SC要求对任何一组多个processor的执行序列P(i),存在一个等价的单一执行序列P(单一执行序列的特点是:上一个操作执行产生的结果立刻生效,对下一个操作可见),P由P(i)交织构成,
P(i)中的成员在P中的先后顺序与其在P(i)中顺序一致。SC要求其涉及的操作是原子的,并且按照program
order生效。

 

在multiprocessorshared memory环境对内存的操作要满足SC,需要的条件:

processor对内存读写的访问是原子的。这里的原子性指的是:如果两个processor多次同时对一个内存地址执行读/写操作,最终读到的值/写到内存的值是完整的某次读写操作的值,而不是两个读写的值的混合;这一点也称为simple-copy
atomic。对于当前的计算机系统,这一点是满足的:对满足特定对齐要求的BYTE、WORD、DWORD、QWORD(64bit系统)的读写都是原子的,而其它的内存读写可以看成是多次原子的内存读写的组合。

注:X++这种并不是一次内存读写,而是一次内存读和一次内存写,如果一个指令对内存的访问如果是原子的,就称为instruction atomic。对X86系统,可以通过LOCK前缀来让非原子的指令变成原子指令;而对ARM,没有类似LOCK前缀这样的东西,一般通过ldrex-strex指令来完成,这时候就不能称为instruction
atomic了。

processor对内存的访问按照对所有processor按照program order生效/可见。这里的生效/可见的含义是:

processor A对某个地址的写操作对某个processor B生效(可见)指的是:生效后processorB可以读取到A写入到该地址的值(或者后续其它processor写入且生效的值);且生效后B对该地址的写操作在该地址对外体现的操作序列上,排在A对该地址的写操作之后(对同一个地址的写操作是满足SC要求的)。

processor A对某个地址的读操作对某个processor B生效(可见)指的是:生效后,
B对该地址的写操作不会影响到A读取到的值。也就是看起来A的读操作已经完成了。

reorder是生效顺序的另一种说法:processor A的操作a和操作b,如果对processorB操作b先于操作a生效;那么可以说对于processor
B,操作a和操作b reorder了(至少和其reorder是等效的)。

所有的processor看到的内存访问生效顺序是一致的。这个条件和条件2不一样,条件二描述的时候一个processor的内存操作的对其它processor体现的顺序,而条件3描述的多个不同的processor的内存操作间体现出的顺序。比如:processor
A执行了W(X)=1。processor B执行了W(Y)=2,如果processor
C 看见的是 W(X)在W(Y)之前发生,那么其他所有的processor都应该看到相同的内存操作生效顺序。

注:内存的访问满足multi-copyatomic,除了上面的条件3,还要求内存的写操作对所有的processor同时生效。而条件3要求的只是相对顺序的一致性即可。一些文献认为SC意味着multi-copy
atomic,但是我感觉没有必要,条件3就够了?

注:这里的processor不仅指CPU,也可以是外设等能够执行内存读写的实体。     

                       例子

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发生。

         两个processor的场景

 

P1: W(X)=1 , W(Y)=1

P2: R(Y)=0 , R(X)=1

以上的执行过程是符合SC的,其可以等价于:P2|R(Y)=0,P1|W(X)=1,P1|W(Y)=1,P2|R(X)=1

 

P1: W(X)=1 , W(Y)=1

P2: R(Y)=1 , R(X)=0

以上的执行过程是不符合SC的,因为:

首先,按照SC,在等价的单一执行队列中,P1|W(X)=1->
P1|W(Y)=1且P2|R(Y)=1-> P2|R(X)=0;

R(Y)=1说明P1|W(Y)=1的写入已经对P2生效,也就是P1|W(Y)=1->P2|R(Y)=1;

按照先后顺序的传递性,P1|W(X)=1->P1|W(Y)=1-> P2|R(Y)=1-> P2|R(X)=0,但是该序列是不合法的,因为P1|W(X)=1->
P2|R(X)=0必定不成立(对X写入了1,应当读到1而不是0)。

         三个processor的场景

P1: W(X)=1

P2: R(X)=1 , W(Y)=1

P3: R(Y)=1,R(X)=0

以上执行过程是不符合SC的,因为:P3|R(Y)=1说明P2|W(Y)=1已经生效,那么就有:

P2|W(Y)=1-> P3|R(Y)=1,

同理P2|R(X)=1表明P1|W(X)=1生效,也就是:P1|W(X)=1->
P2|R(X)=1,按照SC的要求,明显有:P2|R(X)=1->P2|W(Y)=1 P3|R(Y)=1->P3|R(X)=0

于是有P1|W(X)=1 -> P2|R(X)=1
-> P2|W(Y)=1 -> P3|R(Y)=1 ->P3|R(X)=0也就是:

P1|W(X)=1 -> P3|R(X)=0,明显是不合法的。

该例子违反的是对SC的内存操作对所有processor生效顺序一致要求,W(X)=1对P2生效了却对P3没有生效。

         四个processor的场景

P1: W(X)=1

P2: W(Y)=1

P3: R(Y)=1,R(X)=0

P4:R(X)=1,R(Y)=0

以上序列也不符合SC的要求,是因为:

P3|R(X)=0说明P3|R(X)=0 -> P1|W(X)=1

P3|R(Y)=1说明P2|W(Y)=1 -> P3|R(Y)=1

而按照SC必然有P3|R(Y)=1
-> P3|R(X)=0

也就是 P2|W(Y)=1-> P3|R(Y)=1 -> P3|R(X)=0 -> P1|W(X)=1

也就是P2|W(Y)=1-> P1|W(X)=1,但是不符合P4的执行结果。

本质上,该例子违反的也是SC的内存操作对所有processor生效顺序一致要求,也就是P1和P2对X,Y的操作对P3和P4的生效的顺序不一样。

         理想和现实的距离

                       现实

理想:高级语言代码和编译生成的汇编语言(机器指令)是一一对应的,至少语句的顺序和对应汇编指令顺序是一致的。

现实:对当前的高级语言,比如C/C++/JAVA等,该假设不成立,比如对gcc,如果使用-O2,编译器会对C/C++代码通过做静态分析来优化,这些优化可能会改变程序访问内存的顺序和次数,甚至取消某些内存访问,删除经过静态分析认为不必要的语句。

2、理想:对汇编指令的执行满足SC

现实:对于CPU来讲,为了提高性能,处理内存和CPU巨大的速度差异,其需要使用诸如Out-of-orderexecution、speculativeexecution、Store
buffers这些技术,而这些技术都会给保持SequentialConsistency带来挑战,由于简化硬件等原因,现代的CPU基本都不能做到支持SC(只有老的i386能够近似满足)。

                       基本保证

无论编译器如何优化,它都需要保证在程序中不存在并发内存访问(包括不同线程、CPU、外设的并发访问)的情况下,其生成的汇编语句的执行结果和高级语言的描述一致(否则就只能认为是编译器有BUG了)。也就是在不涉及和其他执行实体共享内存的时候,我们不需要关心编译器是如何优化的,它必须保证优化前后的程序执行效果一致,这些优化对程序员不可见。

无论CPU如何优化内存访问,采用了什么样的技术来提高执行效率和内存访问效率,他采用这些技术的时候都需要保证在程序中不存在并发内存访问(包括不同线程、CPU、外设的并发访问)的情况下,其执行结果和汇编指令(和机器码一一对应)的描述一致,也就是这些优化对程序员不可见。也就是:本processor执行的任何内存操作,对本processor来讲,总是按照programmer
order生效的,只有对其它processor才存在乱序。

对基本上所有的体系结构,对于同一地址的读写操作,满足cache一致性的要求。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: