Mein Ziel ist es, für Embedded Linux entwickeln zu können. Ich habe Erfahrung mit Bare-Metal-Embedded-Systemen mit ARM.
Ich habe einige allgemeine Fragen zur Entwicklung für verschiedene CPU-Ziele. Meine Fragen sind wie folgt:
Wenn ich eine Anwendung kompiliert habe, die auf einem ' x86-Zielsystem, Linux-OS-Version xyz ' ausgeführt werden soll, kann ich dann dieselbe kompilierte Binärdatei auf einem anderen ' ARM-Zielsystem, Linux-OS-Version xyz ' ausführen ?
Wenn dies nicht zutrifft, besteht die einzige Möglichkeit darin, den Quellcode der Anwendung mithilfe der entsprechenden Toolchain 'zum Beispiel arm-linux-gnueabi' neu zu erstellen / zu kompilieren.
Ebenso kann ich, wenn ich ein ladbares Kernelmodul (Gerätetreiber) habe, das auf einem ' x86-Ziel, Linux-OS-Version xyz ' funktioniert, dasselbe kompilierte .ko auf einem anderen System ' ARM-Ziel, Linux-OS-Version xyz ' laden / verwenden. ?
Wenn dies nicht zutrifft, besteht die einzige Möglichkeit darin, den Treiber-Quellcode mithilfe der entsprechenden Toolchain 'zum Beispiel arm-linux-gnueabi' neu zu erstellen / zu kompilieren.
Antworten:
Nein. Binärdateien müssen für die Zielarchitektur (neu) kompiliert werden, und Linux bietet nichts Besseres als sofort verfügbare Fat-Binärdateien . Der Grund dafür ist, dass der Code für eine bestimmte Architektur zu Maschinencode kompiliert wird und der Maschinencode zwischen den meisten Prozessorfamilien sehr unterschiedlich ist (z. B. ARM und x86 sind sehr unterschiedlich).
BEARBEITEN: Es ist erwähnenswert, dass einige Architekturen Abwärtskompatibilität bieten (und noch seltener Kompatibilität mit anderen Architekturen). Auf 64-Bit-CPUs ist die Abwärtskompatibilität mit 32-Bit-Editionen üblich (denken Sie jedoch daran, dass Ihre abhängigen Bibliotheken auch 32-Bit-Bibliotheken sein müssen, einschließlich Ihrer C-Standardbibliothek, sofern Sie keine statische Verknüpfung herstellen ). Erwähnenswert ist auch Itanium , wo x86-Code (nur 32-Bit) ausgeführt werden konnte, wenn auch sehr langsam. Die schlechte Ausführungsgeschwindigkeit von x86-Code war zumindest ein Grund dafür, dass er auf dem Markt nicht sehr erfolgreich war.
Beachten Sie, dass Sie Binärdateien, die mit neueren Anweisungen kompiliert wurden, auch in Kompatibilitätsmodi nicht für ältere CPUs verwenden können (z. B. können Sie AVX nicht in einer 32-Bit-Binärdatei auf Nehalem x86-Prozessoren verwenden ; die CPU unterstützt dies nur nicht.
Beachten Sie, dass Kernelmodule für die jeweilige Architektur kompiliert werden müssen. Außerdem funktionieren 32-Bit-Kernelmodule nicht auf 64-Bit-Kerneln oder umgekehrt.
Informationen zu kompilierungsübergreifenden Binärdateien (Sie müssen also keine Toolchain auf dem Ziel-ARM-Gerät haben) finden Sie in der umfassenden Antwort von grochmal weiter unten.
quelle
Elizabeth Myers ist richtig, jede Architektur erfordert eine kompilierte Binärdatei für die betreffende Architektur. Um Binärdateien für eine andere Architektur zu erstellen, als auf Ihrem System ausgeführt wird, benötigen Sie a
cross-compiler
.In den meisten Fällen müssen Sie einen Cross-Compiler kompilieren. Ich habe nur Erfahrung mit
gcc
(aber ich glaube, dassllvm
und andere Compiler ähnliche Parameter haben). Eingcc
Cross-Compiler wird erreicht, indem--target
der Konfiguration Folgendes hinzugefügt wird :Sie müssen kompilieren
gcc
,glibc
undbinutils
mit diesen Parametern (und die Kernel - Header des Kernels auf dem Zielcomputer zur Verfügung stellen).In der Praxis ist dies erheblich komplizierter und auf verschiedenen Systemen treten unterschiedliche Erstellungsfehler auf.
Es gibt verschiedene Anleitungen zum Kompilieren der GNU-Toolchain, aber ich empfehle Linux From Scratch , das ständig gewartet wird und sehr gut erklärt, was die vorgestellten Befehle bewirken.
Eine weitere Option ist eine Bootstrap-Kompilierung eines Cross-Compilers. Dank des Kampfes um das Kompilieren von Cross-Compilern zu unterschiedlichen Architekturen wurden unterschiedliche Architekturen
crosstool-ng
geschaffen. Es gibt einen Bootstrap über die Toolchain, die zum Erstellen eines Cross-Compilers benötigt wird.crosstool-ng
Unterstützt mehrere Ziel-Triplets auf verschiedenen Architekturen. Im Grunde genommen handelt es sich um einen Bootstrap, bei dem die Benutzer ihre Zeit darauf verwenden, Probleme zu lösen, die beim Kompilieren einer Cross-Compiler-Toolchain auftreten.Mehrere Distributionen stellen Cross-Compiler als Pakete zur Verfügung:
arch
liefert einen mingw-cross-compiler und einen arm eabi-cross-compiler aus der schachtel. Neben anderen Cross-Compilern in AUR.fedora
enthält mehrere gepackte Cross-Compiler .ubuntu
bietet auch einen Arm-Cross-Compiler .debian
hat ein ganzes Repository von Cross-ToolchainsMit anderen Worten, überprüfen Sie, was Ihre Distribution in Bezug auf Cross-Compiler zur Verfügung hat. Wenn Ihre Distribution keinen Cross-Compiler für Ihre Bedürfnisse hat, können Sie ihn jederzeit selbst kompilieren.
Verweise:
Kernelmodule beachten
Wenn Sie Ihren Cross-Compiler von Hand kompilieren, haben Sie alles, was Sie zum Kompilieren von Kernel-Modulen benötigen. Dies liegt daran, dass Sie die Kernel-Header zum Kompilieren benötigen
glibc
.Wenn Sie jedoch einen von Ihrer Distribution bereitgestellten Cross-Compiler verwenden, benötigen Sie die Kernel-Header des Kernels, der auf dem Zielcomputer ausgeführt wird.
quelle
crosstool-ng
. Vielleicht möchten Sie das der Liste hinzufügen. Außerdem ist das manuelle Konfigurieren und Kompilieren einer GNU-übergreifenden Toolchain für eine bestimmte Architektur unglaublich aufwändig und weitaus mühsamer als nur das--target
Erstellen von Flags. Ich vermute, das ist einer der Gründe, warum LLVM an Popularität gewinnt. Es ist so aufgebaut, dass Sie keine Neuerstellung benötigen, um auf eine andere Architektur abzuzielen. Stattdessen können Sie mehrere Backends mit derselben Frontend- und Optimierungsbibliothek abzielen.Beachten Sie, dass Sie als letzte Möglichkeit (dh wenn Sie nicht über den Quellcode verfügen) Binärdateien auf einer anderen Architektur mit Emulatoren wie
qemu
,dosbox
oder ausführen könnenexagear
. Einige Emulatoren sind so konzipiert, dass sie andere Systeme als Linux emulieren (z. B.dosbox
MS-DOS-Programme ausführen, und es gibt zahlreiche Emulatoren für beliebte Spielekonsolen). Die Emulation hat einen erheblichen Performance-Overhead: Emulierte Programme laufen 2-10 mal langsamer als ihre nativen Gegenstücke.Wenn Sie Kernelmodule auf einer nicht nativen CPU ausführen müssen, müssen Sie das gesamte Betriebssystem einschließlich des Kernels für dieselbe Architektur emulieren. AFAIK: Es ist unmöglich, fremden Code im Linux-Kernel auszuführen.
quelle
Binärdateien sind nicht nur nicht zwischen x86 und ARM portierbar, es gibt auch verschiedene ARM-Varianten .
In der Praxis werden Sie wahrscheinlich auf ARMv6 gegen ARMv7 stoßen. Raspberry Pi 1 ist ARMv6, spätere Versionen sind ARMv7. So ist es möglich, Code auf den späteren zu kompilieren, der auf dem Pi 1 nicht funktioniert.
Glücklicherweise besteht ein Vorteil von Open Source und Freier Software darin, dass Sie den Quellcode auf jeder Architektur neu erstellen können. Obwohl dies einige Arbeit erfordern kann.
(ARM-Versionierung ist verwirrend, aber wenn ein V vor der Nummer steht, handelt es sich um die Befehlssatzarchitektur (ISA). Wenn nicht, handelt es sich um eine Modellnummer wie "Cortex M0" oder "ARM926EJS". Modellnummern haben nichts zu tun tun mit ISA-Nummern.)
quelle
Sie müssen immer eine Plattform anvisieren . Im einfachsten Fall führt die Ziel-CPU den in der Binärdatei kompilierten Code direkt aus (dies entspricht in etwa den COM-Programmdateien von MS DOS). Betrachten wir zwei verschiedene Plattformen, die ich gerade erfunden habe - Waffenstillstand und Intellio. In beiden Fällen haben wir ein einfaches Hallo-Welt-Programm, das 42 auf dem Bildschirm ausgibt. Ich gehe auch davon aus, dass Sie plattformunabhängig eine Multi-Plattform-Sprache verwenden, sodass der Quellcode für beide gleich ist:
Bei Waffenstillstand gibt es einen einfachen Gerätetreiber, der sich um das Drucken von Zahlen kümmert. Sie müssen also nur auf einen Port ausgeben. In unserer portablen Assemblersprache würde dies etwa so aussehen:
In einem Intellio-System gibt es jedoch keine solche Funktion, sodass es andere Ebenen durchlaufen muss:
Hoppla, wir haben bereits einen signifikanten Unterschied zwischen den beiden, bevor wir überhaupt zum Maschinencode kommen! Dies entspricht in etwa dem Unterschied zwischen Linux und MS DOS oder einem IBM PC und einer X-Box (auch wenn beide dieselbe CPU verwenden).
Aber dafür sind Betriebssysteme da. Nehmen wir an, wir haben eine HAL, die sicherstellt, dass alle unterschiedlichen Hardwarekonfigurationen auf der Anwendungsebene auf die gleiche Weise behandelt werden. Im Grunde genommen verwenden wir den Intellio-Ansatz auch im Waffenstillstand, und unser Code für "tragbare Baugruppen" ist derselbe. Dies wird sowohl von modernen Unix-ähnlichen Systemen als auch von Windows verwendet, häufig sogar in eingebetteten Szenarien. Gut - jetzt können wir auf Armistice und Intellio denselben wirklich portablen Assembler-Code haben. Aber was ist mit den Binärdateien?
Wie wir angenommen haben, muss die CPU die Binärdatei direkt ausführen. Schauen wir uns die erste Zeile unseres Codes
mov a, 10h
in Intellio an:Oh. Es stellt sich heraus, dass
mov a, constant
es so beliebt ist, dass es eine eigene Anweisung mit einem eigenen Opcode hat. Wie geht der Waffenstillstand damit um?Hmm. Da es den Opcode für gibt
mov.reg.imm
, benötigen wir ein anderes Argument, um das Register auszuwählen, dem wir zuweisen. Und die Konstante ist immer ein 2-Byte-Wort in Big-Endian-Notation - so wurde Waffenstillstand konstruiert. Tatsächlich sind alle Anweisungen in Waffenstillstand 4 Byte lang, keine Ausnahmen.Stellen Sie sich nun vor, Sie führen die Binärdatei von Intellio im Waffenstillstand aus: Die CPU beginnt mit der Dekodierung des Befehls und findet den Opcode
20h
. Im Waffenstillstand entspricht dies beispielsweise derand.imm.reg
Anweisung. Es wird versucht, die 2-Byte-Wortkonstante (die10XX
bereits ein Problem anzeigt) und dann die Registernummer (eine andereXX
) zu lesen . Wir führen die falsche Anweisung mit den falschen Argumenten aus. Und was noch schlimmer ist, die nächste Anweisung wird völlig falsch sein, da wir tatsächlich eine andere Anweisung gegessen haben und dachten, es seien Daten.Die Anwendung hat keine Chance zu funktionieren und wird höchstwahrscheinlich fast sofort abstürzen oder hängen bleiben.
Das bedeutet nicht, dass eine ausführbare Datei immer sagen muss, dass sie unter Intellio oder Armistice ausgeführt wird. Sie müssen lediglich eine Plattform definieren, die von der CPU unabhängig ist (wie
bash
unter Unix) oder sowohl von der CPU als auch vom Betriebssystem (wie Java oder .NET und heutzutage sogar von JavaScript). In diesem Fall kann die Anwendung eine ausführbare Datei für alle unterschiedlichen CPUs und Betriebssysteme verwenden, während sich auf dem Zielsystem eine Anwendung oder ein Dienst befindet (der direkt auf die richtige CPU und / oder das richtige Betriebssystem abzielt), der den plattformunabhängigen Code in etwas übersetzt CPU kann tatsächlich ausführen. Dies kann zu Leistungseinbußen, Kosteneinbußen oder Leistungseinbußen führen.CPUs kommen normalerweise in Familien. Beispielsweise verfügen alle CPUs der x86-Familie über einen gemeinsamen Befehlssatz, der genau gleich codiert ist, sodass auf jeder x86-CPU jedes x86-Programm ausgeführt werden kann, sofern keine Erweiterungen verwendet werden (z. B. Gleitkomma- oder Vektoroperationen). Die gängigsten Beispiele für x86 sind heute natürlich Intel und AMD. Atmel ist ein bekanntes Unternehmen, das CPUs der ARM-Familie entwickelt und für Embedded-Geräte sehr beliebt ist. Apple hat beispielsweise auch eigene ARM-CPUs.
ARM ist jedoch absolut nicht mit x86 kompatibel - sie stellen sehr unterschiedliche Designanforderungen und haben nur sehr wenige Gemeinsamkeiten. Die Befehle haben völlig unterschiedliche Opcodes, sie werden auf unterschiedliche Weise dekodiert, die Speicheradressen werden unterschiedlich behandelt ... Es könnte möglich sein, eine Binärdatei zu erstellen, die sowohl auf einer x86-CPU als auch auf einer ARM-CPU ausgeführt wird, indem einige sichere Vorgänge verwendet werden zwischen den beiden unterscheiden und zu zwei völlig verschiedenen Anweisungssätzen springen, aber es bedeutet immer noch, dass Sie für beide Versionen separate Anweisungen haben, mit nur einem Bootstrapper, der zur Laufzeit den richtigen Satz auswählt.
quelle
Es ist möglich, diese Frage in eine vertraute Umgebung umzuwandeln. In Analogie:
"Ich habe ein Ruby-Programm, das ich ausführen möchte, aber meine Plattform verfügt nur über einen Python-Interpreter. Kann ich den Python-Interpreter verwenden, um mein Ruby-Programm auszuführen, oder muss ich mein Programm in Python neu schreiben?"
Eine Befehlssatzarchitektur ("Ziel") ist eine Sprache - eine "Maschinensprache" - und verschiedene CPUs implementieren verschiedene Sprachen. Das Auffordern einer ARM-CPU, eine Intel-Binärdatei auszuführen, ähnelt dem Versuch, ein Ruby-Programm mit einem Python-Interpreter auszuführen.
quelle
gcc verwendet die Begriffe "Architektur", um den "Befehlssatz" einer bestimmten CPU zu bezeichnen, und "Ziel" deckt die Kombination von CPU und Architektur zusammen mit anderen Variablen wie ABI, libc, endian-ness und mehr ab (möglicherweise einschließlich "Bare Metal"). Ein typischer Compiler verfügt über eine begrenzte Anzahl von Zielkombinationen (wahrscheinlich eine ABI, eine CPU-Familie, aber möglicherweise sowohl 32- als auch 64-Bit). Ein Cross-Compiler ist in der Regel entweder ein Compiler mit einem anderen Ziel als dem System, auf dem er ausgeführt wird, oder einer mit mehreren Zielen oder ABIs (siehe auch dies ).
Im Allgemeinen nicht. Eine Binärdatei ist im herkömmlichen Sinne der native Objektcode für eine bestimmte CPU oder CPU-Familie. Es gibt jedoch mehrere Fälle, in denen sie mäßig bis hochgradig portabel sind:
-march=core2
)Wenn Sie es irgendwie schaffen, dieses Problem zu lösen, wird sich das andere tragbare Binärproblem von unzähligen Bibliotheksversionen (glibc ich sehe Sie an) zeigen. (Die meisten eingebetteten Systeme bewahren Sie zumindest vor diesem speziellen Problem.)
Wenn Sie es noch nicht getan haben, ist jetzt ein guter Zeitpunkt, um zu rennen
gcc -dumpspecs
undgcc --target-help
zu sehen, gegen was Sie antreten.Fat Binaries haben verschiedene Nachteile , haben aber immer noch potenzielle Verwendungen ( EFI ).
Bei den anderen Antworten fehlen jedoch zwei weitere Überlegungen: ELF und der ELF-Interpreter sowie die Unterstützung des Linux-Kernels für beliebige Binärformate . Ich werde hier nicht näher auf Binärdateien oder Bytecode für nicht-reale Prozessoren eingehen, obwohl es möglich ist, diese als "native" zu behandeln und Java- oder kompilierte Python-Bytecode-Binärdateien auszuführen. Solche Binärdateien sind unabhängig von der Hardwarearchitektur (hängen jedoch stattdessen ab) auf der entsprechenden VM-Version, auf der letztendlich eine native Binärdatei ausgeführt wird).
Jedes moderne Linux-System verwendet ELF-Binärdateien (technische Details in diesem PDF ). Bei dynamischen ELF-Binärdateien ist der Kernel dafür verantwortlich, das Image in den Speicher zu laden, aber es ist die Aufgabe des in der ELF festgelegten Interpreters Header, um das schwere Heben zu tun. Normalerweise muss dafür gesorgt werden, dass alle abhängigen dynamischen Bibliotheken verfügbar sind (mithilfe des Abschnitts '' Dynamisch '', in dem die Bibliotheken und einige andere Strukturen mit den erforderlichen Symbolen aufgeführt sind) - dies ist jedoch fast eine allgemeine Indirektionsebene.
(
/lib/ld-linux.so.2
Ist auch eine ELF-Binärdatei, hat keinen Interpreter und ist nativer Binärcode.)Das Problem bei ELF ist, dass der Header in binary (
readelf -h /bin/ls
) ihn für eine bestimmte Architektur, Klasse (32- oder 64-Bit), Endian-Ness und ABI kennzeichnet (Apples "universelle" Fat-Binaries verwenden ein alternatives Binärformat Mach-O) Stattdessen wurde dieses Problem durch NextSTEP gelöst. Dies bedeutet, dass eine ausführbare ELF-Datei mit dem System übereinstimmen muss, auf dem sie ausgeführt werden soll. Eine Escape-Luke ist der Interpreter. Dies kann eine beliebige ausführbare Datei sein (einschließlich einer, die architekturspezifische Unterabschnitte der ursprünglichen Binärdatei extrahiert oder abbildet und sie aufruft), Sie sind jedoch weiterhin durch die Art (en) der ELF eingeschränkt, die Ihr System ausführen darf . (FreeBSD hat eine interessante Art und Weise von Linux ELF - Dateien verarbeitet werden , derbrandelf
ändert das ELF ABI Feld.)Unter Linux wird Mach-O (mit
binfmt_misc
) unterstützt. Dort finden Sie ein Beispiel, das zeigt, wie Sie eine Fat-Binärdatei (32- und 64-Bit) erstellen und ausführen. Resource Forks / ADS , wie es ursprünglich auf dem Mac ausgeführt wurde, könnte eine Problemumgehung sein, aber kein natives Linux-Dateisystem unterstützt dies.Mehr oder weniger dasselbe gilt für Kernelmodule,
.ko
Dateien sind ebenfalls ELF (obwohl sie keinen Interpreter-Satz haben). In diesem Fall gibt es eine zusätzliche Ebene, die die Kernel-Version (uname -r
) im Suchpfad verwendet. Dies könnte theoretisch stattdessen in ELF mit Versionierung erfolgen, aber mit einer gewissen Komplexität und wenig Gewinn, wie ich vermute.Wie bereits an anderer Stelle erwähnt, unterstützt Linux Fat-Binaries nicht von Haus aus, es gibt jedoch ein aktives Fat-Binary-Projekt: FatELF . Es gibt es schon seit Jahren , es wurde nicht mehr in den Standard-Kernel integriert, was zum Teil auf (inzwischen abgelaufene) Patentprobleme zurückzuführen ist. Zu diesem Zeitpunkt sind sowohl Kernel- als auch Toolchain-Unterstützung erforderlich. Es wird nicht der
binfmt_misc
Ansatz verwendet, der die ELF-Header-Probleme umgeht und auch Fat-Kernel-Module zulässt.Nicht bei ELF, das lässt du nicht zu.
Die einfache Antwort lautet ja. (Komplizierte Antworten umfassen Emulation, Zwischendarstellungen, Übersetzer und JIT. Abgesehen von der "Herabstufung" einer i686-Binärdatei zur Verwendung von i386-Opcodes sind sie hier wahrscheinlich nicht interessant, und die ABI-Korrekturen sind möglicherweise so schwierig wie die Übersetzung von nativem Code. )
Nein, ELF lässt dich das nicht tun.
Die einfache Antwort lautet ja. Ich glaube, mit FatELF können Sie eine
.ko
Multi-Architektur erstellen , aber irgendwann muss eine Binärversion für jede unterstützte Architektur erstellt werden. Dinge, die Kernelmodule erfordern, kommen oft mit der Quelle und werden nach Bedarf erstellt, z. B. VirtualBox.Dies ist bereits eine lange Antwort, es gibt nur noch einen Umweg. Im Kernel ist bereits eine virtuelle Maschine integriert, allerdings eine dedizierte: die BPF-VM , mit der Pakete abgeglichen werden. Der vom Menschen lesbare Filter "Host foo und nicht Port 22") wird zu einem Bytecode kompiliert und vom Kernel-Paketfilter ausgeführt . Das neue eBPF ist nicht nur für Pakete gedacht. Theoretisch ist VM-Code für alle gängigen Linux- Betriebssysteme portierbar und wird von llvm unterstützt. Aus Sicherheitsgründen ist es jedoch wahrscheinlich nicht für andere Zwecke als für Verwaltungsregeln geeignet.
Abhängig davon, wie großzügig Sie mit der Definition einer ausführbaren Binärdatei umgehen, können Sie (ab) verwenden
binfmt_misc
, um die Unterstützung für Fat Binary mit einem Shell-Skript und ZIP-Dateien als Container-Format zu implementieren:Nenne dies "wozbin" und richte es so ein:
Dadurch werden
.woz
Dateien beim Kernel registriert.wozbin
Stattdessen wird das Skript mit dem ersten Argument aufgerufen , das auf den Pfad einer aufgerufenen.woz
Datei festgelegt ist.Um eine portable (fette)
.woz
Datei zu erhalten, erstellen Sie einfach einetest.woz
ZIP-Datei mit einer Verzeichnishierarchie.Platzieren Sie in jedem arch / OS / libc-Verzeichnis (eine beliebige Auswahl) die architekturspezifische
test
Binärdatei und Komponenten wie z. B..so
Dateien. Wenn Sie es aufrufen, wird das erforderliche Unterverzeichnis in ein speicherinternes tmpfs-Dateisystem (/mnt/tmpfs
hier aktiviert) extrahiert und aufgerufen.quelle
Berry Boot, lösen Sie einige Ihrer Probleme .. aber es ist kein Problem zu lösen, wie man auf Arm-hf, normaler / regulärer Linux-Distribution für x86-32 / 64bit läuft.
Ich denke, es sollte in Isolinux (Bootloader Linux auf USB) ein Live-Konverter eingebaut sein, der regullar Distribution erkennen und in Ride / Live in HF konvertieren könnte.
Warum? Denn wenn jedes Linux von berry boot auf arm-hf konvertiert werden kann, könnte es in der Lage sein, einen Bery-Boot-Mechanismus einzubauen, um das zu isolieren, was wir zum Beispiel für jeden booten, oder eine eingebaute Ubuntu Creat-Startdiskette.
quelle