原文在此,如果英文阅读能力不差还是尽量读原文吧。
先前的文章描述了Intel系列计算机的主板和存储地址映射,在此基础上我们来看看计算机启动的初始阶段。计算机的启动是一个复杂、多阶段并且相当有趣的事情。下图描述了整个计算机启动的过程:
当你按下计算机的电源按钮时,启动过程就开始了。一旦主板电源接通,主板就会初始化主板上的固件——一些芯片组和周边——并且会尝试让CPU运行起来。如果在这一步失败了(比如CPU故障或者没有找到),一般来说除了风扇仍然可以转动,计算机的其他部分都无法工作。少数主板在未找到CPU或者发现CPU故障时会发出“哔哔”的警报音,但是基于我的经验,大部分的主板只会不断的转动风扇,没有其它任何反应。有时候USB或是其他设备都有可能会导致这种情况:如果你发现你的系统突然变成了这样,尝试拔掉所有非必须的设备可能会有帮助。你也可以一个一个的排除出可能导致问题发生的设备。
如果一切正常,CPU会进入正常运行状态。在一个多处理器(multi-processor)或多核(multi-core)的系统中,启动过程中会动态的选定一个CPU作为启动处理器(bootstrap processor – BSP)来运行所有的BIOS和操作系统内核初始化代码,余下的处理器在这个阶段被称为应用处理器(application processors – AP),会停留在终止状态直到操作系统内核显式的激活它们。Intel生产的CPU在这么多年中持续不断的演进但仍保持了完整的向后兼容,所以即使是最先进的CPU也和1978年生产的Intel 8086处理器具有相同的行为,在启动过程中也CPU也正是这样工作的。在电源刚接通后,处理器处于实模式(real mode)并且禁止了内存分页机制,这和原始的MS-DOS一样,只有1MB的可用存储地址空间,用户可以运行任何指令、也可以修改存储区域中的任何位置——在实模式中没有保护和特权指令的概念。
大部分的寄存器(registers)都预先设定了在通电后的初始值,保存着CPU执行指令地址的地址寄存器(EIP)也不例外。虽然在刚刚开机后只能使用1MB的存储地址空间,但是32位的Intel处理器第一条执行指令地址为0xFFFFFFF0(4GB存储空间的最后16字节),这个地址被称为reset vector,是现代Intel系列CPU的标准之一。
主板会保证在reset vector处的指令是一条到BIOS程序入口的跳转指令。在主板芯片组的存储地址映射作用下,所有的存储地址都对应有CPU需要的正确的内容。这些地址都被映射到了包含有BIOS的闪存中,而此时RAM中存在的只是一些无用的随机值。有关存储区域的一个示例如下图:
在这之后CPU就会开始执行BIOS程序来对一些机器的硬件进行初始化。然后BIOS会触发通电自检(Power-on Self Test – POST)过程以检测计算机的众多组件。如果POST检测到显卡故障,BIOS程序会停止并发出蜂鸣声——因为无法在屏幕上显示信息,而如果显卡可以正常工作,一切就美好的多了:制造商的logo会出现在屏幕上,BIOS开始检测内存,喇叭会发出声音。POST过程中的其他错误比如未找到键盘等会在屏幕上打印出错误信息。POST过程涉及到非常多的检测和初始化工作,包括整理出所有可用的资源——中断、内存大小、PCI设备的I/O端口。遵守ACPI的BIOS程序会建立一些描述计算机设备的数据表,这些数据表在之后交给操作系统内核使用。
在完成POST之后,BIOS需要在硬盘、光驱、软驱等地方搜寻操作系统内核的位置以启动操作系统,寻找启动设备的顺序是可以设置的。如果BIOS没有找到可以用于启动操作系统的设备,它会终止并提示“Non-System Disk or Disk Error”。
假设我们的可启动设备为硬盘,则BIOS程序会读取硬盘的开头512个字节(第一个扇区),这512个字节被称为主引导记录(Master Boot Record – MBR),MBR通常包含两个必不可少的部分:一个很小的操作系统各异的引导程序和磁盘的分区表。不过BIOS并不关心这些,BIOS只是简单的把MBR的内容载入到内存地址0x7c00的位置并且跳转到这一地址开始执行MBR中的指令。
MBR中的代码可能是一个Windows的启动器(Loader),也可能是Linux的启动器比如LILO或是GRUB,甚至有可能是一个病毒。而分区表是标准化的:分区表长度为64字节,由4个16字节的分区描述组成,它们描述了磁盘是如何分隔的(所以你可以在同一块磁盘中安装不同的操作系统)。传统的微软MBR代码会检查分区表,找出唯一一个标记为活动的分区,从这个分区中载入启动扇区并且运行扇区上的代码。启动扇区是这个分区上的第一个扇区,相对的MBR是整个磁盘的第一个扇区。如果分区表中有错误你会看到类似“Invalid Partition Table”或者“Missing Operating System”这样的错误提示,这些信息是MBR代码发出的,而不是BIOS,因此具体的错误提示取决于MBR的实现风格。
现在计算机的启动过程已经十分的灵活了,Linux的启动器LILO和GRUB可以处理相当多不同的操作系统、文件系统和启动设置。它们的MBR代码并没有效仿微软的“从活动的分区启动”的方式,具体的过程如下:
- MBR本身只包含启动阶段1的代码,GRUB中称为stage 1。
- 因为MBR本身只有很小的空间(440字节),MBR中的代码只是把磁盘中包含附加启动代码的扇区载入到内存中。这个附加的扇区可能是某个分区的启动扇区,也可能是安装MBR时在MBR中硬编码的某个指定扇区。
- MBR代码加上上一步中载入的代码之后会从磁盘中读出包含第二阶段启动器的文件,在GRUB中为GRUB Stage 2,在Windows中为c:\NTLDR(如果这一步失败了你会看到像“NTLDR is missing”这样的错误提示)。第二阶段启动器运行后会读取一个启动配置文件(GRUB中的grub.conf,Windows中的boot.ini),并在启动选择多于一个时呈现出一个启动选择界面。
- 运行到这一步,boot loader需要启动操作系统内核了,因此它必须知道在启动分区的文件系统中如何读出内核。在Linux中boot loader从文件系统中读取类似“vmlinuz-2.6.22-14-server”这样的包含内核的文件,将文件载入到内存中并且跳转到内核启动代码开始执行。在Windows中启动代码和内核镜像本身是分离的,启动代码嵌入在了NTLDR中,在执行了一系列初始化工作后,NTDLR从文件c:\Windows\System32\ntoskrnl.ext中载入内核镜像,和GRUB一样跳转到内核入口处。
这里有一点值得关注的地方,现在的Linux内核镜像即使经过压缩后也不能够放在实模式的640K内存空间中,我使用的普通Ubuntu内核镜像是1.7MB,然而boot loader为了能够调用BIOS来读取磁盘必须工作在实模式中,因此在这样的情况下内核是无法载入的。采用unreal mode可以解决这个问题。unreal mode并不是真正的处理器模式,而是一种程序在实模式和保护模式之间不断切换的技术,由此获得了超过1MB的内存空间并且同时还可以使用BIOS。如果你阅读GRUB的源代码,你会看到这些切换过程(在stage2中的函数real_to_prot和prot_to_real)。当这些艰难的过程结束后loader成功的将整个内核载入了内存中,并最终将处理器停留在实模式中。
我们现在处在了第一张图的“Boot Loader”和“Early Kernel Initialization”中间的阶段,内核即将开始工作。下一篇文章中我会根据Linux参考手册对Linux内核的初始化过程进行描述,而对于Windows,我会指出一些关键的过程。