ARM 架构中的内存屏障
1、引言在现代 ARM 处理器中由于乱序执行、多级缓存以及多核并发的存在程序中“看起来按顺序执行”的内存访问并不一定以相同顺序被系统中其他观察者看到。内存屏障Memory Barrier正是用于显式约束这种重排行为以保证程序的正确性和可预测性。2、ARM32内存屏障的基本目标ARM 内存屏障主要解决三类问题排序Ordering限制屏障前后的 Load / Store 重排序使内存访问顺序可被预测。完成性Completion确保屏障之前的内存访问已经真正完成而不仅仅是“被发出”。上下文同步Context Synchronization保证系统状态如 MMU、TLB、异常向量的修改对后续指令生效。基于这三种需求ARMv6 / ARMv7 定义了三条核心屏障指令指令关注点典型用途DMB排序普通内存、DMA 同步DSB完成性MMIO、设备时序ISB上下文MMU / TLB / 异常Barriers2.1 DMBDMBData Memory Barrier数据内存屏障用于约束屏障前后数据访问的可观察顺序。根据 DMB 指定的共享域和访问类型屏障前的相关数据访问必须先于屏障后的相关数据访问被内存系统或其他观察者观察到。这里的数据访问是指显式的加载Load和存储Store不包括指令取指。DMB 的核心语义是顺序ordering而不是完成completion。它不保证执行到 DMB 时前面的普通内存访问已经完全结束或到达最终目标。DMB 可以通过 option 指定共享域和访问类型例如SY系统范围默认选项ISH、OSH、NSH不同共享域LD约束屏障前的 Load 与屏障后的数据访问ST约束屏障前后的 Store。例如LDR X0,[X1]// 必须先于后面的 STR 被内存系统观察到DMB ISHLD ADD X2,X2,#1// 非访存指令不受 DMB 的顺序约束STR X3,[X4]// 必须在前面的 LDR 之后被观察到DMB ISHLD约束的是LDR和STR的可观察顺序。中间的ADD不访问内存因此它可能在内存系统观察到LDR之前或之后执行。Armv8-A 还对 cache maintenance operations缓存维护操作作了一项特殊规定屏障前显式发起的数据缓存或统一缓存维护操作必须先完成之后的数据访问才能执行。例如DC CSW,X5// 按 Set/Way 清理数据缓存LDR X0,[X1]// 可能还观察不到缓存清理的效果DMB ISH LDR X2,[X3]// 执行前前面的缓存清理必须已经完成2.1.1 实战DMA 描述符DMA 描述符需要先由 CPU 填写然后才能通知 DMA 控制器开始工作/* 填写 DMA 描述符 */desc[i].cmd_cfgcmd_cfg;desc[i].cmd_arghost-cmd-arg;desc[i].cmd_datasg_dma_address(sg);dma_wmb();/* Ensure descriptors are written before DMA is kicked. */starthost-descs_dma_addr|START_DESC_BUSY;writel(start,host-regsSD_EMMC_START);在 ARM Linux 的相应实现中#definedma_wmb()dmb(oshst)DMB OSHST保证描述符的 Store 先于启动寄存器的 Store 被观察到。因此当 DMA 控制器观察到启动操作时描述符写入在可观察顺序上已经位于它之前。这并不表示 CPU 执行到 dma_wmb() 时前面的普通写操作已经无条件完成。dma_wmb() 建立的是 DMA 描述符写入与 DMA 启动寄存器写入之间的可观察顺序写入 DMA 描述符↓DMB OSHST↓写入 DMA 启动寄存器其保证是当 DMA 控制器观察到启动寄存器的写入时屏障前的描述符写入必须已经对相关共享域内的 DMA 访问可见。换句话说DMB 本身并不要求描述符写入在 CPU 执行到屏障时立即完成而是将后续的启动寄存器写入作为一个顺序边界DMA 控制器不能先观察到“启动”随后才观察到旧的或尚未发布的描述符内容。2.2 DSBDSBData Synchronization Barrier数据同步屏障提供与 DMB 相同的内存访问顺序保证并进一步等待同步操作完成。在 DSB 完成之前程序顺序上位于它之后的指令不能在体系结构意义上继续执行。换句话说DMB 主要约束屏障前后的数据访问顺序而 DSB 还会阻塞后续指令直到同步完成。DSB 的完成条件由其共享域和访问类型决定。对于完整的 DSB SY主要包括屏障前的显式内存访问已经达到体系结构规定的完成条件相关的缓存、TLB 和分支预测器维护操作已经完成DSB 完成之前后续指令不能继续执行。上述“完成”必须结合 DSB 的 option 理解。例如DSB ST 只等待受其约束的前序 Store而不能简单概括为等待所有类型的内存访问。典型场景MMIO 写入后执行 WFI假设处理器先通过 MMIO 寄存器清除中断然后进入低功耗等待状态STR W0,[X1]// 写设备寄存器清除中断DSB SY// 等待前面的写访问完成WFI// 等待下一个中断如果这里只使用 DMBSTR W0,[X1]DMB SY WFIDMB 只能约束数据访问之间的顺序而 WFI 不是数据访问。由于屏障后没有另一个数据访问参与排序前面的 MMIO 写仍可能处于未完成状态处理器就已经执行了 WFI。DSB 则会等待前面的 MMIO 写达到体系结构规定的完成条件然后才允许执行 WFI。这正是 DSB 与 DMB 的关键区别DMB保证前后数据访问的顺序 DSB保证顺序并等待同步完成后才执行后续指令2.2.1 Linux 下的 writelLinux 中ARM 架构下writel函数实现如下#definewritel(v,c)({__iowmb();writel_relaxed(v,c);})#define__iowmb()wmb()#definewmb()__arm_heavy_mb(st)#define__arm_heavy_mb(x...)dsb(x)可以看到writel函数中自带一个 dsb 内存屏障。2.3 ISBISBInstruction Synchronization Barrier指令同步屏障用于同步当前处理器的指令执行上下文。ISB 完成后程序顺序上位于它之后的指令必须在 ISB 完成之后才从 Cache 或内存中重新取指。从流水线角度看ISB 会刷新当前处理器的指令流水线丢弃已经取指、译码或推测执行但位于 ISB 之后的工作然后重新开始取指。因此ISB 也是一个上下文同步事件Context Synchronization Event。ISB 的核心语义是上下文同步保证 ISB 之前已经完成的上下文变更对 ISB 之后重新取出的指令可见。典型的上下文变更包括修改影响指令执行环境的系统控制寄存器例如 MMU、地址转换、访问权限或异常处理相关配置已经完成的 Cache、TLB 或分支预测器维护操作修改指令内容后已经完成的数据 Cache 清理和指令 Cache 无效化操作。这里的“已经完成”非常重要。ISB 本身不保证前面的显式内存访问或维护操作完成也不对普通 Load / Store 建立顺序。如果必须先等待 Cache、TLB 等维护操作完成再让后续指令使用新上下文通常需要使用DSB后接ISB/* 发起 Cache 或 TLB 维护操作 */DSB SY// 等待前面的维护操作完成ISB// 后续指令在更新后的上下文中重新取指ISB 只作用于执行该指令的处理器。它不会把上下文同步事件广播到其他处理器如果其他处理器也需要观察新的指令或执行上下文它们同样需要执行相应的同步序列。“刷新流水线”不等于“清空所有分支预测器状态”。ISB 保证后续指令重新取指并使用最新的体系结构上下文但不能据此推导分支预测器内部状态全部失效。若体系结构要求执行分支预测器维护还必须先显式执行相应的维护操作。典型场景1开启 MMU下面以 AArch32 为例。假设页表和其他前置配置已经准备完成r0中是要写入SCTLR的新值MCR p15,0,r0,c1,c0,0// 写 SCTLR修改 MMU 等系统控制状态ISB// 后续指令使用新的执行上下文写SCTLR改变的不是两次数据访问之间的可观察顺序而是后续指令的取指、地址转换和权限检查方式。因此这里需要的是 ISB而不是 DMB。DMB约束屏障前后数据访问的顺序 DSB约束顺序并等待受约束的操作完成后再执行后续指令 ISB同步指令执行上下文使后续指令在屏障完成后重新取指典型场景2有序读取物理计数器AArch64体系结构允许推测读取CNTPCT_EL0因此该读取可能相对于程序流乱序发生。当计数值用作时间戳且要求采样点位于某个前序条件成立之后时应在MRS之前执行 ISBloop:LDR X1,[X2]// 轮询通信标志CBZ X1,loop ISB// 建立指令流中的采样边界MRS X0,CNTPCT_EL0// 在 ISB 完成后读取物理计数器ISB 保证该读取不会被推测到屏障之前如果计数值与前序程序流之间不存在顺序要求读取CNTPCT_EL0本身不需要额外的 ISB。ISB 不保证前序内存或设备访问已经完成若时间戳还必须晚于此类访问的完成时刻需要先执行适当的 DSB。实现 FEAT_ECV 的处理器还提供自同步寄存器CNTPCTSS_EL0可用于满足上述读取排序要求而无需显式执行 ISB。详细说明参见 Learn the architectureGeneric Timer。DMB约束屏障前后数据访问的顺序 DSB约束顺序并等待受约束的操作完成后再执行后续指令 ISB同步指令执行上下文使后续指令在屏障完成后重新取指3、ARM64ARMv8 在保留 DMB / DSB / ISB 的同时引入了更细粒度的同步原语Load-AcquireLDARStore-ReleaseSTLR它们是附着在具体访存指令上的轻量级屏障用于实现 RCscRelease Consistency, sequentially consistent模型。3.1 Load-AcquireLDAR 保证该加载操作先于程序顺序上位于其后的所有 Load / Store 被观察到但对其之前的访问不施加额外约束。约束方向向后Figure 1. LDAR ordering requirements3.2 Store-ReleaseSTLR 保证程序顺序上位于 STLR 之前的所有 Load / Store先于该 Store 被观察到但对其之后的访问不施加约束。约束方向向前Figure 2. STLR ordering requirements3.3 对于 Device 类型的内存此外对于映射到外设的内存区域memory-mapped peripheral当访问的是系统定义的任意大小、且被定义为 Device 类型内存 的区域时这些指令还具有以下要求对外设内存区域中某地址执行 Load-Acquire可确保所有在体系结构上要求在该 Load-Acquire 之后被观测到的、使用 Device 内存类型的对外设的内存访问将在该 Load-Acquire 的内存访问之后到达该外设。对外设内存区域中某地址执行 Store-Release可确保所有在体系结构上要求在该 Store-Release 之前被观测到的、使用 Device 内存类型的对外设的内存访问将在该 Store-Release 的内存访问之前到达该外设。如果某个对外设内存地址的 Load-Acquire 已观测到由 Store-Release 写入该地址的值那么所有在体系结构上要求在该 Store-Release 之前发生的对外设的内存访问将先于所有在体系结构上要求在该 Load-Acquire 之后发生的对外设的内存访问到达该外设。当访问 Device 类型内存 时LDAR / STLR 会保证对外设的访问按体系结构顺序到达。其效果在语义上接近 DSB。而对普通内存更接近 DMB3.4 Release Acquire真正的同步单独的 STLR 或 LDAR 并不会同步线程。只有当一个线程用 STLR 写入另一个线程用 LDAR 读到该值才建立happens-before关系。经典 publish–subscribe 示例T1:T2:a123;stlr(b,true);if(ldar(b)){print(a);}保证T2 一旦看到 b true就 必然 看到 a 123这正是ARM 架构中的 multi-copy atomicRCsc 模型无锁通信的基础在多处理器系统中对某一内存位置的写操作具有 multi-copy atomic 当且仅当同时满足以下两个条件所有对该同一内存位置的写操作是串行化的serialized即所有观察者observers所观测到的这些写操作的顺序是一致的相同的全局顺序尽管某些观察者可能并未观测到其中全部的写操作。对某一内存位置的读操作不会返回某个写操作所写入的值除非该写操作已被所有观察者观测到。换言之一个写入的值只有在其对系统中所有相关观察者都“全局可见”之后才可能被任何读操作返回。