Assembly 在Cortex-M0中模拟LDREX/STREX(加载/存储专用)

Assembly 在Cortex-M0中模拟LDREX/STREX(加载/存储专用),assembly,arm,atomic,cortex-m,Assembly,Arm,Atomic,Cortex M,在Cortex-M3指令集中,存在一系列LDREX/STREX指令,因此,如果使用LDREX指令读取某个位置,则只有在已知该地址未被触及的情况下,后续STREX指令才能写入该地址。通常,其效果是,如果自LDREX以来没有发生中断(“ARM术语中的异常”),STREX将成功,否则将失败 在大脑皮层M0中模拟这种行为最实际的方法是什么?我想为M3编写C代码,并将其移植到M0。在M3上,可以这样说: __inline void do_inc(unsigned int *dat) { while(_

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

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

__inline void do_inc(unsigned int *dat) { while(__strex(__ldrex(dat)+1,dat)) {} } __内联void do_inc(未签名整数*dat) { 而(uu-strex(u-ldrex(dat)+1,dat)){} } 执行原子增量。我能想到的在Cortex-M0上实现类似功能的唯一方法是:

  • 让“ldrex”禁用异常,让“strex”和“clrex”重新启用它们,要求每个“ldrex”之后必须紧接着一个“strex”或“clrex”。
  • 让“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会将成功/失败指示存储到寄存器中,而不是简单地设置一个标志?它的实际操作需要操作码中的四个额外位,需要一个寄存器来保存成功/失败指示,并且需要一个“cmp r0,#0”用于确定它是否成功。如果没有在寄存器中得到结果,是否预期编译器将无法明智地处理STREX内在函数?将进位放入寄存器需要两个简短的指令。)

    那么。。。您仍然有
    SWP
    剩余,但它是一个功能较弱的原子指令

    尽管如此,中断禁用仍能正常工作。:-)

    编辑:

    没有SWP在-m0上,对不起,超级猫

    好吧,看来你只剩下中断禁用了。 您可以使用gcc可编译内联asm作为如何禁用和正确恢复它的指南:

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

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

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

    m0既不支持ldrex/strex,也不支持swp,但它们基本上给了你什么?他们只是给你一个访问权限,而不受你访问权限的影响。为了防止你踩自己的脚,只需在这段时间内禁用中断,就像我们自黑暗时代以来进行原子访问的方式一样。如果你想保护自己,如果你的外设可能会干扰,那么就没有办法绕过它,甚至交换也可能没有帮助


    因此,只需禁用关键部分周围的中断。

    Cortex-M3设计用于重低延迟和低抖动的多任务处理,即它的中断控制器与内核协作,以保证从中断触发到中断处理的周期数。ldrex/strex的实现是为了配合所有这些(我指的是中断屏蔽和其他细节,例如通过位带别名设置原子位),否则单核、非MMU、非缓存µC几乎没有用处。如果它没有实现它,低优先级任务将不得不持有一个锁,tha可能会产生小的优先级反转,产生延迟和抖动,这是硬实时系统(它的设计,虽然概念过于宽泛)无法应付的,至少不在“重试”允许的数量级内失败的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引入众所周知的链接加载和存储条件,并使用它实现比较和交换语义

    如果您的代码只在比较和交换方面做得很好,您可以在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;
    }
    
    那是最常用的