这里先总体说明一下 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(64KB)开始处。因此从机器加电开始顺序执行的程序见图 6-1 所示。
因为当时 system 模块的长度不会超过 0x80000 字节大小(即 512KB),所以 bootsect 程序把system模块读入物理地址 0x10000 开始位置处时并不会覆盖在 0x90000(576KB)处开始的 bootsect 和 setup 模块上。后面 setup 程序将会把 system 模块移动到物理内存起始位置处,这样 system 模块中代码的地址也即等于实际的物理地址,便于对内核代码和数据进行操作。图 6-2 清晰地显示出 Linux 系统启动时这几个程序或模块在内存中的动态位置。图中,每一竖条框代表某一时刻内存中各程序的映像位置图。在系统加载期间将显示信息"Loading..."。然后控制权将传递给 boot/setup.S 中的代码,这是另一个实模式汇编语言程序。
启动部分识别主机的某些特性以及 VGA 卡的类型。如果需要,它会要求用户为控制台选择显示模式。然后将整个系统从地址 0x10000 移至 0x0000 处,进入保护模式并跳转至系统的余下部分(在 0x0000处)。到此时,所有 32 位运行方式的启动设置均已被完成: IDT、GDT 以及 LDT 被加载,处理器和协处理器也已确认,分页工作也设置好了,最终会调用执行 init/main.c 中的 main()代码。上述操作的源代码是在 boot/head.s 中的,这可能是整个内核中最有诀窍的代码了。注意如果在前述任何一步中出了错,计算机就会死锁。在操作系统还没有完全运转之前是处理不了出错的。
bootsect 的代码为什么不把系统模块直接加载到物理地址 0x0000 开始处而要在 setup 程序中再进行移动呢?这是因为随后执行的 setup 开始部分的代码还需要利用 ROM BIOS 提供的中断调用功能来获取有关机器配置的一些参数(例如显示卡模式、硬盘参数表等)。而当 BIOS 初始化时会在物理内存开始处放置一个大小为 0x400 字节(1KB)的中断向量表,直接把系统模块放在物理内存开始处将导致该中断向量表被覆盖掉。因此引导程序需要在使用完 BIOS 的中断调用后才能将这个区域覆盖掉。
另外,仅在内存中加载了上述内核代码模块还不足以让 Linux 系统运行起来。作为完整可运行的Linux系统还需要有一个基本的文件系统支持,即根文件系统(Root file-system)。Linux 0.12 内核仅支持MINIX 的 1.0 文件系统。根文件系统通常存在于另一个软盘上或者在一个硬盘分区中。为了通知内核所需要的根文件系统在什么地方,bootsect.S 程序第 44 行上给出了根文件系统所在的默认块设备号ROOT_DEV。块设备号的含义请参见程序中的注释。在内核初始化时会使用编译内核时放在引导扇区第509、510(0x1fc--0x1fd)字节中的指定设备号。bootsect.S 程序第 45 行上给出了交换设备号 SWAP_DEV,它指出用作虚拟存储交换空间的外部设备号。
注:内容参考自《Linux内核完全注释》第五版