os-dev-scratch について
最小 OS をゼロから構築する、OS 学習者とカーネル開発者向けのガイドです。起動、メモリ、割り込み、コンテキスト切り替えを扱い、xv6-RISC-V を参考にします。
主な機能
- 01BIOS、UEFI、起動段階の把握
- 02x86-64 ロングモードとページテーブルの設定
- 03IDT、割り込みハンドラー、コントローラーの実装
- 04シリアルとキーボードのドライバー作成
- 05物理・仮想メモリ管理の構築
- 06コンテキスト切り替えの実装と QEMU テスト
活用シーン
- 01電源投入からカーネル入口までの起動学習
- 02x86-64 の保護モードまたはロングモード移行の実装
- 03メモリアロケーターとページテーブル管理の作成
- 04QEMU での割り込みとカーネルのテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill os-dev-scratchインストール後、Claude Code で/os-dev-scratchを入力して読み込まれたか確認します。
詳細
ゼロからの OS 開発
目的
最小限のオペレーティングシステムをゼロから構築する手順をエージェントに案内します。ブートローダーの段階(BIOS/GRUB 対 UEFI/limine)、GDT とページテーブルを使った 64 ビットロングモードのセットアップ、IDT と割り込みハンドラー、PIC/APIC の設定、基本的なキーボードドライバーとシリアルドライバー、物理メモリマネージャーと仮想メモリマネージャー、コンテキストスイッチングを扱い、参照アーキテクチャーとして xv6-RISC-V を取り上げます。
使用する場面
- 電源投入から
main()までの OS の起動方法を学ぶとき - x86-64 でプロテクトモード/ロングモードへの切り替えを実装するとき
- 物理メモリアロケーター(ビットマップ)とページテーブルマネージャーを作成するとき
- タイマー、キーボード、ページフォールトの割り込みを処理するとき
- QEMU の
-kernelとクロスコンパイラーx86_64-elf-gccでテストするとき - xv6 の概念を独自の x86 または RISC-V カーネルに移植するとき
ワークフロー
1. ブート段階の概要
BIOS path (legacy)
├── BIOS POST
├── MBR (512 bytes) → boot sector loads stage2
├── GRUB/multiboot → loads kernel ELF
└── kernel entry (_start)
UEFI path (modern)
├── UEFI firmware
├── EFI bootloader (limine, systemd-boot)
├── Loads kernel + initrd from ESP
└── kernel entry (handoff with memory map)
2. ツールチェーンのセットアップ
# Cross-compiler for bare metal
brew install x86_64-elf-gcc x86_64-elf-binutils # macOS
# or build from source / apt install gcc-x86-64-elf
x86_64-elf-gcc --version
# QEMU for testing
qemu-system-x86_64 --version
リンカースクリプトの基本事項:
/* linker.ld */
ENTRY(_start)
SECTIONS {
. = 0x100000; /* 1MB — typical kernel load address */
.text : { *(.text .text.*) }
.rodata : { *(.rodata .rodata.*) }
.data : { *(.data .data.*) }
.bss : { *(.bss .bss.*) }
}
x86_64-elf-gcc -ffreestanding -nostdlib -c kernel.c -o kernel.o
x86_64-elf-ld -T linker.ld kernel.o -o kernel.elf
3. Multiboot/limine による起動
# QEMU direct kernel boot (no disk)
qemu-system-x86_64 \
-kernel kernel.elf \
-serial stdio \
-m 128M \
-no-reboot -no-shutdown
# With limine (UEFI)
qemu-system-x86_64 \
-bios /usr/share/ovmf/OVMF.fd \
-drive file=disk.img,format=raw \
-serial stdio
4. ロングモードのセットアップ
Protected mode (32-bit) → enable PAE → setup 4-level page tables → enable long mode
// Minimal GDT entry (64-bit flat segments)
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
} __attribute__((packed));
// Page table setup (4KB pages, identity map first 1GB)
uint64_t pml4[512] __attribute__((aligned(4096)));
uint64_t pdpt[512] __attribute__((aligned(4096)));
uint64_t pd[512] __attribute__((aligned(4096)));
void setup_paging(void) {
for (int i = 0; i < 512; i++)
pd[i] = (i * 0x200000) | 0x83; // 2MB huge pages
pdpt[0] = (uint64_t)pd | 0x03;
pml4[0] = (uint64_t)pdpt | 0x03;
__asm__ volatile("mov %0, %%cr3" :: "r"(pml4));
}
5. IDT と割り込みハンドラー
struct idt_entry {
uint16_t offset_low;
uint16_t selector;
uint8_t ist;
uint8_t type_attr;
uint16_t offset_mid;
uint32_t offset_high;
uint32_t zero;
} __attribute__((packed));
// ISR stub (assembly) → common handler → dispatch by vector
void interrupt_handler(struct trap_frame *frame) {
if (frame->vector == 14) // page fault
handle_page_fault(frame->cr2, frame->error_code);
else if (frame->vector == 33) // keyboard IRQ remapped
keyboard_handler();
}
# Test page fault
# QEMU monitor: info registers
6. PIC と APIC
// Legacy PIC remapping (8259)
// Remap IRQ 0-15 to vectors 32-47
outb(0x20, 0x11); outb(0xA0, 0x11);
outb(0x21, 0x20); outb(0xA1, 0x28); // vector offsets
// ...
// Modern: use APIC/IOAPIC (ACPI MADT parsing)
// LAPIC timer for preemption
7. シリアルドライバーとキーボードドライバー
// COM1 serial (0x3F8)
void serial_putc(char c) {
while ((inb(0x3F8 + 5) & 0x20) == 0);
outb(0x3F8, c);
}
// PS/2 keyboard scancode → ASCII lookup table
void keyboard_handler(void) {
uint8_t scancode = inb(0x60);
char c = scancode_to_ascii[scancode];
if (c) serial_putc(c);
outb(0x20, 0x20); // EOI to PIC
}
qemu-system-x86_64 -kernel kernel.elf -serial stdio
# printk output appears in terminal
8. 物理メモリマネージャー
// Bitmap allocator over usable RAM regions
// From multiboot memory map or UEFI memory map
#define PAGE_SIZE 4096
uint8_t *frame_bitmap;
uint64_t total_frames;
uint64_t alloc_frame(void) {
for (uint64_t i = 0; i < total_frames; i++) {
if (!test_bit(frame_bitmap, i)) {
set_bit(frame_bitmap, i);
return i * PAGE_SIZE;
}
}
return 0; // OOM
}
9. コンテキストスイッチング
struct context {
uint64_t rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp;
uint64_t r8, r9, r10, r11, r12, r13, r14, r15;
uint64_t rip;
};
void switch_context(struct context *old, struct context *new);
// Assembly: save callee-saved regs to old, restore from new, ret to new->rip
まず協調的スケジューリングを実装し、その後でタイマー IRQ によるプリエンプションを追加します。
10. xv6-RISC-V の参照資料
git clone https://github.com/mit-pdos/xv6-riscv
cd xv6-riscv && make qemu
| xv6 のコンポーネント | x86 での相当機能 |
|---|---|
kernel/vm.c | ページテーブル管理 |
kernel/trap.c | IDT/割り込みディスパッチ |
kernel/proc.c | コンテキストスイッチ、スケジューラー |
kernel/plic.c | PIC/APIC 割り込みコントローラー |
user/usys.pl | システムコールスタブ |
よくある問題
| 症状 | 原因 | 対処方法 |
|---|---|---|
| 起動時にトリプルフォールトが発生する | GDT/IDT またはスタックが無効 | 割り込みを有効にする前にスタックをセットアップします |
| QEMU の画面が真っ黒になる | シリアル出力が設定されていない | -serial stdio を指定し、早い段階で serial_init を呼び出します |
| カーネルでページフォールトが発生する | アドレスがマップされていない | カーネルをアイデンティティーマップし、CR3 を確認します |
| IRQ が一度も発生しない | PIC のマスク、または IDT がロードされていない | lidt を実行し、PIC で IRQ のマスクを解除します |
| タイマーが動作しない | LAPIC が初期化されていない | ACPI を解析し、LAPIC タイマーを調整します |
| リンカーの再配置エラー | ロードアドレスが誤っている | linker.ld をブートローダーの想定に合わせます |
関連スキル
skills/low-level-programming/assembly-x86— ISR スタブ用の x86-64 アセンブリskills/low-level-programming/assembly-riscv— xv6-RISC-V の参照 ISAskills/platform/riscv-privileged— RISC-V のトラップ処理とページテーブルskills/virtualization/qemu-kvm— カーネル開発向けの QEMU フラグskills/kernel/kernel-internals— これらの概念の Linux における実装skills/low-level-programming/linux-kernel-modules— 基本が動作したら Linux に進む
コメント
Markdown 対応