私の目標は、組み込みLinux用に開発できるようにすることです。 ARMを使用したベアメタル組み込みシステムの経験があります。
さまざまなCPUターゲット用の開発に関する一般的な質問があります。私の質問は以下の通りです:
'x86ターゲット、linux OSバージョンxyz'で実行するようにアプリケーションをコンパイルした場合、別のシステムで同じコンパイル済みバイナリを実行できますか?ARMターゲット、linux OSバージョンxyz '?
上記が当てはまらない場合、関連するツールチェーンを使用してアプリケーションのソースコードを再ビルド/再コンパイルする唯一の方法は、「arm-linux-gnueabiなど」ですか?
同様に、「x86ターゲット、Linux OSバージョンxyz」で動作するロード可能なカーネルモジュール(デバイスドライバー)がある場合、同じコンパイル済み.koを別のシステムにロード/使用できますか? ARMターゲット、Linux OSバージョンxyz '?
上記が当てはまらない場合、唯一の方法は、関連するツールチェーンを使用してドライバーのソースコードを再ビルド/再コンパイルすることです(たとえば、arm-linux-gnueabi)?
いいえ。バイナリはターゲットアーキテクチャ用に(再)コンパイルする必要があり、Linuxは fat binaries のようなものを提供します。その理由は、コードが特定のアーキテクチャのマシンコードにコンパイルされ、マシンコードがほとんどのプロセッサファミリ間で大きく異なるためです(たとえば、ARMとx86は大きく異なります)。
編集:一部のアーキテクチャは、下位互換性のレベル(および他のアーキテクチャとのよりまれな互換性)を提供することに注意してください。 64ビットCPUでは、32ビット版との下位互換性があるのが一般的です(ただし、依存するライブラリも32ビットである必要があります。ただし、 静的にリンク しない限り、C標準ライブラリを含みます)。また、言及する価値があるのは Itanium であり、非常にゆっくりですが、x86コード(32ビットのみ)を実行することが可能でした。 x86コードの実行速度が遅いことが、少なくとも市場で成功しなかった理由の1つでした。
互換モードであっても、古いCPUで新しい命令でコンパイルされたバイナリを使用することはできません(たとえば、 Nehalem x86プロセッサ)の32ビットバイナリでAVXを使用することはできません ; CPUのみそれをサポートしていません。
カーネルモジュールは、関連するアーキテクチャ用にコンパイルする必要があることに注意してください。さらに、32ビットカーネルモジュールは64ビットカーネルでは機能しません。逆も同様です。
バイナリのクロスコンパイルに関する情報(ターゲットARMデバイス)にツールチェーンを用意する必要がないため)については、以下のgrochmalの包括的な回答を参照してください。
最後の手段として(つまり、ソースコードがない場合)、qemu
、dosbox
、exagear
などのエミュレータを使用して、異なるアーキテクチャでバイナリを実行できます。 。一部のエミュレーターは、Linux以外のシステムをエミュレートするように設計されています(たとえば、dosbox
はMS-DOSプログラムを実行するように設計されており、人気のあるゲームコンソール用のエミュレーターがたくさんあります)。エミュレーションには大きなパフォーマンスオーバーヘッドがあります。エミュレートされたプログラムは、ネイティブのプログラムよりも2〜10倍遅く実行されます。
ネイティブでないCPUでカーネルモジュールを実行する必要がある場合は、同じアーキテクチャのカーネルを含むOS全体をエミュレートする必要があります。私の知る限り、Linuxカーネル内で外部コードを実行することは不可能です。
バイナリはx86とARMの間で移植できないだけでなく、ARMにはさまざまなフレーバーがあります。
実際に遭遇する可能性が高いのはARMv6とARMv7です。 Raspberry Pi 1はARMv6、それ以降のバージョンはARMv7です。したがって、Pi 1で動作しないコードを後からコンパイルすることは可能です。
幸い、オープンソースとフリーソフトウェアの1つの利点は、任意のアーキテクチャでソースを再構築できるようにソースを持っていることです。これには多少の作業が必要な場合がありますが。
(ARMのバージョン管理は混乱を招きますが、番号の前にVがある場合は、命令セットアーキテクチャ(ISA)について話します。ない場合は、「Cortex M0」または「ARM926EJS」などのモデル番号です。モデル番号には、 ISA数値です。)
aプラットフォームを常にターゲットにする必要があります。最も単純なケースでは、ターゲットCPUがバイナリでコンパイルされたコードを直接実行します(これは、MS DOSのCOM実行可能ファイルにほぼ対応しています)。私が考案したばかりの2つの異なるプラットフォーム、ArmisticeとIntellioについて考えてみましょう。どちらの場合も、画面に42を出力する単純なhello worldプログラムがあります。また、プラットフォームに依存しない方法でマルチプラットフォーム言語を使用していると想定しているため、ソースコードはどちらも同じです。
Print(42)
Armisticeでは、数値の出力を処理する単純なデバイスドライバーがあるため、必要なのはポートへの出力だけです。移植可能なアセンブリ言語では、これは次のようなものに対応します。
out 1234h, 42
ただし、Intellioシステムにはそのようなものがないため、他のレイヤーを経由する必要があります。
mov a, 10h
mov c, 42
int 13h
おっと、マシンコードに至る前に、2つにはかなりの違いがあります。これは、LinuxとMS DOS、またはIBM PCとX-Boxの違いにほぼ対応します(両方とも同じCPUを使用する場合があります)。
しかし、それがOSの目的です。すべての異なるハードウェア構成がアプリケーション層で同じように処理されることを確認するHALがあるとしましょう。基本的に、ArmisticeでもIntellioアプローチを使用し、「ポータブルアセンブリ」コードは同じになります。これは最近のUnixライクなシステムとWindowsの両方で使用されており、多くの場合、組み込みシナリオでも使用されています。これで、ArmisticeとIntellioの両方で、真に移植可能な同じアセンブリコードを使用できるようになりました。しかし、バイナリはどうですか?
私たちが仮定したように、CPUはバイナリを直接実行する必要があります。 Intellioのコードの最初の行mov a, 10h
を見てみましょう。
20 10
ああ。 mov a, constant
は非常に人気があり、独自の命令と独自のオペコードがあることがわかります。 Armisticeはこれをどのように処理しますか?
36 01 00 10
うーん。 mov.reg.imm
のオペコードがあるため、割り当てるレジスタを選択するには別の引数が必要です。また、定数は常にビッグエンディアン表記の2バイトのWordです。これがArmisticeの設計方法です。実際、Armisticeのすべての命令は4バイト長で、例外はありません。
IntellioからバイナリをArmisticeで実行することを想像してください。CPUが命令のデコードを開始し、オペコード20h
を見つけます。 Armisticeでは、これは、たとえばand.imm.reg
命令に対応します。 2バイトのWord定数(10XX
は既に問題あり)を読み取ってから、レジスタ番号(別のXX
)を読み取ろうとします。間違った引数で間違った命令を実行しています。さらに悪いことに、次の命令は完全に偽物になります。データであると考えて実際に別の命令を食べたからです。
アプリケーションが機能する可能性はなく、ほとんどすぐにクラッシュまたはハングします。
これは、実行可能ファイルが常にIntellioまたはArmisticeで実行されていると言う必要があるという意味ではありません。 CPU(UNIXのbash
など)、またはCPUとOSの両方(Javaまたは.NETなど)に依存しないプラットフォームを定義する必要があるだけで、現在ではJavaScriptのようなものです。この場合、アプリケーションは、すべての異なるCPUとOSに対して1つの実行可能ファイルを使用できますが、プラットフォームを変換するターゲットシステム(正しいCPUやOSを直接ターゲットとする)にアプリケーションまたはサービスがあります。 CPUが実際に実行できる何かへの独立したコードこれは、パフォーマンス、コスト、または機能に影響を与える場合とそうでない場合があります。
CPUは通常、ファミリーに属します。たとえば、x86ファミリのすべてのCPUには、まったく同じ方法でエンコードされた共通の命令セットがあるため、拡張機能を使用しない限り、すべてのx86 CPUがすべてのx86プログラムを実行できます(たとえば、浮動小数点演算またはベクトル演算)。 x86で現在最も一般的な例は、もちろんIntelとAMDです。 Atmelは、組み込みデバイスで非常に人気のあるARMファミリのCPUを設計している有名な会社です。AppleにはARMたとえば、独自のCPU。
しかしARMはx86と完全に互換性がありません-それらは非常に異なる設計要件を持ち、共通点はほとんどありません。命令はまったく異なるオペコードを持ち、それらは異なる方法でデコードされ、メモリアドレスは別の扱い...いくつかの安全な操作を使用して2つを区別し、2つに完全に異なるものにジャンプすることにより、x86 CPUとARM CPUの両方で実行されるバイナリを作成できる可能性があります。命令のセットですが、実行時に正しいセットを選択するブートストラッパーだけで、両方のバージョンに個別の命令があることを意味します。
この質問をより馴染みのある環境にキャストし直すことができます。類推によって:
"Ruby実行したいプログラムがありますが、私のプラットフォームにはPythonインタプリタしかありません。Python私のRubyプログラムを実行するためのインタプリタ、またはPythonでプログラムを書き換える必要がありますか? "
命令セットアーキテクチャ(「ターゲット」)は言語(「マシン言語」)であり、異なるCPUは異なる言語を実装します。したがって、ARM CPUにIntelバイナリを実行するよう依頼することは、RubyプログラムをPython通訳。
gccは、「アーキテクチャ」という用語を使用して特定のCPUの「命令セット」を意味し、「ターゲット」は、ABI、libc、エンディアンネスなどの他の変数とともに、CPUとアーキテクチャの組み合わせをカバーします(おそらく「ベアメタル」を含む)。典型的なコンパイラーは、ターゲットの組み合わせのセットが限られています(おそらく1つのABI、1つのCPUファミリーですが、32ビットと64ビットの両方)。クロスコンパイラーは通常、実行するシステム以外のターゲットを持つコンパイラー、または複数のターゲットまたはABIを持つコンパイラーを意味します( this も参照)。
バイナリは異なるCPUアーキテクチャ間で移植可能ですか?
一般的には違います。従来の用語でのバイナリは、特定のCPUまたはCPUファミリのネイティブオブジェクトコードです。ただし、中程度から非常に移植性の高いケースがいくつかあります。
-march=core2
)なんとかしてこの問題を解決できた場合は、無数のライブラリバージョン(私が見ているglibc)の その他のポータブルバイナリ問題 が表示されます。 (ほとんどの組み込みシステムは、少なくともその特定の問題からあなたを救います。)
まだ行っていない場合は、gcc -dumpspecs
およびgcc --target-help
を実行して、何が問題なのかを確認してください。
ファットバイナリには さまざまな欠点 がありますが、まだ潜在的な用途があります( [〜#〜] efi [〜#〜] )。
ELFとELFインタープリター、および任意の バイナリ形式 に対するLinuxカーネルサポートの2つの考慮事項が他の回答にはありません。ここでは、非実際のプロセッサのバイナリまたはバイトコードについて詳しく説明しませんが、これらを「ネイティブ」として扱い、Javaまたは compiled Pythonバイトコードバイナリ 、このようなバイナリはハードウェアアーキテクチャに依存しません(ただし、関連するVMのバージョンに依存し、最終的にネイティブバイナリが実行されます)。
最新のLinuxシステムはELFバイナリを使用します(技術的な詳細 このPDFで )。動的ELFバイナリの場合、カーネルがイメージをメモリにロードするのはカーネルが担当しますが、これは '' ELFヘッダーに設定されたインタープリター」を使用して、重い作業を行います。通常、これには、依存するすべての動的ライブラリが利用可能であることを確認する必要があります(ライブラリを一覧表示する '' Dynamic ''セクションと、必要なシンボルを一覧表示するその他の構造を使用して)—これはalmost汎用間接層。
$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses \
shared libs), stripped
$ readelf -p .interp /bin/ls
String dump of section '.interp':
[ 0] /lib/ld-linux.so.2
(/lib/ld-linux.so.2
もELFバイナリであり、インタープリターがなく、ネイティブバイナリコードです。)
ELFの問題は、バイナリ(readelf -h /bin/ls
)のヘッダーが特定のアーキテクチャ、クラス(32ビットまたは64ビット)、エンディアン方式、およびABI(Appleの「ユニバーサル」ファットバイナリは代替バイナリ形式 Mach-O この問題を解決する代わりに、これはNeXTSTEPで発生しました)。つまり、ELF実行可能ファイルは、ELFが実行されるシステムと一致する必要があります。エスケープハッチの1つはインタープリターです。これは任意の実行可能ファイル(元のバイナリのアーキテクチャ固有のサブセクションを抽出またはマップしてそれらを呼び出すものを含む)、butシステムの実行を許可するELFのタイプによって制約されます。 (FreeBSDには興味深い方法があります Linux ELFファイルを処理する 、そのbrandelf
はELF ABIフィールドを変更します)。
(binfmt_misc
を使用して) LinuxでのMach-Oのサポート があります。太い(32ビットおよび64ビット)バイナリを作成して実行する方法を示す例があります。 Resource forks/ADS は、もともとMacで行われていたように、回避策である可能性がありますが、これをサポートするネイティブLinuxファイルシステムはありません。
カーネルモジュールにも多かれ少なかれ同じことが当てはまります。.ko
ファイルもELFです(ただし、インタープリターセットはありません)。この場合、検索パスにカーネルバージョン(uname -r
)を使用する追加のレイヤーがあります。これは、バージョン管理を使用してELFで理論的に実行できるものですが、ある程度複雑で、ほとんど利益がないと思います。
他の場所で述べたように、Linuxはネイティブでファットバイナリをサポートしていませんが、アクティブなファットバイナリプロジェクト FatELF があります。 数年前 でしたが、(現在は期限切れの)特許の問題もあり、標準カーネルに統合されていません。現時点では、カーネルとツールチェーンの両方のサポートが必要です。 binfmt_misc
アプローチは使用しません。これにより、ELFヘッダーの問題が回避され、太いカーネルモジュールも使用できるようになります。
- 「x86ターゲット、linux OSバージョンx.y.z」で実行するようにアプリケーションをコンパイルした場合、別のシステム「ARMターゲット、linux OSバージョンx.y.z」で同じコンパイル済みバイナリを実行できますか?
ELFではなく、これを行うことはできません。
- 上記が当てはまらない場合、関連するツールチェーンを使用してアプリケーションのソースコードを再ビルド/再コンパイルする方法は、「arm-linux-gnueabiなど」ですか?
簡単な答えはイエスです。 (複雑な答えには、エミュレーション、中間表現、 トランスレータ 、JITが含まれます。i686バイナリを「ダウングレード」してi386オペコードのみを使用する場合を除いて、ここではおそらく興味がなく、ABIフィックスアップは潜在的に可能です。ネイティブコードの翻訳と同じくらい難しいです。)
- 同様に、「x86ターゲット、Linux OSバージョンxyz」で動作するロード可能なカーネルモジュール(デバイスドライバー)がある場合、同じコンパイル済み.koを別のシステムの「ARMターゲット、Linux OSバージョンxy」にロード/使用できますz '?
いいえ、ELFはこれを許可しません。
- 上記が当てはまらない場合、唯一の方法は、関連するツールチェーンを使用してドライバーのソースコードを再ビルド/再コンパイルすることです(たとえば、arm-linux-gnueabi)?
簡単な答えはイエスです。 FatELFではマルチアーキテクチャの.ko
を構築できると思いますが、サポートされているすべてのアーキテクチャのバイナリバージョンを作成する必要がある場合があります。カーネルモジュールを必要とするものはソースに付属していることが多く、必要に応じてビルドされます。 VirtualBoxがこれを行います。
これはすでに長いとりとめのない答えであり、迂回が1つだけあります。カーネルはすでにに組み込まれていますが、専用の仮想マシンが組み込まれています。パケットの照合に使用される BPF VM です。人間が読めるフィルター「ポート22ではなくホストfoo」)は、バイトコードとカーネルパケットフィルター それを実行する にコンパイルされます。新しいeBPFは、パケットだけではありません。理論的には、VMコードは、あらゆる現代のLinux間で移植可能です。 llvmはこれをサポートしています ですが、セキュリティ上の理由から、おそらく管理ルール以外には適していません。
これで、バイナリ実行可能ファイルの定義にどれだけ寛大であるかに応じて、binfmt_misc
を(ab)使用して、シェルスクリプトとファットバイナリサポートを実装し、コンテナ形式としてZipファイルを実装できます。
#!/bin/bash
name=$1
prog=${1/*\//} # basename
prog=${prog/.woz/} # remove extension
root=/mnt/tmpfs
root=$(TMPDIR= mktemp -d -p ${root} woz.XXXXXX)
shift # drop argv[0], keep other args
Arch=$(uname -m) # i686
uname_s=$(uname -s) # Linux
glibc=$(getconf GNU_LIBC_VERSION) # glibc 2.17
glibc=${glibc// /-} # s/ /-/g
# test that "foo.woz" can unzip, and test "foo" is executable
unzip -tqq "$1" && {
unzip -q -o -j -d ${root} "$1" "${Arch}/${uname_s}/${glibc}/*"
test -x ${root}/$prog && (
export LD_LIBRARY_PATH="${root}:${LD_LIBRARY_PATH}"
#readlink -f "${root}/${prog}" # for the curious
exec -a "${name}" "${root}/${prog}" "$@"
)
rc=$?
#rm -rf -- "${root}/${prog}" # for the brave
exit $rc
}
これを「ウォズビン」と呼び、次のように設定します。
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
printf ":%s:%s:%s:%s:%s:%s:%s" \
"woz" "E" "" "woz" "" "/path/to/wozbin" "" > /proc/sys/fs/binfmt_misc/register
これにより、.woz
ファイルがカーネルに登録され、代わりにwozbin
スクリプトが呼び出され、最初の引数が呼び出された.woz
ファイルのパスに設定されます。
ポータブル(ファット).woz
ファイルを取得するには、ディレクトリ階層を使用してtest.woz
Zipファイルを作成するだけです。
i686/
\- Linux/
\- glibc-2.12/
armv6l/
\- Linux/
\- glibc-2.17/
各Arch/OS/libcディレクトリ(任意の選択)内に、アーキテクチャ固有のtest
バイナリと.so
ファイルなどのコンポーネントを配置します。これを呼び出すと、必要なサブディレクトリがtmpfsのメモリ内ファイルシステム(ここでは/mnt/tmpfs
)に抽出されて呼び出されます。
ベリーブート、問題のいくつかを解決します。しかし、それはarm hfで実行する方法の問題を解決しません。x86-32/ 64ビット用のnormall/regullAr linuxディストリビューション。
Isolinux(boatloader linux on usb)に組み込まれるべきだと思います。regullarディストリビューションを認識できるライブコンバーターやライド/ライブでhfに変換できます。
どうして?なぜなら、各Linuxは、ベリーブートによってarm-hfで動作するように変換できるので、beryブートメカニズムをisolinuxにビルドして、例のようにブートするものや、ubuntu creat起動ディスクをビルドするために使用できるからです。