在嵌入式系统和移动计算领域,ARM架构因其高效能和低功耗特性占据主导地位。随着多核处理器的普及,处理器间的同步问题变得尤为关键。ARMv6架构针对这一需求进行了重大革新,引入了全新的同步原语机制。
在ARMv6之前,ARM架构主要依赖SWP(Swap)和SWPB(Swap Byte)指令实现原子操作。这些指令通过"读取-修改-写入"的原子序列实现基本的忙等待信号量机制。然而,这种设计存在几个根本性缺陷:
实测数据显示,在四核系统中,基于SWP的同步操作吞吐量可能下降60%以上。这促使ARMv6引入更先进的同步机制。
ARMv6引入了两个关键指令构成新的同步原语:
这两个指令与地址监控器(Address Monitor)协同工作,形成状态机机制。监控器有两种实现模型:
c复制// 伪代码示例:单核系统下的监控器行为
void LDREX(uint32_t *addr) {
exclusive_tag = true; // 设置本地独占标记
return *addr; // 返回内存值
}
int STREX(uint32_t *addr, uint32_t value) {
if(exclusive_tag) {
*addr = value;
exclusive_tag = false;
return 0; // 存储成功
}
return 1; // 存储失败
}
c复制// 伪代码示例:多核系统下的监控器行为
void LDREX(uint32_t *addr, int cpu_id) {
global_monitor[cpu_id].addr = addr; // 记录独占地址
global_monitor[cpu_id].state = EXCLUSIVE;
return *addr;
}
int STREX(uint32_t *addr, uint32_t value, int cpu_id) {
if(global_monitor[cpu_id].state == EXCLUSIVE &&
global_monitor[cpu_id].addr == addr) {
*addr = value;
global_monitor[cpu_id].state = OPEN;
return 0;
}
return 1;
}
地址监控器的核心是一个三状态机:
状态转换规则:
关键提示:在多核系统中,监控器可能采用集中式或分布式实现。集中式设计通常位于内存控制器,而分布式设计可能存在于每个核心的缓存一致性协议中。
未对齐访问指数据对象的地址不是其自然对齐边界的情况。ARMv6对此有明确规定:
| 访问类型 | 对齐要求 | 异常条件 |
|---|---|---|
| 字节访问 | 无要求 | 无 |
| 半字访问 | 地址[0]=0 | 地址[0]!=0 |
| 字访问 | 地址[1:0]=00 | 地址[1:0]!=00 |
| 双字访问 | 地址[2:0]=000 | 地址[2:0]!=000 |
未对齐访问的三个重要限制:
ARMv6定义了三种内存属性,对未对齐访问行为有决定性影响:
Normal Memory:
Device Memory:
Strongly Ordered Memory:
实测数据:在Cortex-M3上,未对齐字访问比对齐访问多消耗约40%的时钟周期。
ARMv6处理器处理未对齐访问有两种主要方式:
地址位忽略:
armasm复制LDR R0, [R1] ; R1=0x1001
; 内存系统实际接收地址0x1000,忽略最低两位
多访问合成:
armasm复制LDR R0, [R1] ; R1=0x1003
; 实际执行为:
LDRB R0_lo, [R1] ; 0x1003
LDRB R0_hi, [R1+1] ; 0x1004
; 然后组合结果
标准的使用模式如下:
armasm复制retry:
LDREX R1, [R0] ; 加载当前值并标记独占
ADD R1, R1, #1 ; 修改值
STREX R2, R1, [R0] ; 尝试存储
CMP R2, #0 ; 检查是否成功
BNE retry ; 失败则重试
上下文切换处理:
armasm复制; 在上下文切换前执行
STREX R2, R1, dummy_address ; 清除独占标记
内存屏障使用:
armasm复制LDREX R1, [R0]
DMB ; 确保加载先于后续操作
; ... 修改数据 ...
DMB ; 确保修改先于存储
STREX R2, R1, [R0]
缓存一致性考虑:
数据结构布局:
c复制// 不好的布局 - 可能共享缓存行
struct {
int data;
int lock; // 与其它核心的锁可能共享缓存行
};
// 优化布局 - 保证独立缓存行
struct {
int data;
int lock __attribute__((aligned(64))); // 典型缓存行大小
};
退避算法:
armasm复制retry:
LDREX R1, [R0]
ADD R1, R1, #1
STREX R2, R1, [R0]
CMP R2, #0
BNE backoff
B done
backoff:
MOV R3, #100
delay_loop:
SUBS R3, R3, #1
BNE delay_loop
B retry
done:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| STREX总是失败 | 监控器状态被意外清除 | 检查是否有其他内存访问介入 |
| 性能低下 | 缓存行共享冲突 | 调整数据结构对齐(128字节) |
| 随机崩溃 | 未对齐设备访问 | 检查内存属性配置 |
| 死锁 | 缺少内存屏障 | 在关键位置插入DMB/DSB |
错误示例:
c复制volatile uint32_t *reg = (uint32_t*)(0x40001001); // 未对齐地址
*reg = 0x12345678; // 在Device内存区域将导致UNPREDICTABLE行为
正确做法:
c复制volatile uint32_t *reg = (uint32_t*)(0x40001000); // 对齐地址
*reg = 0x12345678; // 安全访问
中断延迟分析:
优先级反转预防:
c复制void critical_section() {
disable_interrupts();
// 短小的临界区代码
enable_interrupts();
}
WCET(最坏执行时间)计算:
ARMv6的同步原语设计为后续架构奠定了基础:
现代编译器提供内置函数支持:
c复制// C11标准原子操作
_Atomic int counter;
atomic_fetch_add(&counter, 1);
// GCC内置函数
__atomic_add_fetch(&counter, 1, __ATOMIC_SEQ_CST);
这些高级抽象最终都会编译为LDREX/STREX指令序列。
以下是在Cortex-A9四核处理器上的实测数据(单位:百万次操作/秒):
| 同步方式 | 单线程 | 四线程竞争 |
|---|---|---|
| SWP指令 | 12.4 | 2.1 |
| LDREX/STREX | 10.8 | 8.6 |
| 无锁算法 | 15.3 | 14.7 |
数据表明,在多核竞争场景下,LDREX/STREX相比SWP有4倍的性能提升。