os-dev-scratch
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOS 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 and cross-compiler
-kernelx86_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
undefinedbash
undefinedCross-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.elfqemu-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.elf3. Multiboot/limine boot
3. Multiboot/limine 启动
bash
undefinedbash
undefinedQEMU 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
-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
-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
-bios /usr/share/ovmf/OVMF.fd
-drive file=disk.img,format=raw
-serial stdio
undefinedqemu-system-x86_64
-bios /usr/share/ovmf/OVMF.fd
-drive file=disk.img,format=raw
-serial stdio
-bios /usr/share/ovmf/OVMF.fd
-drive file=disk.img,format=raw
-serial stdio
undefined4. Long mode setup
4. 长模式设置
Protected mode (32-bit) → enable PAE → setup 4-level page tables → enable long modec
// 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 modec
// 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
undefinedc
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
undefinedTest page fault
Test page fault
QEMU monitor: info registers
QEMU monitor: info registers
undefinedundefined6. 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 preemptionc
// 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 preemption7. 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 stdioc
// 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 stdioprintk output appears in terminal
printk output appears in terminal
undefinedundefined8. 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->ripCooperative 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 component | x86 equivalent |
|---|---|
| Page table management |
| IDT/interrupt dispatch |
| Context switch, scheduler |
| PIC/APIC interrupt controller |
| System call stubs |
bash
git clone https://github.com/mit-pdos/xv6-riscv
cd xv6-riscv && make qemu| xv6 组件 | x86 对应部分 |
|---|---|
| 页表管理 |
| IDT/中断分发 |
| 上下文切换、调度器 |
| PIC/APIC 中断控制器 |
| 系统调用存根 |
Common Problems
常见问题
| Symptom | Cause | Fix |
|---|---|---|
| Triple fault on boot | Invalid GDT/IDT or stack | Set up stack before enabling interrupts |
| QEMU black screen | No serial output configured | |
| Page fault in kernel | Unmapped address | Identity-map kernel; check CR3 |
| IRQ never fires | PIC mask or IDT not loaded | |
| Timer doesn't tick | LAPIC not initialized | Parse ACPI; calibrate LAPIC timer |
| Linker relocation error | Wrong load address | Match |
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 启动时三重故障 | GDT/IDT 或栈无效 | 在启用中断前设置栈 |
| QEMU 黑屏 | 未配置串口输出 | 添加 |
| 内核缺页 | 地址未映射 | 对内核进行恒等映射;检查CR3 |
| IRQ 从不触发 | PIC 掩码未设置或IDT未加载 | 执行 |
| 定时器不工作 | LAPIC 未初始化 | 解析ACPI;校准LAPIC定时器 |
| 链接器重定位错误 | 加载地址错误 | 确保 |
Related Skills
相关技能
- — x86-64 assembly for ISR stubs
skills/low-level-programming/assembly-x86 - — xv6-RISC-V reference ISA
skills/low-level-programming/assembly-riscv - — RISC-V trap handling and page tables
skills/platform/riscv-privileged - — QEMU flags for kernel development
skills/virtualization/qemu-kvm - — Linux implementation of these concepts
skills/kernel/kernel-internals - — graduate to Linux once basics work
skills/low-level-programming/linux-kernel-modules
- — 用于编写ISR存根的x86-64汇编
skills/low-level-programming/assembly-x86 - — xv6-RISC-V 参考指令集架构(ISA)
skills/low-level-programming/assembly-riscv - — RISC-V 陷阱处理与页表
skills/platform/riscv-privileged - — 用于内核开发的QEMU参数
skills/virtualization/qemu-kvm - — 这些概念在Linux中的实现
skills/kernel/kernel-internals - — 掌握基础后可进阶学习Linux内核模块
skills/low-level-programming/linux-kernel-modules