如何在ARM Cortex A9上实现关键部分


15

我正在将一些旧代码从ARM926内核移植到CortexA9。此代码是裸机代码,不包含操作系统或标准库(全部自定义)。我遇到了与竞态条件有关的故障,应该通过对代码进行严格的分段来避免这种情况。

我想对我的方法提供一些反馈,以了解我的关键部分是否可能未正确为此CPU实施。我正在使用GCC。我怀疑有一些细微的错误。

另外,是否有一个开源库具有针对ARM的这些类型的原语(甚至是一个很好的轻量级的spinlock / semephore库)?

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "orr r1, %[key], #0xC0\n\t"\
    "msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );

#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))

该代码的用法如下:

/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);

<access registers, shared globals, etc...>

ARM_INT_UNLOCK(key);

“键”的想法是允许嵌套的关键部分,这些关键部分在函数的开头和结尾用于创建可重入函数。

谢谢!


1
请参考infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/…请勿在嵌入式asm btw中进行。使它像文章一样起作用。
Jason Hu

我对ARM一无所知,但我希望对于互斥锁(或任何跨线程或跨进程同步功能),您应该使用“内存”遮盖程序来确保a)刷新当前缓存在寄存器中的所有内存值执行asm 之前返回到内存以及b)重新加载asm之后访问的内存中的任何值。请注意,执行呼叫(如HuStmpHrrr建议)应该为您隐式执行此事务。

另外,尽管我仍然不会说ARM,但是您对'key_'的约束看起来并不正确。由于您说要用于重新输入,因此在锁中将其声明为“ = r”似乎很可疑。“ =”表示您打算覆盖它,并且现有值不重要。您似乎更可能打算使用“ +”来表示您打算更新现有值。再一次进行解锁,将其列为输入将告诉gcc您无意更改它,但是如果我没记错的话,您可以(更改)。我猜这也应该列为“ +”输出。

1
+1用于在这样的高规格内核中进行汇编编码。无论如何,这可能与特权模式有关吗?
Dzarda 2014年

我很确定您将需要使用ldrexstrex正确执行此操作。这是一个网页,向您展示如何使用ldrexstrex实现自旋锁。

Answers:


14

在没有操作系统的情况下处理关键部分的最困难部分实际上不是创建互斥体,而是弄清楚如果代码想要使用当前不可用的资源应该发生什么。负载独占和条件存储独占指令使创建“交换”函数相当容易,该函数在给定指向整数的指针的情况下,将自动存储新值,但返回所指向的整数所包含的内容:

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

有了上述功能,就可以通过

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

在没有操作系统的情况下,主要困难通常在于“无法获取互斥体”代码。如果在互斥锁保护的资源繁忙时发生中断,则可能有必要让中断处理代码设置一个标志并保存一些信息以指示其想要执行的操作,然后使用任何类似于main的代码来获取中断。互斥锁将在每次释放互斥锁时进行检查,以查看在保持互斥锁的同时中断是否要执行某项操作,如果是,则代表该中断执行操作。

尽管可以通过简单地禁用中断来避免希望使用互斥锁保护的资源的中断的问题(实际上,禁用中断可以消除​​对任何其他种类的互斥锁的需求),但总的来说,还是希望避免禁用中断的时间超过所需的时间。

一种有用的折衷方法是使用如上所述的标志,但要具有要释放互斥锁禁用中断并在执行此操作之前检查上述标志的主线代码(在释放互斥锁之后重新启用中断)。这种方法不需要长时间禁用中断,但是可以避免以下可能性:如果主线代码在释放互斥锁之后测试了中断的标志,则在看到标志和标志之间的时间之间存在危险。对其进行操作,它可能会被获取并释放互斥锁并根据中断标志起作用的其他代码抢占。如果在释放互斥锁后,主代码未测试中断的标志,

无论如何,最重要的是要有一种方法,当不可用互斥保护的资源时,尝试使用该资源的代码将具有一种一旦资源释放就重复其尝试的方法。


7

这是处理关键部分的繁重方法。禁用中断。如果您的系统有/处理数据故障,则可能无法正常工作。这也会增加中断延迟。在Linux的irqflags.h有一些宏处理这个问题。在cpsiecpsid可能有用的指示; 但是,它们不保存状态,也不允许嵌套。 cps不使用寄存器。

对于Cortex-A系列,ldrex/strex它们效率更高,并且可以形成互斥体为关键部分,或者可以与无算法一起使用以摆脱关键部分。

从某种意义上讲,它ldrex/strex看起来像是ARMv5 swp。但是,它们在实践中实施起来要复杂得多。您需要一个有效的缓存,并且需要在缓存中存储目标内存ldrex/strex。上的ARM文档ldrex/strex非常模糊,因为他们希望机制能在非Cortex-A CPU上工作。但是,对于Cortex-A,使本地CPU缓存与其他CPU保持同步的机制与用于实现ldrex/strex指令的机制相同。对于Cortex-A系列,保留粒度ldrex/strex保留内存的大小)与高速缓存行相同;如果您打算修改多个值(例如使用双链表),则还需要使内存与缓存行对齐。

我怀疑有一些细微的错误。

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

您需要确保该序列永远不会被抢占。否则,您可能会在启用了中断的情况下获得两个关键变量,并且锁释放将不正确。您可以将swp指令与密钥存储器一起使用,以确保ARMv5上的一致性,但是在Cortex-A上不建议使用该指令,ldrex/strex因为它在多CPU系统中更有效。

所有这些取决于您的系统具有哪种调度。听起来您只有主线和中断。您经常需要关键部分原语来使调度程序具有某些挂钩,具体取决于您希望关键部分使用的级别(系统/用户空间/等)。

另外,是否有一个开源库具有针对ARM的这些类型的原语(甚至是一个很好的轻量级的spinlock / semephore库)?

这很难以便携式方式编写。即,对于某些版本的ARM CPU和特定的OS可能存在此类库。


2

我发现这些关键部分存在一些潜在的问题。所有这些都有一些警告和解决方案,但总结如下:

  • 出于优化或其他随机原因,没有什么可以阻止编译器在这些宏之间移动代码。
  • 它们保存并恢复处理器状态的某些部分,编译器希望它们可以内联汇编保持独立(除非另有说明)。
  • 没有什么可以阻止在序列的中间发生中断以及在读取和写入之间改变状态的。

首先,您肯定需要一些编译器内存障碍。GCC将这些实现为掩饰。基本上,这是一种告诉编译器的方法:“不,您不能在此内联汇编程序之间移动内存访问,因为它可能会影响内存访问的结果。” 具体来说,在开始和结束宏上都需要"memory""cc"clobbers。这些也将防止其他事情(例如函数调用)相对于内联程序集重新排序,因为编译器知道它们可能具有内存访问权限。我已经看到使用"memory"Clobber的内联汇编中的条件代码寄存器中的GCC for ARM保持状态,所以您确实需要"cc"

其次,这些关键部分所节省和恢复的内容不仅仅是中断是否被允许。具体来说,他们正在保存和恢复大多数CPSR(当前程序状态寄存器)(该链接用于Cortex-R4,因为我找不到适合A9的图表,但应该相同)。有细微的限制实际上可以修改状态的部分,,但是在这里,这超出了必要。

除其他外,这包括条件代码(将类似指令的结果cmp存储在其中,以便后续的条件指令可以作用于结果)。编译器一定会对此感到困惑。使用以下方法可以轻松解决"cc"如上所述隔板。但是,这会使代码每次都失败,所以听起来好像并没有出现问题。但是,这有点像定时炸弹,因为修改随机的其他代码可能会导致编译器做一些稍有不同的操作,这将因此而被破坏。

这还将尝试保存/恢复用于实现Thumb条件执行的IT位。请注意,如果您从不执行Thumb代码,则无关紧要。除了得出结论,我从未弄清楚GCC的内联汇编如何处理IT位,这意味着编译器绝不能将内联汇编放入IT块中,并且始终希望该汇编结束于IT块之外。我从未见过GCC会生成违反这些假设的代码,并且我已经进行了一些相当复杂的内联汇编并进行了严格的优化,因此我可以肯定地确定它们成立了。这意味着它实际上可能不会尝试更改IT位,在这种情况下,一切都很好。尝试修改这些位为分类为“架构上不可预测”,因此它可能会做各种坏事,但可能根本不会做任何事。

模式位是将要保存/恢复的最后一类位(除了那些实际上禁止中断的位)。这些可能不会更改,因此可能无关紧要,但是,如果您有任何代码故意更改模式,则这些中断部分可能会导致问题。我期望只有在特权模式和用户模式之间进行切换。

第三,没有什么可以阻止中断在MRSMSR中之间更改CPSR的其他部分ARM_INT_LOCK。任何此类更改都可能被覆盖。在大多数合理的系统中,异步中断不会更改被中断代码(包括CPSR)的状态。如果他们这样做了,就很难推理代码将做什么。但是,这是可能的(更改FIQ禁用位对我来说似乎是最有可能的),因此您应该考虑系统是否这样做。

我将以解决我指出的所有潜在问题的方式来实现这些目标:

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "ands %[key], %[key], #0xC0\n\t"\
    "cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
    "tst %[key], #0x40\n\t"\
    "beq 0f\n\t"\
    "cpsie f\n\t"\
    "0: tst %[key], #0x80\n\t"\
    "beq 1f\n\t"\
    "cpsie i\n\t"
    "1:\n\t" :: [key]"r" (key_) : "memory", "cc")

确保进行编译,-mcpu=cortex-a9因为至少某些GCC版本(例如我的)默认为不支持cpsieand 的旧版ARM CPU cpsid

我用ands,而不是仅仅andARM_INT_LOCK所以它如果这是在Thumb代码中使用的16位指令。"cc"无论如何,该垃圾邮件是必需的,因此严格来说,这是性能/代码大小的好处。

0并且1本地标签,以供参考。

它们应该以与您的版本完全相同的方式使用。该ARM_INT_LOCK是一样快/小你原来一个。不幸的是,我无法想出一种方法来ARM_INT_UNLOCK在几乎没有说明的地方进行安全操作。

如果您的系统对何时禁用IRQ和FIQ有限制,则可以简化此操作。例如,如果总是将它们禁用在一起,则可以将其合并为一个cbz+,cpsie if如下所示:

#define ARM_INT_UNLOCK(key_) asm volatile (\
    "cbz %[key], 0f\n\t"\
    "cpsie if\n\t"\
    "0:\n\t" :: [key]"r" (key_) : "memory", "cc")

或者,如果您根本不关心FIQ,则类似于完全放弃启用/禁用它们。

如果你知道,没有别的都没有改变任何其他状态位在锁定和解锁之间CPSR,那么你也可以使用继续非常相似,你的原代码的东西,但经"memory""cc"则会覆盖在这两个ARM_INT_LOCKARM_INT_UNLOCK


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.