Zig選択課題04:OS開発
課題説明
背景
オペレーティングシステム(OS)開発は、コンピュータサイエンスの最も基本的かつ高度な分野の一つです。Zigは、C言語の後継として設計されており、OS開発に必要な低レベル制御、明示的なメモリ管理、コンパイル時の安全性チェックを提供します。この課題では、シンプルなマイクロカーネルOSを開発し、ブートローダー、メモリ管理、割り込み処理、プロセス管理などの基本的なOS機能を実装します。
要件
必須機能
- ブートローダー
- メモリ管理
- 割り込み処理
- 基本的なドライバ
- プロセス管理(オプション)
技術的制約
- フリースタンディング環境(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開発の基礎
- メモリ管理
- 割り込み処理
---
参考リソース
書籍
チュートリアル
---
発展課題
レベル1:基本機能の拡張
- ヒープアロケータの実装
- ファイルシステム(FAT32)
- プロセス管理
レベル2:高度な機能
- マルチタスキング
- ネットワークスタック
- グラフィカルインターフェース
レベル3:最適化
- SMP(マルチコア)対応
- 高度なスケジューリング
- デバイスドライバフレームワーク
---
まとめ
Zigを使用したOS開発により、ハードウェアとソフトウェアの深い理解を得られます。メモリ安全性とパフォーマンスを両立しながら、低レベルシステムプログラミングの本質を学ぶことができます。