こんにちは。見習いエンジニアのごえです。
前回の続きです。
【OS自作入門】知識ゼロからの学習記録:エンジニア初心者の奮闘と挑戦(Part2)
https://blog.future.ad.jp/os-jisaku-2
『ゼロからのOS自作入門|内田 公太 著』
日々サーバーの勉強をしていく中で、もっと根本的なところから理解を進めていきたいと思っていたところで、この本を見つけました。
本の趣旨は、電源を入れてOSを呼び出してから様々なアプリケーションを動かせるようになるまでを、自分の手を動かして体験するというものです。文字を表示させたりマウス入力を導入させたりと、当たり前な機能がどのような原理で備わっているかを学ぶことができます。
本ブログの趣旨
勉強の最終目標は書籍の読了&開発完了で、本ブログで学習記録(試行錯誤してもがいている様子)を残していきたいと思います。将来自分が読み返すためや、OS自作に興味がある方の参考になれば幸いです。
学習記録3-1:カーネルをつくろう編(書籍 第3章)
前回は、
①Ubuntuにディスクイメージを作成
②BOOTX64.EFI というバイナリファイルをディスクイメージに保存
③ディスクイメージをマウント
④qemuというエミュレーターでディスクを実行
という手順で「Hello, world!」を表示させることに成功しました。
次の目標はズバリ、「カーネルを起動させること」です。
書籍第3章では、「OSを呼び出すためにカーネルを作ってみよう」という恐ろしい内容になっています。
とはいっても、まずは簡単なコードで書かれたカーネルを動かすことが目標です。動かしたいカーネルはC言語で記述されています。
while (1) __asm__("hlt");
}
このコードは何もしないで永久ループするという意味です。
このC言語形式のままでは起動することができないので、上記3行のコードを以下の流れで変換します。
A:ソースコード → C言語形式で関数を定義したファイル。
B:オブジェクトファイル → ソースコードをコンパイルした結果の機械語を含んだファイル。オブジェクトファイルは、他のオブジェクトファイルとリンクすることが前提のファイルであるため、そのままCPUが読み取ることはできない。
C:カーネルファイル → オブジェクトファイルや起動に必要なプログラムを結合してつくられたファイル(実行ファイル)。
「kernel.elf」というカーネルファイルに変換することができたら、ようやくブートローダー(OSをハードディスクから起動するプログラム)がカーネルを起動させられます。
実際に変換するためのコマンドは以下です。
→ソースコードをコンパイルしてオブジェクトファイルをつくる
$ ld.lld --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
→オブジェクトファイルをリンカによってカーネルファイルにする
このようにコンパイルとリンクを行うことで、「kernel.elf」というファイルができあがります。
OS本体が完成しました!
※書籍に登場するソースコードは一般公開されています。誰でも見れるのでよかったらのぞいてみてください。(https://github.com/uchan-nos/mikanos)
学習記録3-2:ブートローダーが完成しない編(書籍 第3章)
ここまでで、OS本体となるファイル(kernel.elf)が完成しました。あとはOSをハードディスクから起動するプログラムである〝ブートローダー〟を完成させます。手順は以下です。
①ソースコードをダウンロード(環境構築時にダウンロード済)
②ビルドしてブートローダーを完成させる
③さっき作ったカーネルを起動
具体的なコマンドは以下です。
→ビルドするためのソースコードを指定
$ build
→ビルド
$ $HOME/osbook/devenv/run_qemu.sh Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $HOME/workspace/mikanos/kernel/kernel.elf
→カーネルをブートローダーで起動
しかし、何度「build」コマンドをたたいてもビルドが失敗します。
何週間も格闘し続けましたが、全然原因が分かりませんでした
限界に達したので、前回と同じく先輩エンジニアKKさんにご相談させていただきました。
KKさん「どれどれ、もう一回やってみて」
私「はい。あれ??できちゃった」
なんでかあっさりビルドが完了してしまいました。
私「たぶん、相談する直前に実行したコマンドが効いたんだと思います。たまたま見つけたサイトのコマンドを実行しました。」
なんだかよくわからないけど成功して喜んでる私にKKさんは言いました。
私「読んでません」
KKさん「え?」
私「え?だってメッセージめちゃ長いじゃないですか。よくわかんない文字ばっかりですし、、、」
KKさん「なるほど。たしかにそうだ、でもね。。。」
そういってKKさんは力強くひとこと
KKさん「エラーメッセージは演出じゃないんだよ!」
私「た、たしかに! Σ(・□・;)」
KKさん「雰囲気を出すためにメッセージが流れているのではなく、ちゃんと意味があるものなんだよ。」
書籍と同じ結果は得られましたが、どうやら私は試合に勝って勝負に負けていたようです。
学習記録3-2:エラーの原因特定しよう編(書籍 第3章)
KKさんの言葉をきっかけに、私はビルドのエラーメッセージの理解に挑戦し始めました。ビルドのエラーが解消する直前、私は以下のサイトを発見していました。
『EDK2を使ってメモリマップを取得してみよう』
https://kenpos.dev/edk2%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%9E%E3%83%83%E3%83%97%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86/
上記のサイトによると、「edk2※のバージョンが書籍とは違うことが原因でビルドができない」とのことです。具体的には「RegisterFilterLib」というライブラリが悪さをしているとのことです。
※edk2とは、書籍で使われているファームウェア開発キットのことです。
このことを手掛かりに「RegisterFilterLib」について検索すると、下記のサイトに行きつきました。
『ゼロからのOS自作入門』
https://zero.osdev.jp/faq.html
このサイトでは「2021/04/08 にエラーの原因となる変更が EDK II に導入されたことが原因」と書かれています。
およその原因は判明しましたが、私のケースが本当に上記サイトと同じかを確かめます。そのためにedk2のwikiをみて公式のリポジトリに飛びました。
Wikipedia『TianoCore EDK II』
https://en.wikipedia.org/wiki/TianoCore_EDK_II
↓
github『edk2』
https://github.com/tianocore/edk2/commits/edk2-stable202105
↓
github(疑わしき2021/4/8のバージョンのページ)
https://github.com/tianocore/edk2/commits/master/MdePkg/Library/BaseLib/BaseLib.inf
そして、下記の手順で検証しました。
①2021/4/6のバージョンにコミットしてビルドする
②2021/4/8のバージョンにコミットしてビルドする
その結果、
2021/4/6のバージョン:ビルド成功
2021/4/8のバージョン:ビルド失敗
つまり、2021/4/8の変更でビルドが失敗するようになったということが分かりました!
そして、ブートローダーがビルドできたので、カーネルファイルを読み込み、カーネルを起動させることができました!
メインメモリの様子を見ると、きちんとソースコードの内容が実行されていることが分かります!
これで、晴れてカーネルが起動できました!めでたしめでたし~
感想
KKさんの「エラーメッセージは演出じゃない!」という名言で激震が走りました。これまでは何でもかんでもブラックボックスになって、結局なにも分からずじまいでしたが、今回初めてビルドのエラーを自分で特定することができました。パズルを解いている気分で楽しかったです。
OSを自作するという非常にローレベルの勉強をさせていただいているので、ぜひ今後の業務にも生かしていきたいと思います。
補足:開発環境
おまけで、以下に開発環境を掲載させていただきます。
ノートパソコンでHyper-vという仮想化ソフトを起動させてUbuntuを起動
↓
UbuntuでOSを開発
↓
Ubuntuでエミュレーターを使って、開発したOSを起動させる。
仮想環境上で仮想しています(多分)