見出し画像

Rustで自作OSを起動しようとして、真っ黒画面と殴り合っている話(途中経過)


※ 本記事では Working Set 昇格の内部評価ロジックそのものを断定するものではない。
あくまで観測可能な挙動(WS / PageFault の推移)から、
「アクセスの継続性(滞在時間)が影響しているように見える」
という傾向を示したものである。
本記事の検証は筆者の環境・条件での観測結果であり、
製品の不具合や仕様を断定するものではありません。
同様の結果が再現されるかは環境差があります。

はじめに

Rust で OS を書く、いわゆる Rust OS 開発に手を出している。
目的はシンプルで、

  • QEMU 上で

  • 自作カーネルを起動して

  • 画面に「何か」を表示する

──ただそれだけ。

……のはずだった。

結果から言うと、
画面は真っ黒、時々一瞬で消える、たまに SMM と例外ログが洪水のように流れる。

今回はその 「まだ完成していない段階」までの記録をまとめる。


開発環境

  • OS: Windows

  • Rust: nightly

  • target: x86_64-unknown-none

  • bootloader: 0.9.33

  • 実行環境: QEMU (qemu-system-x86_64)

  • 参考: Writing an OS in Rust 系資料


最初の目標

まずはこれ。

  • VGA テキストモード(0xb8000)

  • 画面に文字を1文字でも出す

HI

これが出れば勝ち。

🔥 panic が教えてくれたこと

― RustOS 最初の“本当のエラー” ―

OS 開発を進めていく中で、最初に画面を赤く染めたのがこのエラーだった。

panicked at src/page_table.rs:105:25:
failed to map segment starting at Page(4KiB) …
PageAlreadyMapped(PhysFrame(4KiB))

最初にこれを見たときは、正直こう思った。

「またどこかおかしいことをしてるな」

でも後から振り返ると、これは**単なるクラッシュではなく、RustOS が“自分で自分を守った瞬間”**だった。


🧨 panic は「暴走を止めるブレーキ」

この panic は、Rust のランタイムが出しているものではない。
自分で書いた OS コードの中で発火している panicだ。

意味は単純で、でも重い。

  • すでに割り当て済みの物理フレームに

  • 再度マッピングしようとした

  • それを「異常」として検出し

  • panic で即座に止めた

もし panic がなければどうなっていたか。

👉 ページテーブル破壊
👉 メモリの二重使用
👉 原因不明の暴走
👉 デバッグ不能な闇

panic は敵ではなく、最後の安全装置だった。


🧠 「PageAlreadyMapped」が意味する設計ミス

このエラーが示しているのは、
単なる実装ミスではなく 設計レベルの問題

  • 仮想アドレスと物理アドレスの対応関係

  • ブート時にどこまでマッピングするか

  • すでに bootloader が確保している領域

  • OS 側が追加で確保しようとしている領域

これらの境界が曖昧なまま進めていたことが原因だった。

つまりこの panic は、

「今のまま進むと、必ず後で詰む」

と教えてくれていた。


🔴 panic が出たからこそ、次に進めた

panic を無理やり消すこともできた。
unwrap() を消すことも、Result を無視することもできた。

でもそれをしなかった。

代わりに、

  • どこでマップしているのか

  • すでにマップ済みかどうか

  • bootloader の責務はどこまでか

真正面から考えることになった

結果として、

  • ページングの理解が一段深くなり

  • QEMU の -d int を使うようになり

  • CPU 例外ログが読めるようになった

panic は「失敗」ではなく、OS開発者としてのレベルが1段上がった合図だった。


最初の壁:ビルドは通る、でも画面は真っ黒

cargo +nightly bootimage

は通る。

qemu-system-x86_64.exe -drive format=raw,file=bootimage-rustos.bin

も起動する。

でも画面は真っ黒。

落ちていない。
再起動もしない。
何も映らない。


画面が一瞬で消える問題

オプションを追加。

-no-reboot

→ 一瞬で消えなくなった
つまり例外は起きている

さらに:

-d int

地獄のようなログが流れる

QEMU から吐き出されるログ(一部抜粋):

check_exception old: 0xffffffff new 0xe
RIP=0000000000000000
CR2=0000000000000000
  • Page Fault

  • RIP = 0

  • IDT 未設定

  • そのまま Double Fault

👉 完全に「OS 初期化途中で死んでる」状態


BootInfo に framebuffer が無い問題

「じゃあ framebuffer で描画しよう」と思い、

boot_info.framebuffer

を書いたら即エラー。

error[E0609]: no field `framebuffer` on type `BootInfo`

原因

  • bootloader 0.9.x には framebuffer は無い

  • feature でも存在しない

  • VGA か vga_320x200 を使うしかない

👉 情報の世代差トラップ


lazy_static / spin が std を要求して死ぬ

さらに進めると:

error[E0463]: can't find crate for `std`

原因:

  • x86_64-unknown-none

  • no_std

  • lazy_static が内部で extern crate std

👉 OS カーネルで std は使えない


「真っ黒=失敗」ではないと気づく

ここで大事な気づき。

  • 真っ黒でも QEMU は生きている

  • -no-reboot を付けると止まる

  • 例外ログが出る=カーネルは実行されている

つまり、

「表示されていない」だけで「起動していない」わけではない


現在地(正直な状態)

  • ✅ bootimage は作れている

  • ✅ QEMU は起動している

  • ✅ カーネルには入っている

  • ❌ VGA 書き込みが見えていない

  • ❌ entry_point / 初期化周りを調整中

  • ❌ IDT 未設定で例外即死


ここまでやって分かったこと

  • Rust OS は「1行ズレるだけで無音で死ぬ」

  • 世代違いの記事は平気で嘘を書く

  • 真っ黒画面は「最初の関門」

  • QEMU の -no-reboot は神

  • ログを読む力がすべて


次にやること(自分用メモ)

  • entry_point!(kernel_main) の再確認

  • linker.ld を一旦捨てる

  • VGA 0xb8000 への直書き最小コードに戻す

  • IDT を最低限用意する

  • framebuffer に逃げない(まだ早い)


おわりに

まだ完成していない。
でも、この段階の記録は未来の自分と誰かを確実に救う。

Rust で OS を書くのは、
「動いたら勝ち」じゃない。

「なぜ動かないか説明できたら勝ち」

続きは、
画面に H が出た日にまた書く。

いいなと思ったら応援しよう!