RustでOSを作りながら学ぶ!オペレーシングシステムの仕組み

Writing an OS in Rust は、ステップバイステップでRustを使って軽量なOSを作成するチュートリアルです。手を動かしながらOSを少しずつ作ることで、カーネル、CPU割り込み、メモリ管理といったOSの基本的な仕組みについて理解を深めることができます。
OSの仕組みについて興味がある方は、ぜひチュートリアルをトライしてみてください!

Writing an OS in Rust とは?

Writing an OS in Rust というウェブサイトは、Rustで小さなオペレーティングシステムを作成するためのブログシリーズです。それぞれの記事では、必要なコードが全て含まれているため、読者が順をおって開発をしていくことが可能です。また、記事に対応するGitHubリポジトリGitHub - phil-opp/blog_os: Writing an OS in Rustソースコードが公開されています。

このブログシリーズは以下のようなセクションに分けられています。

  • 最低限の機能: OSに依存しないRustバイナリ、最小限のRustカーネルVGAテキストモード、テストなどについて説明しています。
  • 割り込み: CPU例外、ダブルフォールト、ハードウェア割り込みなどについて説明しています。
  • メモリ管理: ページングの紹介と実装、ヒープ割り当て、アロケータの設計などについて説明しています。
  • マルチタスク: RustのAsync/Awaitについて説明しつつ、基本サポートをカーネルに追加します。

ちなみに、日本語の翻訳サイトも Writing an OS in Rustにあります。

最低限の機能

最低限の機能として、OSに依存しないRust製のカーネルを作り、ブートプロセスで起動できるようにしていきます。

まず、ベアメタル環境(基盤となるOSなしで動く実行環境)を用意し、Rustのコードを実行できるようにします。
Rustの標準ライブラリはOS機能に依存しており、普段当たり前のように利用していたOSが提供しているスレッド、ヒープメモリ、ネットワーク、乱数、標準出力といったような機能は使えません。そのため、標準ライブラリにリンクしないRustの実行可能ファイルをつくります。Rustの、エントリポイントを上書きし、パニックが発生したときのパニックハンドラーを実装し、ベアメタル環境用にビルドするようにします。

次に、Rustで最小限の64bitカーネルを作ります。 ブートプロセスを理解しながら、OSのカーネルを起動して、"Hello World!"を画面に出力できるようにします。
x86には2つのファームウェアの標準規格があり、BIOS(古く時代遅れだがシンプルで1980年代からサポートされている)とUEFI(セットアップが複雑だが、モダンでより多くの機能をもっている)があり、今回はBIOSの規格でOSを起動させます。

ブート時に"Hello World!"を表示

ブートプロセスの大まかな流れは次のようになっています。

  • コンピュータを起動すると、マザーボードのROMに保存されたファームウェアのコードが実行する。
  • このコードは、起動時の自己テストを実行し、使用可能なRAMを検出し、CPUとハードウェアを事前に初期化する。
  • その後、ブータブルディスクを探し、OSのカーネルを起動させる。

そして、VGAテキストモードを利用して画面にテキストを出力します。
VGAテキストモードで文字を画面に出力するには、VGAハードウェアのテキストバッファに書き込む必要があります。VGAテキストバッファはアドレス 0xb8000 に memory mappged I/O を通じてアクセスでき、RAMではなく直接VGAハードウェアのテキストバッファにアクセスすることができます。

最後に、Rust製カーネル単体テスト結合テストを実行できるようにします。
Rustのカスタムテストフレームワーク機能を使い、ベアメタル環境でシンプルなテストをサポートできるように実装します。また、テスト時の出力をVGAバッファの代わりにコンソールに出力するためにシリアルポートの単純なドライバを実装します。

割り込み

割り込みでは、CPU例外、ダブルフォルト、ハードウェア割り込みを実装していきます。

まず、CPU例外では、CPU例外、IDT(割り込みディスクリプタテーブル)、例外の呼び出し規約について学びます。
CPU例外は、コンピュータのCPUがプログラムの実行中に予期しない状況やエラーに遭遇したときに発生する特殊な状態のことです。予期しない事象が起きたときにプログラムの実行を中断し、それに対応するための特別なコード(例外ハンドラ)を実行します。 CPU例外の例として、ゼロ除算、セグメンテーション違反、CPUが理解できない不正な命令などがあります。詳細 Exceptions - OSDev Wiki にあります。

次にダブルフォルトはなにか、どのような条件で発生するかを学びます。ダブルフォルトがトリプルフォルトにならないようにしたり、ダブルフォルトがスタックオーバーフロー下でも動くようにします。

最後に、ハードウェア割り込みついて実装します。
割り込みコントローラはすべてのデバイスからの割り込みを取りまとめてCPUに通知します。また、割り込みコントローラはプログラム可能で、PIC(Programmable Interrupt Controller)と呼ばれます。ハードウェア割り込みとして、タイマー割り込みとキーボード割り込みのハンドラの実装をしていきます。

Intel 8259 PIC

メモリ管理

メモリ管理では、ページング、ヒープ割り当てについて実装していきます。

ページングでは、2つのメモリ保護技術のセグメンテーションとページングについて理解します。そして、ページングとページテーブルの仕組みについて理解しつつ、ページテーブルに新しいマッピングを作ることでページングを実現できるように実装していきます。

ページテーブルへのアクセス

次に、ヒープによる動的メモリについて実装していきます。
Crateを使ってヒープメモリの実装をしたり、独自のヒープアロケータ(バンプアロケータ、リンクリストアロケータ)を実装したりします。

ヒープとコールスタック

マルチタスク

マルチタスクでは、Rsutのasync/await機能を利用しマルチタスクをできるようにします。
Rustのasync/await機能の登場人物のFuture、Pin、Poll、Executor, Wakerなどを理解しつつ、自前で基本的なExecutorを実装することで、async/awaitの基本サポートをカーネルに追加していきます。

まとめ

Writing an OS in Rust というウェブサイトで、手を動かしながらOSを作ることでOSの基本的な機能や仕組みについて学ぶことができます。 普段なれない概念がたくさんでてくるので難しいこともありますが、CPU割り込み、ダブルフォルト、ページングやヒープなど知識として知っているけど仕組みや実装はどうなっているのかわからないという部分について理解が深めれるので興味がある方はぜひトライしてみてください!