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

fcipmucu  于 2023-11-19  发布在  其他
关注(0)|答案(4)|浏览(128)

在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上实现类似功能的唯一方法是:
1.让“ldrex”禁用异常,让“strex”和“clrex”重新启用它们,要求每个“ldrex”后面必须紧跟着“strex”或“clrex”。
1.让“ldrex”、“strex”和“clrex”是RAM中的非常小的例程,其中一个“ldrex”指令被修补为“str r1,[r2]”或“mov r 0,#1”。让“ldrex”例程将“str”指令插入“strex”例程,并让“clrex”例程插入“mov r 0,#1”。#1”在那里。有可能使“ldrex”序列调用“clrex”无效的所有异常。
根据ldrex/strex函数的使用方式,禁用中断可能会合理地工作,但如果放弃它,更改“加载独占”的语义会导致不良的副作用,这似乎是令人讨厌的。代码修补的想法似乎可以实现所需的语义,但似乎很笨拙。
(BTW,附带问题:我想知道为什么M3上的STREX将成功/失败指示存储到寄存器中,而不是简单地设置一个标志?其实际操作需要操作码中的四个额外位,需要寄存器可用于保存成功/失败指示,并且需要“cmp r 0,如果编译器没有在寄存器中得到结果,那么编译器是否会被认为不能合理地处理STREX内部变量?将进位存入寄存器需要两条简短的指令。)

6kkfgxo0

6kkfgxo01#

你仍然有SWP剩余,但它是一个不太强大的原子指令。
不过,禁用JavaScript肯定会起作用。:-)
编辑:
没有SWP-m0,对不起,超能猫。
好吧,看来你只剩下中断禁用了。你可以使用gcc可编译的内联asm作为如何禁用和正确恢复它的指南:http://repo.or.cz/w/cbaos.git/blob/HEAD:/arch/arm-cortex-m0/include/lock.h

vngu2lb8

vngu2lb82#

Cortex-M3被设计为高延迟和低抖动的多任务处理,即它的中断控制器与核心合作,以保证从中断触发到中断处理的周期数。ldrex/strex被实现为与所有这些合作的一种方式。(我的意思是中断屏蔽和其他细节,例如通过位带别名设置原子位),否则,单核,非MMU,非缓存的μC几乎没有用处。但是,如果它不实现它,低优先级任务将不得不持有锁,这可能会产生小的优先级反转,延迟和抖动是硬真实的时间系统无法科普的,至少不在失败的LDREX/ STREX具有的“重试”语义所允许的数量级内。
顺便说一下,严格地说,在时序和抖动方面,Cortex-M0具有更传统的中断时序配置文件(即,当中断到达时,它不会中止核心上的指令),受到更多抖动和延迟的影响。(同样,严格计时),它更具有可比性的老型号(即ARM/TDMI),其也缺少原子加载/修改/存储以及原子递增和递减以及其它低等待时间协作指令,需要更频繁地中断禁用/启用。
我在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中,并使用它实现了Compare和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之前实现的多核arm中,并且在定义armv7-m之后,在该高速缓存控制器实际检查ldrex/strex地址的情况下,即,只有当该高速缓存控制器不能证明加载/存储所触及的数据线未被修改时,Strex才失败)。
如果要我推测的话,我会说最初的设计有这种语义,因为在早期,这种原子是在库中设计的:你会在汇编程序中实现的函数中返回成功/失败,这需要尊重ABI,并且大多数(我所知道的)使用寄存器或堆栈,而不是标志,来返回值。
此外,编译器在使用寄存器着色时要比在其他指令使用它时破坏标志更好,即考虑一个生成标志的复杂操作,在它的中间你有一个ldrex/strex序列,之后的操作需要标志:编译器必须将标志移动到寄存器,无论如何都需要额外的指令。

yb3bgrhw

yb3bgrhw3#

您可以在返回到故障指令后之前在HardFault句柄中模拟Cortex M0(+)内核上的丢失指令,尽管官方ARM v6 M规范强烈建议将HardFault异常视为致命异常,并在不离开处理程序上下文的情况下保持或重置芯片。
m0FaultDispatch(ab)提供的示例代码使用此功能来模拟另一个丢失的指令(整数除法)。除非您非常小心并且知道芯片上所有可能的HardFault原因,否则这种模拟可能会隐藏其他有效的HardFault原因,让您的代码继续进入未知的沃茨。
而且没有任何仿真可以接近LDREX/STREX在ARM v7 M芯片上的预期性能。
编辑:模拟互斥监视器需要使用MPU处理程序(又称为HardFault) Package 所有其他异常,这是一些更正常的蹦床代码形式,或者为所有中断处理程序添加显式支持。

kqqjbcuj

kqqjbcuj4#

STREX/LDREX是用于多核处理器访问跨核共享的内存中的共享项。ARM在记录这一点方面做得非常糟糕,你必须阅读amba/axi和arm和trm文档中的字里行间才能弄清楚这一点。
它的工作原理是,如果你有一个支持STREX/LDREX的内核,如果你有一个支持独占访问的内存控制器,那么如果内存控制器看到这对独占操作,而没有其他内核访问这对内存,那么你返回EX_OKAY而不是OKAY。(不实现多核功能),那么您不必支持exokay,只需返回okay,从软件的Angular 来看,这会在访问该逻辑时破坏LDREX/STREX对(软件在无限循环中旋转,因为它永远不会返回成功),L1缓存确实支持它,所以感觉它很好用。
对于单处理器和不访问内核间共享内存的情况,请使用SWP。
-m0不支持ldrex/strex和swp,但是这些基本上能给你带来什么呢?它们只是让你获得一个不受你访问影响的访问。为了防止你踩到自己,然后在整个过程中禁用中断,这是我们从黑暗时代开始就采用的原子访问方式。如果你想保护自己和外围设备,如果你有一个外围设备可以干扰,嗯,没有办法绕过这一点,甚至交换可能也没有帮助。
因此,只需禁用临界区周围的中断。

相关问题