Unterschiede zwischen ARM-Architekturen aus Sicht eines C-Programmierers?

Lesezeit: 9 Minuten

Ich bin ziemlich neu in der Programmierung für ARM. Mir ist aufgefallen, dass es mehrere Architekturen wie ARMv4, ARMv5, ARMv6 usw. gibt. Was ist der Unterschied zwischen diesen? Haben sie unterschiedliche Befehlssätze oder Verhaltensweisen?

Am wichtigsten ist, wenn ich C-Code für ARMv6 kompiliere, läuft er dann auf ARMv5? Was ist mit ARMv5-Code, der auf ARMv6 ausgeführt wird? Oder müsste ich mich nur um den Unterschied kümmern, wenn ich Kernel-Assembler-Code schreiben würde?

Die ARM-Welt ist ein bisschen chaotisch.

Für die C-Programmierer ist die Sache einfach: Alle ARM-Architekturen bieten ein reguläres 32-Bit-Programmiermodell mit flacher Adressierung. Solange Sie beim C-Quellcode bleiben, besteht der einzige Unterschied, den Sie möglicherweise sehen, in Endianness und Leistung. Die meisten ARM-Prozessoren (sogar alte Modelle) können sowohl Big-Endian als auch Little-Endian sein; die Wahl wird dann von der Hauptplatine und dem Betriebssystem getroffen. Guter C-Code ist endian neutral: Es wird korrekt kompiliert und funktioniert unabhängig von der Plattform-Endianness (Endian-Neutralität ist gut für die Zuverlässigkeit und Wartbarkeit, aber auch für die Leistung: Nicht neutraler Code ist Code, der über Zeiger unterschiedlicher Größe auf dieselben Daten zugreift, und dies richtet Chaos an der strenge Aliasing-Regeln, die der Compiler verwendet, um den Code zu optimieren).

Die Situation ist ganz anders, wenn man bedenkt binär Kompatibilität (d. h. Wiederverwendung von einmal kompiliertem Code):


  • Es gibt mehrere Befehlssätze:
    1. der ursprüngliche ARM-Befehlssatz mit einem 26-Bit-Programmzähler (sehr alt, heutzutage sehr unwahrscheinlich)
    2. der ARM-Befehlssatz mit einem 32-Bit-Programmzähler (oft als “ARM-Code” bezeichnet)
    3. der Thumb-Befehlssatz (vereinfachte 16-Bit-Opcodes)
    4. der Thumb-2-Befehlssatz (Thumb mit Erweiterungen)

Ein bestimmter Prozessor kann mehrere Befehlssätze implementieren. Der neueste Prozessor, der nur ARM-Code kennt, ist der StrongARM, ein ARMv4-Vertreter, der schon ziemlich alt ist (15 Jahre). Das ARM7TDMI (ARMv4T-Architektur) kennt sowohl ARM als auch Thumb, wie fast alle nachfolgenden ARM-Systeme außer dem Cortex-M. ARM- und Thumb-Code können innerhalb derselben Anwendung gemischt werden, solange der richtige Kleber dort eingefügt wird, wo sich Konventionen ändern; das nennt man daumen interworking und kann automatisch vom C-Compiler verarbeitet werden.

Der Cortex-M0 kennt nur Thumb-Instruktionen. Es kennt ein paar Erweiterungen, denn in “normalen” ARM-Prozessoren muss das Betriebssystem ARM-Code verwenden (für die Behandlung von Interrupts); Der Cortex-M0 kennt also ein paar Thumb-for-OS-Sachen. Für Anwendungscode spielt dies keine Rolle.

Die anderen Cortex-M kennen nur Thumb-2. Daumen-2 ist meist abwärtskompatibel mit Thumb, zumindest auf Assembly-Ebene.


  • Einige Architekturen fügen zusätzliche Anweisungen hinzu.

Wenn also ein Code mit einem Compiler-Schalter kompiliert wird, der angibt, dass dies für ARMv6 gilt, kann der Compiler eine der wenigen Anweisungen verwenden, die ARMv6 hat, aber nicht ARMv5. Dies ist eine häufige Situation, die auf fast allen Plattformen auftritt: zB wenn Sie C-Code auf einem PC mit GCC kompilieren, indem Sie die verwenden -march=core2 Flag, dann kann die resultierende Binärdatei möglicherweise nicht auf einem älteren Pentium-Prozessor ausgeführt werden.


  • Es gibt mehrere Aufrufkonventionen.

Die Aufrufkonvention ist der Satz von Regeln, die festlegen, wie Funktionen Parameter austauschen und Werte zurückgeben. Der Prozessor kennt nur seine Register und hat keine Vorstellung von einem Stapel. Die Aufrufkonvention gibt an, in welche Register Parameter gehen und wie sie codiert sind (z. B. wenn es eine char Parameter, geht es in die niedrigen 8 Bits eines Registers, aber soll der Aufrufer die oberen 24 Bits löschen/vorzeichenerweitern oder nicht?). Es beschreibt die Stapelstruktur und -ausrichtung. Es normalisiert Ausrichtungsbedingungen und Padding für Strukturfelder.

Es gibt zwei Hauptkonventionen für ARM, genannt ATPCS (alt) und AAPCS (neu). Beim Thema Fließkommawerte sind sie ganz anders. Bei ganzzahligen Parametern sind sie größtenteils identisch (aber AAPCS erfordert eine strengere Stapelausrichtung). Natürlich variieren die Konventionen je nach Befehlssatz und dem Vorhandensein von Thumb Interworking.

In einigen Fällen ist es möglich, einen Binärcode zu haben, der sowohl ATPCS als auch AAPCS entspricht, aber das ist nicht zuverlässig und es gibt keine Warnung bei Nichtübereinstimmung. Das Fazit lautet also: Sie können keine echte binäre Kompatibilität zwischen Systemen haben, die unterschiedliche Aufrufkonventionen verwenden.


  • Es gibt optionale Coprozessoren.

Die ARM-Architektur kann mit optionalen Elementen erweitert werden, die dem Kernbefehlssatz ihre eigenen Befehle hinzufügen. Die FPU ist ein solcher optionaler Koprozessor (und in der Praxis sehr selten anzutreffen). Ein weiterer Koprozessor ist NEON, ein SIMD-Befehlssatz, der auf einigen der neueren ARM-Prozessoren zu finden ist.

Code, der einen Coprozessor verwendet, wird nicht auf einem Prozessor ausgeführt, der diesen Coprozessor nicht aufweist, es sei denn, das Betriebssystem fängt die entsprechenden Opcodes ab und emuliert den Coprozessor in Software (dies geschieht mehr oder weniger mit Gleitkommaargumenten bei Verwendung des ATPCS-Aufrufs Konvention, und es ist langsam).


Zusammenfassend: Wenn Sie C-Code haben, kompilieren Sie ihn neu. Versuchen Sie nicht, Code wiederzuverwenden, der für eine andere Architektur oder ein anderes System kompiliert wurde.

Stellen Sie sich dieses ARM-gegen-ARM-Ding wie einen Wintel-Computer gegen einen Intel-Mac vor. Angenommen, Sie haben auf beiden Computern denselben Intel-Chip (Familie), sodass Teile Ihres C-Codes einmal kompiliert und auf beiden Prozessoren problemlos ausgeführt werden können. Wo und warum Ihre Programme variieren, hat nichts mit dem Intel-Prozessor zu tun, sondern alles mit den Chips und dem Motherboard drumherum sowie in diesem Fall dem Betriebssystem.

Bei ARM vs. ARM liegen die meisten Unterschiede nicht im Kern, sondern in der herstellerspezifischen Logik, die den Kern umgibt. Es ist also eine geladene Frage, wenn Ihr C-Code eine Anwendung ist, die Standard-API-Aufrufe aufruft, sollte er auf Arm oder Intel oder PowerPC oder was auch immer kompiliert werden. Wenn Ihre Anwendung mit On-Chip- oder On-Board-Peripheriegeräten spricht, dann wird ein Board, ein Chip unabhängig vom Prozessortyp variieren, und als Ergebnis muss Ihr C-Code für diesen Chip oder dieses Motherboard geschrieben werden. Wenn Sie eine Binärdatei für ARMv6 kompilieren, kann und wird sie Anweisungen enthalten, die auf einem ARMv4 als undefiniert gelten und eine Ausnahme verursachen. Wenn Sie für ARMv4 kompilieren, sollte ARMv6 problemlos laufen.

Wenn Sie sich in diesem Anwendungsbereich befinden, werden Sie bestenfalls nur Leistungsunterschiede sehen. Einige davon haben mit Ihrer Wahl der Compileroptionen zu tun. Und manchmal können Sie mit Ihrem Code helfen. Ich empfehle, Divisionen und Fließkommazahlen nach Möglichkeit zu vermeiden. Ich mag keine Multiplikationen, aber ich nehme eine Multiplikation anstelle einer Division, wenn ich gedrückt werde. x86 hat uns mit nicht ausgerichteten Zugriffen verwöhnt, wenn Sie jetzt mit ausgerichteten E/A beginnen, wird es Sie später ersparen, wenn Sie in andere Chips geraten, die ebenfalls ausgerichtete Zugriffe bevorzugen, und oder Sie werden von den verschiedenen Betriebssystemen und Betriebssystemen gebissen Bootloader konfigurieren den ARM so, dass er reagiert, was Sie von einem x86 nicht gewohnt sind. Behalten Sie diese Gewohnheit ebenfalls bei und Ihr x86-Code wird viel schneller ausgeführt.

Holen Sie sich eine Kopie des ARM ARM (google: ARM Architectural Reference Manual, Sie können es an vielen Stellen kostenlos herunterladen, ich weiß nicht, was die aktuelle Version ist, Version I oder so etwas vielleicht). Durchsuchen Sie den ARM-Befehlssatz und sehen Sie, dass die meisten Befehle auf allen Kernen unterstützt werden und einige im Laufe der Zeit hinzugefügt wurden, wie z. Sie werden sehen, dass zwischen den Kernen nichts zu befürchten ist.

Denken Sie aus einer Systemperspektive, das Wintel vs. das Intel Mac. ARM stellt keine Chips her, sie stellen Kerne her und lizenzieren sie. Die meisten Anbieter, die einen ARM in ihrem Chip verwenden, haben ihre eigene spezielle Sauce drumherum. Es ist also wie Wintel vs. Mac mit demselben Prozessor in der Mitte, aber völlig anders, wenn es um all die Dinge geht, die der Prozessor berührt und verwenden muss. Es hört nicht beim ARM-Kern auf, ARM verkauft Peripheriegeräte, Gleitkommaeinheiten, Caches usw. So wenige ARMv4s sind zum Beispiel gleich, wenn überhaupt. Wenn Ihr Code die Unterschiede berührt, werden Sie Probleme haben, wenn dies nicht der Fall ist.

Für die Armteile des Chips gibt es zusätzlich zum ARM ARM TRMs (Technical Reference Manuals). aber wenn Sie das falsche trm für die von Ihnen verwendete Komponente erhalten, kann es Ihnen Kopfschmerzen bereiten. Das TRM hat möglicherweise Registerbeschreibungen und andere solche Dinge, die das ARM ARM nicht hat, aber wenn Sie im Anwendungsbereich leben, werden Sie wahrscheinlich keine davon brauchen, noch das ARM ARM. Der ARM ARM eignet sich nicht zuletzt für Bildungszwecke. Verstehen, warum Sie nicht ausgerichtete Zugriffe möglicherweise nicht teilen oder verwenden möchten.

  • Ich arbeite an einem Betriebssystem, das auf einer Vielzahl von ARM-Prozessoren läuft. Die Softwareschnittstelle ist bei allen größtenteils gleich, daher bin ich hauptsächlich auf die Binärkompatibilität gespannt. Ich habe den Eindruck, dass der Unterschied eher analog zu i686 vs. i386 oder i686 mit SSE3 ist: größtenteils abwärtskompatibel mit einigen neuen Anweisungen. Ist das genau?

    – Jay Conrod

    8. Dezember 2010 um 17:59 Uhr

  • Was die Armanweisungen angeht, ja, die neueren Kerne haben neue Anweisungen hinzugefügt. Die Fließkommaeinheiten sind jedoch nicht binärkompatibel, also müssen Sie dort vorsichtig sein. Theoretisch könnten Sie eine Binärdatei mit dem kleinsten gemeinsamen Nenner kompilieren, die auf der ganzen Linie funktioniert, aber möglicherweise nicht gut genug funktioniert. Und / oder Sie könnten If-Then-Else-Code im Programm haben, der, wenn dieser Kern oder was auch immer erkannt wird, ihn sonst nicht verwendet. Der ARM ARM zeigt Ihnen die ARM-Befehlskompatibilität.

    – Oldtimer

    8. Dezember 2010 um 20:14 Uhr

ARM selbst ist ziemlich kompatibel, vorausgesetzt, Sie halten sich an den Benutzercode (Kernel-Code ist natürlich anders). In einer Umgebung mit gehosteten Betriebssystemen bleiben Sie wahrscheinlich bei ARMv5 (ARM926-Prozessoren).

Der große Unterschied ergibt sich aus:

  1. Das Cache-Verhalten ist sehr unterschiedlich. Der Cache auf einigen ARMs wird sogar virtuell adressiert, was Prozesswechsel schmerzhaft machen kann.
  2. Die FPU gibt es in verschiedenen Geschmacksrichtungen (VFP, NEON und mehr!). Viele kleinere Prozessoren haben nicht einmal eine FPU.
  3. Der Daumenmodus hat sich dramatisch verändert. Der Thumb-Modus zwischen ARMv5 ist nicht auf Thumb2 (ARMv6+) portierbar und auch nicht abwärtskompatibel.

  • Dies beantwortet nicht wirklich die gestellte Frage (die “aus der Sicht eines C-Programmierers” war).

    – NUR MEINE richtige MEINUNG

    8. Dezember 2010 um 9:17 Uhr


  • Danke für diese Antwort. Es hört sich so an, als wäre die Binärkompatibilität ziemlich gut, solange Sie sich von FP-Operationen und Daumenmodus fernhalten.

    – Jay Conrod

    8. Dezember 2010 um 17:56 Uhr

Wenn Ihnen der Unterschied wirklich so wichtig ist, sollten Sie ihn der öffentlichen Dokumentation von ARM entnehmen können.

Aber der springende Punkt beim Schreiben in einer höheren Sprache (auch wenn sie nur so “hoch” wie C ist) ist zu Mach dir keine Sorgen. Alles, was Sie tun, ist neu kompilieren. Sogar innerhalb des Kernels muss nicht wirklich viel in Assembler geschrieben werden; und wann du es tust müssen, zu … haben Schreiben Sie etwas in Assembler (dh nicht nur, um die maximale Leistung zu erzielen), liegt dies normalerweise an mehr als nur der Wahl der CPU (z. B. was wurde wo direkt speicherzugeordnet?).

Sehr schnelle und schmutzige Liste von Bereichen, die beim Portieren zwischen Architekturen im Allgemeinen überprüft werden müssen:

  • Endianness: Union-Nutzung, Casting von Datentypen, Bitfelder, gemeinsame Nutzung von Daten
  • Ausrichtung: Ausrichtungsanforderungen, aber auch Leistungsmerkmale eines möglichen nicht ausgerichteten Zugriffs
  • Speichermodell: schwach gegen stark?
  • Mehrkern: Wie funktioniert Kohärenz?
  • Sonstig: signierte vs. unsignierte Datentypen, Packen von Datenstrukturen, Stack-Nutzung, Enum-Datentyp …

1385040cookie-checkUnterschiede zwischen ARM-Architekturen aus Sicht eines C-Programmierers?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy