実行可能な例
技術的には、OSなしで実行されるプログラムはOSです。それでは、ごくわずかなHello World OSを作成して実行する方法を見てみましょう。
以下のすべての例のコードは、このGitHubリポジトリにあります。
ブートセクター
x86では、ブートセクタの一種であるマスターブートセクタ(MBR)を作成して、ディスクにインストールすることができます。
ここでは、1回のprintf
呼び出しで作成します。
printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
結果:
Ubuntu 18.04、QEMU 2.11.1でテスト済み。
main.img
次のものが含まれます。
\364
8進数== 0xf4
16進数:hlt
命令のエンコード。CPUに動作を停止するよう指示します。
したがって、プログラムは何も行いません。開始と停止のみです。
\x
16進数はPOSIXで指定されていないため、8進数を使用します。
このエンコードは次の方法で簡単に取得できます。
echo hlt > a.asm
nasm -f bin a.asm
hd a
もちろん、0xf4
エンコーディングはIntelのマニュアルにも記載されています。
%509s
509個のスペースを作成します。バイト510までファイルに入力する必要がありました。
\125\252
8進数==の0x55
後に0xaa
:ハードウェアに必要なマジックバイト。バイト511および512でなければなりません。
存在しない場合、ハードウェアはこれを起動可能なディスクとして扱いません。
何もしなくても、いくつかの文字がすでに画面に印刷されていることに注意してください。これらはファームウェアによって印刷され、システムを識別するのに役立ちます。
実際のハードウェアで実行する
エミュレータは楽しいものですが、ハードウェアは本当に重要です。
ただし、これは危険であり、誤ってディスクを消去する可能性があることに注意してください。これは、重要なデータが含まれていない古いマシンでのみ実行してください。さらに良いことには、Raspberry Piなどの開発ボードについては、以下のARMの例を参照してください。
典型的なラップトップの場合、次のようなことをする必要があります。
画像をUSBスティックに書き込みます(データが破壊されます!):
sudo dd if=main.img of=/dev/sdX
USBをコンピューターに接続します
それをオン
USBから起動するように指示します。
これは、ファームウェアがハードディスクの前にUSBを選択することを意味します。
それがお使いのマシンのデフォルトの動作ではない場合、電源投入後、USBから起動することを選択できる起動メニューが表示されるまで、Enter、F12、ESCなどの奇妙なキーを押し続けます。
多くの場合、これらのメニューで検索順序を構成できます。
たとえば、古いLenovo Thinkpad T430、UEFI BIOS 1.16では、次のように表示されます。
こんにちは世界
最小限のプログラムを作成したので、こんにちは世界に移りましょう。
明らかな質問は次のとおりです。IOを行う方法は?いくつかのオプション:
- ファームウェア(BIOSやUEFIなど)に依頼してください
- VGA:書き込まれた場合に画面に印刷される特別なメモリ領域。保護モードで使用できます。
- ドライバーを作成し、ディスプレイハードウェアと直接話します。これは「適切な」方法です。より強力ですが、より複雑です。
シリアルポート。これは、ホスト端末から文字を送受信する非常に単純な標準化されたプロトコルです。
ソース。
残念ながら、ほとんどの最新のラップトップでは公開されていませんが、開発ボードを使用する一般的な方法です。以下のARMの例を参照してください。
このようなインターフェースは、たとえばLinuxカーネルのデバッグに非常に役立つため、これは本当に残念です。
チップのデバッグ機能を使用します。例えば、ARMはセミホスティングを呼び出します。実際のハードウェアでは、追加のハードウェアとソフトウェアのサポートが必要ですが、エミュレーターでは無料の便利なオプションになります。例。
ここでは、x86でより簡単なBIOSの例を実行します。ただし、これは最も堅牢な方法ではないことに注意してください。
main.S
.code16
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
link.ld
SECTIONS
{
. = 0x7c00;
.text :
{
__start = .;
*(.text)
. = 0x1FE;
SHORT(0xAA55)
}
}
アセンブルとリンク:
gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o
結果:
テスト済み:Lenovo Thinkpad T430、UEFI BIOS 1.16。Ubuntu 18.04ホストで生成されたディスク。
標準のユーザーランドアセンブリ手順の他に、次のものがあります。
.code16
:GASに16ビットコードを出力するよう指示します
cli
:ソフトウェア割り込みを無効にします。これらは、プロセッサがhlt
int $0x10
:BIOS呼び出しを行います。これは、文字を1つずつ印刷するものです。
重要なリンクフラグは次のとおりです。
--oformat binary
:生のバイナリアセンブリコードを出力します。通常のユーザーランド実行可能ファイルの場合のように、ELFファイル内でワープしないでください。
アセンブリの代わりにCを使用
Cはアセンブリにコンパイルされるため、標準ライブラリなしでCを使用するのは非常に簡単なので、基本的には次のものが必要です。
- メモリ内の適切な場所に配置するリンカスクリプト
- GCCに標準ライブラリを使用しないよう指示するフラグ
- に必要なC状態を設定する小さなアセンブリエントリポイント
main
、特に:
TODO:GitHubでx86の例をリンクします。これは私が作成したARMの1つです。
ただし、標準ライブラリを使用する場合は、POSIXを介してC標準ライブラリ機能の多くを実装するLinuxカーネルがないため、物事はさらに楽しくなります。
Linuxのような本格的なOSに移行することなく、いくつかの可能性があります。
腕
ARMでは、一般的な考え方は同じです。アップロードしました:
Raspberry Piの場合、https://github.com/dwelch67/raspberrypiは、今日入手可能な最も人気のあるチュートリアルのように見えます。
x86との違いは次のとおりです。
IOは、マジックアドレスに直接書き込むことで行われin
、out
指示はありません。
これは、メモリマップIOと呼ばれます。
Raspberry Piなどの一部の実際のハードウェアでは、ファームウェア(BIOS)を自分でディスクイメージに追加できます。
ファームウェアの更新がより透過的になるため、これは良いことです。
ファームウェア
実際、ブートセクタは、システムのCPUで実行される最初のソフトウェアではありません。
実際に最初に実行されるのは、ソフトウェアであるファームウェアと呼ばれるものです。
- ハードウェアメーカーによって作られた
- 通常はクローズドソースですが、Cベースの可能性があります
- 読み取り専用メモリに保存されるため、ベンダーの同意なしに変更することは難しく/不可能です。
よく知られているファームウェアは次のとおりです。
- BIOS:古いすべてが存在するx86ファームウェア。SeaBIOSは、QEMUで使用されるデフォルトのオープンソース実装です。
- UEFI:BIOSの後継で、より標準化されていますが、より機能的で、非常に肥大化しています。
- Coreboot:高貴なクロスアーチオープンソースの試み
ファームウェアは次のようなことを行います:
起動可能なものが見つかるまで、各ハードディスク、USB、ネットワークなどをループします。
QEMUを実行する-hda
と、それmain.img
はハードウェアに接続されたハードディスクであり、
hda
最初に試行されるものであり、使用されます。
最初の512バイトをRAMメモリアドレス0x7c00
にロードし、そこにCPUのRIPを配置して実行します
ブートメニューやBIOS印刷呼び出しなどをディスプレイに表示する
ファームウェアは、ほとんどのOSが依存するOSのような機能を提供します。たとえば、PythonサブセットがBIOS / UEFIで実行するように移植されています:https : //www.youtube.com/watch?v=bYQ_lq5dcvM
ファームウェアはOSと見分けがつかず、ファームウェアは唯一可能な「真の」ベアメタルプログラミングであると主張できます。
このCoreOS開発者が言うように:
難しい部分
PCの電源を入れたとき、チップセットを構成するチップ(ノースブリッジ、サウスブリッジ、SuperIO)はまだ適切に初期化されていません。BIOS ROMは可能な限りCPUから取り外されていますが、CPUからアクセスする必要があります。そうしないと、CPUには実行する命令がありません。これは、BIOS ROMが完全にマップされることを意味するものではなく、通常はマップされます。ただし、ブートプロセスを開始するのに十分な量がマップされています。他のデバイスは、忘れてください。
QEMUでCorebootを実行すると、Corebootの上位層とペイロードを試すことができますが、QEMUは低レベルのスタートアップコードを試す機会がほとんどありません。1つには、RAMは最初から正しく機能します。
BIOS初期状態のポスト
ハードウェアの多くのものと同様に、標準化は脆弱であり、依存すべきでないことの1つは、BIOSの後にコードが実行を開始するときのレジスタの初期状態です。
だから、あなた自身に賛成して、次のようないくつかの初期化コードを使用してください:https : //stackoverflow.com/a/32509555/895245
レジスタのように%ds
して%es
、重要な副作用を持っているので、あなたが明示的にそれらを使用していない場合でも、それらをゼロにする必要があります。
一部のエミュレーターは実際のハードウェアよりも優れており、初期状態が優れていることに注意してください。その後、実際のハードウェアで実行すると、すべてが壊れます。
GNU GRUBマルチブート
ブートセクターは単純ですが、あまり便利ではありません。
- ディスクごとに1つのOSのみを使用できます
- ロードコードは本当に小さく、512バイトに収まる必要があります。これは、int 0x13 BIOS呼び出しで解決できます。
- 保護モードに移行するなど、多くのスタートアップを自分で行う必要があります
これらの理由により、GNU GRUBはマルチブートと呼ばれるより便利なファイル形式を作成しました。
最小限の作業例:https : //github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
また、GitHubサンプルリポジトリで使用して、USBを100万回書き込むことなく、実際のハードウェアですべてのサンプルを簡単に実行できるようにします。QEMUでは次のようになります。
OSをマルチブートファイルとして準備すると、GRUBは通常のファイルシステム内でそれを見つけることができます。
これは、ほとんどのディストリビューションが行うことで、OSイメージをの下に置きます/boot
。
マルチブートファイルは、基本的に特別なヘッダーを持つELFファイルです。GRUBで指定されています:https : //www.gnu.org/software/grub/manual/multiboot/multiboot.html
を使用して、マルチブートファイルを起動可能なディスクに変換できますgrub-mkrescue
。
エル・トリト
CDに書き込むことができる形式:https : //en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
ISOまたはUSBで動作するハイブリッドイメージを作成することもできます。これはgrub-mkrescue
(例)で実行できます。また、make isoimage
を使用してLinuxカーネルでも実行できますisohybrid
。
資源