在Cortex-M3指令集中,存在一系列LDREX / STREX指令,如果使用LDREX指令读取某个位置,则只有在已知该地址的情况下,以下STREX指令才能写入该地址。不变。通常,效果是如果自LDREX以来没有发生中断(ARM术语中的“异常”),则STREX将成功,否则失败。
在Cortex M0中模拟此类行为的最实用方法是什么?我想为M3编写C代码并将其移植到M0。在M3上,人们可以这样说:
__inline void do_inc(unsigned int *dat) { while(__strex(__ldrex(dat)+1,dat)) {} }
执行原子增量。我能想到在Cortex-M0上实现类似功能的唯一方法是:
根据ldrex / strex函数的使用方式,禁用中断可能会合理地工作,但是改变“load-exclusive”的语义似乎很糟糕,如果它被放弃会导致不良副作用。代码修补的想法似乎会达到理想的语义,但看起来很笨拙。
(顺便说一句,旁边的问题:我想知道为什么M3上的STREX将成功/失败指示存储到寄存器而不是简单地设置一个标志?它的实际操作需要操作码中的四个额外位,要求寄存器可用于保持成功/失败指示,并要求使用“cmp r0,#0”来确定它是否成功。如果没有得到结果,编译器是否会无法理智地处理STREX内在函数在寄存器中?进入寄存器需要两个简短的指示。)
答案 0 :(得分:3)
嗯......你还剩下 SWP
,但这是一个不太强大的原子指令。
中断禁用肯定会起作用。 : - )
编辑:
-m0上没有SWP,抱歉超级猫。
好的,似乎你只剩下中断禁用了。 您可以使用gcc-compilable inline asm作为指导如何禁用并正确恢复它: http://repo.or.cz/w/cbaos.git/blob/HEAD:/arch/arm-cortex-m0/include/lock.h
答案 1 :(得分:1)
Cortex-M3设计用于重低延迟和低抖动的多任务处理,即它的中断控制器与内核协同工作,以保证自中断触发到中断处理以来的周期数。 ldrex / strex被实现为一种与所有这些(我所说的中断屏蔽和其他细节,例如通过位带别名的原子位设置)合作的方式,作为单核,非MMU,非缓存μC否则将几乎没有用它。如果它没有实现它,低优先级任务将必须保持锁定并且可以产生小的优先级反转,产生延迟和抖动,这是硬实时系统(它的设计为此,尽管概念太宽泛)至少不能处理失败的ldrex / strex所具有的“重试”语义所允许的数量级。
另一方面,严格讲述时序和抖动,Cortex-M0具有更传统的中断时序配置文件(即当中断到达时它不会中止核心上的指令),受到更多限制抖动和延迟。在这个问题上(同样,严格的时间),它更像旧的模型(即arm7tdmi),它也缺乏原子载荷/修改/存储以及原子增量&递减和其他低延迟协作指令,需要更频繁地中断禁用/启用。
我在Cortex-M3中使用类似的东西:
#define unlikely(x) __builtin_expect((long)(x),0)
static inline int atomic_LL(volatile void *addr) {
int dest;
__asm__ __volatile__("ldrex %0, [%1]" : "=r" (dest) : "r" (addr));
return dest;
}
static inline int atomic_SC(volatile void *addr, int32_t value) {
int dest;
__asm__ __volatile__("strex %0, %2, [%1]" :
"=&r" (dest) : "r" (addr), "r" (value) : "memory");
return dest;
}
/**
* atomic Compare And Swap
* @param addr Address
* @param expected Expected value in *addr
* @param store Value to be stored, if (*addr == expected).
* @return 0 ok, 1 failure.
*/
static inline int atomic_CAS(volatile void *addr, int32_t expected,
int32_t store) {
int ret;
do {
if (unlikely(atomic_LL(addr) != expected))
return 1;
} while (unlikely((ret = atomic_SC(addr, store))));
return ret;
}
换句话说,它将ldrex / strex转换为众所周知的Linked-Load和Store Conditional,并且它还实现了Compare-and-Swap语义。
如果您的代码仅使用compare-and-swap完成,您可以像下面这样为cortex-m0实现它:
static inline int atomic_CAS(volatile void *addr, int32_t expected,
int32_t store) {
int ret = 1;
__interrupt_disable();
if (*(volatile uint32_t *)addr) == expected) {
*addr = store;
ret = 0;
}
__interrupt_enable();
return ret;
}
这是最常用的模式,因为有些架构只有它(x86浮现在脑海中)。通过CAS实现LL / SC模式的仿真从我的立场看起来很难看。特别是当SC除LL之外的多个指令时,虽然非常常见,但ARM并不特别推荐它在Cortex-M3情况下,因为任何中断会使strex失败,如果你开始在ldrex / strex之间花费太长时间,你的代码将在循环重试strex中花费大量时间。这是滥用模式,而不是使用它。
至于你的方面问题,在cortex-m3情况下,strex在寄存器中返回,因为语义已经由更高级别的架构定义(strex / ldrex存在于armv7-m之前实现的多核臂中定义,之后,缓存控制器实际检查ldrex / strex地址,即只有当缓存无法证明数据线的地址点未被修改时,strex才会失败)。
如果我要推测,我会说这是因为在早期这种原子被设计在库中思考:你将在汇编程序中实现的函数返回成功/失败,这需要尊重ABI和其中大多数(我所知道的都是)使用寄存器或堆栈而不是标志来返回值。也可能是因为编译器在使用寄存器着色方面比在某些其他指令使用它时破坏标志更好,即考虑生成标志的复杂操作,并且在其中间你有ldrex / strex序列,并且操作之后出现需要标志:编译器无论如何都必须将标志移动到寄存器中。
答案 2 :(得分:-1)
STREX / LDREX用于访问内核中共享的共享项的多核处理器。 ARM做了非常糟糕的记录工作,你必须在amba / axi和arm和trm文档中读取这些内容来解决这个问题。
如何工作如果你有一个支持STREX / LDREX的核心,如果你有一个支持独占访问的内存控制器,那么如果内存控制器看到一对独占操作而没有其他核心访问那个内存,那么你返回EX_OKAY而不是OKAY。 arm文档告诉芯片设计者它是否是一个单处理器(没有实现多核功能)然后你不必支持exokay只是返回没问题,从软件的角度来看,打破LDREX / STREX对的访问达到那个逻辑(软件)在无限循环中旋转,因为它永远不会返回成功),L1缓存确实支持它,所以感觉就像它有效。
对于单处理器以及在不访问内核共享内存的情况下使用SWP。
-m0不支持ldrex / strex也不支持swp,但那些基本上是什么?他们只是为您提供不受您访问影响的访问权限。为了防止你踩到自己,然后在持续时间内禁用中断,这是我们从黑暗时代开始进行原子访问的方式。如果你想要保护你和外围设备,如果你有一个可以干扰的外围设备,那么就没有办法解决这个问题,即使交换也没有帮助。
所以只需禁用关键部分周围的中断。