跳转到内容

启动流程

OpenOS 的启动过程从 BIOS 开始,经过引导加载器,最终进入内核主函数。整个过程涉及多个阶段的硬件初始化和软件配置。

┌─────────────┐
│ BIOS │
│ (POST) │
└──────┬──────┘
┌─────────────┐
│ 引导扇区 │ 0x7C00
│ (512 字节) │
└──────┬──────┘
┌─────────────┐
│ 引导加载器 │ bootloader 0.11
│ (BIOS) │
└──────┬──────┘
┌─────────────┐
│ 页表设置 │ 高半内核映射
└──────┬──────┘
┌─────────────┐
│ kernel_main │ 0xFFFFFFFF80100000
└──────┬──────┘
┌─────────────┐
│ 硬件初始化 │ VGA, GDT, IDT, PIC
└──────┬──────┘
┌─────────────┐
│ 子系统初始化│ 堆, IPC, 调度器
└──────┬──────┘
┌─────────────┐
│ 空闲循环 │ hlt 指令
└─────────────┘

BIOS 执行开机自检:

  • 检测和初始化硬件
  • 检测内存
  • 初始化显示
  • 搜索可引导设备

BIOS 加载引导扇区到内存:

  • 地址:0x7C00
  • 大小:512 字节
  • 执行引导加载器的第一阶段

bootloader crate 提供的引导加载器:

功能:

  • 检测内存映射
  • 加载内核到物理地址 0x100000 (1 MiB)
  • 设置页表(高半内核映射)
  • 切换到长模式(64 位)
  • 跳转到内核入口点

页表设置:

PML4[511] → PDPT
PDPT[510] → PD
PD[0] → PT
PT[0] → 物理页面 0x100000
PT[1] → 物理页面 0x101000
...
映射到虚拟地址 0xFFFFFFFF80100000
kernel/src/main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
// 此时已在高半地址运行
// 虚拟地址 0xFFFFFFFF80100000
kernel_main();
loop {
x86_64::instructions::hlt();
}
}
drivers/vga.rs
pub fn init() {
// 清屏
clear_screen();
// 设置默认颜色
set_color(Color::Green, Color::Black);
// 打印启动信息
println!("OpenOS v{}", env!("CARGO_PKG_VERSION"));
}

VGA 文本缓冲区:

  • 物理地址:0xB8000
  • 大小:80×25 字符
  • 格式:字符 + 属性(前景色 + 背景色)
drivers/serial.rs
pub fn init() {
// UART 16550 配置
// 端口:0x3F8 (COM1)
// 波特率:38400
// 数据位:8
// 停止位:1
// 无校验
}

串口输出:

  • 用于调试输出
  • QEMU 可通过 -serial stdio 捕获
  • 提供 serial_print!serial_println!
arch/x86_64/gdt.rs
pub fn init() {
// GDT 布局:
// Index 0: 空描述符 (0x00)
// Index 1: 内核代码 (0x08, Ring 0)
// Index 2: 内核数据 (0x10, Ring 0)
// Index 3: 用户数据 (0x18, Ring 3)
// Index 4: 用户代码 (0x20, Ring 3)
// Index 5-6: TSS
// TSS 配置:
// RSP0: 内核栈指针
// IST[0]: 双重故障栈 (20 KiB)
}
arch/x86_64/interrupts.rs
pub fn init() {
// 设置异常处理函数:
// - 断点异常 (INT 3)
// - 双重故障 (INT 8)
// - 页错误 (INT 14)
// - 通用保护错误 (INT 13)
// 设置 IRQ 处理函数:
// - 定时器 (IRQ 0 → INT 32)
// - 键盘 (IRQ 1 → INT 33)
// - 串口 (IRQ 4 → INT 36)
}
// 使用 pic8259 crate
pub fn init() {
// 重映射 IRQ:
// IRQ 0-7 → INT 32-39
// IRQ 8-15 → INT 40-47
// 启用 IRQ 0 (定时器)
// 启用 IRQ 1 (键盘)
}
// 启用硬件中断
x86_64::instructions::interrupts::enable();

中断处理流程:

硬件中断
CPU 查找 IDT
执行中断处理函数
发送 EOI 到 PIC
返回被中断的代码
memory/allocator.rs
pub fn init_heap() {
// 堆起始地址:0x4444_4444_0000
// 堆大小:100 KiB
// 分配器:linked_list_allocator
unsafe {
ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE);
}
}
syscall/mod.rs
pub fn init() {
// 配置 SYSCALL/SYSRET MSR
// 设置 syscall 处理函数
}
ipc/mod.rs
pub fn init() {
// 初始化端口管理器
// 创建默认端口
}
task/scheduler.rs
pub fn init() {
// 创建空闲任务
// 初始化就绪队列
// 启动调度器
}
main.rs
loop {
x86_64::instructions::hlt(); // 停止 CPU 直到下一个中断
}
阶段时间(估计)说明
BIOS POST~100 ms硬件初始化
bootloader~10 ms加载内核,设置页表
VGA 初始化~1 ms清屏,设置颜色
GDT/IDT~1 msCPU 结构初始化
PIC 初始化~1 ms中断控制器配置
堆初始化~1 ms内存分配器设置
总计~115 ms到达空闲循环
Terminal window
# 启动时启用串口输出
make run-serial
# 查看启动日志
# 每个初始化步骤都会打印信息
Terminal window
# 启动 QEMU 并等待 GDB 连接
make debug
# 在另一个终端
gdb target/x86_64-unknown-none/debug/openos-kernel
(gdb) target remote :1234
(gdb) break _start
(gdb) continue

问题:启动后立即重启

  • 可能原因:GDT/IDT 配置错误
  • 解决:检查段选择子和中断处理函数

问题:无 VGA 输出

  • 可能原因:VGA 缓冲区地址错误
  • 解决:确认物理地址 0xB8000 的映射

问题:中断不工作

  • 可能原因:PIC 未正确初始化
  • 解决:检查 IRQ 重映射和中断使能