シリーズ一覧
| Part | タイトル | 内容 |
|---|---|---|
| Part1 | 令和にCでOS書く狂人の記録 | VGA出力 |
| Part2 | アセンブリとCの橋渡し | リンカスクリプト |
| Part3 | メモリ管理とmalloc | 動的メモリ |
| Part4 | printfを自作する | フォーマット出力 |
| Part5 | Rustで書き直したくなった | 本記事(最終回) |
はじめに
Part1で「令和にCでOS書く狂人」を自称して始めたこのシリーズ。
Part3でメモリリークに苦しみ、Part4でINT_MINのオーバーフローにハマった。
そして今、こう思っている:
「Rustで書き直したい」
なぜそう思うのか
Cで感じた痛み
1. メモリ安全性
char* ptr = kmalloc(100);
kfree(ptr);
ptr[0] = 'A'; // Use-After-Free(コンパイルエラーにならない)
Rustなら:
let ptr = Box::new([0u8; 100]);
drop(ptr);
ptr[0] = b'A'; // コンパイルエラー!
2. NULLポインタ
char* s = get_string();
printf("%s", s); // sがNULLだったら?
Rustなら:
let s: Option<String> = get_string();
match s {
Some(s) => println!("{}", s),
None => println!("(none)"),
}
// もしくは
println!("{}", s.unwrap_or_default());
3. バッファオーバーフロー
char buf[10];
strcpy(buf, user_input); // 長さチェックなし
Rustなら:
let mut buf = [0u8; 10];
// user_input.len() > 10 なら panic または切り詰め
// どちらにしても未定義動作にはならない
Cの良かったところ
でも、Cで書いて良かったこともある:
1. メモリの挙動が直接見える
volatile unsigned short* vga = (unsigned short*)0xB8000;
vga[0] = 0x0F41; // 白背景に'A'
ポインタの中身、メモリのレイアウトが全部わかる。Rustだと unsafe が必要で、むしろ難しく感じることも。
2. 学習リソースが豊富
- 「30日でできる!OS自作入門」
- 「ゼロからのOS自作入門」
- 大学の教科書
だいたいCかC++で書かれてる。
3. ビルドがシンプル
gcc -c kernel.c -o kernel.o
ld -T linker.ld -o kernel.bin boot.o kernel.o
Rustだと Cargo の設定、ターゲット指定、#![no_std] の呪文が必要。
C vs Rust 比較表
| 観点 | C | Rust |
|---|---|---|
| メモリ安全性 | ❌ 手動管理 | ⭕ コンパイル時検証 |
| NULL | ❌ ヌルポ怖い | ⭕ Option型 |
| 学習曲線 | ⭕ シンプル | ❌ 所有権難しい |
| 学習リソース | ⭕ 豊富 | △ 増えてきた |
| ビルド | ⭕ シンプル | △ 設定多め |
| 低レイヤー | ⭕ 直接的 | ⭕ unsafeで可能 |
| エコシステム | ⭕ 枯れてる | ⭕ 活発 |
Rustで書き直すなら
プロジェクト構成
my-rust-os/
├── Cargo.toml
├── rust-toolchain.toml
├── x86_64-my_os.json # カスタムターゲット
└── src/
├── main.rs
├── vga.rs
└── memory.rs
Cargo.toml
[package]
name = "my-rust-os"
version = "0.1.0"
edition = "2021"
[dependencies]
bootloader = "0.9"
spin = "0.9"
x86_64 = "0.14"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
main.rs
#![no_std]
#![no_main]
use core::panic::PanicInfo;
mod vga;
#[no_mangle]
pub extern "C" fn _start() -> ! {
vga::clear_screen();
vga::print("Hello from Rust OS!");
loop {
x86_64::instructions::hlt();
}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
vga::print("KERNEL PANIC: ");
if let Some(message) = info.message() {
// panic メッセージを表示
}
loop {}
}
vga.rs(Rust版)
use core::fmt;
use spin::Mutex;
const VGA_BUFFER: *mut u16 = 0xB8000 as *mut u16;
const WIDTH: usize = 80;
const HEIGHT: usize = 25;
pub static WRITER: Mutex<Writer> = Mutex::new(Writer {
x: 0,
y: 0,
color: 0x0F,
});
pub struct Writer {
x: usize,
y: usize,
color: u8,
}
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.newline(),
byte => {
if self.x >= WIDTH {
self.newline();
}
let offset = self.y * WIDTH + self.x;
unsafe {
*VGA_BUFFER.add(offset) =
(self.color as u16) << 8 | byte as u16;
}
self.x += 1;
}
}
}
fn newline(&mut self) {
self.x = 0;
self.y += 1;
if self.y >= HEIGHT {
self.scroll();
}
}
fn scroll(&mut self) {
// スクロール処理
unsafe {
core::ptr::copy(
VGA_BUFFER.add(WIDTH),
VGA_BUFFER,
WIDTH * (HEIGHT - 1)
);
}
self.y = HEIGHT - 1;
}
}
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.write_byte(byte);
}
Ok(())
}
}
pub fn print(s: &str) {
use core::fmt::Write;
WRITER.lock().write_str(s).unwrap();
}
#[macro_export]
macro_rules! println {
() => ($crate::vga::print("\n"));
($($arg:tt)*) => ({
use core::fmt::Write;
writeln!($crate::vga::WRITER.lock(), $($arg)*).unwrap();
});
}
結論:書き直す?
正直に言うと...
今は書き直さない
理由:
- 学習目的は達成した - Cでの低レイヤーの感覚がつかめた
- Rustの学習コスト - 所有権をOSコンテキストで理解するのは大変
- このOSの寿命 - 趣味プロジェクト、そこまで延命しない
でも次のプロジェクトはRustで
新しく作るなら絶対Rust。理由:
- メモリバグの時間が減る - デバッグ時間の半分はメモリ関連
- リファクタリングが安全 - コンパイラが守ってくれる
- モダンなツール - Cargo、rustfmt、clippy
シリーズまとめ
全5回で作ったもの:
Part1: Multibootカーネル、VGA出力
Part2: リンカスクリプト、シンボル解決
Part3: malloc/free、フリーリスト
Part4: printf(可変長引数)
Part5: Rust移行の検討(本記事)
学んだこと
- Cは自由だけど危険 - ポインタは諸刃の剣
- 低レイヤーの基礎 - メモリマップ、リンカ、呼び出し規約
- デバッグ力 - printf最強説
- Rustへの理解 - Cの危険性を知ってこそRustの価値がわかる
最終的なファイル構成
~/c-os/
├── boot/
│ ├── boot.asm # エントリポイント
│ └── io.asm # I/Oポートアクセス
├── src/
│ ├── kernel.c # カーネルメイン
│ ├── vga.c # VGA出力
│ ├── memory.c # メモリ管理
│ └── printf.c # フォーマット出力
├── include/
│ ├── vga.h
│ ├── memory.h
│ └── printf.h
├── linker.ld
├── build.sh
└── Makefile
おわりに
「令和にCでOS書く狂人」として始めたこのシリーズ。
結論:Cで書いて良かった。でも次はRustで。
Cの経験があると、Rustの unsafe が怖くなくなる。というか、Rustがなぜああいう設計なのかがわかる。
みんなも一度はCで低レイヤー触ってみて。痛い目見てからRustに行くと、ありがたみがわかるから。
ここまで読んでくれてありがとう!🎉
参考資料
- OSDev Wiki
- Writing an OS in Rust
- 「30日でできる!OS自作入門」
- 「ゼロからのOS自作入門」