Zig選択課題04:OS開発

課題説明

背景

オペレーティングシステム(OS)開発は、コンピュータサイエンスの最も基本的かつ高度な分野の一つです。Zigは、C言語の後継として設計されており、OS開発に必要な低レベル制御、明示的なメモリ管理、コンパイル時の安全性チェックを提供します。この課題では、シンプルなマイクロカーネルOSを開発し、ブートローダー、メモリ管理、割り込み処理、プロセス管理などの基本的なOS機能を実装します。

要件

必須機能

  • ブートローダー
- Multiboot2対応 - 保護モードへの移行 - カーネルへのジャンプ

  • メモリ管理
- 物理メモリアロケータ - 仮想メモリ管理(ページング) - ヒープアロケータ - メモリマップの管理

  • 割り込み処理
- IDT(Interrupt Descriptor Table)の設定 - ISR(Interrupt Service Routine)の実装 - 例外ハンドラ - タイマー割り込み

  • 基本的なドライバ
- VGA テキストモード - キーボード入力 - タイマー(PIT)

  • プロセス管理(オプション)
- タスクスイッチング - スケジューラ - コンテキストスイッチ

技術的制約

  • フリースタンディング環境(OS なし)
  • x86またはx86_64アーキテクチャ
  • QEMUまたはBochsでの動作確認
  • GRUBまたはカスタムブートローダー
  • インラインアセンブリの使用
  • 評価ポイント

  • ブートプロセスの正確な実装
  • メモリ管理の安全性と効率性
  • 割り込み処理の適切な実装
  • ドライバの機能性
  • コードの可読性と構造
  • エラーハンドリング
  • ---

    想定解答

    プロジェクト構造

    zigos/
    ├── src/
    │   ├── kernel/
    │   │   ├── main.zig
    │   │   ├── boot.zig
    │   │   ├── gdt.zig
    │   │   ├── idt.zig
    │   │   └── interrupts.zig
    │   ├── memory/
    │   │   ├── pmm.zig          # Physical Memory Manager
    │   │   ├── vmm.zig          # Virtual Memory Manager
    │   │   └── heap.zig
    │   ├── drivers/
    │   │   ├── vga.zig
    │   │   ├── keyboard.zig
    │   │   └── timer.zig
    │   └── arch/
    │       └── x86_64/
    │           ├── boot.S
    │           ├── gdt.zig
    │           └── idt.zig
    ├── linker.ld
    ├── build.zig
    └── grub.cfg
    

    build.zig

    const std = @import("std");
    
    pub fn build(b: *std.Build) void {
        const target = b.resolveTargetQuery(.{
            .cpu_arch = .x86_64,
            .os_tag = .freestanding,
            .abi = .none,
        });
    
        const optimize = b.standardOptimizeOption(.{});
    
        const kernel = b.addExecutable(.{
            .name = "kernel.elf",
            .root_source_file = .{ .path = "src/kernel/main.zig" },
            .target = target,
            .optimize = optimize,
        });
    
        // フリースタンディング設定
        kernel.setLinkerScript(.{ .path = "linker.ld" });
        kernel.code_model = .kernel;
    
        b.installArtifact(kernel);
    
        // ISOイメージ作成
        const iso_cmd = b.addSystemCommand(&[_][]const u8{
            "grub-mkrescue",
            "-o",
            "zigos.iso",
            "iso",
        });
        iso_cmd.step.dependOn(&kernel.step);
    
        const iso_step = b.step("iso", "Create bootable ISO image");
        iso_step.dependOn(&iso_cmd.step);
    
        // QEMU実行
        const qemu_cmd = b.addSystemCommand(&[_][]const u8{
            "qemu-system-x86_64",
            "-cdrom",
            "zigos.iso",
            "-serial",
            "stdio",
        });
        qemu_cmd.step.dependOn(iso_step);
    
        const run_step = b.step("run", "Run the OS in QEMU");
        run_step.dependOn(&qemu_cmd.step);
    }
    

    src/kernel/main.zig

    const std = @import("std");
    const vga = @import("../drivers/vga.zig");
    const gdt = @import("gdt.zig");
    const idt = @import("idt.zig");
    const pmm = @import("../memory/pmm.zig");
    const vmm = @import("../memory/vmm.zig");
    
    // エントリポイント
    export fn _start() callconv(.Naked) noreturn {
        // スタックポインタの設定
        asm volatile (
            \\movq $stack_top, %rsp
            \\call kmain
        );
        while (true) {
            asm volatile ("hlt");
        }
    }
    
    // カーネルメイン
    export fn kmain() noreturn {
        // VGA初期化
        vga.init();
        vga.clearScreen();
        vga.println("ZigOS Kernel starting...");
    
        // GDT初期化
        vga.println("Initializing GDT...");
        gdt.init();
    
        // IDT初期化
        vga.println("Initializing IDT...");
        idt.init();
    
        // 割り込み有効化
        asm volatile ("sti");
    
        // メモリ管理初期化
        vga.println("Initializing memory management...");
        pmm.init();
        vmm.init();
    
        vga.println("Kernel initialized successfully!");
        vga.println("");
        vga.println("Welcome to ZigOS!");
        vga.println("Type 'help' for available commands.");
    
        // メインループ
        mainLoop();
    }
    
    fn mainLoop() noreturn {
        while (true) {
            asm volatile ("hlt");
        }
    }
    
    // パニックハンドラ
    pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
        _ = error_return_trace;
        _ = ret_addr;
    
        vga.setColor(vga.Color.Red, vga.Color.Black);
        vga.println("KERNEL PANIC!");
        vga.println(msg);
    
        while (true) {
            asm volatile ("cli; hlt");
        }
    }
    
    // スタック定義
    export var stack_bytes: [16 * 1024]u8 align(16) linksection(".bss") = undefined;
    const stack_top = @intFromPtr(&stack_bytes) + @sizeOf(@TypeOf(stack_bytes));
    

    src/drivers/vga.zig

    const std = @import("std");
    
    pub const Color = enum(u8) {
        Black = 0,
        Blue = 1,
        Green = 2,
        Cyan = 3,
        Red = 4,
        Magenta = 5,
        Brown = 6,
        LightGray = 7,
        DarkGray = 8,
        LightBlue = 9,
        LightGreen = 10,
        LightCyan = 11,
        LightRed = 12,
        LightMagenta = 13,
        Yellow = 14,
        White = 15,
    };
    
    const VGA_WIDTH = 80;
    const VGA_HEIGHT = 25;
    const VGA_BUFFER = 0xB8000;
    
    var row: usize = 0;
    var column: usize = 0;
    var fg_color: Color = .LightGray;
    var bg_color: Color = .Black;
    var buffer: [*]volatile u16 = @ptrFromInt(VGA_BUFFER);
    
    pub fn init() void {
        row = 0;
        column = 0;
        fg_color = .LightGray;
        bg_color = .Black;
    }
    
    pub fn clearScreen() void {
        const blank = makeChar(' ', fg_color, bg_color);
        var i: usize = 0;
        while (i < VGA_WIDTH * VGA_HEIGHT) : (i += 1) {
            buffer[i] = blank;
        }
        row = 0;
        column = 0;
    }
    
    pub fn setColor(fg: Color, bg: Color) void {
        fg_color = fg;
        bg_color = bg;
    }
    
    pub fn putChar(c: u8) void {
        switch (c) {
            '\n' => {
                column = 0;
                row += 1;
            },
            '\r' => {
                column = 0;
            },
            '\t' => {
                column = (column + 8) & ~@as(usize, 7);
            },
            else => {
                const char = makeChar(c, fg_color, bg_color);
                const index = row * VGA_WIDTH + column;
                buffer[index] = char;
                column += 1;
            },
        }
    
        if (column >= VGA_WIDTH) {
            column = 0;
            row += 1;
        }
    
        if (row >= VGA_HEIGHT) {
            scroll();
        }
    
        updateCursor();
    }
    
    pub fn print(str: []const u8) void {
        for (str) |c| {
            putChar(c);
        }
    }
    
    pub fn println(str: []const u8) void {
        print(str);
        putChar('\n');
    }
    
    fn makeChar(c: u8, fg: Color, bg: Color) u16 {
        const color: u16 = (@intFromEnum(fg) | (@intFromEnum(bg) << 4));
        return c | (color << 8);
    }
    
    fn scroll() void {
        const blank = makeChar(' ', fg_color, bg_color);
    
        // 1行目から最終行-1までを1行ずつ上にコピー
        var i: usize = 0;
        while (i < (VGA_HEIGHT - 1) * VGA_WIDTH) : (i += 1) {
            buffer[i] = buffer[i + VGA_WIDTH];
        }
    
        // 最終行をクリア
        i = (VGA_HEIGHT - 1) * VGA_WIDTH;
        while (i < VGA_HEIGHT * VGA_WIDTH) : (i += 1) {
            buffer[i] = blank;
        }
    
        row = VGA_HEIGHT - 1;
    }
    
    fn updateCursor() void {
        const pos: u16 = @intCast(row * VGA_WIDTH + column);
    
        // カーソル位置の下位バイトを送信
        outb(0x3D4, 0x0F);
        outb(0x3D5, @truncate(pos));
    
        // カーソル位置の上位バイトを送信
        outb(0x3D4, 0x0E);
        outb(0x3D5, @truncate(pos >> 8));
    }
    
    fn outb(port: u16, value: u8) void {
        asm volatile ("outb %[value], %[port]"
            :
            : [value] "{al}" (value),
              [port] "N{dx}" (port),
        );
    }
    

    src/kernel/idt.zig

    const std = @import("std");
    const vga = @import("../drivers/vga.zig");
    
    const IDT_ENTRIES = 256;
    
    const IDTEntry = packed struct {
        offset_low: u16,
        selector: u16,
        ist: u8,
        type_attr: u8,
        offset_mid: u16,
        offset_high: u32,
        zero: u32,
    };
    
    const IDTPtr = packed struct {
        limit: u16,
        base: u64,
    };
    
    var idt: [IDT_ENTRIES]IDTEntry = undefined;
    var idtp: IDTPtr = undefined;
    
    pub fn init() void {
        // IDTをゼロクリア
        @memset(std.mem.asBytes(&idt), 0);
    
        // 例外ハンドラの設定
        setGate(0, @intFromPtr(&isr0), 0x08, 0x8E); // Division by Zero
        setGate(1, @intFromPtr(&isr1), 0x08, 0x8E); // Debug
        setGate(2, @intFromPtr(&isr2), 0x08, 0x8E); // NMI
        setGate(3, @intFromPtr(&isr3), 0x08, 0x8E); // Breakpoint
        setGate(4, @intFromPtr(&isr4), 0x08, 0x8E); // Overflow
        setGate(5, @intFromPtr(&isr5), 0x08, 0x8E); // Bound Range
        setGate(6, @intFromPtr(&isr6), 0x08, 0x8E); // Invalid Opcode
        setGate(7, @intFromPtr(&isr7), 0x08, 0x8E); // Device Not Available
        setGate(8, @intFromPtr(&isr8), 0x08, 0x8E); // Double Fault
        setGate(13, @intFromPtr(&isr13), 0x08, 0x8E); // General Protection
        setGate(14, @intFromPtr(&isr14), 0x08, 0x8E); // Page Fault
    
        // IRQハンドラの設定
        setGate(32, @intFromPtr(&irq0), 0x08, 0x8E); // Timer
        setGate(33, @intFromPtr(&irq1), 0x08, 0x8E); // Keyboard
    
        // PICの再マッピング
        remapPIC();
    
        // IDTのロード
        idtp.limit = @sizeOf(@TypeOf(idt)) - 1;
        idtp.base = @intFromPtr(&idt);
    
        asm volatile ("lidt (%[idtp])"
            :
            : [idtp] "r" (&idtp),
        );
    }
    
    fn setGate(num: u8, base: u64, sel: u16, flags: u8) void {
        idt[num].offset_low = @truncate(base);
        idt[num].offset_mid = @truncate(base >> 16);
        idt[num].offset_high = @truncate(base >> 32);
        idt[num].selector = sel;
        idt[num].ist = 0;
        idt[num].type_attr = flags;
        idt[num].zero = 0;
    }
    
    fn remapPIC() void {
        // ICW1: 初期化開始
        outb(0x20, 0x11);
        outb(0xA0, 0x11);
    
        // ICW2: IRQベースアドレス設定
        outb(0x21, 0x20); // Master PIC: IRQ 0-7 -> INT 32-39
        outb(0xA1, 0x28); // Slave PIC: IRQ 8-15 -> INT 40-47
    
        // ICW3: カスケード設定
        outb(0x21, 0x04); // Master: IRQ2にSlave接続
        outb(0xA1, 0x02); // Slave: カスケードID 2
    
        // ICW4: 8086モード
        outb(0x21, 0x01);
        outb(0xA1, 0x01);
    
        // すべてのIRQをマスク解除
        outb(0x21, 0x00);
        outb(0xA1, 0x00);
    }
    
    fn outb(port: u16, value: u8) void {
        asm volatile ("outb %[value], %[port]"
            :
            : [value] "{al}" (value),
              [port] "N{dx}" (port),
        );
    }
    
    // 割り込みハンドラ(アセンブリで定義)
    extern fn isr0() callconv(.Naked) void;
    extern fn isr1() callconv(.Naked) void;
    extern fn isr2() callconv(.Naked) void;
    extern fn isr3() callconv(.Naked) void;
    extern fn isr4() callconv(.Naked) void;
    extern fn isr5() callconv(.Naked) void;
    extern fn isr6() callconv(.Naked) void;
    extern fn isr7() callconv(.Naked) void;
    extern fn isr8() callconv(.Naked) void;
    extern fn isr13() callconv(.Naked) void;
    extern fn isr14() callconv(.Naked) void;
    
    extern fn irq0() callconv(.Naked) void;
    extern fn irq1() callconv(.Naked) void;
    
    // ISRハンドラ(C呼び出し規約)
    export fn isrHandler(int_no: u64, err_code: u64) void {
        vga.setColor(.Red, .Black);
        vga.print("Exception ");
        printNumber(int_no);
        vga.print(" Error Code: ");
        printNumber(err_code);
        vga.println("");
    
        // 無限ループ
        while (true) {
            asm volatile ("cli; hlt");
        }
    }
    
    // IRQハンドラ
    export fn irqHandler(int_no: u64) void {
        switch (int_no) {
            32 => {
                // Timer interrupt
            },
            33 => {
                // Keyboard interrupt
                const key = inb(0x60);
                vga.putChar(key);
            },
            else => {},
        }
    
        // EOI送信
        if (int_no >= 40) {
            outb(0xA0, 0x20); // Slave PIC
        }
        outb(0x20, 0x20); // Master PIC
    }
    
    fn inb(port: u16) u8 {
        return asm volatile ("inb %[port], %[result]"
            : [result] "={al}" (-> u8),
            : [port] "N{dx}" (port),
        );
    }
    
    fn printNumber(num: u64) void {
        var buffer: [20]u8 = undefined;
        var i: usize = 0;
        var n = num;
    
        if (n == 0) {
            vga.putChar('0');
            return;
        }
    
        while (n > 0) : (i += 1) {
            buffer[i] = @truncate('0' + (n % 10));
            n /= 10;
        }
    
        while (i > 0) {
            i -= 1;
            vga.putChar(buffer[i]);
        }
    }
    

    src/memory/pmm.zig

    const std = @import("std");
    
    const PAGE_SIZE = 4096;
    const TOTAL_MEMORY = 128 * 1024 * 1024; // 128MB
    const TOTAL_PAGES = TOTAL_MEMORY / PAGE_SIZE;
    
    var bitmap: [TOTAL_PAGES / 8]u8 = undefined;
    var total_pages: usize = TOTAL_PAGES;
    var used_pages: usize = 0;
    
    pub fn init() void {
        // ビットマップを初期化(すべてのページを使用中としてマーク)
        @memset(&bitmap, 0xFF);
    
        // 利用可能なメモリ領域をフリーとしてマーク
        // 実際にはMultibootから取得したメモリマップを使用
        const available_start = 0x100000; // 1MB以降
        const available_end = TOTAL_MEMORY;
    
        var addr = available_start;
        while (addr < available_end) : (addr += PAGE_SIZE) {
            freePage(addr);
        }
    }
    
    pub fn allocPage() ?usize {
        var i: usize = 0;
        while (i < bitmap.len) : (i += 1) {
            if (bitmap[i] != 0xFF) {
                var j: u3 = 0;
                while (j < 8) : (j += 1) {
                    const bit: u8 = @as(u8, 1) << j;
                    if ((bitmap[i] & bit) == 0) {
                        bitmap[i] |= bit;
                        used_pages += 1;
                        const page = (i * 8 + j) * PAGE_SIZE;
                        return page;
                    }
                }
            }
        }
        return null;
    }
    
    pub fn freePage(addr: usize) void {
        const page = addr / PAGE_SIZE;
        const index = page / 8;
        const bit: u8 = @as(u8, 1) << @truncate(page % 8);
    
        if ((bitmap[index] & bit) != 0) {
            bitmap[index] &= ~bit;
            used_pages -= 1;
        }
    }
    
    pub fn getUsedMemory() usize {
        return used_pages * PAGE_SIZE;
    }
    
    pub fn getFreeMemory() usize {
        return (total_pages - used_pages) * PAGE_SIZE;
    }
    

    ---

    解説と応用

    OS開発の基礎

  • ブートプロセス
- BIOS → Bootloader → Kernel - リアルモード → 保護モード → ロングモード(x86_64)

  • メモリ管理
- 物理メモリ管理(ビットマップ/バディシステム) - 仮想メモリ(ページング) - ヒープ管理

  • 割り込み処理
- 例外(CPU生成) - IRQ(ハードウェア割り込み) - システムコール(ソフトウェア割り込み)

---

参考リソース

書籍

チュートリアル

---

発展課題

レベル1:基本機能の拡張

  • ヒープアロケータの実装
  • ファイルシステム(FAT32)
  • プロセス管理

レベル2:高度な機能

  • マルチタスキング
  • ネットワークスタック
  • グラフィカルインターフェース

レベル3:最適化

  • SMP(マルチコア)対応
  • 高度なスケジューリング
  • デバイスドライバフレームワーク

---

まとめ

Zigを使用したOS開発により、ハードウェアとソフトウェアの深い理解を得られます。メモリ安全性とパフォーマンスを両立しながら、低レベルシステムプログラミングの本質を学ぶことができます。