ARM 世界有点混乱。
对于 C 程序员来说,事情很简单:所有 ARM 架构都提供了一个常规的 32 位平面寻址编程模型。只要您使用 C 源代码,您可能会看到的唯一区别是字节顺序和性能。大多数 ARM 处理器(甚至是旧型号)都可以是 big-endian 和 little-endian;然后由逻辑板和操作系统做出选择。好的 C 代码是端中立的:无论平台端如何,它都能正确编译和工作(端中立有利于可靠性和可维护性,但也有利于性能:非中立代码是通过不同大小的指针访问相同数据的代码,这对编译器用于优化代码的严格别名规则造成严重破坏)。
如果您考虑二进制兼容性(即重用已编译一次的代码),情况就完全不同了:
- 有几个指令集:
- 带有 26 位程序计数器的原始 ARM 指令集(非常古老,现在不太可能遇到)
- 带有 32 位程序计数器的 ARM 指令集(通常称为“ARM 代码”)
- Thumb 指令集(16 位简化操作码)
- Thumb-2 指令集(带扩展的 Thumb)
一个给定的处理器可以实现几个指令集。最新的只知道 ARM 代码的处理器是 StrongARM,它是 ARMv4 的代表,已经很老了(15 年)。ARM7TDMI(ARMv4T 架构)既了解 ARM 又了解 Thumb,几乎所有后续的 ARM 系统都了解,除了 Cortex-M。ARM 和 Thumb 代码可以在同一个应用程序中混合在一起,只要在约定改变的地方插入适当的胶水;这称为拇指互通,可以由 C 编译器自动处理。
Cortex-M0 只知道 Thumb 指令。它知道一些扩展,因为在“普通”ARM 处理器中,操作系统必须使用 ARM 代码(用于处理中断);因此,Cortex-M0 知道一些 Thumb-for-OS 的东西。这对于应用程序代码无关紧要。
另一个 Cortex-M 只知道 Thumb-2。Thumb-2大多向后兼容 Thumb,至少在汇编级别上是这样。
因此,如果使用编译器开关编译某些代码,告知这是针对 ARMv6 的,那么编译器可能会使用 ARMv6 的少数指令之一,但不会使用 ARMv5。这是一种常见情况,几乎在所有平台上都会遇到:例如,如果您在 PC 上使用 GCC 使用-march=core2
标志编译 C 代码,则生成的二进制文件可能无法在较旧的 Pentium 处理器上运行。
调用约定是一组规则,指定函数如何交换参数和返回值。处理器只知道它的寄存器,没有堆栈的概念。调用约定说明参数在哪些寄存器中,以及它们是如何编码的(例如,如果有一个char
参数,它会进入寄存器的低 8 位,但调用者是否应该清除/符号扩展高 24 位,或不 ?)。它描述了堆栈结构和对齐方式。它标准化结构字段的对齐条件和填充。
ARM 有两个主要约定,称为 ATPCS(旧)和 AAPCS(新)。它们在浮点值的主题上有很大的不同。对于整数参数,它们大多相同(但 AAPCS 需要更严格的堆栈对齐)。当然,约定因指令集和 Thumb 互通的存在而异。
在某些情况下,可能会有一些同时符合 ATPCS 和 AAPCS 的二进制代码,但这并不可靠,并且不匹配时不会发出警告。所以底线是:你不能在使用不同调用约定的系统之间实现真正的二进制兼容性。
ARM 体系结构可以使用可选元素进行扩展,这些元素将自己的指令添加到核心指令集中。FPU 就是这样一个可选的协处理器(在实践中很少遇到)。另一个协处理器是 NEON,它是一些较新的 ARM 处理器上的 SIMD 指令集。
使用协处理器的代码不会在没有协处理器的处理器上运行,除非操作系统捕获相应的操作码并在软件中模拟协处理器(使用 ATPCS 调用时浮点参数或多或少会发生这种情况约定,而且速度很慢)。
总而言之,如果你有 C 代码,那么重新编译它。不要尝试重用为其他架构或系统编译的代码。