启动流程
OpenOS 的启动过程从 BIOS 开始,经过引导加载器,最终进入内核主函数。整个过程涉及多个阶段的硬件初始化和软件配置。
┌─────────────┐│ BIOS ││ (POST) │└──────┬──────┘ │ ▼┌─────────────┐│ 引导扇区 │ 0x7C00│ (512 字节) │└──────┬──────┘ │ ▼┌─────────────┐│ 引导加载器 │ bootloader 0.11│ (BIOS) │└──────┬──────┘ │ ▼┌─────────────┐│ 页表设置 │ 高半内核映射└──────┬──────┘ │ ▼┌─────────────┐│ kernel_main │ 0xFFFFFFFF80100000└──────┬──────┘ │ ▼┌─────────────┐│ 硬件初始化 │ VGA, GDT, IDT, PIC└──────┬──────┘ │ ▼┌─────────────┐│ 子系统初始化│ 堆, IPC, 调度器└──────┬──────┘ │ ▼┌─────────────┐│ 空闲循环 │ hlt 指令└─────────────┘详细启动步骤
Section titled “详细启动步骤”1. BIOS POST(开机自检)
Section titled “1. BIOS POST(开机自检)”BIOS 执行开机自检:
- 检测和初始化硬件
- 检测内存
- 初始化显示
- 搜索可引导设备
2. 引导扇区加载
Section titled “2. 引导扇区加载”BIOS 加载引导扇区到内存:
- 地址:
0x7C00 - 大小:512 字节
- 执行引导加载器的第一阶段
3. bootloader 0.11 执行
Section titled “3. bootloader 0.11 执行”bootloader crate 提供的引导加载器:
功能:
- 检测内存映射
- 加载内核到物理地址
0x100000(1 MiB) - 设置页表(高半内核映射)
- 切换到长模式(64 位)
- 跳转到内核入口点
页表设置:
PML4[511] → PDPT PDPT[510] → PD PD[0] → PT PT[0] → 物理页面 0x100000 PT[1] → 物理页面 0x101000 ... 映射到虚拟地址 0xFFFFFFFF801000004. 内核入口点
Section titled “4. 内核入口点”#[no_mangle]pub extern "C" fn _start() -> ! { // 此时已在高半地址运行 // 虚拟地址 0xFFFFFFFF80100000
kernel_main();
loop { x86_64::instructions::hlt(); }}5. VGA 初始化
Section titled “5. VGA 初始化”pub fn init() { // 清屏 clear_screen();
// 设置默认颜色 set_color(Color::Green, Color::Black);
// 打印启动信息 println!("OpenOS v{}", env!("CARGO_PKG_VERSION"));}VGA 文本缓冲区:
- 物理地址:
0xB8000 - 大小:80×25 字符
- 格式:字符 + 属性(前景色 + 背景色)
6. 串口初始化
Section titled “6. 串口初始化”pub fn init() { // UART 16550 配置 // 端口:0x3F8 (COM1) // 波特率:38400 // 数据位:8 // 停止位:1 // 无校验}串口输出:
- 用于调试输出
- QEMU 可通过
-serial stdio捕获 - 提供
serial_print!和serial_println!宏
7. GDT + TSS 初始化
Section titled “7. GDT + TSS 初始化”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)}8. IDT 初始化
Section titled “8. IDT 初始化”pub fn init() { // 设置异常处理函数: // - 断点异常 (INT 3) // - 双重故障 (INT 8) // - 页错误 (INT 14) // - 通用保护错误 (INT 13)
// 设置 IRQ 处理函数: // - 定时器 (IRQ 0 → INT 32) // - 键盘 (IRQ 1 → INT 33) // - 串口 (IRQ 4 → INT 36)}9. PIC 初始化
Section titled “9. PIC 初始化”// 使用 pic8259 crate
pub fn init() { // 重映射 IRQ: // IRQ 0-7 → INT 32-39 // IRQ 8-15 → INT 40-47
// 启用 IRQ 0 (定时器) // 启用 IRQ 1 (键盘)}10. 开启中断
Section titled “10. 开启中断”// 启用硬件中断x86_64::instructions::interrupts::enable();中断处理流程:
硬件中断 ↓CPU 查找 IDT ↓执行中断处理函数 ↓发送 EOI 到 PIC ↓返回被中断的代码11. 堆分配器初始化
Section titled “11. 堆分配器初始化”pub fn init_heap() { // 堆起始地址:0x4444_4444_0000 // 堆大小:100 KiB // 分配器:linked_list_allocator
unsafe { ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE); }}12. 系统调用初始化
Section titled “12. 系统调用初始化”pub fn init() { // 配置 SYSCALL/SYSRET MSR // 设置 syscall 处理函数}13. IPC 子系统初始化
Section titled “13. IPC 子系统初始化”pub fn init() { // 初始化端口管理器 // 创建默认端口}14. 任务调度器初始化
Section titled “14. 任务调度器初始化”pub fn init() { // 创建空闲任务 // 初始化就绪队列 // 启动调度器}15. 进入空闲循环
Section titled “15. 进入空闲循环”loop { x86_64::instructions::hlt(); // 停止 CPU 直到下一个中断}| 阶段 | 时间(估计) | 说明 |
|---|---|---|
| BIOS POST | ~100 ms | 硬件初始化 |
| bootloader | ~10 ms | 加载内核,设置页表 |
| VGA 初始化 | ~1 ms | 清屏,设置颜色 |
| GDT/IDT | ~1 ms | CPU 结构初始化 |
| PIC 初始化 | ~1 ms | 中断控制器配置 |
| 堆初始化 | ~1 ms | 内存分配器设置 |
| 总计 | ~115 ms | 到达空闲循环 |
调试启动问题
Section titled “调试启动问题”使用串口输出
Section titled “使用串口输出”# 启动时启用串口输出make run-serial
# 查看启动日志# 每个初始化步骤都会打印信息使用 GDB 调试
Section titled “使用 GDB 调试”# 启动 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 重映射和中断使能