前言

个人电脑(PC)的出现极大的普及了计算机的应用,从1980年IBM推出了以Intel的x86构架搭配微软的MS-DOS操作系统的PC以来,现代PC的硬件设备几乎都是由Wintel架构垄断的,不过今天我们并不深入探讨历史,而是介绍一下这种构架的电脑启动的原理。

PC启动原理

BIOS

从按下电源按钮开始,计算机进行加电自检(Power On Self Test ,POST),POST过后初始化用于启动的硬件(磁盘、键盘控制器等),然后从主板的ROM或Flash芯片中加载基本输入/输出系统(Basic Input/Output System,BIOS)到内存中,进行初始化,随后从CMOS中读取用户自定义的设置,这时候根据设置信息,BIOS通过启动顺序(BootSequence)列表查找对应设备上的启动文件。

MBR格式磁盘

在使用MBR格式的磁盘时,这种格式的磁盘第一个扇区保存有主引导记录(Master Boot Becord,MBR),这个记录有512字节大小,如果最后两个字节是0x55和0xAA,表明这个设备可以用于启动,那么它将接管BIOS传递的控制权,否则,BIOS将查找下一个设备。

主引导记录

主引导记录由三部分组成

1
2
3
第1-446字节:调用操作系统的机器码
第447-510字节:分区表(Partition table)
第511-512字节:主引导记录签名(0x55和0xAA)

由于分区表的长度只有64个字节,一个分区需要占用16个字节,所以MBR格式硬盘最多只能存在四个主分区。MBR格式磁盘的弊端不仅如此:由于主分区只有16个字节,它又由6个部分组成:

1
2
3
4
5
6
第1个字节:如果为0x80,就表示该主分区是激活分区
第2-4个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号等等)
第5个字节:主分区类型
第6-8个字节:主分区最后一个扇区的物理位置
第9-12字节:该主分区第一个扇区的逻辑地址
第13-16字节:主分区的扇区总数,决定了这个主分区的长度。

扇区总数为最多不超过2的32次方,如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘,只有2个方法:一是提高每个扇区的字节数,二是增加扇区总数。为了解决这些问题,人们发明了GPT格式的硬盘。

继续前面的话题,当BIOS在存储设备找到MBR并且MBR存在启动信息时,这时又会遇到三种情况

  1. 卷引导记录
    四个主分区里只有一个是激活的,那么计算机会读取激活分区的第一个扇区,叫做卷引导记录(Volume Boot Record,VBR),它会告诉计算机操作系统所在分区的位置,然后计算机开始加载操作系统
  2. 扩展分区和逻辑分区
    由于MBR磁盘只能存在四个主分区,但是在硬盘越来越大的今天是远远不够用的,所以,人们把其中一个主分区定义成扩展分区(Extended Partition),在这个分区中又可以划分出多个逻辑分区(Logical Partition),理论上可以划分为无数个逻辑分区,每一个逻辑分区都有一个和MBR结构类似的扩展引导记录,如果操作系统安装在扩展分区,计算机会先读取扩展分区的第一个扇区中的扩展引导记录,但是一般很少使用这种方式启动操作系统。
  3. Bootloader
    当使用启动管理器时,计算机读取主引导记录前面446字节的机器码之后,不再把控制权转交给某一个分区,而是运行事先安装的启动管理器(Bootloader),由用户选择启动哪一个操作系统。这也是当今操作系统启动的主流方式。

在Windows系统中

  • Windows NT开始为NTLDR
  • Winsows Vista开始为Windows Boot Manager

在Linux系统中

  • CentOS 6为grub legacy
  • CentOS 7为grub2

UEFI

随着计算机技术的不断进步,老旧的BIOS已经不能适应现在的要求,于是统一可扩展固件接口(Unified Extensible Firmware Interface,UEFI)技术出现了。

与BIOS相比,UEFI使用模块化理念,它可以加载ESP分区中的EFI应用程序和EFI驱动程序,支持安全启动、GUI等新功能,这些取决于PC制造商是否添加,它还能实现BIOS的兼容模式。只需要把操作系统的引导程序做成一个EFI应用程序就可以使用UEFI加载引导了,如果存在多个操作系统,只需要在UEFI引导界面选择相应的程序就行了。
UEFI的启动流程
首先系统开机,然后进行自检(Power On Self Test,POST),随后UEFI 固件被加载,并由它初始化启动要用的硬件。随后启动UEFI引导管理器,它将通过NVRAM中定义的配置决定如何加载UEFI驱动和UEFI可执行文件。已启动的UEFI应用还可以启动其他应用比如bootloader或者启动内核及initramfs,我们只要把操作系统的bootloader做成一个EFI可执行文件,放入EFI分区对应的目录就可以使用UEFI读取启动了。

GPT格式磁盘

全局唯一标识分区表(GUID Partition Table,GPT),与MBR最大4个分区表项的限制相比,GPT对分区数量没有限制,其分区数量只受操作系统限制,GPT可管理硬盘大小达到了18EB。只有基于UEFI平台的主板才支持GPT分区引导启动。


Linux系统启动流程

1.内核的引导

存储设备上的Bootloader接过BIOS的控制权后进行Linux的内核引导,这时根文件系统并未挂载,而内核要读取文件系统就必须要挂载根文件系统,但此时并没有文件系统能提供挂载点,为了解决这个问题内核就先读取存储介质中的初始RAM磁盘(Initialized RAM Disk,initrd)并将其加载到内存作为一个临时的根目录,加载一部分驱动,最重要的是用来挂载真正的根文件系统(这时是以只读方式),然后切换到真正的根文件系统,完成初始化任务,最后内核会运行第一个程序/sbin/init并将系统控制权交给它。

2.启动初始化进程

Linux的init程序经过了好几个版本,以CentOS为例

系统版本 init程序
CentOS 5及以前 SysV,配置文件: /etc/inittab
CentOS 6 Upstart,配置文件: /etc/inittab, /etc/init/*.conf
CentOS 7 Systemd,配置文件: /usr/lib/systemd/system、 /etc/systemd/system

在使用SysV的系统上,内核文件加载之后,就开始运行第一个程序/sbin/init,它负责初始化系统环境,他的pid为1,其他所有进程都由它衍生,都是他的子进程。
在采用systemd的系统上,运行的第一个程序为/usr/lib/systemd/systemd,它的的pid同样为1,也是所有进程的父进程。

3.设置运行级别

运行级别用于设定Linux操作系统不同的运行模式,运行级别控制Linux系统通过init程序为不同场合分配不同的开机启动程序。

Linux系统有7个运行级别(runlevel):

运行级别 说明
runlevel 0 系统停机、关机,系统默认运行级别不能设为0,否则不能正常启动
runlevel 1 单用户状态,root权限,用于系统维护,禁止远程登陆,无网络连接
runlevel 2 多用户状态,无网络连接,不运行守护进程,无NFS
runlevel 3 完全的多用户状态,有NFS,登陆后进入控制台命令行模式
runlevel 4 系统未使用,保留
runlevel 5 多用户,X11控制台,登陆后进入图形GUI模式
runlevel 6 系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动

init程序读取/etc/inittab定义的运行级别来进行系统初始化。

在systemd中runlevel已被target取代,systemd会读取/etc/systemd/system/default.target来决定启动到什么样的target(sysv中称为runlevel),这是一个符号链接,指向/usr/lib/systemd/system/下相应的target,由于可以实现并行启动,systemd没有严格的启动顺序。在CLI环境default.target指向/lib/systemd/system/multi-user.target,systemd通过读取target文件进行下一步操作,比如运行/usr/lib/systemd/system/sysinit.target开始系统初始化,这些都依赖于相应的target文件中的配置。

4.系统初始化

当设置好了runlevel之后,init程序会首先执行/etc/rc.d/rc.sysinit脚本,它是每个runlevel都要执行的重要脚本,它主要进行以下操作:

1
2
3
4
5
6
7
8
9
10
11
1.设置主机名称;
2.设置启动的欢迎信息;
3.激活udev和SELinux
4.挂载/etc/fstab文件中定义的所有有效文件系统;
5.激活各个swap设备;
6.检测rootfs,并且以读写的方式重新挂载rootfs;
7.设置系统时间;
8.根据/etc/sysctl.conf文件设置内核参数;
9.激活lvm和软RAID等高级逻辑设备;
10.加载额外的设备的驱动程序;
11.完成清理工作;

然后init程序根据相应的级别加载对应配置的程序,所有由rc脚本关闭或启动的链接文件的源文件都存在于/etc/rc.d/init.d,通过链接的方式放入不同的runlevel文件夹。比如当引导至运行级别 5 时,init 程序会在 /etc/rc.d/rc5.d/ 目录中查看并确定要启动和停止的进程。当init程序启动完对应的程序与守护进程后,这是系统环境基本已经搭建好了。

在systemd中,/usr/lib/systemd/system/sysinit.target、/usr/lib/systemd/system/basic.target等target会根据对应的依赖关系启动,执行相应的系统初始化任务。

5.用户登陆

用户可以通过三种方式登陆Linux

  • CLI登陆
  • SSH登陆
  • GUI登陆

这几种登陆方式会读取不同的配置文件,在Bash Shell相关博文会做详细介绍。

用户登陆系统后,开机过程就算完成了。


总结

简化的Linux系统启动流程

BIOS + MBR

1
POST --> BIOS --> MBR --> Bootloader --> kernel + ramdisk --> rootfs(read-only) --> /sbin/init(systemd) --> login

UEFI + GPT

1
POST --> UEFI --> EFI Application(Bootloader) --> kernel + ramdisk --> rootfs(read-only) --> /sbin/init(systemd) --> login

附录

Overview of systemd for RHEL 7:https://access.redhat.com/articles/754933