从开机到 main 函数的执行分三步完成,其目的是实现从启动盘加载操作系统程序,完成执行 main 函数所需要的准备工作。
第一步,启动 BIOS,准备实模式下的中断向量表和中断服务程序
第二步,从启动盘加载操作系统程序到内存,加载操作系统程序的工作就是利用第一步中准备的中断服务程序实现的
第三步,为执行 32 位的 main 函数做过渡工作
一、启动 BIOS
Intel 将所有 80x86 系列的 CPU 的硬件都设计为加电即进入 16 位实模式状态运行。同时,将 CPU 硬件逻辑设计为加电瞬间强行将 CS 的值置为 0xFFFF
,IP 的值置为 0x0000
。这样 CS:IP
就指向 0xFFFF0
这个地址位置。
BIOS 程序的入口地址是 0xFFFF0
,BIOS 程序被固化在计算机主机板上的一块很小的 ROM 芯片里(ROM,只读存储器,断电仍能保存信息)。随着 BIOS 程序的执行,会检测显卡、内存等,然后在屏幕上显示显卡的信息、内存的信息等等,还会在内存中建立中断向量表和中断服务程序。
简单介绍下 BIOS 程序。假设我们选择的 BIOS 程序只有 8K,所占地址段为 0xFE000 - 0xFFFFF
。那么 BIOS 程序在内存 [0x00000, 0x003FF]
这 1KB 的内存空间构建中断向量表,并在接下来的 [0x00400, 0x004FF]
这 256 字节的内存空间构建 BIOS 数据区,在大约 56K 以后的位置(0x0E2CE
)加载了 8KB 左右的与中断向量表相应的若干中断服务程序。其中中断向量表中有 256 个中断向量,每个中断向量占 4 字节,分别是 CS、IP 寄存器的值,每个中断向量都指向一个具体的中断服务程序。
二、加载必要程序
接下来,将分三批次逐次加载操作系统的代码。
- 第一批由 BIOS 中断
int 0x19
把第一扇区 bootsect 的内容加载到内存 - 第二批和第三批在 bootsect 的指挥下,分别把其后的四个扇区和随后的 240 个扇区的内容加载至内存。
1. 加载引导程序(bootsect)
由于把软盘设置为启动设备,CPU 执行 0x19 中断向量,这个中断服务程序的作用就是把软盘的第一个扇区(0 磁头 0 磁道 1 扇区)中的程序(512B)加载到内存中指定位置(0x07C00
)处。
2. 加载 setup
实模式下,寻址的最大范围是 1MB。bootsect 会合理安排内存空间分布。bootsect 会将他自身全部的 512 字节内容从内存 0x07C00
处复制到内存 0x90000
处。
指令 int 0x13
可以把指定扇区的代码加载到内存的指定位置。加载 setup 的过程,为将软盘从第 2 扇区开始的 4 个扇区,即 setup.s
对应的程序加载到 0x90200
处。
继续使用 int 0x13
中断,将系统模块载入内存,即将软盘第 6 扇区开始的约 240 个扇区加载到内存 0x10000
处往后的 120KB 空间中。
之后,会确定一下根设备号,经过一系列检测,得知软盘为根设备,所以就把根设备号保存在 root_dev 中,这个根设备号作为机器系统数据之一。(根文件系统设备:Linux 0.11 使用 Minix 操作系统的文件系统管理方式,要求系统必须存在一个根文件系统,其他文件系统挂接其上)
此时 bootsect 引导程序的任务完了,跳转到 0x90200
处执行 setup 的程序。
3. 执行 setup 程序
setup 程序做的第一件事情就是利用 BIOS 提供的中断服务程序从设备上提取内核运行所需的机器系统数据,其中包括光标位置和显示页面等数据,并分别从中断向量 0x41
和 0x46
向量值所指的内存地址处获取硬盘参数表 1 和硬盘参数表 2,把他们存放在 0x9000:0x0080
和 0x9000:0x0090
处。这些数据后面会用到。
三、进入 32 位模式
接下来操作系统要使计算机在 32 位保护模式下工作。执行的操作包括打开 32 位的寻址空间、打开保护模式、建立保护模式下的中断响应机制等与保护模式配置的相关工作、建立内存的分页机制,以及做好调用 main 函数的准备。下面详细说明
先关中断并将 system 移动到内存地址起始位置 0x00000
。关中断即将 CPU 的标志寄存器(eflags)中的中断允许标志(IF位)置 0。一直到 main 函数中能够适应保护模式的中断服务体系被重建完毕才会打开中断,而那时候的中断服务程序将不再是 BIOS 提供的中断服务程序,而是由系统自身提供的中断服务程序。
0x00000
这个位置原来存放着由 BIOS 建立的中断向量表及 BIOS 数据区,这个复制动作将 BIOS 中断向量表和 BIOS 数据区完全覆盖。这样做有如下好处:
- 废除 BIOS 的中断向量表,等价于废除了 BIOS 提供的实模式下的中断服务程序
- 收回使用寿命刚刚结束的程序所占的内存空间
- 让内核代码占据内存物理地址最开始的、最天然的、最有利的位置
设置中断描述符表和全局描述符表。通过 setup 程序自身提供的数据信息对中断描述符表寄存器 IDTR 和全局描述符表寄存器 GDTR 进行初始化设置。
打开 A20,实现 32 位寻址。打开 A20,意味着 CPU 可以进行 32 位寻址,最大寻址空间为 4GB。
为了建立保护模式下的中断机制,setup 程序将对可编程中断控制器 8259A 进行重新编程。
到这里,setup 就执行完毕了,他为系统能够在保护模式下运行做了一系列的准备工作,后续的准备工作将由 head 程序完成。
head.s
和 bootsect、setup 的加载方式不同,大致的过程是,先将 head.s
汇编成目标代码,将用 C 语言编写的内核程序编译成目标代码,然后链接成 system 模块。也就是说,system 模块中,既有内核程序,又有 head 程序;重点是 head 程序在前,内核程序在后。head 程序在内存中占有 25KB+184B
的空间。因此 head 程序的起始地址是 0x00000
位置。
head 程序会在自身所在的内存空间创建内核分页机制,即在 0x000000
位置创建页目录表、页表、缓冲区、GDT、IDT,并将 head 程序已经执行过的代码所占的内存空间覆盖,这意味着 head 程序自己将自己废弃。
一切就绪后,跳到 main 函数开始执行。 此时仍然是关中断的状态。