RustでOSを自作する (1)カーネルの呼び出しまで

数年前に「30日ででできる! OS自作入門」を読みながらOSを作ったことがあった。
当時はふむふむと思い一通りOSができたことでOSを理解していたつもりになっていたが、
最近になって「OSよくわからん」みたいな気分に再びなってきた。
また当時はgitのようなVCSも使っていなかったのもあり、書いたコードが残っていない。
よって再びOSを作ることで今度こそ本当にOSを理解していく。

multiboot header

アセンブラで1st stage loader、2nd stage loaderとガリガリ書いていくのはさすがに面倒なのでローダはgrubを使用する。
まずはmultiboot headerの設定から。
以下を参考にした。
http://nongnu.askapache.com/grub/phcoder/multiboot.pdf

最低限必要なのは、magic、architecture、header size、checksum、tagぐらい。
この辺はただ単にざっと並べて行けば良い。

gist89ab43b8a6a05adeab304d4d8128892e

カーネル(アセンブラ部分)

つづいて、さっそくカーネルを書いていく。
やっていることは単純で外部ファイルで定義されているはずのkernel_main関数を呼び出している。
このkernel_main関数は後ほどRustで書いていく。

gist810e038da8daf292d8a50fe530a58597

続いてMakefileを書く。以下の通り。

gist2582f090fc0b6ca15aebcf179a702412

今回のOSは32bitで作っていきたいため、nasmのオプションでアウトプットとして32bitのELFフォーマットを指定している。
これでmakeコマンドを叩けば*.oというバイナリファイルができあがる。

bootという名前でディレクトリを作り、以上のコードをまとめて入れておく。

カーネル(Rust部分)

OSを書く場合はC言語が使われることが多いが個人的な趣味の問題でRustで書いていく。
早速Rustのプロジェクトを作成

cargo new --lib kernel

続いてプロジェクトの情報を指定するファイルを作成。
1つはおなじみのCargo.toml、もう一つはRustが実行されるOSの情報を記述するJSONファイル。
後者は通常のRustプロジェクトでは必要ないものだが、
自作OSのようなRustのtargetとしてサポートされていないような環境への実行ファイルを作るために必要になる。
まずはCargo.tomlから。

gist80b0ea193d9702d11d4bcaf36a3414a2

自作OSではライブラリの動的リンクはできない(そもそも環境上にライブラリが存在しない)ため静的なオブジェクトを作る必要がある。
そのためcrate-typeでstaticlibを指定している。

続いて環境情報を記述したJSONファイル。

gistf95a79c9739a495d7a58e1a54fa440be

実際これはx86アーキテクチャ向けの情報とほとんど変わらないのだが、OSは既存のものではないため"none"としている。

以上、2つのファイルが出来たらいよいよカーネル本体へ。

gisteff30cbbebb05721dc283331e2052400

先程boot.Sでexternしたkernel_main関数を定義している。
内部で行っていることは至極単純で、
メモリの0xb8000から始まるVGA text bufferに値を書き込むことで画面上に文字列「SawayakanaAsa」を表示している。
またpanic関数はRustがPanic状態に陥った時に呼び出される関数で定義していないとコンパイルエラーになる。
現状は内部で特になにもしていない。

続いてMakefile

gistc89015334427d17b931ad33725442d8b

指定したtarget向けのバイナリファイルが出力するため、コンパイルにはXargoを使う。
github.com

リンカ

以上でOSの起動までに必要なコードは揃った。
最後にリンクを行えば見事OSが起動する。
ldscriptという名前でディレクトリを作り、その下にリンカスクリプトを配置する。
リンカスクリプトは以下の通り。

gistdc66edea7ba38b00610158a08c011e4e

メモリの1M(=0x1000000)以降に.multiboot_headerセクションを、続いてboot.SとRustで書いたカーネル部分に含まれるであろう.textセクションを展開している。

GRUB

今回はブートローダーにGRUBを使うため、その設定ファイルも必要。

gist5e526a3c57abadb73ccaf4177f0c5fd7

また、最終的に全てのビルド作業をmakeコマンドで一括実行したいのでMakefileを作る。

gist88d24ac704cfa7ef33fd972f3db5141b

ディレクトリ構造

ここまで作成したファイルを以下の様に配置する。

.
├── boot
│   ├── boot.S
│   ├── Makefile
│   └── multiboot_header.S
├── grub.cfg
├── kernel
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── i386-sawayakanaos.json
│   ├── Makefile
│   └── src
│       └── lib.rs
├── ldscript
│   └── linker.ld
└── Makefile

あとは1番上のディレクトリでmakeコマンドを叩けば、buildディレクトリが作成され、その直下にSawayakanaOS.isoが作成されているはず。
これをQEMUで実行してみる。

make run

f:id:itto-ki:20180805165710p:plain

OSが立ち上がり、画面上に「SawayakanaAsa」と表示される。めでたい。

注意点としてgrub-pc-binをインストールしないとmake内でgrub-mkrescueした際にEFIイメージが作成される。
今回はGRUBイメージを作成したいのでこれをインストールすること。

雑記

Rustで書いたカーネルのファイルサイズがかなりでかい。
既に752Kbyteもある。

github.com