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

早い物でこのシリーズも4記事目です。 内容もだんだん難しくなってきて、Rust に移植するのも大変になってきましたが、頑張っていきましょう。

第8章

メモリ管理を実装する章です。 前回 の記事 (第6章その1) では USB マウスへ対応させようとしていたのですが、 USB デバイスの制御に必要な MMIO 領域の仮想アドレスが割り当てられていないためにアクセスができず、USB ドライバーが動作しませんでした。 この問題に対処するため、先に8章を実装することにしたのでした。

UEFI メモリマップの表示 (day08a)

ブートローダーから渡されるメモリマップ情報を表示させます。

github.com

bootloader クレートのブートローダーから渡されるメモリマップ情報 (memory_regions) では、連続した領域でもページ毎に(?)バラバラになっており、そのまま println すると出力が大量となってしまいます。 この現象への対処として、 MemoryRegions というイテレータを作成し、連続した領域は一つにまとめるようにしています。

ブートローダーから渡される情報メモリマップ情報では各領域の種別は以下 enum で表されます。

MemoryRegionKind in bootloader::boot_info - Rust より

#[non_exhaustive]
#[repr(C)]
pub enum MemoryRegionKind {
    Usable,
    Bootloader,
    UnknownUefi(u32),
    UnknownBios(u32),
}

Usableカーネルで自由に利用可能な領域です。 UnknownUefi(u32) についても u32 が特定の値の場合は利用可能だと思うのですが、どの数値が何に対応しているのかが分からなかったこと (UEFIのヘッダを見れば分かるのでしょうが)、また、全領域合計でたかだか2MB程度だったので当該領域の再利用はやめ、 Usable の領域だけ利用することとしました。

これにより、UEFI データ領域に割り当てられている領域の移動は不要になりますので、スタック領域の移動、セグメンテーションの設定は省略しました。

ページングの設定

C++版ではページング設定として物理アドレスを同じ仮想アドレスにマッピング (アイデンティティマッピング) していました。 Rust では bootloader クレートが物理メモリのマッピングに対応しているのでそれをそのまま使い楽をしました。

github.com

やったことは簡単で、 Cargo.toml に以下を書き足しただけです。

[package.metadata.bootloader]
map-physical-memory = true

bootloader クレートのマッピング方式は物理アドレスに固定のオフセット値を足した仮想アドレスにマッピングする方式です。 今のところはページテーブルの設定等でこの差異を気にする必要があります。 ひとまず、 Writing an OS in Rust の Paging の章 を参照し、ページテーブルの取得とアドレス変換について実装してみました。

フレームアロケーターの作成 (day08c)

ページフレームを割り当てを管理するフレームアロケーターを作成しました。

github.com

実装は C++ と同じです。 一点引っかかった点として、 "Writing an OS in Rust" のようにフレームアロケーターをローカル変数としてスタック上に割り当てたところ、カーネルが起動しなくなったという点がありました。

これは、恐らくスタックオーバーフローが発生していたためです。 「ゼロからのOS自作入門」のフレームアロケーターは128GiB分のフレームの管理領域を保持するため32MiBの固定長配列を獲得します。 これは bootloader クレートで設定されるスタックサイズ 80KiB より大きいです。

フレームアロケーターを static 変数とすることでスタックオーバーフローを回避しました。

まとめ

メモリ関連の処理を実装しました。 ページテーブルへのアクセスも可能になったため、当初の目的である6章を先に進めることも可能になったことでしょう。 というわけで次回は6章に戻ろうと思います。お楽しみに。