硬核How-to-Make-a-Computer-Operating-System:实模式到保护模式
你是否曾好奇操作系统如何从开机自检到加载内核?本文将带你深入探索x86架构下操作系统启动的关键转折——从实模式到保护模式的跃迁。通过How-to-Make-a-Computer-Operating-System项目的实战代码,你将掌握GDT(全局描述符表)配置、内存保护机制等核心概念,理解操作系统如何突破1MB内存限制,为后续虚拟内存和多任务打下基础。
实模式:x86架构的"遗产模式"
当x86计算机通电启动时,CPU首先进入实模式(Real Mode)——这是1981年Intel 8086处理器的原始工作模式,为保持兼容性沿用至今。在实模式下:
- 内存寻址空间仅1MB(20位地址总线)
- 不支持内存保护和特权级
- 只能运行16位代码
- 通过段寄存器:偏移量方式寻址(物理地址=段寄存器值×16+偏移量)
项目中Chapter-1/README.md详细介绍了x86架构背景,指出实模式是"为维持向后兼容性的默认状态"。这种模式下,BIOS完成自检后会将启动设备的第一个扇区(512字节)加载到物理内存0x7C00处,并跳转执行——这就是引导程序的起点。
保护模式:突破1MB限制的关键
实模式的1MB内存限制和缺乏安全机制严重制约现代操作系统发展。保护模式(Protected Mode) 作为x86架构的"成年模式",带来三大突破:
- 32位地址总线:支持4GB物理内存
- 内存分段保护:通过段描述符控制访问权限
- 特权级机制:实现内核态与用户态隔离
项目采用GRUB引导程序自动完成实模式到保护模式的切换。如Chapter-3/README.md所述,GRUB遵循Multiboot规范,能直接加载32位内核并传递硬件信息(通过multiboot_info结构体)。但GRUB提供的临时GDT并不适合我们的内核,需要重新配置自定义GDT。
GDT:保护模式的"交通信号灯"
全局描述符表(GDT) 是保护模式的核心数据结构,定义了内存段的基地址、大小和访问权限。项目中Chapter-6/README.md详细阐述了GDT的实现,其代码位于src/kernel/arch/x86/x86.cc。
GDT结构解析
GDT由GDTR(GDT寄存器) 和段描述符数组组成:
- GDTR存储GDT的基地址和长度
- 每个段描述符占8字节,定义一个内存段属性
GDTR的C语言定义:
struct gdtr {
u16 limite; // GDT表长度
u32 base; // GDT表基地址
} __attribute__ ((packed)); // 禁止编译器字节对齐
单个段描述符结构如下,包含段基地址、限长和访问控制位:
其C语言定义:
struct gdtdesc {
u16 lim0_15; // 段限长0-15位
u16 base0_15; // 段基地址0-15位
u8 base16_23; // 段基地址16-23位
u8 acces; // 访问权限字节
u8 lim16_19:4; // 段限长16-19位
u8 other:4; // 其他标志
u8 base24_31; // 段基地址24-31位
} __attribute__ ((packed));
内核自定义GDT配置
项目内核定义了6个关键段描述符,区分内核态与用户态:
| 段类型 | 选择子 | 权限字节 | 用途说明 |
|---|---|---|---|
| 内核代码段 | 0x08 | 0x9B | 存放内核可执行代码 |
| 内核数据段 | 0x10 | 0x93 | 内核数据读写 |
| 内核栈段 | 0x18 | 0x97 | 内核调用栈 |
| 用户代码段 | 0x20 | 0xFF | 用户程序代码(特权级3) |
| 用户数据段 | 0x28 | 0xF3 | 用户程序数据 |
| 用户栈段 | 0x30 | 0xF7 | 用户程序栈 |
初始化代码位于src/kernel/arch/x86/x86.cc的init_gdt函数:
// 初始化段描述符
init_gdt_desc(0x0, 0xFFFFF, 0x9B, 0x0D, &kgdt[1]); // 内核代码段
init_gdt_desc(0x0, 0xFFFFF, 0x93, 0x0D, &kgdt[2]); // 内核数据段
init_gdt_desc(0x0, 0x0, 0x97, 0x0D, &kgdt[3]); // 内核栈段
// 用户段定义...
// 加载GDT
asm("lgdtl (kgdtr)");
// 更新段寄存器
asm("movw $0x10, %ax\n" // 数据段选择子
"movw %ax, %ds\n"
"movw %ax, %es\n"
"movw %ax, %fs\n"
"movw %ax, %gs\n"
"ljmp $0x08, $next\n" // 远跳转更新CS寄存器
"next:");
从实模式到保护模式的切换流程
结合项目代码,完整切换流程如下:
- BIOS自检:执行POST(加电自检),初始化硬件
- GRUB引导:BIOS加载GRUB到0x7C00,GRUB完成:
- 切换到保护模式
- 启用A20地址线(突破1MB限制)
- 加载内核ELF文件到内存
- 内核初始化:
- 配置自定义GDT(src/kernel/arch/x86/x86.cc)
- 初始化C++运行时(src/kernel/runtime/cxx.cc)
- 设置临时内核栈
- 进入内核主函数:调用kmain()开始执行内核逻辑
项目Makefile(src/Makefile)通过以下命令编译内核:
# 编译32位内核,禁用标准库
g++ -m32 -fno-builtin -fno-exceptions -fno-rtti -nostdlib ...
实践验证:如何测试模式切换
项目提供完整的测试环境,通过Vagrant快速搭建开发环境:
- 克隆仓库:
git clone https://gitcode.com/gh_mirrors/ho/How-to-Make-a-Computer-Operating-System
- 启动开发环境:
cd src
vagrant up # 启动虚拟机
vagrant ssh # 进入开发环境
cd /vagrant
- 编译并运行:
make all # 编译内核和用户程序
make run # 用QEMU启动操作系统
成功启动后,QEMU窗口将显示内核初始化信息,其中GDT配置日志由src/kernel/arch/x86/x86.cc的init_gdt函数输出。
总结与后续挑战
实模式到保护模式的切换是操作系统启动的关键一跃。通过How-to-Make-a-Computer-Operating-System项目的实战代码,我们掌握了:
- x86架构的两种工作模式特性
- GDT的结构与配置方法
- GRUB引导流程与Multiboot规范
后续章节将在此基础上实现分页机制(Chapter-8)、进程调度和虚拟内存。这些技术共同构建起现代操作系统的内存管理基石。
提示:深入理解GDT可参考Intel官方手册第3卷《System Programming Guide》,项目中src/kernel/arch/x86/architecture.h定义了更多硬件相关结构体。收藏本文,下期我们将解析分页机制如何实现内存虚拟化!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考





