Mon objectif est de pouvoir développer pour Linux embarqué. J'ai de l'expérience sur les systèmes embarqués bare-metal utilisant ARM.
J'ai quelques questions générales sur le développement pour différentes cibles de CPU. Mes questions sont les suivantes:
Si j'ai une application compilée pour s'exécuter sur une « cible x86, linux OS version xyz », puis-je simplement exécuter le même binaire compilé sur un autre système « ARM target, linux OS version xyz »?
Si ce n'est pas vrai, le seul moyen est d'obtenir le code source de l'application pour reconstruire / recompiler en utilisant la chaîne d'outils appropriée 'par exemple, arm-linux-gnueabi'?
De même, si j'ai un module de noyau chargeable (pilote de périphérique) qui fonctionne sur une « cible x86, linux OS version xyz », puis-je simplement charger / utiliser le même .ko compilé sur un autre système « cible ARM, linux OS version xyz » ?
Si ce n'est pas vrai, le seul moyen est d'obtenir le code source du pilote pour reconstruire / recompiler en utilisant la chaîne d'outils appropriée 'par exemple, arm-linux-gnueabi'?
Réponses:
Non. Les binaires doivent être (re) compilés pour l'architecture cible, et Linux n'offre rien de tel que les gros binaires prêts à l'emploi. La raison en est que le code est compilé en code machine pour une architecture spécifique et que le code machine est très différent entre la plupart des familles de processeurs (ARM et x86 par exemple sont très différents).
EDIT: il convient de noter que certaines architectures offrent des niveaux de compatibilité descendante (et encore plus rare, la compatibilité avec d'autres architectures); sur les processeurs 64 bits, il est courant d'avoir une compatibilité descendante avec les éditions 32 bits (mais rappelez-vous: vos bibliothèques dépendantes doivent également être 32 bits, y compris votre bibliothèque standard C, sauf si vous établissez une liaison statique ). Il convient également de mentionner Itanium , où il était possible d'exécuter du code x86 (32 bits uniquement), quoique très lentement; la faible vitesse d'exécution du code x86 était au moins en partie la raison pour laquelle il n'a pas été très réussi sur le marché.
Gardez à l'esprit que vous ne pouvez toujours pas utiliser de binaires compilés avec des instructions plus récentes sur des CPU plus anciens, même dans les modes de compatibilité (par exemple, vous ne pouvez pas utiliser AVX dans un binaire 32 bits sur les processeurs Nehalem x86 ; le CPU ne le prend tout simplement pas en charge.
Notez que les modules du noyau doivent être compilés pour l'architecture appropriée; en outre, les modules de noyau 32 bits ne fonctionneront pas sur les noyaux 64 bits ou vice versa.
Pour plus d'informations sur la compilation croisée des fichiers binaires (vous n'avez donc pas besoin d'avoir une chaîne d'outils sur le périphérique ARM cible), voir la réponse complète de grochmal ci-dessous.
la source
Elizabeth Myers a raison, chaque architecture nécessite un binaire compilé pour l'architecture en question. Pour créer des binaires pour une architecture différente de celle sur laquelle votre système fonctionne, vous avez besoin d'un fichier
cross-compiler
.Dans la plupart des cas, vous devez compiler un compilateur croisé. Je n'ai qu'une expérience avec
gcc
(mais je crois quellvm
, et d'autres compilateurs, ont des paramètres similaires). Ungcc
compilateur croisé est obtenu en ajoutant--target
à la configuration:Vous devez compiler
gcc
,glibc
etbinutils
avec ces paramètres (et fournir les en-têtes du noyau du noyau sur la machine cible).En pratique, cela est considérablement plus compliqué et différentes erreurs de construction apparaissent sur différents systèmes.
Il existe plusieurs guides sur la façon de compiler la chaîne d'outils GNU mais je recommanderai Linux From Scratch , qui est continuellement maintenu et fait un très bon travail pour expliquer ce que font les commandes présentées.
Une autre option est une compilation bootstrap d'un compilateur croisé. Merci à la difficulté de compiler des compilateurs croisés pour différentes architectures sur différentes architectures
crosstool-ng
été créé. Il donne un bootstrap sur la chaîne d'outils nécessaire pour construire un compilateur croisé.crosstool-ng
prend en charge plusieurs triplets cibles sur différentes architectures, il s'agit essentiellement d'un bootstrap où les gens consacrent leur temps à résoudre les problèmes survenant lors de la compilation d'une chaîne d'outils de compilation croisée.Plusieurs distributions fournissent des compilateurs croisés sous forme de packages:
arch
fournit un compilateur croisé mingw et un compilateur croisé bras eabi hors de la boîte. A part d'autres compilateurs croisés en AUR.fedora
contient plusieurs compilateurs croisés emballés .ubuntu
fournit un compilateur croisé de bras .debian
dispose d'un référentiel complet de chaînes d'outils croiséesEn d'autres termes, vérifiez ce que votre distribution propose en termes de compilateurs croisés. Si votre distribution n'a pas de compilateur croisé pour vos besoins, vous pouvez toujours le compiler vous-même.
Les références:
Note sur les modules du noyau
Si vous compilez votre compilateur croisé à la main, vous avez tout ce dont vous avez besoin pour compiler les modules du noyau. C'est parce que vous avez besoin des en-têtes du noyau pour compiler
glibc
.Mais, si vous utilisez un compilateur croisé fourni par votre distribution, vous aurez besoin des en-têtes de noyau du noyau qui s'exécute sur la machine cible.
la source
crosstool-ng
. Vous voudrez peut-être ajouter cela à la liste. De plus, la configuration et la compilation manuelle d'une chaîne d'outils croisés GNU pour une architecture donnée est incroyablement impliquée et beaucoup plus fastidieuse que de simples--target
indicateurs. Je soupçonne que cela fait partie de la raison pour laquelle le LLVM gagne en popularité; Il est conçu de telle manière que vous n'avez pas besoin d'une reconstruction pour cibler une autre architecture - à la place, vous pouvez cibler plusieurs backends en utilisant les mêmes bibliothèques de frontend et d'optimiseur.Notez qu'en dernier recours (c'est-à-dire lorsque vous n'avez pas le code source), vous pouvez exécuter des binaires sur une architecture différente en utilisant des émulateurs comme
qemu
,dosbox
ouexagear
. Certains émulateurs sont conçus pour émuler des systèmes autres que Linux (par exempledosbox
sont conçus pour exécuter des programmes MS-DOS, et il existe de nombreux émulateurs pour les consoles de jeux populaires). L'émulation a un surdébit de performances significatif: les programmes émulés s'exécutent 2 à 10 fois plus lentement que leurs homologues natifs.Si vous devez exécuter des modules du noyau sur un processeur non natif, vous devrez émuler l'ensemble du système d'exploitation, y compris le noyau pour la même architecture. AFAIK il est impossible d'exécuter du code étranger dans le noyau Linux.
la source
Non seulement les binaires ne sont pas portables entre x86 et ARM, il existe différentes versions d'ARM .
Celui que vous rencontrerez probablement dans la pratique est ARMv6 vs ARMv7. Raspberry Pi 1 est ARMv6, les versions ultérieures sont ARMv7. Il est donc possible de compiler du code sur les derniers qui ne fonctionnent pas sur le Pi 1.
Heureusement, l'un des avantages de l'open source et des logiciels gratuits est d'avoir la source afin que vous puissiez la reconstruire sur n'importe quelle architecture. Bien que cela puisse nécessiter un certain travail.
(La version ARM prête à confusion, mais s'il y a un V avant le nombre, il s'agit de l'architecture du jeu d'instructions (ISA). S'il n'y en a pas, c'est un numéro de modèle comme "Cortex M0" ou "ARM926EJS". Les numéros de modèle n'ont rien à faire avec les numéros ISA.)
la source
Vous devez toujours cibler une plate - forme. Dans le cas le plus simple, le CPU cible exécute directement le code compilé dans le binaire (cela correspond grosso modo aux exécutables COM de MS DOS). Prenons deux plates-formes différentes que je viens d'inventer - Armistice et Intellio. Dans les deux cas, nous aurons un simple programme hello world qui affiche 42 à l'écran. Je suppose également que vous utilisez un langage multi-plateforme d'une manière indépendante de la plate-forme, donc le code source est le même pour les deux:
Sur Armistice, vous disposez d'un pilote de périphérique simple qui prend en charge l'impression des numéros, donc tout ce que vous avez à faire est de sortir sur un port. Dans notre langage d'assemblage portable, cela correspondrait à quelque chose comme ceci:
Cependant, ou le système Intellio n'a rien de tel, il doit donc passer par d'autres couches:
Oups, nous avons déjà une différence significative entre les deux, avant même de passer au code machine! Cela correspondrait à peu près au genre de différence que vous avez entre Linux et MS DOS, ou un PC IBM et une X-Box (même si les deux peuvent utiliser le même processeur).
Mais c'est à cela que servent les systèmes d'exploitation. Supposons que nous ayons un HAL qui s'assure que toutes les différentes configurations matérielles sont gérées de la même manière sur la couche application - en gros, nous utiliserons l'approche Intellio même sur l'Armistice, et notre code "d'assemblage portable" finira par être le même. Il est utilisé à la fois par les systèmes modernes de type Unix et Windows, souvent même dans des scénarios intégrés. Bon - maintenant, nous pouvons avoir le même code d'assemblage vraiment portable sur Armistice et Intellio. Mais qu'en est-il des binaires?
Comme nous l'avons supposé, le CPU doit exécuter le binaire directement. Regardons la première ligne de notre code
mov a, 10h
, sur Intellio:Oh. Il s'avère que
mov a, constant
c'est si populaire qu'il a sa propre instruction, avec son propre opcode. Comment l'Armistice gère-t-il cela?Hmm. Il y a l'opcode pour
mov.reg.imm
, nous avons donc besoin d'un autre argument pour sélectionner le registre auquel nous assignons. Et la constante est toujours un mot de 2 octets, en notation big-endian - c'est ainsi que l'Armistice a été conçu, en fait, toutes les instructions de l'Armistice ont une longueur de 4 octets, sans exception.Imaginez maintenant exécuter le binaire d'Intellio sur Armistice: le CPU commence à décoder l'instruction, trouve l'opcode
20h
. À l'armistice, cela correspond, disons, à l'and.imm.reg
instruction. Il essaie de lire la constante de mot de 2 octets (qui lit10XX
, déjà un problème), puis le numéro de registre (un autreXX
). Nous exécutons la mauvaise instruction, avec les mauvais arguments. Et pire encore, la prochaine instruction sera complètement fausse, car nous avons en fait mangé une autre instruction, pensant qu'il s'agissait de données.L'application n'a aucune chance de fonctionner, et elle se bloquera ou se bloquera presque immédiatement.
Maintenant, cela ne signifie pas qu'un exécutable doit toujours dire qu'il fonctionne sur Intellio ou Armistice. Il vous suffit de définir une plate-forme indépendante du processeur (comme
bash
sur Unix), ou à la fois du processeur et du système d'exploitation (comme Java ou .NET, et même aujourd'hui JavaScript, en quelque sorte). Dans ce cas, l'application peut utiliser un exécutable pour tous les différents CPU et OS, tandis qu'il y a une application ou un service sur le système cible (qui cible directement le CPU et / ou OS correct) qui traduit le code indépendant de la plateforme en quelque chose que Le CPU peut réellement s'exécuter. Cela peut ou non avoir un impact sur les performances, les coûts ou les capacités.Les CPU viennent généralement en famille. Par exemple, tous les CPU de la famille x86 ont un ensemble commun d'instructions qui sont encodées exactement de la même manière, donc chaque CPU x86 peut exécuter chaque programme x86, tant qu'il n'essaie pas d'utiliser des extensions (par exemple, opérations en virgule flottante ou opérations vectorielles). Sur x86, les exemples les plus courants aujourd'hui sont Intel et AMD, bien sûr. Atmel est une entreprise bien connue qui conçoit des processeurs de la famille ARM, assez populaire pour les appareils embarqués. Apple a également ses propres processeurs ARM, par exemple.
Mais ARM est totalement incompatible avec x86 - ils ont des exigences de conception très différentes et ont très peu en commun. Les instructions ont des opcodes entièrement différents, elles sont décodées d'une manière différente, les adresses mémoire sont traitées différemment ... Il pourrait être possible de créer un binaire qui s'exécute à la fois sur un processeur x86 et un processeur ARM, en utilisant des opérations sûres pour faire la distinction entre les deux et passer à deux ensembles d'instructions complètement différents, mais cela signifie toujours que vous avez des instructions distinctes pour les deux versions, avec juste un bootstrapper qui sélectionne le bon ensemble au moment de l'exécution.
la source
Il est possible de reformuler cette question dans un environnement qui pourrait être plus familier. Par analogie:
"J'ai un programme Ruby que je veux exécuter, mais ma plate-forme n'a qu'un interprète Python. Puis-je utiliser l'interpréteur Python pour exécuter mon programme Ruby, ou dois-je réécrire mon programme en Python?"
Une architecture de jeu d'instructions ("cible") est un langage - un "langage machine" - et différents CPU implémentent différents langages. Donc, demander à un processeur ARM d'exécuter un binaire Intel revient à essayer d'exécuter un programme Ruby à l'aide d'un interpréteur Python.
la source
gcc utilise les termes «architecture» pour désigner le «jeu d'instructions» d'un processeur spécifique, et «cible» couvre la combinaison du processeur et de l'architecture, ainsi que d'autres variables telles que ABI, libc, endian-ness et plus (incluant éventuellement le "métal nu"). Un compilateur typique a un ensemble limité de combinaisons cibles (probablement un ABI, une famille de CPU, mais peut-être à la fois 32 et 64 bits). Un compilateur croisé signifie généralement soit un compilateur avec une cible autre que le système sur lequel il s'exécute, soit un compilateur avec plusieurs cibles ou ABI (voir aussi ceci ).
En général, non. Un binaire en termes conventionnels est un code objet natif pour un CPU ou une famille de CPU particulier. Mais, il existe plusieurs cas où ils peuvent être modérément à hautement portables:
-march=core2
)Si vous parvenez d'une manière ou d'une autre à résoudre ce problème, l' autre problème binaire portable des innombrables versions de bibliothèque (glibc que je vous regarde) se présentera alors. (La plupart des systèmes embarqués vous évitent au moins ce problème particulier.)
Si vous ne l'avez pas déjà fait, c'est le bon moment pour courir
gcc -dumpspecs
etgcc --target-help
voir ce que vous affrontez.Les gros fichiers binaires ont divers inconvénients , mais ont encore des utilisations potentielles ( EFI ).
Il y a cependant deux autres considérations qui manquent dans les autres réponses: ELF et l'interpréteur ELF, et le support du noyau Linux pour les formats binaires arbitraires . Je n'entrerai pas dans les détails sur les binaires ou le bytecode pour les processeurs non réels ici, bien qu'il soit possible de les traiter comme "natifs" et d'exécuter des binaires de bytecode Java ou Python compilés , ces binaires sont indépendants de l'architecture matérielle (mais dépendent à la place sur la version de VM appropriée, qui exécute finalement un binaire natif).
Tout système Linux contemporain utilisera des binaires ELF (détails techniques dans ce PDF ), dans le cas des binaires ELF dynamiques, le noyau est chargé de charger l'image en mémoire mais c'est le travail de `` l'interpréteur '' défini dans l'ELF en-têtes pour faire le gros du travail. Normalement, cela implique de s'assurer que toutes les bibliothèques dynamiques dépendantes sont disponibles (à l'aide de la section `` Dynamique '' qui répertorie les bibliothèques et certaines autres structures qui répertorient les symboles requis) - mais il s'agit presque d' une couche d'indirection générale.
(
/lib/ld-linux.so.2
est également un binaire ELF, il n'a pas d'interprète et est un code binaire natif.)Le problème avec ELF est que l'en-tête dans le binaire (
readelf -h /bin/ls
) le marque pour une architecture spécifique, classe (32 ou 64 bits), endian-ness et ABI (les gros binaires "universels" d'Apple utilisent un autre format binaire Mach-O au lieu de cela, qui résout ce problème, il est originaire de NextSTEP). Cela signifie qu'un exécutable ELF doit correspondre au système sur lequel il doit être exécuté. Une trappe d'échappement est l'interpréteur, il peut s'agir de n'importe quel exécutable (y compris celui qui extrait ou mappe des sous-sections spécifiques à l'architecture du binaire d'origine et les appelle), mais vous êtes toujours limité par le ou les types d'ELF que votre système autorisera à exécuter . (FreeBSD a une manière intéressante de gérer les fichiers Linux ELF , ilbrandelf
modifie le champ ELF ABI.)Il existe (en utilisant
binfmt_misc
) la prise en charge de Mach-O sur Linux , il y a un exemple qui vous montre comment créer et exécuter un gros binaire (32 et 64 bits). Les fourchettes de ressources / ADS , comme cela a été fait à l'origine sur le Mac, pourraient être une solution de contournement, mais aucun système de fichiers Linux natif ne prend en charge cela.Plus ou moins la même chose s'applique aux modules du noyau, les
.ko
fichiers sont également ELF (bien qu'ils n'aient pas d'interpréteur). Dans ce cas, il y a une couche supplémentaire qui utilise la version du noyau (uname -r
) dans le chemin de recherche, quelque chose qui pourrait théoriquement être fait à la place dans ELF avec le versioning, mais à une certaine complexité et peu de gain, je suppose.Comme indiqué ailleurs, Linux ne prend pas en charge nativement les binaires fat, mais il existe un projet fat-binary actif: FatELF . Il existe depuis des années , il n'a jamais été intégré au noyau standard en partie à cause de problèmes de brevets (maintenant expirés). À l'heure actuelle, il nécessite à la fois la prise en charge du noyau et de la chaîne d'outils. Il n'utilise pas l'
binfmt_misc
approche, cela contourne les problèmes d'en-tête ELF et permet également d'utiliser des modules de noyau lourds.Pas avec ELF, ça ne vous laissera pas faire ça.
La réponse simple est oui. (Les réponses compliquées incluent l'émulation, les représentations intermédiaires, les traducteurs et JIT; à l'exception du cas de "rétrogradation" d'un binaire i686 pour utiliser uniquement les opcodes i386, ils ne sont probablement pas intéressants ici, et les corrections ABI sont potentiellement aussi difficiles que la traduction de code natif. )
Non, ELF ne vous laissera pas faire ça.
La réponse simple est oui. Je crois que FatELF vous permet de créer un
.ko
multi-architecture, mais à un moment donné, une version binaire pour chaque architecture prise en charge doit être créée. Les choses qui nécessitent des modules du noyau sont souvent fournies avec la source et sont construites selon les besoins, par exemple VirtualBox le fait.C'est déjà une longue réponse décousue, il n'y a qu'un détour de plus. Le noyau a déjà une machine virtuelle intégrée, bien que dédiée: la machine virtuelle BPF qui est utilisée pour faire correspondre les paquets. Le filtre lisible par l'homme "hôte foo et non port 22") est compilé en un bytecode et le filtre de paquets du noyau l' exécute . Le nouvel eBPF n'est pas seulement pour les paquets, en théorie que le code VM est portable sur n'importe quel linux contemporain, et llvm le supporte mais pour des raisons de sécurité, il ne sera probablement pas adapté à autre chose que des règles administratives.
Maintenant, en fonction de votre générosité avec la définition d'un exécutable binaire, vous pouvez (ab) utiliser
binfmt_misc
pour implémenter un support binaire gras avec un script shell et des fichiers ZIP comme format de conteneur:Appelez cela "wozbin", et configurez-le avec quelque chose comme:
Cela enregistre les
.woz
fichiers avec le noyau, lewozbin
script est appelé à la place avec son premier argument défini sur le chemin d'un.woz
fichier appelé .Pour obtenir un
.woz
fichier portable (gras) , créez simplement untest.woz
fichier ZIP avec une hiérarchie de répertoires afin:Dans chaque répertoire arch / OS / libc (un choix arbitraire), placez le
test
binaire spécifique à l'architecture et les composants tels que les.so
fichiers. Lorsque vous l'appelez, le sous-répertoire requis est extrait dans un système de fichiers en mémoire tmpfs (/mnt/tmpfs
ici) et appelé.la source
berry boot, résolvez certains de vos problèmes .. mais cela ne résout pas le problème de fonctionnement sur arm hf, distribution distall normall / regullAr pour x86-32 / 64bit.
Je pense qu'il devrait être intégré dans isolinux (boatloader linux sur usb) un convertisseur en direct ce qui pourrait reconnaître la distribution regullar et dans ride / live convertir en hf.
Pourquoi? Parce que si chaque Linux peut être converti par Berry Boot en travaillant sur arm-hf, il pourrait être en mesure de construire un mécanisme de démarrage bery pour isolinux ce que nous démarrons en utilisant pour chacun exaple ou un disque de démarrage intégré ubuntu creat.
la source