news 2026/7/17 21:57:20

从零手写X86操作系统:实模式到保护模式实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零手写X86操作系统:实模式到保护模式实战

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远程调试
  • 磁盘工具:dd(Linux)或 Rufus(Windows)
# Ubuntu环境安装示例 sudo apt install qemu-system-x86 gcc-i686-elf nasm mtools

2.2 项目目录结构

建议采用模块化代码组织方式:

os-project/ ├── boot/ # 引导程序 │ ├── boot.asm # BIOS引导扇区 │ └── loader.asm # 二级加载器 ├── kernel/ # 内核代码 │ ├── main.c # 内核入口 │ └── include/ # 头文件 ├── scripts/ # 构建脚本 └── diskimg/ # 生成的磁盘镜像

3. BIOS引导实现

3.1 引导扇区开发

BIOS会将磁盘第一个扇区(512字节)加载到内存0x7C00处执行。这段代码必须:

  1. 以0xAA55结束(Magic Number)
  2. 使用16位实模式汇编
  3. 禁用中断(cli)
  4. 设置栈指针(sp=0x7C00)
; 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 二级加载器设计

由于引导扇区空间有限,我们需要二级加载器完成:

  1. 检测内存布局(通过BIOS中断0x15)
  2. 启用A20地址线(解决8086兼容性问题)
  3. 加载GDT并进入保护模式
  4. 将内核从磁盘加载到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位保护模式需要:

  1. 构建全局描述符表(GDT)
  2. 加载GDTR寄存器
  3. 设置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 内存管理

简易物理内存管理器实现思路:

  1. 通过GRUB或自行检测内存大小
  2. 维护空闲页帧位图
  3. 实现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中断控制器编程步骤:

  1. 配置8259A PIC(或APIC)
  2. 设置IDT(中断描述符表)
  3. 实现中断服务例程(ISR)
void 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 镜像生成与烧录

制作可启动镜像的完整流程:

  1. 创建空白磁盘镜像(dd if=/dev/zero)
  2. 格式化为FAT16(mkfs.fat)
  3. 安装GRUB或直接写入引导扇区
  4. 拷贝内核文件
# 生成镜像示例 dd if=/dev/zero of=os.img bs=1M count=32 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/loop0

5.2 主流BIOS兼容性处理

不同厂商BIOS的特殊处理:

  • Dell/联想:可能需要关闭Secure Boot
  • 华硕:注意USB Legacy Support设置
  • 虚拟机:确保选择正确的BIOS类型

避坑指南:如果U盘无法被识别为启动设备,尝试以下步骤:

  1. 使用USB 2.0接口(非蓝色)
  2. 在BIOS中将USB模式设为"Full Initialization"
  3. 换用Ventoy等新型启动工具

6. 调试技巧与常见问题

6.1 QEMU调试方法

启动调试会话:

qemu-system-i386 -s -S -drive format=raw,file=os.img # 另开终端 gdb -ex "target remote localhost:1234"

常用调试命令:

  • info registers:查看寄存器状态
  • x/10i $eip:反汇编当前指令
  • break *0x7C00:在引导扇区设断点

6.2 典型问题排查

  1. 三重错误(Triple Fault):

    • 检查GDT/IDT设置是否正确
    • 确认栈指针未越界
  2. 屏幕无输出:

    • 验证VGA显存地址(0xB8000)
    • 检查CR0 PG位是否过早启用
  3. 键盘中断不触发:

    • 确认PIC初始化序列
    • 检查键盘控制器状态(端口0x64)

7. 进阶开发建议

完成基础内核后,可以考虑:

  1. 添加ELF加载器支持用户程序
  2. 实现分页内存管理(MMU)
  3. 增加系统调用接口(int 0x80)
  4. 移植Newlib标准库

我在实际开发中发现,最耗时的不是功能实现,而是各种硬件兼容性问题。建议购买一个便宜的旧笔记本(如ThinkPad X220)作为专用测试机,比虚拟机调试效率高很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 21:56:49

多智能体的协作方式-Agent Team和Agent Room

最近在实践多智能体的设计方式,也准备考虑给ActSpace添加多智能体的功能,主要是多智能体如果设计得当的话,是可以在提高效果的同时节省成本的,这对于很多做应用的团队来说,成本的控制是可以带来极好的用户体验的&#…

作者头像 李华
网站建设 2026/7/17 21:55:32

OpenDesign Deployment完全指南:从入门到精通的部署实践

OpenDesign Deployment完全指南:从入门到精通的部署实践 【免费下载链接】opendesign-deployment The repository of OpenDesign deployment 项目地址: https://gitcode.com/openeuler/opendesign-deployment 前往项目官网免费下载:https://ar.op…

作者头像 李华
网站建设 2026/7/17 21:50:42

Apollo感知模块解构:工业级可替换流水线设计原理

1. 项目概述:为什么 Apollo 感知模块不是“一个黑箱”,而是一套可拆解、可替换、可验证的工业级流水线Apollo 感知模块,这个名字在自动驾驶圈里几乎等同于“可靠”和“可落地”的代名词。但如果你真去翻它的源码、跑它的 demo、调它的参数&am…

作者头像 李华
网站建设 2026/7/17 21:49:12

REGEXP正则表达式实现中英文数字自动拆分的高效方案

在日常数据处理和文本分析中,经常遇到需要从混合字符串中快速分离中文、英文和数字的场景。无论是处理用户输入、清洗数据还是文本挖掘,传统的手动拆分方法效率低下且容易出错。本文将分享一套基于REGEXP正则表达式的高效解决方案,只需一个公…

作者头像 李华