内存屏障 & Memory barrier
Posted 笨鸟居士的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存屏障 & Memory barrier相关的知识,希望对你有一定的参考价值。
Memory Barrier
http://www.wowotech.net/kernel_synchronization/memory-barrier.html
这里面讲了Memory Barrier
对于一个c程序员,我们的编写的代码能所见即所得吗?我们看到的c程序的逻辑是否就是最后CPU运行的结果呢?很遗憾,不是,我们的“所见”和最后的执行结果隔着:
1、编译器
2、CPU取指执行
编译器了解底层CPU的思维模式,因此,它可以在将c翻译成汇编的时候进行优化(例如内存访问指令的重新排序),让产出的汇编指令在CPU上运行的时候更快。然而,这种优化产出的结果未必符合程序员原始的逻辑,因此,作为程序员,作为c程序员,必须有能力了解编译器的行为,并在通过内嵌在c代码中的memory barrier来指导编译器的优化行为(这种memory barrier又叫做优化屏障,Optimization barrier),让编译器产出即高效,又逻辑正确的代码。
我们先看下面的一个例子:
preempt_disable()
临界区
preempt_enable
我们知道所谓的preempt enable和disable其实就是对当前进程的struct thread_info中的preempt_count进行加一和减一的操作。具体的代码如下:
#define preempt_disable() \\
do { \\
preempt_count_inc(); \\
barrier(); \\
} while (0)
使用do...while(0)的好处可见:
http://www.cnblogs.com/charlesblc/p/6080315.html
linux kernel中的定义和我们的想像一样,除了barrier这个优化屏障。barrier就象是c代码中的一个栅栏,将代码逻辑分成两段,barrier之前的代码和barrier之后的代码在经过编译器编译后顺序不能乱掉。也就是说,barrier之后的c代码对应的汇编,不能跑到barrier之前去,反之亦然。之所以这么做是因为在我们这个场景中,如果编译为了榨取CPU的performace而对汇编指令进行重排,那么临界区的代码就有可能位于preempt_count_inc之外,从而起不到保护作用。
barrier是否够呢?
对于multi-core的系统,只有当该task被调度到该CPU上执行的时候,该CPU才会访问该task的preempt count,因此对于preempt enable和disable而言,不存在多个CPU同时访问的场景。
但是,即便这样,如果CPU是乱序执行(out-of-order excution)的呢?其实,我们也不用担心,正如前面叙述的,preempt count这个memory实际上是不存在多个cpu同时访问的情况,因此,它实际上会本cpu的进程上下文和中断上下文访问。能终止当前thread执行preempt_disable的只有中断。为了方便描述,我们给代码编址,如下:
地址 | 该地址的汇编指令 | CPU的执行顺序 |
a | preempt_disable() | 临界区指令1 |
a+4 | 临界区指令1 | preempt_disable() |
a+8 | 临界区指令2 | 临界区指令2 |
a+12 | preempt_enable | preempt_enable |
当发生中断的时候,硬件会获取当前PC值,并精确的得到了发生指令的地址。有两种情况:
(1)在地址a发生中断。对于out-of-order的CPU,临界区指令1已经执行完毕,preempt_disable正在pipeline中等待执行。由于是在a地址发生中断,也就是preempt_disable地址上发生中断,对于硬件而言,它会保证a地址之前(包括a地址)的指令都被执行完毕,并且a地址之后的指令都没有执行。因此,在这种情况下,临界区指令1的执行结果被抛弃掉,因此,实际临界区指令不会先于preempt_disable执行
(2)在地址a+4发生中断。这时候,虽然发生中断的那一刻的地址上的指令(临界区指令1)已经执行完毕了,但是硬件会保证地址a+4之前的所有的指令都执行完毕,因此,实际上CPU会执行完preempt_disable,然后跳转的中断异常向量执行。
注意:如果CPU是乱序执行(out-of-order excution)的,barrier只是保证compiler输出的汇编指令的顺序是OK的,不能确保CPU执行时候的乱序。
CPU会乱排,但是有的顺序不会调换,根据load和store型指令,不同处理器的策略不同,可以见:
Java内存模型(可以结合着看)
http://www.cnblogs.com/charlesblc/p/6126551.html
对这个问题的回答来自ARM architecture的内存访问模型:对于program order是A1-->A2的情况(A1和A2都是对Device或是Strongly-ordered的memory进行访问的指令),ARM保证A1也是先于A2执行的。因此,在这样的场景下,使用barrier足够了。 对于X86也是类似的,虽然它没有对IO space采样memory mapping的方式,但是,X86的所有操作IO端口的指令都是被顺执行的,不需要考虑memory access order。
以上是关于内存屏障 & Memory barrier的主要内容,如果未能解决你的问题,请参考以下文章
Linux C/C++并发编程实战2万字解读C/C++ 6种内存时序memory_order(内存屏障)指令重排原子操作
ARM有几条memory barrier 的指令?分别有什么区别?