0
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?

【完全ガイド】1000行未満でRISC-V OSを自作する - ブートからファイルシステムまで

Posted at

この記事で学べること

  • ✅ OSの基本構造(ブート、メモリ管理、プロセス管理)
  • ✅ RISC-Vアーキテクチャの基礎
  • ✅ ページングによる仮想メモリ管理
  • ✅ システムコールの実装方法
  • ✅ virtio-blkデバイスドライバの仕組み
  • ✅ 簡易ファイルシステムの実装
  • ✅ QEMUを使ったカーネルデバッグ手法

所要時間: 初心者で3〜5日、経験者なら1〜2日
難易度: 中級(C言語の基礎知識が必要)


はじめに

本記事は、公式サイト「1000行でOSを作ってみよう」の内容を基に作成した学習ガイドです。

重要な免責事項

  • このガイドは主要な部分のみを抜粋した教育用補助教材です
  • 完全に動作するコードは公式GitHubを参照してください
  • virtio-blk、ファイルシステム、システムコールの詳細実装は省略されています

前提知識

必須

  • C言語の基礎(ポインタ、構造体、関数ポインタ)
  • UNIXコマンドライン操作(bash, make等)
  • 基本的なメモリの概念(スタック、ヒープ)

あると良い

  • アセンブリ言語の基礎知識
  • コンピュータアーキテクチャの基礎
  • makefileやビルドシステムの知識

不要

  • RISC-Vの詳しい知識(本記事で学べます)
  • OS理論の深い理解(実装を通じて学べます)

実装の全体像

┌─────────────────────────────────┐
│         アプリケーション         │
│         (shell.c)               │
└──────────────┬──────────────────┘
               │ システムコール
┌──────────────┴──────────────────┐
│           カーネル               │
│  ┌─────────────────────────┐   │
│  │  プロセス管理・スケジューラ │   │
│  └─────────────────────────┘   │
│  ┌─────────────────────────┐   │
│  │  メモリ管理・ページング    │   │
│  └─────────────────────────┘   │
│  ┌─────────────────────────┐   │
│  │  デバイスドライバ(virtio) │   │
│  └─────────────────────────┘   │
│  ┌─────────────────────────┐   │
│  │  ファイルシステム(tar)    │   │
│  └─────────────────────────┘   │
└──────────────┬──────────────────┘
               │ SBI呼び出し
┌──────────────┴──────────────────┐
│          OpenSBI                │
│       (ファームウェア)            │
└──────────────┬──────────────────┘
               │
┌──────────────┴──────────────────┐
│      QEMU (RISC-V エミュレータ)  │
└─────────────────────────────────┘

目次

  1. 開発環境のセットアップ
  2. リンカスクリプト
  3. 共通ヘッダとライブラリ
  4. カーネルヘッダ
  5. カーネル実装
  6. ビルドスクリプト
  7. 省略されている実装
  8. デバッグ方法
  9. よくある問題
  10. 次のステップ

1. 開発環境のセットアップ

macOS

brew install llvm lld qemu
export PATH="$PATH:$(brew --prefix)/opt/llvm/bin"

clangのパス確認:

ls $(brew --prefix)/opt/llvm/bin/clang

Ubuntu

sudo apt update && sudo apt install -y clang llvm lld qemu-system-riscv32 curl

OpenSBIのダウンロード(必須):

curl -LO https://github.com/qemu/qemu/raw/v8.0.4/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin

注意: opensbi-riscv32-generic-fw_dynamic.binは実行時のカレントディレクトリに配置する必要があります。


2. リンカスクリプト

kernel.ld

ENTRY(boot)

SECTIONS {
    . = 0x80200000;
    
    .text : {
        KEEP(*(.text.boot));
        *(.text .text.*);
    }
    
    .rodata : ALIGN(4) {
        *(.rodata .rodata.*);
    }
    
    .data : ALIGN(4) {
        *(.data .data.*);
    }
    
    .bss : ALIGN(4) {
        __bss = .;
        *(.bss .bss.* .sbss .sbss.*);
        __bss_end = .;
    }
    
    . = ALIGN(4);
    . += 128 * 1024;
    __stack_top = .;
    
    . = ALIGN(4096);
    __free_ram = .;
    . += 64 * 1024 * 1024;
    __free_ram_end = .;
}

重要: __free_ram__free_ram_endリンカスクリプトで定義されます。

user.ld

ENTRY(start)

SECTIONS {
    . = 0x1000000;
    
    .text : {
        KEEP(*(.text.start));
        *(.text .text.*);
    }
    
    .rodata : ALIGN(4) {
        *(.rodata .rodata.*);
    }
    
    .data : ALIGN(4) {
        *(.data .data.*);
    }
    
    .bss : ALIGN(4) {
        *(.bss .bss.* .sbss .sbss.*);
        
        . = ALIGN(16);
        . += 64 * 1024;
        __stack_top = .;
    }
}

3. 共通ヘッダとライブラリ

common.h

#pragma once

// 型定義
typedef int bool;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef uint32_t size_t;
typedef uint32_t paddr_t;
typedef uint32_t vaddr_t;

// 定数
#define true 1
#define false 0
#define NULL ((void *) 0)
#define PAGE_SIZE 4096

// マクロ
#define align_up(value, align)   __builtin_align_up(value, align)
#define is_aligned(value, align) __builtin_is_aligned(value, align)
#define offsetof(type, member)   __builtin_offsetof(type, member)
#define va_list                  __builtin_va_list
#define va_start                 __builtin_va_start
#define va_end                   __builtin_va_end
#define va_arg                   __builtin_va_arg

// 関数宣言
void *memset(void *buf, char c, size_t n);
void *memcpy(void *dst, const void *src, size_t n);
char *strcpy(char *dst, const char *src);
int strcmp(const char *s1, const char *s2);
void printf(const char *fmt, ...);

common.c

#include "common.h"

void putchar(char ch);

void *memset(void *buf, char c, size_t n) {
    uint8_t *p = (uint8_t *) buf;
    while (n--)
        *p++ = c;
    return buf;
}

void *memcpy(void *dst, const void *src, size_t n) {
    uint8_t *d = (uint8_t *) dst;
    const uint8_t *s = (const uint8_t *) src;
    while (n--)
        *d++ = *s++;
    return dst;
}

char *strcpy(char *dst, const char *src) {
    char *d = dst;
    while (*src)
        *d++ = *src++;
    *d = '\0';
    return dst;
}

int strcmp(const char *s1, const char *s2) {
    while (*s1 && *s2) {
        if (*s1 != *s2)
            break;
        s1++;
        s2++;
    }
    return *(unsigned char *) s1 - *(unsigned char *) s2;
}

void printf(const char *fmt, ...) {
    va_list vargs;
    va_start(vargs, fmt);
    
    while (*fmt) {
        if (*fmt == '%') {
            fmt++;
            switch (*fmt) {
                case '\0':
                    putchar('%');
                    goto end;
                case '%':
                    putchar('%');
                    break;
                case 's': {
                    const char *s = va_arg(vargs, const char *);
                    while (*s) {
                        putchar(*s);
                        s++;
                    }
                    break;
                }
                case 'd': {
                    int value = va_arg(vargs, int);
                    unsigned magnitude = value;
                    if (value < 0) {
                        putchar('-');
                        magnitude = -magnitude;
                    }
                    
                    unsigned divisor = 1;
                    while (magnitude / divisor > 9)
                        divisor *= 10;
                    
                    while (divisor > 0) {
                        putchar('0' + magnitude / divisor);
                        magnitude %= divisor;
                        divisor /= 10;
                    }
                    break;
                }
                case 'x': {
                    unsigned value = va_arg(vargs, unsigned);
                    for (int i = 7; i >= 0; i--) {
                        unsigned nibble = (value >> (i * 4)) & 0xf;
                        putchar("0123456789abcdef"[nibble]);
                    }
                }
                // 注意: 元の実装ではbreakが無い(switch文の最後なので問題なし)
            }
        } else {
            putchar(*fmt);
        }
        fmt++;
    }
    
end:
    va_end(vargs);
}

4. カーネルヘッダ(主要定義)

kernel.h(抜粋)

#pragma once
#include "common.h"

// 定数定義
#define PROCS_MAX 8
#define PROC_UNUSED   0
#define PROC_RUNNABLE 1
#define PROC_EXITED   2
#define SATP_SV32 (1u << 31)
#define SSTATUS_SPIE (1 << 5)
#define SSTATUS_SUM  (1 << 18)
#define SCAUSE_ECALL 8

// ページテーブルフラグ
#define PAGE_V (1 << 0)
#define PAGE_R (1 << 1)
#define PAGE_W (1 << 2)
#define PAGE_X (1 << 3)
#define PAGE_U (1 << 4)

// アドレス
#define USER_BASE 0x1000000
#define VIRTIO_BLK_PADDR 0x10001000

// CSRマクロ
#define READ_CSR(reg) \
    ({ \
        unsigned long __tmp; \
        __asm__ __volatile__("csrr %0, " #reg : "=r"(__tmp)); \
        __tmp; \
    })

#define WRITE_CSR(reg, value) \
    do { \
        uint32_t __tmp = (value); \
        __asm__ __volatile__("csrw " #reg ", %0" ::"r"(__tmp)); \
    } while (0)

#define PANIC(fmt, ...) \
    do { \
        printf("PANIC: %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
        while (1) {} \
    } while (0)

// 構造体
struct sbiret {
    long error;
    long value;
};

struct trap_frame {
    uint32_t ra, gp, tp, t0, t1, t2, t3, t4, t5, t6;
    uint32_t a0, a1, a2, a3, a4, a5, a6, a7;
    uint32_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
    uint32_t sp;
} __attribute__((packed));

struct process {
    int pid;
    int state;
    vaddr_t sp;
    uint32_t *page_table;
    uint8_t stack[8192];
};

// その他の構造体(virtio、tar、file)は公式参照

5. カーネル実装(主要部分)

kernel.c(主要な関数のみ)

#include "kernel.h"
#include "common.h"

extern char __bss[], __bss_end[], __stack_top[];
extern char __free_ram[], __free_ram_end[];
extern char _binary_shell_bin_start[], _binary_shell_bin_size[];

struct process procs[PROCS_MAX];
struct process *current_proc;
struct process *idle_proc;

// ブート処理
__attribute__((section(".text.boot")))
__attribute__((naked))
void boot(void) {
    __asm__ __volatile__(
        "mv sp, %[stack_top]\n"
        "j kernel_main\n"
        :
        : [stack_top] "r" (__stack_top)
    );
}

// SBI呼び出し
struct sbiret sbi_call(long arg0, long arg1, long arg2, long arg3, long arg4,
                       long arg5, long fid, long eid) {
    register long a0 __asm__("a0") = arg0;
    register long a1 __asm__("a1") = arg1;
    register long a2 __asm__("a2") = arg2;
    register long a3 __asm__("a3") = arg3;
    register long a4 __asm__("a4") = arg4;
    register long a5 __asm__("a5") = arg5;
    register long a6 __asm__("a6") = fid;
    register long a7 __asm__("a7") = eid;
    
    __asm__ __volatile__("ecall"
        : "=r"(a0), "=r"(a1)
        : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7)
        : "memory");
    
    return (struct sbiret){.error = a0, .value = a1};
}

void putchar(char ch) {
    sbi_call(ch, 0, 0, 0, 0, 0, 0, 1);
}

// メモリ割り当て(公式実装通り)
paddr_t alloc_pages(uint32_t n) {
    static paddr_t next_paddr = (paddr_t) __free_ram;
    paddr_t paddr = next_paddr;
    next_paddr += n * PAGE_SIZE;
    
    if (next_paddr > (paddr_t) __free_ram_end)
        PANIC("out of memory");
    
    memset((void *) paddr, 0, n * PAGE_SIZE);
    return paddr;
}

// ページマッピング
void map_page(uint32_t *table1, uint32_t vaddr, paddr_t paddr, uint32_t flags) {
    if (!is_aligned(vaddr, PAGE_SIZE))
        PANIC("unaligned vaddr %x", vaddr);
    if (!is_aligned(paddr, PAGE_SIZE))
        PANIC("unaligned paddr %x", paddr);
    
    uint32_t vpn1 = (vaddr >> 22) & 0x3ff;
    if ((table1[vpn1] & PAGE_V) == 0) {
        uint32_t pt_paddr = alloc_pages(1);
        table1[vpn1] = ((pt_paddr / PAGE_SIZE) << 10) | PAGE_V;
    }
    
    uint32_t vpn0 = (vaddr >> 12) & 0x3ff;
    uint32_t *table0 = (uint32_t *) ((table1[vpn1] >> 10) * PAGE_SIZE);
    table0[vpn0] = ((paddr / PAGE_SIZE) << 10) | flags | PAGE_V;
}

// 例外ハンドラ(レジスタ保存部分は省略)
__attribute__((naked))
__attribute__((aligned(4)))
void kernel_entry(void) {
    __asm__ __volatile__(
        "csrrw sp, sscratch, sp\n"
        "addi sp, sp, -4 * 31\n"
        // レジスタ保存(省略: 全31個のレジスタ)
        "sw ra, 4 * 0(sp)\n"
        "sw gp, 4 * 1(sp)\n"
        // ... 他のレジスタ ...
        "csrr a0, sscratch\n"
        "sw a0, 4 * 30(sp)\n"
        "addi a0, sp, 4 * 31\n"
        "csrw sscratch, a0\n"
        "mv a0, sp\n"
        "call handle_trap\n"
        // レジスタ復元(省略)
        "lw ra, 4 * 0(sp)\n"
        // ... 他のレジスタ ...
        "lw sp, 4 * 30(sp)\n"
        "sret\n"
    );
}

void handle_trap(struct trap_frame *f) {
    uint32_t scause = READ_CSR(scause);
    uint32_t stval = READ_CSR(stval);
    uint32_t user_pc = READ_CSR(sepc);
    
    if (scause == SCAUSE_ECALL) {
        handle_syscall(f);
        user_pc += 4;
        WRITE_CSR(sepc, user_pc);
    } else {
        PANIC("unexpected trap scause=%x, stval=%x, sepc=%x\n", 
              scause, stval, user_pc);
    }
}

// コンテキストスイッチ
__attribute__((naked))
void switch_context(uint32_t *prev_sp, uint32_t *next_sp) {
    __asm__ __volatile__(
        "addi sp, sp, -13 * 4\n"
        "sw ra, 0 * 4(sp)\n"
        "sw s0, 1 * 4(sp)\n"
        "sw s1, 2 * 4(sp)\n"
        "sw s2, 3 * 4(sp)\n"
        "sw s3, 4 * 4(sp)\n"
        "sw s4, 5 * 4(sp)\n"
        "sw s5, 6 * 4(sp)\n"
        "sw s6, 7 * 4(sp)\n"
        "sw s7, 8 * 4(sp)\n"
        "sw s8, 9 * 4(sp)\n"
        "sw s9, 10 * 4(sp)\n"
        "sw s10, 11 * 4(sp)\n"
        "sw s11, 12 * 4(sp)\n"
        "sw sp, (a0)\n"
        "lw sp, (a1)\n"
        "lw ra, 0 * 4(sp)\n"
        "lw s0, 1 * 4(sp)\n"
        "lw s1, 2 * 4(sp)\n"
        "lw s2, 3 * 4(sp)\n"
        "lw s3, 4 * 4(sp)\n"
        "lw s4, 5 * 4(sp)\n"
        "lw s5, 6 * 4(sp)\n"
        "lw s6, 7 * 4(sp)\n"
        "lw s7, 8 * 4(sp)\n"
        "lw s8, 9 * 4(sp)\n"
        "lw s9, 10 * 4(sp)\n"
        "lw s10, 11 * 4(sp)\n"
        "lw s11, 12 * 4(sp)\n"
        "addi sp, sp, 13 * 4\n"
        "ret\n"
    );
}

// スケジューラ
void yield(void) {
    struct process *next = idle_proc;
    for (int i = 0; i < PROCS_MAX; i++) {
        struct process *proc = &procs[(current_proc->pid + i) % PROCS_MAX];
        if (proc->state == PROC_RUNNABLE && proc->pid > 0) {
            next = proc;
            break;
        }
    }
    
    if (next == current_proc)
        return;
    
    __asm__ __volatile__(
        "sfence.vma\n"
        "csrw satp, %[satp]\n"
        "sfence.vma\n"
        "csrw sscratch, %[sscratch]\n"
        :
        : [satp] "r" (SATP_SV32 | ((uint32_t) next->page_table / PAGE_SIZE)),
          [sscratch] "r" ((uint32_t) &next->stack[sizeof(next->stack)])
    );
    
    struct process *prev = current_proc;
    current_proc = next;
    switch_context(&prev->sp, &next->sp);
}

// プロセス作成(簡略版)
struct process *create_process(const void *image, size_t image_size) {
    // プロセス検索
    struct process *proc = NULL;
    for (int i = 0; i < PROCS_MAX; i++) {
        if (procs[i].state == PROC_UNUSED) {
            proc = &procs[i];
            break;
        }
    }
    if (!proc)
        PANIC("no free process slots");
    
    // スタック初期化
    uint32_t *sp = (uint32_t *) &proc->stack[sizeof(proc->stack)];
    *--sp = 0; *--sp = 0; *--sp = 0; *--sp = 0;
    *--sp = 0; *--sp = 0; *--sp = 0; *--sp = 0;
    *--sp = 0; *--sp = 0; *--sp = 0; *--sp = 0;
    *--sp = (uint32_t) user_entry;
    
    // ページテーブル作成
    uint32_t *page_table = (uint32_t *) alloc_pages(1);
    
    // カーネルページをマッピング(省略: 元サイト参照)
    // ユーザープログラムをマッピング(省略: 元サイト参照)
    
    proc->pid = i + 1;
    proc->state = PROC_RUNNABLE;
    proc->sp = (uint32_t) sp;
    proc->page_table = page_table;
    
    return proc;
}

// メイン関数
void kernel_main(void) {
    memset(__bss, 0, (size_t) __bss_end - (size_t) __bss);
    
    printf("\n\nHello World!\n");
    
    WRITE_CSR(stvec, (uint32_t) kernel_entry);
    
    // メモリ割り当てテスト
    paddr_t paddr0 = alloc_pages(2);
    paddr_t paddr1 = alloc_pages(1);
    printf("paddr0=%x, paddr1=%x\n", paddr0, paddr1);
    
    // 以降、virtio_blk_init()、fs_init()、プロセス作成など
    // 詳細は元サイト参照
    
    for (;;) {
        __asm__ __volatile__("wfi");
    }
}

6. ビルドスクリプト

run.sh

#!/bin/bash
set -xue

QEMU=qemu-system-riscv32
CC=clang  # macOS: /opt/homebrew/opt/llvm/bin/clang
OBJCOPY=llvm-objcopy  # macOS: /opt/homebrew/opt/llvm/bin/llvm-objcopy

CFLAGS="-std=c11 -O2 -g3 -Wall -Wextra --target=riscv32 -ffreestanding -nostdlib"

# カーネルをビルド
$CC $CFLAGS -Wl,-Tkernel.ld -Wl,-Map=kernel.map -o kernel.elf \
    kernel.c common.c

# QEMUを起動
$QEMU -machine virt -bios default -nographic -serial mon:stdio --no-reboot \
    -kernel kernel.elf

実行:

chmod +x run.sh
./run.sh

7. 省略されている実装

このドキュメントでは以下が省略されています。必ず元サイトを参照してください:

  1. 例外ハンドラの完全版(31個のレジスタ保存/復元)
  2. ユーザーモード実装(user_entry、プロセスマッピング)
  3. virtio-blkドライバ(完全実装が必要)
  4. ファイルシステム(tar解析、読み書き)
  5. システムコールハンドラ(全ケース)
  6. シェル実装(shell.c、user.c)

8. デバッグ方法

QEMUモニタ

QEMUモニタへの切り替え: Ctrl+AC

便利なコマンド:

  • info registers - レジスタの内容を表示
  • info mem - ページテーブルの内容を表示
  • xp /x 物理アドレス - 物理メモリの内容を16進数で表示
  • x /x 仮想アドレス - 仮想メモリの内容を16進数で表示
  • stop - 実行を一時停止
  • cont - 実行を再開
  • q - QEMUを終了

QEMUモニタから抜ける: Ctrl+AC
QEMUを即座に終了: Ctrl+AX

LLVMツール

# 逆アセンブル
llvm-objdump -d kernel.elf

# アドレスからソース行を特定
llvm-addr2line -e kernel.elf 0x80200010

# シンボル一覧
llvm-nm kernel.elf

# ELFファイルの情報
llvm-readelf -a kernel.elf

9. よくある問題

ページフォルト (scause=0xd)

原因:

  • ページテーブルの設定ミス
  • SSTATUS_SUMビットの未設定
  • 物理ページ番号と物理アドレスの混同

確認方法:

(qemu) stop
(qemu) info mem
(qemu) info registers

システムコールから戻らない

原因: sepc+=4していない

修正:

if (scause == SCAUSE_ECALL) {
    handle_syscall(f);
    user_pc += 4;  // 重要!
    WRITE_CSR(sepc, user_pc);
}

OpenSBIが見つからない

エラー:

qemu-system-riscv32: Unable to load the RISC-V firmware

解決法:

curl -LO https://github.com/qemu/qemu/raw/v8.0.4/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin

カレントディレクトリに配置する必要があります。

メモリ不足エラー

エラー: PANIC: out of memory

確認:

llvm-nm kernel.elf | grep __free_ram

リンカスクリプトの__free_ram_endの値を確認・調整してください。


10. FAQ

Q1: macOSでclangが見つからない

A: Homebrewのllvmパッケージをインストールしてください。

brew install llvm
export PATH="$(brew --prefix)/opt/llvm/bin:$PATH"

Q2: ページフォルトが頻発する

A: 次を確認してください:

  1. SSTATUS_SUMビットの設定
  2. ページテーブルの物理ページ番号(アドレスではない)
  3. info memでマッピング状態を確認
(qemu) info mem

Q3: QEMUが起動しない

A: opensbi-riscv32-generic-fw_dynamic.binがカレントディレクトリにあることを確認してください。

ls opensbi-riscv32-generic-fw_dynamic.bin

Q4: ビルドエラー: undefined reference to '__stack_top'

A: kernel.ldのリンカスクリプトが正しく指定されているか確認してください。

$CC $CFLAGS -Wl,-Tkernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.c common.c

Q5: コンパイルは通るが何も表示されない

A: QEMUモニタ(Ctrl+AC)でinfo registersを実行し、pcの値を確認してください。

(qemu) info registers

pckernel_main付近を指しているか確認します。


11. さらに学ぶには

本記事で基礎を学んだ後、以下にチャレンジしてみましょう:

初級

  • 複数のシェルコマンドを追加(ls, cat, echo等)
  • より多くのプロセスを同時実行
  • プロセスの優先度実装
  • メモリ使用量の表示機能

中級

  • タイマー割り込みの実装
  • プリエンプティブマルチタスク
  • プロセス間通信(パイプ、メッセージキュー)
  • より高度なファイルシステム(ディレクトリ対応)
  • 動的メモリ割り当て(malloc/free)

上級

  • マルチコアCPU対応
  • ネットワークドライバ実装
  • メモリ保護の強化
  • ELFローダーの実装
  • デバッガの組み込み

12. 参考資料

書籍

オンライン資料

コミュニティ


13. 関連リンク


14. 実装のヒント

ヒント1: デバッグの基本

OS開発ではprintfデバッグが最強の武器です:

printf("DEBUG: reached line %d\n", __LINE__);
printf("DEBUG: value=%x\n", some_value);

ヒント2: ページテーブルのデバッグ

ページング関連の問題はinfo memで確認:

(qemu) stop
(qemu) info mem
vaddr    paddr            size     attr
-------- ---------------- -------- -------
80200000 0000000080200000 00001000 rwx----

ヒント3: レジスタダンプの読み方

(qemu) info registers
pc       80200014  # プログラムカウンタ
sp       80220018  # スタックポインタ
a0       00000000  # 引数/戻り値レジスタ

pcの値をllvm-addr2lineで確認:

llvm-addr2line -e kernel.elf 0x80200014

ヒント4: メモリダンプの活用

仮想アドレスの内容を確認:

(qemu) x /10x 0x80200000
80200000: 0x80220537 0x01850513 0x0000812a 0x0060006f

物理アドレスの内容を確認:

(qemu) xp /10x 0x80200000

15. ライセンスと謝辞

ライセンス

本記事の内容は、元サイト「1000行でOSを作ってみよう」に準じます:

謝辞

本記事はnuta氏の「1000行でOSを作ってみよう」を参考に作成しました。素晴らしい教材を公開していただき、ありがとうございます。


まとめ

このガイドは、RISC-V OSの基本的な骨組みを提供する教育用教材です。

✅ このガイドで学んだこと

  • OSのブート処理とメモリ管理
  • ページングによる仮想メモリ
  • プロセス管理とコンテキストスイッチ
  • 例外処理とシステムコール
  • デバイスドライバの基礎
  • QEMUを使ったデバッグ技術

⚠️ 次にやるべきこと

完全な実装には以下が必須です

  1. 公式サイト: https://operating-system-in-1000-lines.vercel.app/ja/
  2. GitHubソースコード: https://github.com/nuta/operating-system-in-1000-lines

特に以下の章は公式で必ず確認してください:

  • 09章: メモリ割り当て
  • 11章: ページテーブル
  • 12章: ユーザーモード
  • 14章: システムコール
  • 15章: virtio-blk
  • 16章: ファイルシステム
  • 17章: シェル

🎉 最後に

OS自作は難しいですが、動いたときの感動はひとしおです。ぜひ挫折せずに完成させてください!

質問や問題があれば、コメント欄や公式GitHubのIssuesで質問してみましょう。

Happy OS Hacking! 🚀

0
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
0
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?