13

在 Cortex-M3 指令集中,存在一系列 LDREX/STREX 指令,因此如果使用 LDREX 指令读取某个位置,则只有在已知该地址未被触及的情况下,后续的 STREX 指令才能写入该地址。通常,如果自 LDREX 以来没有发生中断(ARM 术语中的“异常”),则 STREX 将成功,否则将失败。

在 Cortex M0 中模拟这种行为的最实用方法是什么?我想为 M3 编写 C 代码并将其移植到 M0。在 M3 上,可以这样说:

__inline void do_inc(unsigned int *dat)
{
  而(__strex(__ldrex(dat)+1,dat)){}
}

执行原子增量。我能想到的在 Cortex-M0 上实现类似功能的唯一方法是:

  1. 让“ldrex”禁用异常并让“strex”和“clrex”重新启用它们,要求每个“ldrex”之后必须紧跟“strex”或“clrex”。
  2. 让“ldrex”、“strex”和“clrex”成为 RAM 中的一个非常小的例程,其中一条“ldrex”指令被修补到“str r1,[r2]”或“mov r0,#1”。让“ldrex”例程将“str”指令插入“strex”例程,并让“clrex”例程在那里插入“mov r0,#1”。让所有可能使“ldrex”序列无效的异常调用“clrex”。

根据 ldrex/strex 函数的使用方式,禁用中断可能会合理地工作,但更改“load-exclusive”的语义似乎很棘手,以便在放弃时造成不良的副作用。代码修补的想法似乎可以实现所需的语义,但看起来很笨拙。

(顺便说一句,附带问题:我想知道为什么 M3 上的 STREX 将成功/失败指示存储到寄存器而不是简单地设置一个标志?它的实际操作需要操作码中的四个额外位,需要一个寄存器来保存成功/failure 指示,并要求使用“cmp r0,#0”来确定它是否成功。如果编译器没有在寄存器中获得结果,是否可以合理地处理 STREX 内在函数? ? 进入寄存器需要两个简短的指令。)

4

4 回答 4

6

嗯......你还有SWP剩余,但它是一个不太强大的原子指令。

不过,中断禁用肯定会起作用。:-)

编辑:

-m0 上没有 SWP,对不起 supercat。

好的,看来您只剩下禁用中断了。您可以使用 gcc 可编译的内联 asm 作为指导如何禁用和正确恢复它: http ://repo.or.cz/w/cbaos.git/blob/HEAD:/arch/arm-cortex-m0/include/锁.h

于 2011-04-25T10:22:24.547 回答
3

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 and Store Conditional,并且它还实现了比较和交换语义。

如果您的代码仅使用比较和交换就可以正常工作,您可以像这样为 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 序列,以及随后的操作需要标志:编译器必须将标志移动到寄存器,无论如何都需要额外的指令。

于 2015-02-05T22:53:54.360 回答
0

即使官方 ARM v6M 规范强烈建议将 HardFault 异常视为致命异常并在不离开处理程序上下文的情况下保持或重置芯片,您也可以在 HardFault 句柄中模拟 Cortex M0(+) 内核上丢失的指令,然后再返回到错误指令后.

m0FaultDispatch (ab)提供的示例代码使用此功能来模拟其他缺失的指令(整数除法)。除非您非常小心并且知道芯片上出现 HardFault 的所有可能原因,否则这样的仿真可能会隐藏其他有效的 HardFault 原因,让您的代码继续进入未知领域。

并且任何仿真都无法与 ARM v7M 芯片上的 LDREX/STREX 的预期性能相媲美。

编辑:模拟互斥监视器需要使用 MPU 处理程序(又称为 HardFault)包装所有其他异常,一些更正常形式的蹦床代码,或为所有中断处理程序添加显式支持。

于 2022-01-03T16:21:45.267 回答
-2

STREX/LDREX 用于多核处理器访问跨内核共享的内存中的共享项目。ARM 在记录这一点方面做得非常糟糕,您必须阅读 amba/axi 和 arm 和 trm 文档中的行之间的内容才能弄清楚这一点。

它的工作原理是,如果您有一个支持 STREX/LDREX 的内核,并且如果您有一个支持独占访问的内存控制器,那么如果内存控制器看到这对独占操作而没有其他内核访问该内存,那么您返回 EX_OKAY 而不是比OKAY。arm 文档告诉芯片设计人员它是否是单处理器(未实现多核功能),那么您不必支持 exokay 只需返回 OK,从软件的角度来看,这会破坏 LDREX/STREX 对以进行访问该逻辑的访问(软件在无限循环中旋转,因为它永远不会返回成功),但 L1 缓存确实支持它,所以感觉它可以工作。

对于单处理器和不访问跨内核共享内存的情况,请使用 SWP。

-m0 不支持 ldrex/strex 也不支持 swp,但那些基本上是什么?他们只是为您提供不受您访问影响的访问权限。为了防止你踩到自己,然后在持续时间内禁用中断,就像我们自黑暗时代以来进行原子访问的方式一样。如果你想保护你和外围设备,如果你有一个可以干扰的外围设备,那么没有办法解决这个问题,甚至交换也可能没有帮助。

所以只需禁用关键部分周围的中断。

于 2014-03-14T17:12:38.610 回答