程序在执行时,指令一般不按照源程序顺序执行,原因是为提高程序执行性能,会对它进行优化,主要为两种:编译器优化和CPU执行优化,可能会导致内存访问乱序。
内存屏障(Memory Barrier)是一种保证内存访问顺序的方法,用来解决下面这些内存访问乱序问题。
1)编译器编译代码时可能重新排列汇编指令,使编译出来的程序在处理器上运行更快,但是有时候优化的结果可能不符合程序员的意图,导致程序运行过程中出现问题。
2)现代的处理器采用超标量体系结构(每个流水线阶段能同时执行多条指令)和乱序执行技术,能够在一个时钟周期并行执行多条指令。处理器按照程序顺序取出一批指令,分析找出没有依赖关系的指令,发给多个独立的执行单元并行执行,最后按照程序顺序提交执行结果。即顺序取指令**,乱序执行,顺序提交执行结果。乱序执行的两条指令之间,可能存在依赖关系,但处理器不能识别出依赖关系**,就会导致意想不到的问题。
3)在多处理器系统中,硬件工程师使用存储缓冲区(Store Buffer)、使无效队列(Invalidation Queue)协助缓存和缓存一致性协议实现高性能,引入了处理器之间的内存访问乱序问题。一个处理器修改数据,可能不会把数据立即同步到自己的缓存或者其他处理器的缓存存,导致其他处理器不能立即看到最新的数据。
内核主要支持两种内存屏障。
- 优化屏障(Optimization barrier),也叫编译器屏障,解决1的问题。
- 处理器内存屏障,解决2、3的问题。
优化屏障(Optimization barrier)
为了提高程序的执行速度,编译器会对代码进行优化,对于不存在数据依赖或控制依赖的汇编指令,重新排列它们的顺序,但是有时候优化产生的指令顺序不符合程序员的真实意图,引入问题,程序员需要使用优化屏障(编译器屏障)指导编译器。优化屏障是防止编译器对指令进行重排的一种机制,但对 CPU 没有影响。
优化屏障避免编译的重新排序优化操作,保证编译程序时在优化屏障之前的指令不会在优化屏障之后执行。
Linux使用宏<font style="color:rgb(0,0,0);">barrier</font>实现优化屏障,如gcc编译器的优化屏障宏定义如下。
#define barrier() __asm__ __volatile__("": : :"memory")
这条语句本身什么都不做(汇编是空的),但告诉编译器,前后的内存操作不要重排。
优化屏障的一个特定应用是内核抢占机制。禁止内核抢占preempt_disable、开启内核抢占preempt_enable中包含barrier,代码如下。
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \
__preempt_schedule(); \
} while (0)
这样,在禁止内核抢占保护的临界区,编译器就不会对指令进行重排。
处理器内存屏障
处理器内存屏障用来解决处理器之间的内存访问乱序问题和处理器访问外围设备的乱序问题。
内存访问乱序,主要有以下几个原因。
- 多处理器访问相同内存,如以下代码:
while (f == 0);
print x;
x = 42;
f = 1;
我们不能武断地认为CPU0上打印的x一定等于42,因为CPU1上即便“f=1”编译在“x=42”后面,执行时
仍然可能先于“x=42”完成,所以这个时候CPU0上打印的x不一定就是42。
- 存储缓冲区(Store Buffer)导致的写操作乱序。
- 使无效队列(Invalidation Queue)导致的读操作乱序。
存储缓冲区、使无效队列等,此处就不展开了,后面会在处理器缓存文章中在介绍。
内核有以下几种基本的处理器内存屏障。
| 屏障类型 | 宏名 | 说明 |
|---|---|---|
| 读写屏障 | mb() | Full memory barrier:之前的读写操作必须全部完成 |
| 读屏障 | rmb() | Read memory barrier:之前的读操作必须完成 |
| 写屏障 | wmb() | Write memory barrier:之前的写操作必须完成 |
| SMP 屏障 | smp_mb()/ smp_rmb()/ smp_wmb() | 只在SMP系统中起作用 |
参考资料
- Professional Linux Kernel Architecture,Wolfgang Mauerer
- Linux内核深度解析,余华兵
- Linux设备驱动开发详解,宋宝华
- linux kernel 4.12

1547

被折叠的 条评论
为什么被折叠?



