「ゼロからのOS自作入門」を Rust でやる (第1章~第4章)

ゼロからのOS自作入門一通り写経 したところ、 Rust に移植したくなったのでやっていきます。

github.com Rust で OS なので名前は "錆OS" です。安直です。 "sabios" でざっとググったところ、スペイン語で "賢い" って意味があるようです。 良いですね。 クレバーな実装を目指したいところです。

方針

「ゼロからのOS自作入門」の章立てに沿って1章から順番に実装していきます。 C++ で一通り写経は完了しているので完成形の OS と関係ない節はスキップしていきます。

せっかく Rust で実装するので安全性や抽象化という点で MikanOS との差異を出せたら良いなと思っています。

それでは早速やっていきましょう。

第1章~3章

UEFIブートローダーを作成し自作カーネルを起動する章です。 uefi-rs を利用してブートローダーを自作している方も多かったですが、 今回は楽をして Writing an OS in Rust の作者が提供している bootloader を利用することにしました。 bootloader は 2021/5/5 に公開された v0.10 より UEFI でのブートに対応しています。

bootloader を使ったブートローダーの作成は特殊な手順を踏む必要があるのですが、 Writing an OS in Rust の 3rd edition のドラフトpost-02 で詳しく説明されているのでこれに従って実装します。当該記事では cargo の alias を利用してビルドコマンド実行を簡易化していますが、あまり好みではなかったので sabios では Makefile を採用しました。

記事に従って実装していくとあら不思議、3章完了時点の MikanOS とほとんど同じ画面出力が得られました。 これにて3章まで完了とします。 (2章のメモリマップファイルの作成は UEFI 側の処理なので省略しました。)

3章完了時点のソースは以下。

github.com

第4章

本格的なOS実装の始まりです。 最初の一歩はフレームバッファを利用した画面描画用モジュールの作成です。

最初の実装

まずは細かい事を気にせずに、 Rust のスタイルでベタに書いてみました。 フレームバッファへの描画を担当する framebuffer::Drawer 構造体を作成しました。 この構造体を main 関数内で初期化し、描画処理を呼び出しています。

github.com

特に難しいことはないですね。 おまけで MikanOS 本では対応していなかった PixelFormat::U8フレームバッファにも対応しています。

Rust では同一領域に対する &mut の参照が複数同時に存在すると Undefined Behavior となるため、 ブートローダーから渡される FrameBuffer からバッファのスライス &mut [u8] を取り出して利用するのではなく、 FrameBuffer 構造体そのものを framebuffer::Drawer のフィールドとして保持するようにしています。

フレームバッファをどこからでも使えるようにする

これで描画はできるようになったのですが、このままではフレームバッファに描画するために framebuffer::Drawer への参照をあちこちに取り回す必要があります。 それではあまりにも不便なので framebuffer::Drawer 型のグローバル変数 (static 変数) を作成し、カーネル内のどこからでもアクセスできるようにします。

github.com

FrameBufferカーネルのエントリポイント (kernel_main) の引数として渡される情報であるため、static 変数の初期化のタイミングでは利用することができません。 static 変数の初期化のタイミングでは未初期化状態のままにしておき、 kernel_main から framebuffer::init を呼び出すタイミングで初期化します。 このような初期化動作を実現するために conquer_once::OnceCell を利用しています。 また、 static 変数への mutable アクセスを実現するために spin::Mutex を利用しています。 spin::Mutexno_std でも利用できる Mutex です。名前の通りスピンロックが使われています。 どちらの crate も Writing an OS in Rust で知りました (post-03post-12)。本当に良い記事です。

フレームバッファの性能改善

ここまででフレームバッファを使うための基本的な機能は一通り揃いました。 続いては性能改善です。 MikanOS では C++ の仮想関数を使うことで override により条件分岐の回数を大幅に減らすことで性能改善していました。 sabios では dyn Trait を使った動的ディスパッチにより同様の性能改善を実現しました。 画面を見ながらストップウォッチで描画速度を計測したところ、だいたい2倍強高速になったようです。やったね!

github.com

framebuffer::Drawer 全体を dyn Trait 化するのはややこしくなりそうな気がしたので、分岐のある部分のみ dyn Trait として抽出しています。

性能改善の度合いを測定するため、カーネルを release ビルドできるように Makefile も改造しています (コミットメッセージが typo してて意味不明になってますが)。

github.com

まとめ

「ゼロからのOS自作入門」の第4章までを Rust で実装しました。 ここまでは大きな引っかかりもなくスムーズに実装できました。 次の記事では第5章以降を実装していきます。