我对ARM编程还比较新。我注意到有几种体系结构,如ARMv4、ARMv5、ARMv6等等。它们之间有什么区别?它们是否具有不同的指令集或行为?
最重要的是,如果我将一些C代码编译为ARMv6,它能在ARMv5上运行吗?相反,ARMv5的代码能否在ARMv6上运行?或者我只需要担心差异,如果我写内核汇编代码的话?
我对ARM编程还比较新。我注意到有几种体系结构,如ARMv4、ARMv5、ARMv6等等。它们之间有什么区别?它们是否具有不同的指令集或行为?
最重要的是,如果我将一些C代码编译为ARMv6,它能在ARMv5上运行吗?相反,ARMv5的代码能否在ARMv6上运行?或者我只需要担心差异,如果我写内核汇编代码的话?
ARM世界有些混乱。
对于C程序员来说,事情很简单:所有ARM架构都提供了一个常规的、32位扁平地址编程模型。只要你使用C源代码,你可能看到的唯一区别是字节序和性能。大多数ARM处理器(甚至是旧型号)都可以是大端或小端;这个选择由逻辑板和操作系统决定。好的C代码是"字节序中立"的:它编译并正常工作,不管平台字节序如何(字节序中立有利于可靠性和可维护性,也有利于性能:非中立的代码是通过不同大小的指针访问相同数据的代码,这会破坏编译器用来优化代码的严格别名规则)。
如果考虑二进制兼容性(即重用已经编译过的代码),情况就大不相同:
一个处理器可能实现多个指令集。最新的只知道ARM代码的处理器是StrongARM,它是一个ARMv4代表,已经相当古老了(15年)。ARM7TDMI(ARMv4T架构)既知道ARM又知道Thumb,几乎所有后续的ARM系统也是如此,除了Cortex-M。在同一应用程序中可以混合使用ARM和Thumb代码,只要在约定更改的地方插入适当的粘合剂;这称为"Thumb互操作",可以由C编译器自动处理。
Cortex-M0只知道Thumb指令。它知道一些扩展,因为在"正常的"ARM处理器中,操作系统必须使用ARM代码(用于处理中断);因此,Cortex-M0知道一些针对操作系统的Thumb东西。这对应用程序代码没有影响。
其他 Cortex-M 只支持 Thumb-2 指令集。Thumb-2 在汇编级别上与 Thumb 有很好的向后兼容性。因此,如果使用编译器开关告诉编译器为 ARMv6 编译代码,则编译器可能使用 ARMv6 具有但 ARMv5 没有的指令之一。这是一种常见情况,在几乎所有平台上都会遇到:例如,如果在 PC 上使用 GCC 编译 C 代码,并使用 -march=core2
标志,则生成的二进制文件可能无法在较旧的 Pentium 处理器上运行。
调用约定是一组规则,用于指定函数如何交换参数和返回值。处理器只知道其寄存器,对栈没有任何概念。调用约定告诉我们参数放在哪些寄存器中,以及如何编码它们(例如,如果有一个 char 参数,则它放在寄存器的低 8 位中,但调用者应该清除/扩展寄存器的高 24 位,还是不用?)它描述了堆栈结构和对齐方式。它将结构字段的对齐条件规范化,并进行填充。
ARM 有两种主要的调用约定,称为 ATPCS(旧)和 AAPCS(新)。它们在浮点值方面相当不同。对于整数参数,它们大多数相同(但 AAPCS 需要更严格的堆栈对齐)。当然,约定因指令集和 Thumb 交互的存在而异。
在某些情况下,可能会有一些遵守 ATPCS 和 AAPCS 两种调用约定的二进制代码,但这并不可靠,并且没有不匹配的警告。所以底线是:使用不同的调用约定的系统之间不能具有真正的二进制兼容性。
ARM架构可以添加可选元素来扩展其指令集,这些元素会向核心指令集中添加自己的指令。FPU就是这样一个可选的协处理器(但实际很少遇到)。另一个协处理器是NEON,它是一种SIMD指令集,存在于一些较新的ARM处理器上。
使用协处理器的代码将无法在不带有该协处理器的处理器上运行,除非操作系统拦截相应的操作码并在软件中模拟协处理器(这与使用ATPCS调用约定时浮点参数发生的情况几乎相同,并且速度慢)。
总之,如果您有C代码,则重新编译它。不要尝试重用为另一种架构或系统编译的代码。
对于芯片的ARM部分,除了ARM ARM之外还有TRMs(技术参考手册)。但是,如果您获取了错误的组件TRM,则可能会让您感到头痛。 TRM可能具有寄存器描述和其他类似的内容,而ARM ARM则没有,但如果您生活在应用程序空间中,则可能不需要它们,也不需要ARM ARM。 如果仅出于教育目的,ARM ARM也是很好的。理解为什么您可能不希望进行除法或使用未对齐的访问。
ARM本身相当兼容,只要您坚持使用用户代码(内核代码当然是不同的)。在托管操作系统环境中,您将很可能坚持使用ARMv5(ARM926处理器)。
巨大的区别来自于:
在移植不同架构的程序时需要注意以下几个方面: