「ゼロからの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章完了時点のソースは以下。
第4章
本格的なOS実装の始まりです。 最初の一歩はフレームバッファを利用した画面描画用モジュールの作成です。
最初の実装
まずは細かい事を気にせずに、 Rust のスタイルでベタに書いてみました。
フレームバッファへの描画を担当する framebuffer::Drawer
構造体を作成しました。
この構造体を main
関数内で初期化し、描画処理を呼び出しています。
特に難しいことはないですね。
おまけで MikanOS 本では対応していなかった PixelFormat::U8
なフレームバッファにも対応しています。
Rust では同一領域に対する &mut
の参照が複数同時に存在すると Undefined Behavior となるため、 ブートローダーから渡される FrameBuffer
からバッファのスライス &mut [u8]
を取り出して利用するのではなく、 FrameBuffer
構造体そのものを framebuffer::Drawer
のフィールドとして保持するようにしています。
フレームバッファをどこからでも使えるようにする
これで描画はできるようになったのですが、このままではフレームバッファに描画するために framebuffer::Drawer
への参照をあちこちに取り回す必要があります。
それではあまりにも不便なので framebuffer::Drawer
型のグローバル変数 (static
変数) を作成し、カーネル内のどこからでもアクセスできるようにします。
FrameBuffer
はカーネルのエントリポイント (kernel_main
) の引数として渡される情報であるため、static 変数の初期化のタイミングでは利用することができません。
static 変数の初期化のタイミングでは未初期化状態のままにしておき、 kernel_main
から framebuffer::init
を呼び出すタイミングで初期化します。
このような初期化動作を実現するために conquer_once::OnceCell
を利用しています。
また、 static 変数への mutable アクセスを実現するために spin::Mutex
を利用しています。
spin::Mutex
は no_std
でも利用できる Mutex です。名前の通りスピンロックが使われています。
どちらの crate も Writing an OS in Rust で知りました (post-03 と post-12)。本当に良い記事です。
フレームバッファの性能改善
ここまででフレームバッファを使うための基本的な機能は一通り揃いました。 続いては性能改善です。 MikanOS では C++ の仮想関数を使うことで override により条件分岐の回数を大幅に減らすことで性能改善していました。 sabios では dyn Trait を使った動的ディスパッチにより同様の性能改善を実現しました。 画面を見ながらストップウォッチで描画速度を計測したところ、だいたい2倍強高速になったようです。やったね!
framebuffer::Drawer
全体を dyn Trait 化するのはややこしくなりそうな気がしたので、分岐のある部分のみ dyn Trait として抽出しています。
性能改善の度合いを測定するため、カーネルを release ビルドできるように Makefile も改造しています (コミットメッセージが typo してて意味不明になってますが)。
まとめ
「ゼロからのOS自作入門」の第4章までを Rust で実装しました。 ここまでは大きな引っかかりもなくスムーズに実装できました。 次の記事では第5章以降を実装していきます。