6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZigでOSを作るシリーズ

Part1 基本 Part2 ブート Part3 割り込み Part4 メモリ Part5 プロセス Part6 FS
✅ Done ✅ Done ✅ Done ✅ Done 👈 Now -

はじめに

前回はメモリ管理を実装した。

今回はプロセス管理!タスク構造体、コンテキストスイッチ、スケジューラで、複数のタスクを同時実行できるようになる。

マルチタスクが動くと、いよいよOSっぽくなってくるよね。楽しいパートだよ!

プロセス管理の概要

┌─────────────────────────────────────────────────────────────────┐
│                   プロセス管理の全体像                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐        │
│   │ Task 0  │   │ Task 1  │   │ Task 2  │   │ Task N  │        │
│   │ (Idle)  │   │ (Shell) │   │ (App)   │   │  ...    │        │
│   └────┬────┘   └────┬────┘   └────┬────┘   └────┬────┘        │
│        │             │             │             │              │
│        └─────────────┴──────┬──────┴─────────────┘              │
│                             │                                    │
│                             ↓                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                     Scheduler                           │   │
│   │    ┌─────────────────────────────────────────────────┐  │   │
│   │    │  Ready Queue: [T1] → [T2] → [T3] → ...         │  │   │
│   │    └─────────────────────────────────────────────────┘  │   │
│   └───────────────────────────┬─────────────────────────────┘   │
│                               │                                  │
│                               ↓                                  │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │               Context Switcher                          │   │
│   │  Save/Restore: Registers, Stack, Page Table            │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

タスク構造体

// task.zig
const heap = @import("heap.zig");
const vmm = @import("vmm.zig");

pub const TaskState = enum {
    ready,
    running,
    blocked,
    sleeping,
    terminated,
};

pub const KERNEL_STACK_SIZE: usize = 16 * 1024;  // 16KB

// CPU状態を保存する構造体
pub const Context = extern struct {
    // 汎用レジスタ(callee-saved)
    r15: u64,
    r14: u64,
    r13: u64,
    r12: u64,
    rbp: u64,
    rbx: u64,

    // 命令ポインタ
    rip: u64,

    // スタックポインタ
    rsp: u64,

    // ページテーブル
    cr3: u64,
};

pub const Task = struct {
    id: u32,
    name: [32]u8,
    state: TaskState,
    priority: u8,

    // コンテキスト
    context: Context,

    // スタック
    kernel_stack: u64,
    kernel_stack_top: u64,

    // スケジューリング情報
    time_slice: u32,
    remaining_slice: u32,
    total_ticks: u64,

    // リンク(スケジューラキュー用)
    next: ?*Task,

    // ページテーブル
    page_table: u64,

    // 終了コード
    exit_code: i32,

    pub fn init(id: u32, name: []const u8, entry: *const fn () void) ?*Task {
        const task = heap.alloc(@sizeOf(Task)) orelse return null;
        const task_ptr: *Task = @ptrCast(@alignCast(task));

        // スタックを確保
        const stack = heap.alloc(KERNEL_STACK_SIZE) orelse {
            heap.free(task);
            return null;
        };

        // 名前をコピー
        var task_name: [32]u8 = [_]u8{0} ** 32;
        for (name, 0..) |c, i| {
            if (i >= 31) break;
            task_name[i] = c;
        }

        const stack_top = @intFromPtr(stack) + KERNEL_STACK_SIZE;

        task_ptr.* = .{
            .id = id,
            .name = task_name,
            .state = .ready,
            .priority = 1,
            .context = .{
                .r15 = 0,
                .r14 = 0,
                .r13 = 0,
                .r12 = 0,
                .rbp = stack_top,
                .rbx = 0,
                .rip = @intFromPtr(entry),
                .rsp = stack_top,
                .cr3 = getCR3(),
            },
            .kernel_stack = @intFromPtr(stack),
            .kernel_stack_top = stack_top,
            .time_slice = 10,
            .remaining_slice = 10,
            .total_ticks = 0,
            .next = null,
            .page_table = getCR3(),
            .exit_code = 0,
        };

        return task_ptr;
    }

    pub fn deinit(self: *Task) void {
        heap.free(@ptrFromInt(self.kernel_stack));
        heap.free(@ptrCast(self));
    }
};

fn getCR3() u64 {
    return asm volatile ("mov %%cr3, %[cr3]"
        : [cr3] "=r" (-> u64),
    );
}

コンテキストスイッチ

ここはアセンブリで実装する必要がある部分なんだよね。

// context_switch.zig
const Task = @import("task.zig").Task;
const Context = @import("task.zig").Context;

// Zigからアセンブリを呼び出す
extern fn switch_context(old: *Context, new: *Context) void;

// アセンブリで実装されるコンテキストスイッチ
comptime {
    asm (
        \\.global switch_context
        \\.type switch_context, @function
        \\switch_context:
        \\    // 現在のコンテキストを保存
        \\    mov %r15, 0(%rdi)
        \\    mov %r14, 8(%rdi)
        \\    mov %r13, 16(%rdi)
        \\    mov %r12, 24(%rdi)
        \\    mov %rbp, 32(%rdi)
        \\    mov %rbx, 40(%rdi)
        \\
        \\    // リターンアドレスを保存
        \\    lea 1f(%rip), %rax
        \\    mov %rax, 48(%rdi)
        \\
        \\    // スタックポインタを保存
        \\    mov %rsp, 56(%rdi)
        \\
        \\    // CR3を保存
        \\    mov %cr3, %rax
        \\    mov %rax, 64(%rdi)
        \\
        \\    // 新しいコンテキストを復元
        \\1:
        \\    mov 0(%rsi), %r15
        \\    mov 8(%rsi), %r14
        \\    mov 16(%rsi), %r13
        \\    mov 24(%rsi), %r12
        \\    mov 32(%rsi), %rbp
        \\    mov 40(%rsi), %rbx
        \\
        \\    // ページテーブルを切り替え(同じなら不要)
        \\    mov 64(%rsi), %rax
        \\    mov %cr3, %rcx
        \\    cmp %rax, %rcx
        \\    je 2f
        \\    mov %rax, %cr3
        \\2:
        \\
        \\    // スタックを切り替え
        \\    mov 56(%rsi), %rsp
        \\
        \\    // 新しいタスクにジャンプ
        \\    jmp *48(%rsi)
    );
}

pub fn switchTo(current: *Task, next: *Task) void {
    switch_context(&current.context, &next.context);
}

スケジューラ

ラウンドロビン方式のスケジューラを実装。

// scheduler.zig
const Task = @import("task.zig").Task;
const TaskState = @import("task.zig").TaskState;
const context_switch = @import("context_switch.zig");
const vga = @import("vga.zig");
const pic = @import("pic.zig");

const MAX_TASKS: usize = 64;

var task_list: [MAX_TASKS]?*Task = [_]?*Task{null} ** MAX_TASKS;
var current_task: ?*Task = null;
var ready_queue_head: ?*Task = null;
var ready_queue_tail: ?*Task = null;
var next_task_id: u32 = 0;
var scheduler_enabled: bool = false;

pub fn init() void {
    // アイドルタスクを作成
    const idle = createTask("idle", idleTask) orelse unreachable;
    idle.priority = 0;  // 最低優先度
    current_task = idle;
    idle.state = .running;

    scheduler_enabled = true;
}

fn idleTask() void {
    while (true) {
        asm volatile ("hlt");
    }
}

pub fn createTask(name: []const u8, entry: *const fn () void) ?*Task {
    const id = next_task_id;
    next_task_id += 1;

    const task = Task.init(id, name, entry) orelse return null;

    // タスクリストに追加
    for (&task_list) |*slot| {
        if (slot.* == null) {
            slot.* = task;
            break;
        }
    }

    // Readyキューに追加
    addToReadyQueue(task);

    return task;
}

fn addToReadyQueue(task: *Task) void {
    task.state = .ready;
    task.next = null;

    if (ready_queue_tail) |tail| {
        tail.next = task;
        ready_queue_tail = task;
    } else {
        ready_queue_head = task;
        ready_queue_tail = task;
    }
}

fn removeFromReadyQueue() ?*Task {
    if (ready_queue_head) |head| {
        ready_queue_head = head.next;
        if (ready_queue_head == null) {
            ready_queue_tail = null;
        }
        head.next = null;
        return head;
    }
    return null;
}

// タイマー割り込みから呼ばれる
pub fn tick() void {
    if (!scheduler_enabled) return;

    if (current_task) |task| {
        task.total_ticks += 1;
        task.remaining_slice -= 1;

        if (task.remaining_slice == 0) {
            schedule();
        }
    }
}

pub fn schedule() void {
    if (!scheduler_enabled) return;

    const current = current_task orelse return;

    // 次のタスクを選択
    const next = selectNextTask() orelse return;

    if (current == next) return;

    // 現在のタスクをキューに戻す
    if (current.state == .running) {
        current.remaining_slice = current.time_slice;
        addToReadyQueue(current);
    }

    // 次のタスクを実行
    next.state = .running;
    current_task = next;

    context_switch.switchTo(current, next);
}

fn selectNextTask() ?*Task {
    // 優先度ベースの選択(簡略化版)
    var best: ?*Task = null;
    var best_priority: u8 = 0;
    var prev: ?*Task = null;
    var best_prev: ?*Task = null;

    var current = ready_queue_head;
    while (current) |task| {
        if (task.priority > best_priority or best == null) {
            best = task;
            best_priority = task.priority;
            best_prev = prev;
        }
        prev = task;
        current = task.next;
    }

    if (best) |task| {
        // キューから削除
        if (best_prev) |p| {
            p.next = task.next;
        } else {
            ready_queue_head = task.next;
        }
        if (ready_queue_tail == task) {
            ready_queue_tail = best_prev;
        }
        task.next = null;
        return task;
    }

    // 実行可能なタスクがない場合はアイドルタスク
    return task_list[0];
}

pub fn yield() void {
    schedule();
}

pub fn sleep(ms: u32) void {
    if (current_task) |task| {
        task.state = .sleeping;
        // TODO: タイマーキューに追加
        schedule();
    }
}

pub fn exit(code: i32) void {
    if (current_task) |task| {
        task.state = .terminated;
        task.exit_code = code;
        schedule();
    }
}

pub fn getCurrentTask() ?*Task {
    return current_task;
}

pub fn getTaskCount() usize {
    var count: usize = 0;
    for (task_list) |slot| {
        if (slot != null) count += 1;
    }
    return count;
}

pub fn printTaskList() void {
    vga.print("\n=== Task List ===\n");
    vga.print("ID   Name             State     Ticks\n");
    vga.print("---- ---------------- --------- --------\n");

    for (task_list) |slot| {
        if (slot) |task| {
            printNumber(task.id, 4);
            vga.print(" ");
            printName(&task.name, 16);
            vga.print(" ");
            printState(task.state);
            vga.print(" ");
            printNumber(task.total_ticks, 8);
            vga.print("\n");
        }
    }
}

fn printNumber(n: u64, width: usize) void {
    var buf: [20]u8 = undefined;
    var i: usize = buf.len;
    var num = n;

    if (num == 0) {
        while (i > buf.len - width + 1) : (i -= 1) {
            buf[i - 1] = ' ';
        }
        buf[i - 1] = '0';
        i -= 1;
    } else {
        while (num > 0) {
            i -= 1;
            buf[i] = @truncate('0' + (num % 10));
            num /= 10;
        }
    }

    while (buf.len - i < width) {
        i -= 1;
        buf[i] = ' ';
    }

    for (buf[i..]) |c| {
        vga.putChar(c);
    }
}

fn printName(name: []const u8, width: usize) void {
    var printed: usize = 0;
    for (name) |c| {
        if (c == 0) break;
        vga.putChar(c);
        printed += 1;
    }
    while (printed < width) : (printed += 1) {
        vga.putChar(' ');
    }
}

fn printState(state: TaskState) void {
    const str = switch (state) {
        .ready => "Ready    ",
        .running => "Running  ",
        .blocked => "Blocked  ",
        .sleeping => "Sleeping ",
        .terminated => "Term     ",
    };
    vga.print(str);
}

TSS(Task State Segment)

x86_64では、割り込み時のスタック切り替えにTSSが必要。

// tss.zig
const gdt = @import("gdt.zig");

pub const TSS = extern struct {
    reserved0: u32 = 0,
    rsp0: u64 = 0,        // Ring 0 スタックポインタ
    rsp1: u64 = 0,
    rsp2: u64 = 0,
    reserved1: u64 = 0,
    ist1: u64 = 0,        // Interrupt Stack Table
    ist2: u64 = 0,
    ist3: u64 = 0,
    ist4: u64 = 0,
    ist5: u64 = 0,
    ist6: u64 = 0,
    ist7: u64 = 0,
    reserved2: u64 = 0,
    reserved3: u16 = 0,
    iopb_offset: u16 = @sizeOf(TSS),
};

var tss: TSS = .{};
var interrupt_stack: [4096]u8 align(16) = undefined;

pub fn init() void {
    // 割り込みスタックを設定
    tss.rsp0 = @intFromPtr(&interrupt_stack) + interrupt_stack.len;
    tss.ist1 = tss.rsp0;

    // GDTにTSSディスクリプタを追加
    gdt.installTss(@intFromPtr(&tss), @sizeOf(TSS));

    // TRにロード
    loadTr();
}

pub fn setKernelStack(stack: u64) void {
    tss.rsp0 = stack;
}

fn loadTr() void {
    asm volatile ("ltr %[selector]"
        :
        : [selector] "r" (@as(u16, 0x28)),  // TSSセレクタ
    );
}

サンプルタスク

// tasks.zig
const scheduler = @import("scheduler.zig");
const vga = @import("vga.zig");
const pit = @import("pit.zig");

// カウンタータスク
pub fn counterTask() void {
    var count: u32 = 0;
    while (true) {
        count += 1;
        vga.setCursor(0, 20);
        vga.print("Counter: ");
        printNumber(count);
        vga.print("   ");

        // 少し待つ
        for (0..100000) |_| {
            asm volatile ("nop");
        }

        scheduler.yield();
    }
}

// スピナータスク
pub fn spinnerTask() void {
    const chars = "|/-\\";
    var idx: usize = 0;
    while (true) {
        vga.setCursor(50, 20);
        vga.print("Spinner: ");
        vga.putChar(chars[idx % 4]);

        idx += 1;

        for (0..50000) |_| {
            asm volatile ("nop");
        }

        scheduler.yield();
    }
}

// 計算タスク
pub fn calcTask() void {
    var result: u64 = 0;
    while (true) {
        // フィボナッチ計算
        var a: u64 = 0;
        var b: u64 = 1;
        for (0..40) |_| {
            const tmp = a + b;
            a = b;
            b = tmp;
        }
        result = b;

        vga.setCursor(0, 21);
        vga.print("Fib(40) = ");
        printNumber(result);
        vga.print("   ");

        scheduler.yield();
    }
}

fn printNumber(n: u64) void {
    var buf: [20]u8 = undefined;
    var i: usize = buf.len;
    var num = n;

    if (num == 0) {
        vga.putChar('0');
        return;
    }

    while (num > 0) {
        i -= 1;
        buf[i] = @truncate('0' + (num % 10));
        num /= 10;
    }

    for (buf[i..]) |c| {
        vga.putChar(c);
    }
}

メインの更新

// boot.zig
const scheduler = @import("scheduler.zig");
const tasks = @import("tasks.zig");
const tss = @import("tss.zig");
const vga = @import("vga.zig");

export fn kernel_main() callconv(.C) noreturn {
    vga.clear();
    vga.print("Zig OS Kernel booting...\n\n");

    // 初期化...
    // (前回までのコード)

    // TSS初期化
    vga.print("[OK] TSS initialized\n");
    tss.init();

    // スケジューラ初期化
    vga.print("[OK] Scheduler initialized\n");
    scheduler.init();

    // タスクを作成
    _ = scheduler.createTask("counter", tasks.counterTask);
    _ = scheduler.createTask("spinner", tasks.spinnerTask);
    _ = scheduler.createTask("calc", tasks.calcTask);

    vga.print("[OK] Tasks created\n\n");

    // タスク一覧を表示
    scheduler.printTaskList();

    vga.print("\nStarting multitasking...\n\n");

    // 割り込み有効化
    idt.enableInterrupts();

    // シェル起動(メインタスクとして動作)
    shell.run();

    while (true) {
        asm volatile ("hlt");
    }
}

タイマーハンドラの更新

// pit.zig
const scheduler = @import("scheduler.zig");

pub fn timerInterrupt() callconv(.Interrupt) void {
    tick_count += 1;

    // スケジューラを呼び出し
    scheduler.tick();

    pic.sendEoi(0);
}

実行結果

Zig OS Kernel booting...

[OK] GDT initialized
[OK] IDT initialized
[OK] PIC initialized
[OK] PIT initialized (1000Hz)
[OK] Keyboard initialized
[OK] TSS initialized
[OK] Scheduler initialized
[OK] Tasks created

=== Task List ===
ID   Name             State     Ticks
---- ---------------- --------- --------
   0 idle             Running         0
   1 counter          Ready           0
   2 spinner          Ready           0
   3 calc             Ready           0

Starting multitasking...

Counter: 12345   Spinner: /
Fib(40) = 165580141

zigOS> 

まとめ

今回実装したこと:

コンポーネント 説明
Task構造体 ID、状態、コンテキスト、スタック
コンテキストスイッチ レジスタ保存/復元
スケジューラ ラウンドロビン + 優先度
TSS 割り込み時のスタック切り替え
┌──────────────────────────────────────────────────────────────┐
│                    タスク状態遷移図                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│                  create()                                    │
│      ┌──────────────────────────────┐                       │
│      │                              ↓                       │
│      │                        ┌──────────┐                  │
│      │                        │  Ready   │◄────────┐        │
│      │                        └────┬─────┘         │        │
│      │                             │               │        │
│      │                    schedule()│               │        │
│      │                             ↓               │        │
│      │                        ┌──────────┐         │        │
│      │                        │ Running  │─────────┘        │
│      │                        └────┬─────┘  preempt         │
│      │                             │                        │
│      │          ┌──────────────────┼──────────────────┐     │
│      │          │                  │                  │     │
│      │          ↓                  ↓                  ↓     │
│      │    ┌──────────┐       ┌──────────┐       ┌─────────┐ │
│      │    │ Blocked  │       │ Sleeping │       │  Term   │ │
│      │    └────┬─────┘       └────┬─────┘       └─────────┘ │
│      │         │                  │                         │
│      │         └───────┬──────────┘                         │
│      │                 │ wakeup                             │
│      │                 ↓                                    │
│      │            ┌──────────┐                              │
│      └───────────→│  Ready   │                              │
│                   └──────────┘                              │
│                                                              │
└──────────────────────────────────────────────────────────────┘

次回は最終回、ファイルシステムを実装するよ。RAMディスク上の簡単なFSで、ファイルの読み書きができるようになる!

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

6
1
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
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?