4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cで書いたOS、Rustで書き直したくなった Part5

Last updated at Posted at 2025-12-12

シリーズ一覧

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();
    });
}

結論:書き直す?

正直に言うと...

今は書き直さない

理由:

  1. 学習目的は達成した - Cでの低レイヤーの感覚がつかめた
  2. Rustの学習コスト - 所有権をOSコンテキストで理解するのは大変
  3. このOSの寿命 - 趣味プロジェクト、そこまで延命しない

でも次のプロジェクトはRustで

新しく作るなら絶対Rust。理由:

  1. メモリバグの時間が減る - デバッグ時間の半分はメモリ関連
  2. リファクタリングが安全 - コンパイラが守ってくれる
  3. モダンなツール - Cargo、rustfmt、clippy

シリーズまとめ

全5回で作ったもの:

Part1: Multibootカーネル、VGA出力
Part2: リンカスクリプト、シンボル解決
Part3: malloc/free、フリーリスト
Part4: printf(可変長引数)
Part5: Rust移行の検討(本記事)

学んだこと

  1. Cは自由だけど危険 - ポインタは諸刃の剣
  2. 低レイヤーの基礎 - メモリマップ、リンカ、呼び出し規約
  3. デバッグ力 - printf最強説
  4. 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に行くと、ありがたみがわかるから。

ここまで読んでくれてありがとう!🎉


参考資料

関連シリーズ

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?