
1. 项目概述为什么要手写X86操作系统2008年我在大学计算机实验室第一次见到GRUB引导界面时就被操作系统启动过程的神秘感深深吸引。当时教授说理解操作系统最好的方式就是自己写一个。如今十五年过去我依然记得在QEMU模拟器上看到自己编写的Hello OS成功启动时的激动心情。这个项目将带您从零构建一个能通过U盘启动的X86操作系统内核。不同于市面上大多数教程只停留在理论层面我们将完整实现以下核心功能实模式到保护模式的切换含GDT构建物理内存管理简易版页表实现键盘中断处理PS/2控制器编程VGA文本模式输出直接操作显存简易文件系统FAT16格式支持注意本教程需要读者具备C语言基础和基本的计算机组成原理知识特别是对寄存器、内存地址等概念的理解。完全零基础者建议先学习《深入理解计算机系统》CSAPP前四章。2. 开发环境搭建2.1 工具链选型经过对比测试我推荐以下开发工具组合编译器GCC交叉编译工具链target: i686-elf模拟器QEMU版本≥5.0调试器GDB with QEMU远程调试磁盘工具ddLinux或 RufusWindows# Ubuntu环境安装示例 sudo apt install qemu-system-x86 gcc-i686-elf nasm mtools2.2 项目目录结构建议采用模块化代码组织方式os-project/ ├── boot/ # 引导程序 │ ├── boot.asm # BIOS引导扇区 │ └── loader.asm # 二级加载器 ├── kernel/ # 内核代码 │ ├── main.c # 内核入口 │ └── include/ # 头文件 ├── scripts/ # 构建脚本 └── diskimg/ # 生成的磁盘镜像3. BIOS引导实现3.1 引导扇区开发BIOS会将磁盘第一个扇区512字节加载到内存0x7C00处执行。这段代码必须以0xAA55结束Magic Number使用16位实模式汇编禁用中断cli设置栈指针sp0x7C00; boot.asm [bits 16] [org 0x7C00] start: cli mov ax, 0 mov ds, ax mov es, ax mov ss, ax mov sp, 0x7C00 sti ; 加载第二段加载器 mov bx, 0x7E00 mov ah, 0x02 mov al, 4 ; 读取4个扇区 mov ch, 0 ; 柱面0 mov cl, 2 ; 从第2扇区开始 mov dh, 0 ; 磁头0 int 0x13 jmp 0:0x7E00 ; 跳转到loader times 510-($-$$) db 0 dw 0xAA55经验现代BIOS默认从USB设备模拟的FDD模式启动因此磁盘CHS参数需要特别处理。建议先用QEMU测试再烧录U盘。3.2 二级加载器设计由于引导扇区空间有限我们需要二级加载器完成检测内存布局通过BIOS中断0x15启用A20地址线解决8086兼容性问题加载GDT并进入保护模式将内核从磁盘加载到1MB以上内存// loader.c关键片段 void enable_a20() { // 通过键盘控制器启用A20 while (inb(0x64) 0x2); outb(0x64, 0xD1); while (inb(0x64) 0x2); outb(0x60, 0xDF); }4. 内核核心功能实现4.1 保护模式初始化进入32位保护模式需要构建全局描述符表GDT加载GDTR寄存器设置CR0寄存器PE位struct gdt_entry { uint16_t limit_low; uint16_t base_low; uint8_t base_middle; uint8_t access; uint8_t granularity; uint8_t base_high; } __attribute__((packed)); void load_gdt() { struct gdt_ptr gp; gp.limit sizeof(gdt) - 1; gp.base (uint32_t)gdt; asm volatile(lgdt %0 : : m(gp)); }4.2 内存管理简易物理内存管理器实现思路通过GRUB或自行检测内存大小维护空闲页帧位图实现kmalloc/kfree接口#define PAGE_SIZE 4096 struct page_frame { uint32_t addr; uint8_t used; }; void* kmalloc(size_t size) { uint32_t pages (size PAGE_SIZE - 1) / PAGE_SIZE; // 在位图中查找连续空闲页... }4.3 中断处理X86中断控制器编程步骤配置8259A PIC或APIC设置IDT中断描述符表实现中断服务例程ISRvoid isr_handler(struct regs *r) { if (r-int_no 33) { // 键盘中断 uint8_t scancode inb(0x60); handle_keypress(scancode); } outb(0x20, 0x20); // EOI }5. U盘启动适配5.1 镜像生成与烧录制作可启动镜像的完整流程创建空白磁盘镜像dd if/dev/zero格式化为FAT16mkfs.fat安装GRUB或直接写入引导扇区拷贝内核文件# 生成镜像示例 dd if/dev/zero ofos.img bs1M count32 sudo losetup /dev/loop0 os.img sudo mkfs.fat -F16 /dev/loop0 sudo mount /dev/loop0 /mnt sudo cp kernel.bin /mnt sudo umount /mnt sudo losetup -d /dev/loop05.2 主流BIOS兼容性处理不同厂商BIOS的特殊处理Dell/联想可能需要关闭Secure Boot华硕注意USB Legacy Support设置虚拟机确保选择正确的BIOS类型避坑指南如果U盘无法被识别为启动设备尝试以下步骤使用USB 2.0接口非蓝色在BIOS中将USB模式设为Full Initialization换用Ventoy等新型启动工具6. 调试技巧与常见问题6.1 QEMU调试方法启动调试会话qemu-system-i386 -s -S -drive formatraw,fileos.img # 另开终端 gdb -ex target remote localhost:1234常用调试命令info registers查看寄存器状态x/10i $eip反汇编当前指令break *0x7C00在引导扇区设断点6.2 典型问题排查三重错误Triple Fault检查GDT/IDT设置是否正确确认栈指针未越界屏幕无输出验证VGA显存地址0xB8000检查CR0 PG位是否过早启用键盘中断不触发确认PIC初始化序列检查键盘控制器状态端口0x647. 进阶开发建议完成基础内核后可以考虑添加ELF加载器支持用户程序实现分页内存管理MMU增加系统调用接口int 0x80移植Newlib标准库我在实际开发中发现最耗时的不是功能实现而是各种硬件兼容性问题。建议购买一个便宜的旧笔记本如ThinkPad X220作为专用测试机比虚拟机调试效率高很多。