从C程序员的角度看,ARM架构有哪些区别?

36

我对ARM编程还比较新。我注意到有几种体系结构,如ARMv4、ARMv5、ARMv6等等。它们之间有什么区别?它们是否具有不同的指令集或行为?

最重要的是,如果我将一些C代码编译为ARMv6,它能在ARMv5上运行吗?相反,ARMv5的代码能否在ARMv6上运行?或者我只需要担心差异,如果我写内核汇编代码的话?

5个回答

41

ARM世界有些混乱。

对于C程序员来说,事情很简单:所有ARM架构都提供了一个常规的、32位扁平地址编程模型。只要你使用C源代码,你可能看到的唯一区别是字节序和性能。大多数ARM处理器(甚至是旧型号)都可以是大端或小端;这个选择由逻辑板和操作系统决定。好的C代码是"字节序中立"的:它编译并正常工作,不管平台字节序如何(字节序中立有利于可靠性和可维护性,也有利于性能:非中立的代码是通过不同大小的指针访问相同数据的代码,这会破坏编译器用来优化代码的严格别名规则)。

如果考虑二进制兼容性(即重用已经编译过的代码),情况就大不相同:


  • 有几种指令集:
    1. 原始的ARM指令集,带有26位程序计数器(非常古老,现在很少遇到)
    2. 带有32位程序计数器的ARM指令集(通常称为"ARM代码")
    3. Thumb指令集(16位简化操作码)
    4. Thumb-2指令集(Thumb带有扩展)

一个处理器可能实现多个指令集。最新的只知道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代码,则重新编译它。不要尝试重用为另一种架构或系统编译的代码。


5
把ARM和ARM比作一个wintel电脑和一个intel mac电脑。假设两台计算机上都有相同的英特尔芯片(系列),因此您的C代码的部分可以编译一次并在两个处理器上运行得很好。您的程序在何处和为什么不同,与英特尔处理器无关,而与周围的芯片、主板以及操作系统有关。
在ARM vs ARM中,大部分差异不是核心,而是包围核心的特定于供应商的逻辑。因此这是一个棘手的问题,如果您的C代码是调用标准API调用的某些应用程序,则应该可以在arm或intel或powerpc或其他处理器上编译。如果您的应用程序开始与芯片上或板载外设进行通信,则无论处理器类型如何,一个板子,一个芯片将有所不同,因此您的C代码必须为该芯片或主板编写。如果您为ARMv6编译二进制文件,则可能会在ARMv4上产生未定义的指令,并导致异常。如果您为ARMv4编译,则ARMv6应该可以很好地运行它。
最好的情况是,如果您处于此应用程序空间,则可能只会看到性能差异。其中一些与您选择的编译器选项有关。有时您可以通过代码帮助。我建议尽可能避免除法和浮点数。我不喜欢乘法,但如果推动的话,会采取乘法而不是除法。x86让我们对齐访问感到宠爱,如果您现在开始使用对齐的I / O,那么当您涉及其他也喜欢对齐访问的芯片时,或者您被各种操作系统和引导加载程序所配置的ARM所咬时,这将为您节省开支,这些都不是您在x86上习惯的。同样,保持这个习惯,您的x86代码将运行得更快。
获得ARM ARM的副本(谷歌:ARM架构参考手册,您可以在许多地方免费下载,我不知道当前版本是什么,可能是I版之类的)。浏览ARM指令集,看到大多数指令都支持所有核心,并且随着时间的推移添加了一些指令,例如除法和字节交换等。您会发现核心之间没有什么可担心的。
从系统角度来看,wintel vs intel mac。ARM不制造芯片,他们制造和授权核心。大多数使用ARM芯片的供应商都有自己的特殊配料。因此,就像中间有相同处理器的wintel vs mac一样,但是当涉及到处理器接触和必须使用的所有东西时完全不同。它不止于ARM核心,ARM还销售外设、浮点单元、缓存等。因此,例如很少有任何ARMv4是相同的。如果您的代码涉及差异,则会出现问题,如果没有,则不会。

对于芯片的ARM部分,除了ARM ARM之外还有TRMs(技术参考手册)。但是,如果您获取了错误的组件TRM,则可能会让您感到头痛。 TRM可能具有寄存器描述和其他类似的内容,而ARM ARM则没有,但如果您生活在应用程序空间中,则可能不需要它们,也不需要ARM ARM。 如果仅出于教育目的,ARM ARM也是很好的。理解为什么您可能不希望进行除法或使用未对齐的访问。


我正在开发一个运行在各种ARM处理器上的操作系统。软件接口在所有处理器上基本相同,因此我主要关心二进制兼容性。我认为这种差异更类似于i686与i386或带有SSE3的i686:大多数情况下向后兼容,但也有一些新指令。这个理解准确吗? - Jay Conrod
就ARM指令而言,是的,新的内核添加了新的指令。但浮点单元不具有二进制兼容性,因此您必须小心处理。理论上,您可以编译一个最少公共分母二进制文件,可在所有平台上运行,但它可能无法达到足够的性能。或者,您可以在程序中使用if-then-else代码,如果检测到此内核或其他内容,则使用它,否则不使用。 ARM ARM将向您展示ARM指令的兼容性。 - old_timer

4

ARM本身相当兼容,只要您坚持使用用户代码(内核代码当然是不同的)。在托管操作系统环境中,您将很可能坚持使用ARMv5(ARM926处理器)。

巨大的区别来自于:

  1. 缓存行为大不相同。某些 ARM 上的缓存甚至是虚拟寻址的,这可能会使进程切换变得麻烦。
  2. FPU 有几种不同的类型(VFP、NEON 等等!)。许多较小的处理器甚至没有FPU。
  3. Thumb 模式发生了巨大变化。ARMv5 之间的 Thumb 模式不能迁移到 Thumb2 (ARMv6 +),也不向后兼容。

这并没有真正回答问题(问题是“从C程序员的角度来看”)。 - JUST MY correct OPINION
1
谢谢你的回答。听起来只要避免使用FP操作和Thumb模式,二进制兼容性就相当不错。 - Jay Conrod

3
如果你真的认为这个区别很重要,你应该能够从ARM的公共文档中找到答案。
但是编写高级语言(即使只是C语言)的主要目的是“不必担心这些问题”。你所需要做的就是重新编译。即使在内核中,也没有太多的内容需要用汇编语言编写;当你确实需要使用汇编语言编写某些内容时(即不仅仅是为了获得最大的性能),通常是因为除了CPU选择之外还有其他原因(例如直接内存映射在哪里?)。

3

在移植不同架构的程序时需要注意以下几个方面:

  • 字节序:联合使用、数据类型转换、位域、数据共享等
  • 对齐方式:对齐要求以及非对齐访问的性能特征
  • 内存模型:弱还是强?
  • 多核心:一致性如何工作?
  • 其他:有符号和无符号数据类型、数据结构紧缩排列、堆栈使用、枚举数据类型等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接