在ARMv7架构中,内存访问顺序是确保多核系统正确运行的关键机制。现代处理器为了提高性能,通常会采用乱序执行、指令流水线和多级缓存等技术,这些优化可能导致内存操作的实际执行顺序与程序代码中的顺序不一致。
ARMv7将内存分为三种主要类型:
重要提示:设备内存和强序内存的访问顺序要求完全相同,这是ARMv7架构的一个关键设计特点。
ARMv7通过两种依赖性机制来保证内存访问顺序:
地址依赖性:
assembly复制LDR R1, [R0] ; 从R0指向的地址读取数据到R1
LDR R2, [R1] ; 使用R1的值作为地址读取数据 - 存在地址依赖
控制依赖性:
assembly复制LDR R1, [R0] ; 读取数据
CMP R1, #10 ; 设置条件标志
LDREQ R2, [R3] ; 条件加载 - 存在控制依赖
ARMv7定义了不同类型内存访问之间的顺序要求:
| 先访问类型 \ 后访问类型 | Normal | Device | Strongly-ordered |
|---|---|---|---|
| Normal | - | - | - |
| Device | < | < | < |
| Strongly-ordered | < | < | < |
符号说明:
<:必须严格按程序顺序执行-:可以乱序执行ARMv7提供了三种内存屏障指令:
DMB(数据内存屏障):
assembly复制STR R0, [R1] ; 存储数据
DMB ; 确保存储完成
LDR R2, [R3] ; 加载数据
DSB(数据同步屏障):
assembly复制STR R0, [R1] ; 修改页表项
DSB ; 确保修改完成
ISB ; 清空流水线
ISB(指令同步屏障):
assembly复制MCR p15, 0, R0, c1, c0, 0 ; 修改SCTLR
ISB ; 确保修改生效
内存屏障可以指定作用域和访问类型:
作用域选项:
访问类型选项:
示例(限制作用域的DMB):
assembly复制DMB ISH ; 内部可共享域的内存屏障
DMB SY ; 全系统内存屏障(SY是Full System的别名)
ARMv7使用"观察者"概念来描述可以访问内存的实体:
每个观察者对内存的访问必须遵循架构定义的一致性规则,特别是在共享内存区域。
在多核系统中,缓存可能导致一致性问题:
数据一致性问题:
assembly复制STR R0, [R1] ; 存储新数据
DMB ; 确保存储对其他核心可见
指令一致性问题:
assembly复制STR R0, [R1] ; 写入新指令
DSB ; 确保存储完成
ISB ; 清空流水线
访问外设寄存器时,必须使用正确的内存屏障:
assembly复制; 初始化设备
STR R0, [R1] ; 写入控制寄存器
DSB ; 确保配置生效前不执行后续操作
; 等待设备完成
poll_loop:
LDR R2, [R3] ; 读取状态寄存器
TST R2, #1 ; 检查完成位
BEQ poll_loop
多核间共享数据需要谨慎处理:
assembly复制; 核心A - 发布数据
STR R0, [R1] ; 写入数据
DMB ; 确保数据写入对其他核心可见
STR R1, [flag] ; 设置标志位
; 核心B - 读取数据
wait_flag:
LDR R2, [flag]
CMP R2, #0
BEQ wait_flag
DMB ; 确保标志读取在数据读取之前
LDR R3, [R1] ; 读取数据
动态生成代码时需要严格顺序:
assembly复制; 写入新指令
STR R0, [R1] ; 第一条指令
STR R2, [R1, #4]; 第二条指令
DSB ; 确保写入完成
ISB ; 清空流水线
BX R1 ; 跳转到新代码
症状:
排查步骤:
最小化屏障使用:
内存布局优化:
屏障指令选择:
虽然本文聚焦ARMv7,但了解与ARMv8的主要区别很有价值:
内存类型:
屏障指令:
多核一致性:
在实际项目中,理解这些内存访问顺序规则和正确使用内存屏障,是开发稳定、高效嵌入式系统的关键。特别是在实时系统、设备驱动和多核应用中,精确控制内存可见性往往能避免最棘手的并发问题。