こんにちは、turtlekazuです。全体像を把握したところで、「ゼロからのOS自作入門」を読み進めていきたいと思います。「その1」では、ブートローダの作成について書いていきます。各章が長く、分割することにしたため、タイトルがややこしくなっています…。
ブートローダとは
OSをメインメモリに読み込み、起動させるプログラムのこと
内田公太,「ゼロからのOS自作入門」,マイナビ出版, 2021, p20
だそうです。こちらの記事によれば、ちょうどBIOSとOSの間にあって、BIOSからの呼び出しに応じてOSを目覚めさせるプログラムだといえます。
第1章 PCの仕組みとハローワールド
OSの自作の最初のステップとして、”Hello World”を表示するシンプルなOSを作ります。バイナリエディタで作成するので、コンピュータと通訳なしで直接対話してる感じがあります。
実行ファイル(.EFI)の作成
「Okteta」というバイナリエディタをインストールして…
$ sudo apt install okteta
ひたすら0と一部数値を入力!(BOOTX64.EFIという名前で保存)

チェックサムというナイスな技術を使って、中身に間違いがないか確認。誤り検出符号とかいうらしい。
$ sum BOOTX64.EFI
-> 出力値が 「12430 2」 ならOK
ちなみに、表示される文字のうち、1つ目がチェックサムの値で、2つ目が1024バイト単位のブロック数(切り上げ)です。ブロック数は、0x60行=1行16バイト×96行(16^1*6 + 16^0*0)=1536バイトなので、2というわけですね。
その後、自分は専用のテスト端末が用意できなかったので、QEMUによる起動。とその前に、開発環境をダウンロードする必要がありました。
開発環境のダウンロードとインストール
開発環境は、こちらからダウンロードします。めちゃ詳しいREADMEに感動しつつ、案内にそって実行していきます。「ビルド環境の構築」と「MikanOSのソースコードの入手」の部分だけでOKでした。
一点だけ、つまったところがありました。自分がUbuntuの中にPyenvを入れて、Python環境をいじっていたせいなのか、「ansible」をインストールしても、
ModuleNotFoundError: No module named 'ansible'
となってしまいます。Ansible経由でいろんなものをダウンロードするので、なんとしても使えるようにしたい。
アンインストールをして入れ直してもだめだったので、Pipでインストールしてみるもうまくいきません。Pyenvを消してからaptとpipで入れ直すと、今度は消したはずのPyenvのパスにアクセスしてしまっている…。
アクセスするPythonパスの上書きを/.local/ansible/ansible.cfg
の中にある[default]
項目にinterpreter_python=...
と書くとできるよという記事を見つけてやってみるも、結局反映されず。
結局、pipでインストールする際に、ansibleとansible-coreの2つをインストールしていたらしく、ansible-coreをアンインストールし忘れていたのがパスが古いままの原因でした。ansible-coreも消してから入れ直すと、無事にansibleが認識されました。
QEMUでの実行
作成した実行ファイルを、以下のコマンドでイメージファイル(.img)に変換します。
$ qumu-img create -f raw disk.img 200M
-> 「disk.img」という名前で200MBのRaw形式でフォーマットしたイメージを作成
$ mkfs.fat -n 'MIKAN OS' -s 2 -f 2 -R 32 -F 32 disk.img
-> FAT形式のファイルシステムをdisk.imgに作成、オプション詳細はこちら
$ mkdir -p mnt
-> 「mnt」という名前のフォルダを作成(必要に応じて親ディレクトリも作成)
$ sudo mount -o loop disk.img mnt
-> ループバックデバイス(直接中身を操作できるデバイス)として、mntフォルダにdisk.imgをマウントする(ディレクトリ内に埋め込む)
$ sudo mkdir -p mnt/EFI/BOOT$ sudo cp BOOTX64.EFI mnt/EFI/BOOT/BOOTX64.EFI
-> 管理者権限で、mnt/EFI/BOOTというフォルダを作成(必要に応じて親ディレクトリも作成)
$ sudo umount mnt
-> mntフォルダからdisk.imgをアンマウント(ディレクトリ内埋め込みを解除)
こうして完成した起動可能なディスクイメージを、QEMUで実行します。
$ qemu-system-x86_64 \
-drive if=pflash,file=$HOME/osbook/devenv/OVMF_CODE.fd \
-drive if=pflash,file=$HOME/osbook/devenv/OVMF_VARS.fd \
-hda disk.img
-> x86_64アーキテクチャ(64bitのIntel系CPU)で、UEFIモードにてdisk.imgをQEMUで起動する(hdaは、おそらくハードディスクAの略)。OVFMはOpen Virtual Machine Firmwareのことで、仮想マシン上でUEFIブートをしたいときに使うようです。
なお、QEMUでの実行のために必要なコマンドを一気に実行してくれるシェルスクリプトを著者の方が用意してくださっています。
$ $HOME/osbook/devenv/run_qemu.sh BOOTX64.EFI
QEMUが起動すると、「Hello, world!」が真っ黒い画面の中に浮かび上がります。自分は文字列(Hello, world!)を書くところの行を間違えて書いていたため、最初何も表示されず焦りました(チェックサムは通っていたのでなおさら)。

C言語で実装する
実行ファイルをバイナリエディタで作成するのは、実体を知る上では効果的ですが、あまり効率がいい手段ではありません。C言語で書けば、かなりサクッとかけてしまいます。
/* hello.c */
EFI_STATUS EfiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello, world!\n");
while (1); /* 無限ループ */
return (0);
}
文字列の頭にLをつけているのは、UEFIで文字表示をするにはワイド文字を使う必要があるためだそうです。できた「hello.c」のファイルを、Clangでコンパイルして、LLDでリンクづけを行います。なぜLLDなのか、由来を調べたのですが出てこなかったため、LLVMのリンカ(GCCでのld)という解釈を勝手にしています。なぜGCCでリンカがldなのかも、分かりませんでした。Link Dataとかですかねー
以下、コンパイルのコマンドです。(hello.cを保存しているディレクトリにて)
$ clang -target x86_64-pc-win32-coff \
-mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c hello.c
-> 2つ目のオプションはx86_64コードの「レッドゾーン」の不使用、3つ目のオプションはスタック保護の不使用、4つ目のオプションはワイド文字型の2バイトでの定義、5つ目のオプションは「壁」ではなく「Warning all」の略で、全ての警告の出力を意味している
hello.oというオブジェクトファイルが生成されるので、続いてリンカのコマンド(同上)を実行。
$ lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o
->subsystemは、実行可能ファイルの環境を指定、entryは、開始アドレスを設定(この場合、関数名)
これで生成されるhello.efiというファイルが実行ファイルです。実行ファイルのある場所()に移動し、以下のコマンドでQEMUにて起動できます。
$ $HOME/osbook/devenv/run_qemu.sh hello.efi
無事に起動すると、再び「Hello, world!」を拝めます。
結び
バイナリエディタをいじったのは人生で初めてだったのでワクワクしました。同時に、高級言語がいかに便利かも痛感しました。
ちなみに、C言語で作った方の.EFIファイルをバイナリエディタで開くと、前半はそっくりなのですが真ん中あたりで内容に違いがありました。バイナリベタ打ちのものでは文字数が多くなるとダメだったのですが、C言語の方ではちゃんと表示できていたので、その辺の処理をきちんとやってくれているんですかね。
次回以降では、EDKⅡという開発キットを使って実行ファイルを作っていきます。