OS开发笔记(1)——硬盘引导的尝试
看前提醒:这一系列笔记完全是按照我的思考顺序写的,中间可能会绕弯路
定义
为了避免概念的混淆,我先在这里作一下(仅适用于本文的)名词的解释:
- 引导程序
/
boot程序
:特指磁盘MBR或者VBR扇区中存放的程序 - 加载器
/
loader程序
:指由引导程序加载执行的程序,用于加载操作系统的内核 - 引导
:指从BIOS调转到引导程序到真正执行内核之间的整个流程
背景
在研究引导程序时,我发现很多书籍和文章都设定系统从软盘引导,因此引导扇区的代码通常是直接写死,从第一个软盘读取加载器(loader)。为了迁移到硬盘引导,顺便改成一个通用的引导,我参考了IBM的BIOS文档进行设计。
BIOS中关于引导的规定
由于引导扇区仅有
512字节
大小,所以需要有引导程序加载另外的loader程序来打破大小的限制,否则仅凭这点空间什么都做不了。首先,boot程序无法确定引导位于哪个磁盘上也就无法加载loader,很多的书也都是默认在
第一个软盘
上来写的,如果需要读取磁盘中的loader就需要
磁盘的设备号
。
但考虑到现在的引导都没有这种问题,肯定是由解决方案的。传统BIOS引导都遵循着IBM的规范,所以我决定到IBM的文档中去寻找答案。
于是经过一番快速搜索,我在BIOS的中断调用功能中找到了可能的答案:
根据IBM BIOS文档,
INT 19
中断功能的目的是启动
Bootstrap Loader
。在调用该中断后时,BIOS会设置以下寄存器:
- CS = 0000H,IP = 7C00H
:指定引导扇区的内存加载地址。 - DL
:保存引导设备的驱动器号。
看到这个
7C00H
就感觉DNA动了,虽然文档表明这似乎是用于从指定驱动器重新引导的功能,但我们不妨大胆的猜测在初次启动
Boot程序
是也是同样的状态,因为重新从磁盘引导时磁盘上的
引导程序
大概率也是按照直接启动的情况写的。
实践验证
为了验证引导过程的具体行为,我使用了
QEMU
配合
GDB
进行调试,在0x7C00处设置断点并观察寄存器的值。测试结果如下:
- 当参数指定从
软盘(0号软盘)
引导时,
DL = 0x00
。 - 当参数指定从
硬盘(0号硬盘)
引导时,
DL = 0x80
。
由于在BIOS中:
- 软盘驱动器号范围为
0x00 ~ 0x7F
。 - 硬盘驱动器号范围为
0x80 ~ 0xFF
。
由此可以确定,启动时,引导设备的驱动器号会保存在
DL寄存器
中。
引导程序设计
基于以上分析,我的引导程序设计思路如下:
- 保存设备号
:在引导程序运行时,直接保存DL的值,作为后续加载程序(loader)的驱动器号。这使得引导程序能够同时兼容新老磁盘。 - 读取磁盘功能:
- 支持软盘的
标准CHS
(柱面-磁头-扇区)寻址模式读取。 - 支持硬盘的
扩展LBA
(逻辑块地址)模式读取,适配
超过CHS寻址范围
(
约8GB
)的大容量硬盘。
- 支持软盘的
设计思路
- 引导程序(boot)的设计
:
- 位置
:引导程序位于硬盘的第0扇区,同时兼容MBR(主引导记录)。 - 结构
:从扇区偏移0x1BE开始的64字节保留用于保存分区表信息。
- 位置
- 加载程序(loader)的功能
:
- 遍历分区表,寻找唯一具有可引导标志的分区。
- 读取启动磁盘参数,选择CHS或LBA模式,读取目标分区的第0扇区。
- 仅支持
FAT文件系统
(也许兼容FAT系列文件系统)。 - 检测FAT的
BPB
(BIOS参数块)结构,根据BPB信息定位到根目录的数据扇区。 - 循环读取文件目录项,找到名为
KERNEL.ELF
的文件(FAT短目录项文件名均为大写),并加载到内存中。
- 内核加载
:
- 通过支持FAT文件系统的设计,内核文件的大小可以动态变化,并且能够被文件管理系统灵活修改。
- 读取内核文件到内存后,跳转执行,完成引导流程。
注意事项
在实现过程中需要特别注意:
- 使用
DX寄存器
时,应提前保存设备号,以免在后续操作中丢失。 - 引导程序应尽可能简洁,以节省空间并提高兼容性。
结局
咳咳,看到这个小标题就能说明不会有什么好下场。实际上原本经过好一番折磨终于在16位模式下写出了加载内核的汇编代码,先是因为16位模式的寻址范围限制在
1MB
导致BIOS加载的时候直接卡住,原本想着留着做纪念的结果又不小心把他给删了TAT