内存屏障
内存屏障
编译器优化乱序和
-
优化屏障
(Optimization Barrier) :避免编译器的重排序优化操作,保证编译程序时在优化屏障之前的指令不会在优化屏障之后执行;这就保证了编译时期的优化不会影响到实际代码逻辑顺序。 -
内存屏障
(Memory Barrier) 分为写屏障(Store Barrier) 、读屏障(Load Barrier)和全屏障(Full Barrier) ,其作用有两个:防止指令之间的重排序、保证数据的可见性。- 写屏障:强制将写缓冲器中的内容写入到高速缓存中,或者将屏障之后的指令全部写到写缓冲器直到之前写缓冲器中的内容全部被刷回缓存中,也就是处理
0 必须等到所有的invalidate ack 消息后,才能执行后续的操作,相当于flush 操作; - 读屏障:处理器在读取数据前,必须强制检查无效队列中是否有
invalidate 消息,如果有必须先处理完无效队列汇总的无效消息,再进行数据读取, 相当于refresh 操作。
- 写屏障:强制将写缓冲器中的内容写入到高速缓存中,或者将屏障之后的指令全部写到写缓冲器直到之前写缓冲器中的内容全部被刷回缓存中,也就是处理
通过加入读写屏障保证了可见性与有序性。之所以说保证了有序性,是因为指令乱序现象就是写缓冲器异步接收到其他处理器中的
内存屏障的意义重大,是确保正确并发的关键。通过正确的设置内存屏障可以确保指令按照我们期望的顺序执行。这里需要注意的是内存屏蔽只应该作用于需要同步的指令或者还可以包含周围指令的片段。如果用来同步所有指令,目前绝大多数处理器架构的设计就会毫无意义。
Lock 指令
以lock addl $0×0,(%esp);
,通过查
-
Lock 前缀指令会引起处理器缓存回写到内存。Lock 前缀指令导致在执行指令期间,声言处理器的LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占任何共享内存。但是,在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销的比较大。对于Intel486 和Pentium 处理器,在锁操作时,总是在总线上声言LOCK# 信号。但在P6 和目前的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK# 信号。相反,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。 -
一个处理器的缓存回写到内存会导致其他处理器的缓存无效。
IA-32 处理器和Intel 64 处理器使用MESI (修改、独占、共享、无效)控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候,IA-32 和Intel 64 处理器能嗅探其他处理器访问系统内存和它们的内部缓存。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。例如,在Pentium 和P6 family 处理器中,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。
MB_R 代表读内存屏障,它保证读取操作不会重排到该指令调用之后。MB_W 代表写内存屏障,它保证写入操作不会重排到该指令调用之后。MB 代表读写内存屏障,可保证之前的指令不会重排到该指令调用之后。
这些屏障指令在单核处理器上同样有效,因为单处理器虽不涉及多处理器间数据同步问题,但指令重排和缓存仍然影响数据的正确同步。指令重排是非常底层的且实 现效果差异非常大,尤其是不同体系架构对内存屏障的支持程度,甚至在不支持指令重排的体系架构中根本不必使用屏障指令。具体如何使用这些屏障指令是支持的 平台、编译器或虚拟机要实现的,我们只需要使用这些实现的