Cortex-M33 LDREX/STREX原子RMW操作STREX返回1问题咨询
问题背景
- 开发环境:基于ARM Cortex-M33器件开展开发,目标器件无原生位带(bit-banding)支持,需要自定义实现原子读-改-写(RMW)位操作例程
- 故障表现:使用-O0优化等级(关闭编译优化)编译代码时,编译器会在
__LDREXW与__STREXW指令之间插入标准栈存储(STR)指令,导致STREX指令持续返回1(操作失败),最终触发无限重试死循环 - 原始故障实现代码:
/* Atomic Bit Set using Exclusive Load/Store Primitives */ void bb_set_bit(volatile uint32_t *addr, uint32_t bit) { uint32_t value; uint32_t status; do { value = __LDREXW(addr); value |= (1UL << bit); status = __STREXW(value, addr); } while (status != 0); }
核心技术问题解答
STREX返回状态值1的行为逻辑
Cortex-M33的独占访问机制依赖本地独占监视器标记独占访问权限合法性:
- 执行
LDREX类独占加载指令时,硬件会将本地独占监视器置为有效状态,标记当前执行流持有对应地址区间的独占访问权限 - 只要在
LDREX和配对的STREX之间出现任意以下情况,独占监视器会被立刻清除为无效状态:- 执行了任意非独占的内存存储指令(包括对栈空间的STR写入、对其他任意地址的普通存储操作)
- 触发异常进入中断服务流程,且中断上下文执行了存储操作或
CLREX指令清除独占标记 - 总线上其他主控设备对当前独占监控的地址区间执行了写入操作
- 当
STREX执行时检测到独占监视器为无效状态,将不会完成目标地址的写入操作,直接返回状态值1代表本次独占访问失败;只有独占标记有效时才会完成写入,返回0代表操作成功。
场景下的死循环判定
该场景下程序会永久卡在重试循环中,根因是-O0优化下编译器不会执行寄存器分配优化,默认将所有局部变量分配到栈空间存储,每次循环的执行流程完全固定:
- 执行
__LDREXW加载目标地址值,置位本地独占监视器为有效状态 - 执行位或运算修改
value后,通过STR指令将value写回栈内存,该操作直接清除独占监视器的有效状态 - 执行
__STREXW尝试写回目标地址,此时已无合法独占权限,固定返回1 - 检测到返回值不为0,回到循环起始位置重复上述流程,永远无法满足退出循环的条件。
修复方案
- 对独占访问区间内使用的局部变量添加
register关键字修饰,强制编译器将这类变量分配到通用寄存器,从根源上避免在LDREX和STREX之间生成栈存储指令,修改后代码如下:
void bb_set_bit(volatile uint32_t *addr, uint32_t bit) { register uint32_t value; register uint32_t status; do { value = __LDREXW(addr); value |= (1UL << bit); status = __STREXW(value, addr); } while (status != 0); }
- 添加编译器屏障约束,阻止编译器在独占加载和独占存储指令之间插入任意内存操作,GCC/Arm Compiler环境下可在
LDREX调用前插入asm volatile("" ::: "memory")实现约束。 - 注意:-O1及以上优化等级下,编译器默认会将短生命周期的局部变量分配到寄存器,不会触发该问题,但生产级代码不能依赖优化等级规避问题,必须显式添加变量修饰或编译屏障做强制约束。
内容的提问来源于stack exchange,提问作者user32852102
相关产品推荐
相关产品推荐

