什么是内存屏障
在 ARM 架构中,内存屏障是一类特殊的指令,用于强制对内存访问操作的顺序进行约束,确保在多核处理器系统或存在 DMA 等总线主控器的系统中,内存访问行为符合程序员的预期,避免因硬件优化(如乱序执行、写缓冲区延迟、缓存一致性延迟)导致的可见性和顺序性问题。
为什么需要内存屏障?
现代处理器为了提高性能,会采用多种优化技术:
- 乱序执行: CPU 核心可能不会严格按照程序指令的顺序执行内存读写操作。
- 写缓冲区: 写操作可能先进入一个快速的写缓冲区,稍后才实际写入缓存或内存。
- 缓存: 每个核心有自己的缓存,数据在不同核心缓存间的传播存在延迟。
- 编译器优化: 编译器可能在不改变单线程语义的前提下重排内存访问指令。
在多线程、多核或涉及 I/O 设备 (DMA) 的场景下,这些优化可能导致以下问题:
- 可见性问题: 一个核心写入的数据,另一个核心(或设备)不能及时看到。
- 顺序问题: 不同核心观察到的两个内存操作的顺序不一致。
ARM 内存屏障指令的核心作用
内存屏障指令的作用主要体现在两个方面:
-
限制指令重排:
- 屏障之前 vs. 屏障之后: 屏障确保在它之前发出的所有内存访问指令(读或写,取决于屏障类型)都完成(效果对系统其他部分可见)之后,才允许在它之后发出的内存访问指令开始执行。
- 屏障之间: 屏障本身并不强制屏障之前的指令之间或屏障之后的指令之间的相对顺序(除非使用特定屏障类型)。
-
确保访问完成:
- 对于写操作,屏障可以确保在屏障之后的指令执行前,屏障之前的所有写操作的结果已经到达它们在内存系统中的最终目标位置(例如,已写穿缓存到达主存,或者至少已到达所有其他核心/设备能看到的共享一致性点)。
- 对于读操作,屏障可以确保在屏障之后的指令执行前,屏障之前的所有读操作已完成,并且屏障之后的读操作不会“越过”屏障去预取更早的数据。
ARM 中主要的内存屏障指令
ARMv7 和 ARMv8 架构提供了明确的内存屏障指令:
-
DMB(Data Memory Barrier):- 作用: 确保在
DMB之前发出的所有内存访问指令(指定类型的,见下文范围)都完成(即它们的效果对系统中所有能发起内存访问的主体都可见)之后,才允许在DMB之后发出的任何内存访问指令开始执行。 - 关键点: 只约束内存访问指令之间的顺序。不影响非内存访问指令(如算术运算)的顺序。
DMB之后的指令可以开始执行,但它们的内存访问部分必须等待DMB之前的访问完成。 - 范围:
DMB指令通常带有一个选项 (如DMB ISH,DMB SY) 来指定屏障作用的范围:- Full System (
SY): 影响整个系统(所有核心、DMA 控制器等)。 - Outer Shareable (
OSH): 影响所有可以共享数据的外部设备(如 DMA)。 - Inner Shareable (
ISH): 影响同一集群内的所有核心。 - Non-shareable (
NSH) / Uniprocessor (UN): 仅影响当前核心(主要用于维护指令顺序,防止编译器重排)。
- Full System (
- 用途: 最常用。用于保护共享数据结构的访问(临界区),确保一个核心的修改在释放锁(或发布数据)之前对其他核心是可见的;确保 DMA 描述符在启动 DMA 传输之前已准备好。
- 作用: 确保在
-
DSB(Data Synchronization Barrier):- 作用: 比
DMB更强。它确保在DSB之前发出的所有内存访问指令(指定类型的)都完成之后,才允许执行DSB之后的任何指令(不仅仅是内存访问指令)。DSB会真正“阻塞”CPU 流水线,直到所有指定的内存访问完成。 - 关键点: 约束所有指令(内存访问和非内存访问)的顺序。
DSB之后的任何指令都必须等待DSB之前的内存访问完成。 - 范围: 同样有范围选项 (
DSB ISH,DSB SY等)。 - 用途: 需要严格保证内存访问完成才能进行下一步操作的场景。例如:
- 修改页表/内存管理单元配置后,需要
DSB确保新配置生效后再执行后续指令。 - 修改异常向量表地址后。
- 执行
ISB之前通常需要DSB。 - 进入低功耗状态前确保所有内存访问完成。
- 在自修改代码(修改自身指令)后执行新指令之前。
- 修改页表/内存管理单元配置后,需要
- 作用: 比
-
ISB(Instruction Synchronization Barrier):- 作用: 刷新处理器的流水线和预取缓冲区。确保在
ISB之后执行的任何指令都是从内存中重新获取的,并且是在ISB之前完成的所有上下文更改操作(如系统控制寄存器的修改、DSB完成的内存访问)之后获取的。 - 关键点: 主要影响指令的获取和译码阶段。它确保后续指令看到的是最新的系统配置和内存内容(特别是代码区域)。
- 范围: 通常作用于当前核心 (
ISB指令本身不指定范围,效果是本地的)。 - 用途: 在修改影响指令行为的系统寄存器(如控制寄存器、内存属性、页表、调试寄存器)之后,在修改程序代码本身(JIT, 自修改代码)之后,在更新异常向量表之后,在
DSB之后确保新配置/代码被立即使用。ISB通常紧跟在DSB之后使用。
- 作用: 刷新处理器的流水线和预取缓冲区。确保在
与 Linux 内核屏障 API 的关系
在操作系统(如 Linux)中,会提供高级的屏障宏,它们最终会被编译成适当的 ARM 屏障指令:
mb(): Memory barrier. 通常对应DSB SY(确保所有内存访问完成,阻塞后续所有指令)。rmb(): Read memory barrier. 通常对应DMB ISHLD(确保屏障前的读操作完成,屏障后的读操作不会越过屏障)。wmb(): Write memory barrier. 通常对应DMB ISHST(确保屏障前的写操作完成,屏障后的写操作不会越过屏障)。smp_mb(),smp_rmb(),smp_wmb(): 针对 SMP (多核) 优化的版本,通常在单核系统上是空操作,在多核系统上分别对应DMB ISH,DMB ISHLD,DMB ISHST。dma_wmb(),dma_rmb(): 用于 CPU 与 DMA 设备之间的内存同步屏障,通常对应DMB OSHST和DMB OSHLD。
总结
ARM 内存屏障 (DMB, DSB, ISB) 是协调多核、多设备系统中内存访问顺序和可见性的关键机制。它们强制处理器遵守程序员指定的内存操作顺序约束,解决了由硬件优化带来的并发问题。
DMB: 控制内存访问指令之间的相对顺序。DSB: 更强,确保内存访问完成才执行后续任何指令。ISB: 刷新指令流水线,确保后续指令看到最新的系统状态。
理解何时以及使用哪种屏障对于编写正确、高效的多线程程序、设备驱动程序和系统级软件至关重要。在 Linux 等操作系统中,通常会通过提供的屏障宏 (mb(), wmb() 等) 来使用这些指令。
浙公网安备 33010602011771号