os-dev-scratch

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OS Development from Scratch

从零开始的OS开发

Purpose

用途

Guide agents through building a minimal operating system from scratch: bootloader stages (BIOS/GRUB vs UEFI/limine), 64-bit long mode setup with GDT and page tables, IDT and interrupt handlers, PIC/APIC configuration, basic keyboard and serial drivers, physical and virtual memory managers, context switching, with xv6-RISC-V as a reference architecture.
指导Agent从零构建一个最小化操作系统:bootloader阶段(BIOS/GRUB 与 UEFI/limine)、通过GDT和页表设置64位长模式、IDT与中断处理程序、PIC/APIC配置、基础键盘与串口驱动、物理与虚拟内存管理器、上下文切换,以xv6-RISC-V作为参考架构。

When to Use

适用场景

  • Learning how an OS boots from power-on to
    main()
  • Implementing protected/long mode transitions on x86-64
  • Writing a physical memory allocator (bitmap) and page table manager
  • Handling timer, keyboard, and page fault interrupts
  • Testing with QEMU
    -kernel
    and cross-compiler
    x86_64-elf-gcc
  • Porting concepts from xv6 to a custom x86 or RISC-V kernel
  • 学习OS从上电到
    main()
    的启动流程
  • 在x86-64上实现保护模式/长模式的切换
  • 编写物理内存分配器(位图)和页表管理器
  • 处理定时器、键盘和缺页中断
  • 使用QEMU
    -kernel
    和交叉编译器
    x86_64-elf-gcc
    进行测试
  • 将xv6中的概念移植到自定义x86或RISC-V内核

Workflow

工作流程

1. Boot stages overview

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)
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. Toolchain setup

2. 工具链设置

bash
undefined
bash
undefined

Cross-compiler for bare metal

Cross-compiler for bare metal

brew install x86_64-elf-gcc x86_64-elf-binutils # macOS
brew install x86_64-elf-gcc x86_64-elf-binutils # macOS

or build from source / apt install gcc-x86-64-elf

or build from source / apt install gcc-x86-64-elf

x86_64-elf-gcc --version
x86_64-elf-gcc --version

QEMU for testing

QEMU for testing

qemu-system-x86_64 --version

Linker script essentials:

```ld
/* linker.ld */
ENTRY(_start)
SECTIONS {
    . = 0x100000;          /* 1MB — typical kernel load address */
    .text : { *(.text .text.*) }
    .rodata : { *(.rodata .rodata.*) }
    .data : { *(.data .data.*) }
    .bss : { *(.bss .bss.*) }
}
bash
x86_64-elf-gcc -ffreestanding -nostdlib -c kernel.c -o kernel.o
x86_64-elf-ld -T linker.ld kernel.o -o kernel.elf
qemu-system-x86_64 --version

链接脚本要点:

```ld
/* linker.ld */
ENTRY(_start)
SECTIONS {
    . = 0x100000;          /* 1MB — typical kernel load address */
    .text : { *(.text .text.*) }
    .rodata : { *(.rodata .rodata.*) }
    .data : { *(.data .data.*) }
    .bss : { *(.bss .bss.*) }
}
bash
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 boot

3. Multiboot/limine 启动

bash
undefined
bash
undefined

QEMU direct kernel boot (no disk)

QEMU direct kernel boot (no disk)

qemu-system-x86_64
-kernel kernel.elf
-serial stdio
-m 128M
-no-reboot -no-shutdown
qemu-system-x86_64
-kernel kernel.elf
-serial stdio
-m 128M
-no-reboot -no-shutdown

With limine (UEFI)

With limine (UEFI)

qemu-system-x86_64
-bios /usr/share/ovmf/OVMF.fd
-drive file=disk.img,format=raw
-serial stdio
undefined
qemu-system-x86_64
-bios /usr/share/ovmf/OVMF.fd
-drive file=disk.img,format=raw
-serial stdio
undefined

4. Long mode setup

4. 长模式设置

Protected mode (32-bit) → enable PAE → setup 4-level page tables → enable long mode
c
// 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));
}
Protected mode (32-bit) → enable PAE → setup 4-level page tables → enable long mode
c
// 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 and interrupt handlers

5. IDT与中断处理程序

c
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();
}
bash
undefined
c
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();
}
bash
undefined

Test page fault

Test page fault

QEMU monitor: info registers

QEMU monitor: info registers

undefined
undefined

6. PIC and APIC

6. PIC与APIC

c
// 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
c
// 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. Serial and keyboard drivers

7. 串口与键盘驱动

c
// 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
}
bash
qemu-system-x86_64 -kernel kernel.elf -serial stdio
c
// 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
}
bash
qemu-system-x86_64 -kernel kernel.elf -serial stdio

printk output appears in terminal

printk output appears in terminal

undefined
undefined

8. Physical memory manager

8. 物理内存管理器

c
// 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
}
c
// 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. Context switching

9. 上下文切换

c
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
Cooperative scheduling first; add timer IRQ preemption later.
c
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 reference

10. xv6-RISC-V 参考

bash
git clone https://github.com/mit-pdos/xv6-riscv
cd xv6-riscv && make qemu
xv6 componentx86 equivalent
kernel/vm.c
Page table management
kernel/trap.c
IDT/interrupt dispatch
kernel/proc.c
Context switch, scheduler
kernel/plic.c
PIC/APIC interrupt controller
user/usys.pl
System call stubs
bash
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
系统调用存根

Common Problems

常见问题

SymptomCauseFix
Triple fault on bootInvalid GDT/IDT or stackSet up stack before enabling interrupts
QEMU black screenNo serial output configured
-serial stdio
; early
serial_init
Page fault in kernelUnmapped addressIdentity-map kernel; check CR3
IRQ never firesPIC mask or IDT not loaded
lidt
; unmask IRQ in PIC
Timer doesn't tickLAPIC not initializedParse ACPI; calibrate LAPIC timer
Linker relocation errorWrong load addressMatch
linker.ld
with bootloader expectation
症状原因解决方法
启动时三重故障GDT/IDT 或栈无效在启用中断前设置栈
QEMU 黑屏未配置串口输出添加
-serial stdio
参数;尽早执行
serial_init
内核缺页地址未映射对内核进行恒等映射;检查CR3
IRQ 从不触发PIC 掩码未设置或IDT未加载执行
lidt
指令;在PIC中取消对应IRQ的掩码
定时器不工作LAPIC 未初始化解析ACPI;校准LAPIC定时器
链接器重定位错误加载地址错误确保
linker.ld
与bootloader的预期一致

Related Skills

相关技能

  • skills/low-level-programming/assembly-x86
    — x86-64 assembly for ISR stubs
  • skills/low-level-programming/assembly-riscv
    — xv6-RISC-V reference ISA
  • skills/platform/riscv-privileged
    — RISC-V trap handling and page tables
  • skills/virtualization/qemu-kvm
    — QEMU flags for kernel development
  • skills/kernel/kernel-internals
    — Linux implementation of these concepts
  • skills/low-level-programming/linux-kernel-modules
    — graduate to Linux once basics work
  • skills/low-level-programming/assembly-x86
    — 用于编写ISR存根的x86-64汇编
  • skills/low-level-programming/assembly-riscv
    — xv6-RISC-V 参考指令集架构(ISA)
  • skills/platform/riscv-privileged
    — RISC-V 陷阱处理与页表
  • skills/virtualization/qemu-kvm
    — 用于内核开发的QEMU参数
  • skills/kernel/kernel-internals
    — 这些概念在Linux中的实现
  • skills/low-level-programming/linux-kernel-modules
    — 掌握基础后可进阶学习Linux内核模块