第3章 引导启动程序(boot)
3.1 概述
本章主要描述 boot/目录中的三个汇编代码文件,见列表 3-1 所示。正如在前一章中提到的,这三个 文件虽然都是汇编程序,但却使用了两种语法格式。bootsect.s 和 setup.s 采用近似于 Intel 的汇编语言语法,需要使用 Intel 8086 汇编编译器和连接器 as86 和 ld86,而 head.s 则使用 GNU 的汇编程序格式,并且运行在保护模式下,需要用 GNU 的 as 进行编译。这是一种 AT&T 语法的汇编语言程序。
使用两种编译器的主要原因是由于对于 Intel x86 处理器系列来讲,GNU 的编译器仅支持 i386 及以 后出的 CPU。不支持生成运行在实模式下的程序。
列表 3-1 linux/boot/目录
阅读这些代码除了你需要知道一些一般 8086 汇编语言的知识以外,还要对采用 Intel 80X86 微处理器的 PC 机的体系结构以及 80386 32 位保护模式下的编程原理有些了解。所以在开始阅读源代码之前可以先大概浏览一下附录中有关 PC 机硬件接口控制编程和 80386 32 位保护模式的编程方法,在阅读代码时再就事论事地针对具体问题参考附录中的详细说明。
3.2 总体功能
这里先总的说明一下 Linux 操作系统启动部分的主要执行流程。当 PC 的电源打开后,80x86 结构的 CPU 将自动进入实模式,并从地址 0xFFFF0 开始自动执行程序代码,这个地址通常是 ROM-BIOS 中的 地址。PC 机的 BIOS 将执行某些系统的检测,并在物理地址 0 处开始初始化中断向量。此后,它将可启动设备的第一个扇区(磁盘引导扇区,512 字节)读入内存绝对地址 0x7C00 处,并跳转到这个地方。启动设备通常是软驱或是硬盘。这里的叙述是非常简单的,但这已经足够理解内核初始化的工作过程了。
Linux 的最最前面部分是用 8086 汇编语言编写的(boot/bootsect.s),它将由 BIOS 读入到内存绝对地址 0x7C00(31KB)处,当它被执行时就会把自己移到绝对地址 0x90000(576KB)处,并把启动设备中后 2kB字节代码(boot/setup.s)读入到内存 0x90200 处,而内核的其它部分(system 模块)则被读入到从地址0x10000 开始处,因为当时 system 模块的长度不会超过 0x80000 字节大小(即 512KB),所以它不会覆 盖在 0x90000 处开始的 bootsect 和 setup 模块。后面 setup 程序将会把 system 模块移动到内存起始处,这样 system 模块中代码的地址也即等于实际的物理地址,便于对内核代码和数据的操作。图 3-1 清晰地显示出 Linux 系统启动时这几个程序或模块在内存中的动态位置。其中,每一竖条框代表某一时刻内存中各程序的映像位置图。在系统加载期间将显示信息"Loading..."。然后控制权将传递给 boot/setup.s 中的代 码,这是另一个实模式汇编语言程序。
图 3-1 启动引导时内核在内存中的位置和移动后的位置情况
启动部分识别主机的某些特性以及 vga 卡的类型。如果需要,它会要求用户为控制台选择显示模式。 然后将整个系统从地址 0x10000 移至 0x0000 处,进入保护模式并跳转至系统的余下部分(在 0x0000 处)。 此时所有 32 位运行方式的设置启动被完成: IDT、GDT 以及 LDT 被加载,处理器和协处理器也已确认, 分页工作也设置好了;最终调用 init/main.c 中的 main()程序。上述操作的源代码是在 boot/head.S 中的, 这可能是整个内核中最有诀窍的代码了。注意如果在前述任何一步中出了错,计算机就会死锁。在操作系统还没有完全运转之前是处理不了出错的。
为什么不把系统模块直接加载到物理地址 0x0000 开始处而要在 setup 程序中再进行移动呢?这是因 为在 setup 程序代码开始部分还需要利用 ROM BIOS 中的中断调用来获取机器的一些参数(例如显示卡 模式、硬盘参数表等)。当 BIOS 初始化时会在物理内存开始处放置一个大小为 0x400 字节(1Kb)的中断向量表,因此需要在使用完 BIOS 的中断调用后才能将这个区域覆盖掉。
3.3 bootsect.s 程序
3.3.1 功能描述
bootsect.s 代码是磁盘引导块程序,驻留在磁盘的第一个扇区中(引导扇区,0 磁道(柱面),0 磁头,第 1 个扇区)。在 PC 机加电 ROM BIOS 自检后,引导扇区由 BIOS 加载到内存 0x7C00 处,然后将自己移动到内存 0x90000 处。该程序的主要作用是首先将 setup 模块(由 setup.s 编译成)从磁盘加载到内存, 紧接着 bootsect 的后面位置(0x90200),然后利用 BIOS 中断 0x13 取磁盘参数表中当前启动引导盘的参数,接着在屏幕上显示“Loading system...”字符串。再者将 system 模块从磁盘上加载到内存 0x10000 开始的地方。随后确定根文件系统的设备号,若没有指定,则根据所保存的引导盘的每磁道扇区数判别出盘的类型和种类(是 1.44M A 盘吗?)并保存其设备号于root_dev(引导块的 0x508 地址处),最后长跳转到 setup 程序的开始处(0x90200)执行 setup 程序。
3.3.2 代码注释
程序 3-1 linux/boot/bootsect.s