Legacy BIOS引导流程详解

虽然传统BIOS(Legacy BIOS)已经逐渐被UEFI BIOS取代,但是对传统BIOS的深入了解,也将有助于我们更好地理解UEFI BIOS、了解Linux的设计理念。在下文中我们将传统BIOS直接简称为BIOS。

概述

下图为BIOS的引导流程。

在系统首次被引导或者重置时,处理器会首先执行一段位置已知的代码。通常这段代码保存在基本输入/输出系统(BIOS,Basic Input/Output System)中,或者由CPU调用重置向量来进行寻址。

在找到引导设备后,第一阶段的引导加载程序(Boot Loader)就被装入RAM执行。第一阶段引导加载程序的主要作用是找到第二阶段的引导加载程序。

第二阶段的引导加载程序被执行时,会显示用户界面,从命令行到GUI各式各样,并将Linux内核和可选的初始RAM磁盘(initrd)加载到内存。第二阶段的控制权会被交给内核,然后由内核进行解压和初始化。在这个阶段中,第二阶段的引导加载程序会检测系统硬件、枚举系统链接的硬件设备、挂载根设备,然后加载必要的内核模块。

然后启动第一个用户空间程序-init,并执行高级系统初始化工作。

系统启动

在PC平台引导时,BIOS的第一步是加电自检(Power On Self Test,POST),对硬件进行检测,用于检查设备是否良好。

然后是初始化,包括创建中断向量、设置寄存器、对外部设备的初始化和检测。

最后对本地设备的枚举,用以寻找引导设备,BIOS 运行时会按照 CMOS 的设置定义的顺序来搜索处于活动状态并且可以引导的设备。引导设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备,甚至是 USB 闪存。当检测到有引导设备满足要求后,BIOS将控制权交给相应引导设备。

引导加载程序

由于BIOS只能访问很少的数据量,所以大部分引导加载程序分为两个阶段。在引导的第一阶段中,BIOS引导一部分引导加载程序,即初始程序加载程序(Initial Program loader,IPL)。IPL查询分区表,从而加载位于不同介质上的数据,进而定位第二阶段的引导加载程序,最终将控制权交给后者。

第二阶段的引导加载程序通常被认为是引导加载程序的主体,它负责用户界面和内核引导等重要功能。

我们将第一阶段的引导加载程序称为主引导加载程序,将第二阶段的引导加载程序称为次引导加载程序

第一阶段引导加载程序

通常情况下,Linux是从硬盘上的主引导记录(Master Boot Record,MBR)中存放的引导加载程序开始引导的。

MBR是一个512字节大小的扇区,位于磁盘的第一个扇区中,每块硬盘都有一个MBR,它在硬盘上的三维地址为(柱面,磁头,扇区)=(0,0,1)。其中包含程序代码和一个分区表。MBR分区结构如下图所示。

  • 启动代码区:前446字节是是主引导加载程序,包括可执行代码和错误消息文本(有时也将开头的前446字节称作MBR,但在本文中我们还是将前512字节称作MBR),代码主要功能是检查分区表是否有效,以及查找并加载次引导加载程序(如LILO、GRUB等),并转交控制权。

    ​ 它先查找活动的主分区,并检查其他分区,确保该分区是唯一活动的主分区,其他分区都是不活动的,然后将该分区的引导记录从设备读入RAM并执行。这个过程不依赖于任何操作系统,且启动代码也是可以改变的,因而可以实现多系统引导。如果将一个操作系统单独地安装到逻辑分区上,是无法引导的,但如果作为双系统的话,是可以将第二个系统放到逻辑分区上。

  • 硬盘分区表(Disk Partition Table, DPT):然后是64字节的分区表,包含4个分区的记录,每条记录16字节,包含分区的状态、起始结束的地址、相对扇区号、总扇区数等信息。分区结构信息表如下图所示。

由于每条分区记录都只有16字节,所以磁盘的分区也变得很受限制。分区的相对扇区号和扇区数都只有4字节,这就限制了MBR型分区结构的硬盘大小只能控制在2TB(232×512 = 2,199,023,255,552字节),受CHS寻址方式的限制,起始扇区也必须在前2TB内。

由于只预留了4条分区记录的空间,所以MBR型分区结构的硬盘只能识别4个主分区,这就引出了逻辑分区的概念,在本文中不再赘述。

  • 结束标志字:最后MBR以两个特殊的数字字节0x55AA结束,用以检查MBR的有效性。

MBR结构就是由上述3个部分组成。其从系统启动阶段到读取流程如下:

(1)BIOS加电自检。

(2)读取MBR。BIOS按照CMOS设置的顺序检测可用的启动设备,将启动设备的第一个扇区,也就是MBR扇区读入内存。

(3)检查MBR。BIOS检查MBR,检查包括结束标志位、是否有写保护等,如果当前设备不满足启动要求,则尝试下一个设备,重复(3)(4)。

(4)检测到有合适的启动设备时,BIOS移交控制权。启动设备的MBR将自己复制到内存中,然后继续执行MBR中的代码。

(5)根据MBR中的引导代码启动引导程序。

第二阶段引导加载程序

次引导加载程序是引导加载程序真正的核心部分,这个阶段的任务是加载Linux内核和可选的初始RAM磁盘。在 PC 环境中,第一阶段和第二阶段的引导加载程序一起称为 Linux Loader(LILO)或 GRand Unified Bootloader(GRUB)。GRUB克服了LILO的诸多缺点,所以在这里只研究GRUB。

在MBR中。引导代码最多只能占446字节,这对于像LILO、GRUB这种复杂的引导程序来说就显得太小,所以这里用到了多级引导的概念。

Stage 1

将GRUB安装到MBR时,实际上只是将GRUB的stage 1(第一阶段引导加载程序)放入了MBR的代码区,Stage 1的任务是通过加载磁盘起始处附近从一个固定地址开始的一些扇区来加载GRUB的下一阶段,即stage 1.5或stage 2。

Stage 1.5

Stage 1.5是stage 1和stage 2的中间桥梁。Stage 1.5包含在MBR后的30千字节中,stage 1.5的作用是加载stage 2。Stage 1.5包含了具体的文件系统驱动,该驱动允许stage 1.5直接加载位于文件系统/boot/grub下的stage 2,将后者装入内存并执行。

Stage 2

Stage 2加载配置文件,根据配置文件,选择性地呈现一个界面让用户选择要启动的操作系统内核。用户可以选择启动内核甚至修改附加内核参数,也可以使用命令行对引导过程进行手动控制。选择完成后,会加载内核映像和initrd映像到内存中。然后GRUB将控制权转交给内核,其工作至此也完成。

内核

内核被加载到内存,GRUB将控制权释放后,内核阶段就开始了。

内核映像不是一个可执行的内核,而是一个压缩过的内核映像,它的格式通常是zImage(小于512KB,适用于小内核)或bzImage(Big zImage,大于512KB,适用于大内核)。

内核映像的前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核(注意区分内核映像和内核)进行解压,然后将其放入高端内存中,如果有初始 RAM 磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。

在引导过程中,初始RAM磁盘(initrd)是在stage 2中被加载到内存中的,它会被复制到RAM并被挂载到系统上。这个initrd会作为RAM中的临时根文件系统使用,允许内核在没有挂载任何物理磁盘的情况下完整地实现引导。initrd的最初的目的是为了把内核的引导分成两个阶段:在内核中保留最少最基本的引导代码,然后把对各种各样硬件设备的支持以模块的方式放在initrd中,这样就在启动过程中可以initrd临时根文件系统中装载需要的模块。这样的一个好处就是在保持内核不变的情况下,通过修改initrd中的内容就可以灵活的支持不同的硬件,而不需要每次都重新编译内核,因而内核可以做到非常小。

在内核引导完成之后,initrd根文件系统将会被卸载,并挂载真正的根文件系统。

Init

当内核被引导和进行初始化之后,内核就可以启动自己的第一个用户态程序了。在多数Linux中,这个程序通常是/sbin/init。由init开始,计算机就进入了系统初始化阶段。在系统初始化的方法繁多,但已经不在本文的研究范畴了。

最后附上一张个人认为比较完整的Linux系统启动流程图(出处)。

参考: