我看到这些关键部分存在几个潜在问题。所有这些都有一些警告和解决方案,但作为总结:
- 出于优化或随机其他原因,没有什么可以阻止编译器在这些宏之间移动代码。
- 它们保存和恢复编译器期望内联汇编保持不变的处理器状态的某些部分(除非另有说明)。
- 没有什么可以阻止在序列中间发生中断并在读取和写入之间更改状态。
首先,您肯定需要一些编译器内存屏障。GCC 将这些实现为clobbers。基本上,这是一种告诉编译器“不,你不能在这段内联汇编中移动内存访问,因为它可能会影响内存访问的结果”的一种方式。具体来说,在 begin 和 end 宏上都需要"memory"
和"cc"
clobbers。这些也将防止其他事物(如函数调用)相对于内联程序集被重新排序,因为编译器知道它们可能具有内存访问权限。我已经看到用于 ARM 的 GCC 在带有"memory"
clobbers 的内联汇编中的条件代码寄存器中保持状态,所以你肯定需要"cc"
clobber。
其次,这些关键部分的保存和恢复不仅仅是中断是否启用。具体来说,他们正在保存和恢复大部分CPSR(当前程序状态寄存器)(链接适用于 Cortex-R4,因为我找不到 A9 的漂亮图表,但它应该是相同的)。实际可以修改哪些状态有一些微妙的限制,但在这里它是不必要的。
除其他外,这包括条件代码(其中cmp
存储了类似指令的结果,以便后续条件指令可以对结果进行操作)。编译器肯定会对此感到困惑。"cc"
使用上面提到的clobber很容易解决这个问题。但是,这会使代码每次都失败,因此听起来不像您看到的问题。虽然有点像定时炸弹,因为修改随机其他代码可能会导致编译器做一些不同的事情,这将被打破。
这还将尝试保存/恢复用于实现 Thumb 条件执行的 IT 位。请注意,如果您从不执行 Thumb 代码,这无关紧要。我从来没有弄清楚 GCC 的内联汇编是如何处理 IT 位的,除了得出的结论之外,这意味着编译器决不能将内联汇编放在 IT 块中,并且总是期望汇编在 IT 块之外结束。我从未见过 GCC 生成违反这些假设的代码,而且我已经做了一些相当复杂的内联汇编并进行了大量优化,所以我有理由相信它们是成立的。这意味着它可能实际上不会尝试更改 IT 位,在这种情况下一切都很好。尝试修改这些位被归类为“架构不可预测”,所以它可以做各种坏事,但可能根本不会做任何事情。
最后一类将被保存/恢复的位(除了实际禁用中断的位)是模式位。这些可能不会改变,因此可能无关紧要,但是如果您有任何故意更改模式的代码,这些中断部分可能会导致问题。在特权模式和用户模式之间切换是我期望的唯一情况。
第三,没有什么能阻止中断在MRS
和MSR
in之间改变 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 版本(如我的)默认使用不支持cpsie
和cpsid
.
我使用了 inands
而不是and
inARM_INT_LOCK
所以如果在 Thumb 代码中使用它是一个 16 位指令。无论如何,"cc"
clobber 都是必要的,所以它严格来说是性能/代码大小的好处。
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 中的任何其他状态位,那么您也可以使用 continue 与您的原始代码非常相似的东西,除了两者都使用"memory"
和"cc"
clobbersARM_INT_LOCK
和ARM_INT_UNLOCK