第一幕:冰冷启动
本部分参考了:Boot Sequence - OSDev Wiki
通电与POST
当按下电源键,主板上的电压稳定后,CPU 的复位引脚被拉高,它将程序计数器(PC/ IP)设置为一个固定的物理地址——通常是 0xFFFFFFF0(对于现代 x86 CPU)或 0xFFFF0(对于 8086 兼容模式)。这个地址指向主板 ROM 中固化的 BIOS 代码。
之后,BIOS会进行一系列自检过程,这部分称为POST(开机自检)。在此之后,它会依据固件设置的顺序,跳转到一个可以引导的设备(如CD-ROM、硬盘、软盘、或者是你的U盘等存储设备)。
MBR
对于传统的 BIOS 系统,可引导介质的 0 号扇区(LBA 0)被称为 主引导记录(MBR, Master Boot Record)。它的大小正好是 512 字节,结构如下:
| 偏移(字节) | 内容 | 大小 |
|---|---|---|
| 0x0000 – 0x01BD | 可执行代码(引导程序) | 446 字节 |
| 0x01BE – 0x01FD | 四个分区表项(每个 16 字节) | 64 字节 |
| 0x01FE – 0x01FF | 引导签名 0x55 0xAA | 2 字节 |
BIOS 会检查该扇区的最后两个字节是否为 0x55 0xAA。如果是,则认为这是一个有效的引导扇区,并将整个 512 字节加载到内存地址 0x0000:0x7c00(即物理地址 0x07c00)。然后,BIOS 执行一条跳转指令,将控制权移交给这个刚刚加载的引导代码。
为什么是 0x7c00? 这是 IBM PC 历史上的约定:内存低端保留给中断向量表和 BIOS 数据区,
0x7c00是 32KB 内存区域的末尾,既能避开系统关键数据,又能保证引导代码不会太小。今天这个地址已经成为事实标准。
跳转到可引导设备后,BIOS的使命就结束了。之后的一切都由这不到 500B 的 bootloader 来接力。
bootloader
我们不再细究bootloader的实现。只需要知道:它会帮我们进行环境的初始化,然后带着我们的RIP跳到我们的内核起始点_start()函数,这就对了。
第一阶段( MBR bootloader ):寻找并加载第二阶段的 bootloader 到内存,然后跳转过去。
第二阶段( 更大的 bootloader ):识别文件系统(如FAT、ext2等文件系统),找到内核文件并将其加载到内存,并设置CPU模式到保护模式(如果是64位系统,还会进一步设置到长模式),最后跳转到内核的入口点
_start()。
由于操作系统复杂度的增加,手写一个 bootloader 变得愈发繁琐。我们这一部分的内容是为了学习操作系统,而不是为了学如何写一个 bootloader。将精力放在写一个 bootloader 是没有必要且浪费时间的。
在现代开发中,我们通常使用成熟的 bootloader,如我们后面将使用的limine,以及常见的grub,或者是更加现代的UEFI引导管理器。它们已经帮我们处理好了硬件初始化、模式切换、内核加载等事务。
下一幕,我们将简要了解limine是如何接管这些工作,并带着我们和CPU进入64位长模式的。
小结
这一部分我们主要了解了:
计算机通电后,BIOS首先被加载,并进行上电自检(POST)。之后,BIOS进行可引导设备的探测,并读入配置的首要可引导设备的MBR。
bootloader 的任务就是:读取内核到内存,并为内核准备好启动环境,最终把控制权交给内核。
