编译器屏障移植
在程序编译的时候,特别是加入了优化选项-O2或-O3之后,编译器可能会将代码打乱顺序执行,即编译生成后的汇编代码的执行顺序可能与原始的高级语言代码中的执行顺序不一致。所以编译器提供了编译阶段的内存屏障,用于指导编译器及时刷新寄存器的值到内存中,保证该编译器屏障前后的内存访问指令在编译后是定序排布的。
常见的编译型屏障定义如下所示:
#define barrier() __asm__ __volatile__("": : :"memory")
x86属于强内存序架构,大部分情况使用编译型屏障就可以保证多线程内存访问的一致性。但是,在ARM架构下就无法保证这一点。举例说明如下:
#define barrier() __asm__ __volatile__("": : :"memory")
// init: flag = data = 0;
thread0(void){
data = 1;
barrier();
flag =1;
}
thread1(void){
if(flag != 1) return;
barrier();
assert(data == 1);
}
在以上代码中,thread0和thread1分别运行在不同的CPU上,在x86架构下不会触发thread1中的断言,但是在ARM架构下就可能出现(flag == 1 && data == 0)的情况,如表1中所示,ARM架构下可允许写-写乱序,所以存在flag被置为1但是实际上data仍未被赋值的情况,从而导致了thread1中断言被触发。
参考Linux内核代码中的平台相关的宏定义,如下所示:
// x86
#define barrier() __asm__ __volatile__("": : :"memory")
#define smp_rmb() barrier()
#define smp_wmb() barrier()
#define smp_mb() asm volatile("lock; addl $0,-132(%%rsp)" ::: "memory", "cc")
// arm
#define smp_mb() asm volatile("dmb ish" ::: "memory")
#define smp_wmb() asm volatile("dmb ishst" ::: "memory")
#define smp_rmb() asm volatile("dmb ishld" ::: "memory")
在x86架构下,读屏障和写屏障的宏smp_rmb()和smp_wmb()都设置成了编译器内存屏障,而在ARM架构下则都设置成了CPU指令级内存屏障,从这里也可看出两个架构之间的差异。所以为了确保thread1中的断言不被触发,我们需要将原代码中的编译型屏障改写成CPU级内存屏障,如下所示:
#define smp_wmb() asm volatile("dmb ishst" ::: "memory")
#define smp_rmb() asm volatile("dmb ishld" ::: "memory")
// init: flag = data = 0;
thread0(void) {
data = 1;
smp_wmb();
flag = 1;
}
thread1(void) {
if (flag != 1)
return;
smp_rmb();
assert(data == 1);
}
父主题: 代码移植注意事项