文章目录
- Linux的启动流程
- BIOS、boot loader与kernel加载
- BIOS
- boot loader
- 加载内核检测硬件与 initramfs 的功能
Linux的启动流程
本文章参考: [Uncertainty!!]的Linux的启动流程
Linux的启动流程是一个非常复杂的过程,下面是对Linux启动流程的详细分析:
假设以个人计算机使用的Linux为例,当你按下电源按键后,精神境界硬件会主动地读取BIOS或UEFI BIOS来加载硬件信息及进行硬件系统的自我测试,之后系统会主动地读取第一个可启动的设备(由BIOS设置),此时就可以读入启动引导程序了。
启动应道程序可以指定使用哪个内核文件来启动,并实际加载内核到内存当中解压缩与执行。此时内核就能够开始在内存中活动,并检测所有硬件信息与加载适当的驱动程序来使整台主机开始运行,等到内核检测硬件与加载驱动程序完毕后,一个最普通的操作系统就开始在你的PC上运行了。
主机系统开始运行后,此时Linux才会调用外部程序开始准备软件执行的环境,并且加载所有操作系统运行所需要的软件程序,最后系统就会开始等待你的登录与操作。
简单来说,操作系统启动的经过可以集合成下面的流程:
加载BIOS
的硬件信息与进行自我检测
(自检),并根据设置取得第一个可启动的设备;- 读取并
执行第一个启动设备内MBR的启动引导程序
(就是grub2等程序) - 在硬件驱动成功后,Kernel会主动调用systemd程序,并以
default.target
流程启动:systemd
执行sysinit.target
初始化系统及basic.target
准备操作系统systemd
启动multi-user.target
下的本机与服务器服务systemd
执行multi-user.target
下的 /etc/rc.d/rc.local 文件systemd
执行multi-user.target
下的getty.target 及登录服务systemd
执行graphical
需要的服务
大概流程就上面写的,你会发现systemd
占的比重非常重。
流程图如下
接下来我们来详细介绍一下
BIOS、boot loader与kernel加载
- BIOS:不论传统BIOS还是UEFI BIOS 都会被简称为 BIOS
- MBR:虽然分区表有传统MBR以及新式GPT,不过GPT也有保留一块兼容MBR的区块
因此,下面的说明在安装 boot loader的部分,还是简称为 MBR。总之,MBR就代表该磁盘的最前面可安装boot loader的那个区块
BIOS
在个人计算机架构下,要启动整个系统,首先需要加载BIOS程序。BIOS程序是计算机的基本输入输出系统,它会读取CMOS(可擦写只读存储器)中存储的设置信息,包括CPU和接口设备的通信频率、启动设备的查找顺序、硬盘的大小和类型、周边总线是否启用插拔即用设备(PnP)以及各接口设备的输入输出地址等信息。
BIOS在读取完CMOS中的设置信息后,会进行自我检测,然后对硬件设备进行初始化,并设置PnP设备,最后定义出可启动的设备顺序。
接下来,BIOS会开始从启动设备中读取数据,以启动操作系统。由于操作系统通常存储在硬盘中,所以BIOS会指定启动设备号,以便读取磁盘中的操作系统内核文件。但是,不同的操作系统使用的文件系统格式不同,因此需要使用一个启动引导程序来加载内核文件。这个启动引导程序就是boot loader,它通常安装在启动设备的第一个扇区,也就是MBR(主引导记录)。
boot loader
boot loader的最主要功能是识别操作系统的文件格式,并将内核文件加载到内存中去执行。
由于不同操作系统使用的文件格式不同,因此每种操作系统都有自己的boot loader
。只有使用相应操作系统的boot loader,才能正确地加载内核文件。因此,Linux系统的boot loader只能加载Linux内核文件,Windows系统的boot loader只能加载Windows内核文件,其他操作系统也一样。只有正确加载了内核文件,操作系统才能正常运行。
那么问题来了,你应该听说过多重操作系统吧?也就是再一台主机上面安装多种不同的操作系统。既然必须要使用自己的loader才能够加载属于自己的操作系统内核,而系统的MBR只有一个,那你怎么会有办法同时再一台主机上面安装Windows与Linux呢?
每个文件系统都有一个启动扇区
,也称为boot sector
。这个启动扇区的作用是让操作系统安装boot loader。通常情况下,每个操作系统都会默认将自己的boot loader安装到它根目录所在的文件系统的boot sector上
。这样,在计算机启动时,BIOS会读取MBR(Master Boot Record,主引导记录),MBR再读取对应文件系统的boot sector,最终执行其中的boot loader程序,把操作系统加载到内存中并启动。因此,每个操作系统都需要一个boot loader来启动,而这个boot loader通常被安装在操作系统所在的文件系统的boot sector上
。在安装多个操作系统时,每个操作系统都会安装自己的boot loader到对应文件系统的boot sector上
,因此MBR和boot sector都可能被不同的boot loader覆盖,以便启动不同的操作系统。
虽然操作系统都可以安装一份boot loader到它的boot sector中,这样操作系统可以通过自己的boot loader来加载内核,问题是系统的MBR只有一个,你要怎么执行boot sector 里面的loader呢?这就要使用到了 boot loader
Boot loader的主要功能有三个:提供选项
、加载内核文件
和转交其他loader
。
首先,boot loader提供选项让用户可以选择不同的启动选项,这是多重引导的重要功能,比如用户可以选择不同的操作系统或者进入恢复模式等。
其次,boot loader的另一个主要功能是加载内核文件。当用户选择一个启动选项后,boot loader会直接指向可启动的程序区域来启动操作系统,即加载内核文件。
最后,boot loader还可以将启动管理功能转交给其他loader负责。这个功能可以让boot loader更加灵活,比如可以将控制权转交给其他操作系统的boot loader,以实现多个操作系统的共存。
由于具有选项功能,因此我们可以选择不同的内核来启动,而由于具有控制权转交功能,因此我们可以加载其他boot sector 内的loader。不过 Windows 的 loader 默认不具有控制权转交功能,因此你不能使用 Windows 的loader来加载 Linux 的loader。
上述图片借用博客: https://aerobaticswu.blog.csdn.net/article/details/115030430
如上图所示,MBR使用了Linux的Grub2启动引导程序,里面已经有了三个选项。
第一个选项:可以直接指向Linux的内核文件来启动Linux操作系统
MBR(grub2) → kernel file → booting
第二个选项:可以将启动管理权限交给Windows来管理。这时候Windows的启动管理器就会接管启动流程,启动Windows操作系统。
MBR(grub2) → boot sector(Windows loader) → Windows kernel → booting
第三个选项则是:使用Linux在boot sector内的启动引导程序,这时候会跳出另外一个Grub2选项,用户可以在这个选项中选择其他启动选项。
MBR(grub2) → boot sector(grub2) → kernel file → booting
通过这种方式,用户可以在同一台计算机上安装多个操作系统,并且通过Grub2启动引导程序来选择不同的启动选项,实现多重引导的功能。
boot loader的功能就是【加载内核文件】
boot loader 的两个stage
在BIOS读取完信息后,会到第一个启动设备的MBR去读取boot loader。这个boot loader可以具有选项功能、直接加载内核文件以及控制权限移交功能等。操作系统必须要有loader才能加载该操作系统的内核。然而,MBR只有446B的大小,不足以存储完整的boot loader程序,就算是GPT也没有更大的扇区来存储loader的数据。那么,如何安装boot loader呢?
在Linux中,boot loader的程序代码执行与设置值的加载分成了两个阶段,分别是stage 1和stage 2:
stage 1:执行boot loader主程序
。
第一阶段执行 boot loader 的主程序,这个主程序必须安装在启动区,就是MBR或启动扇区(boot sector)。但如前所述,因为MBR实在太小了,所以MBR或启动扇区通常仅安装 boot loader的最小主程序,并没有安装loader的相关配置文件
stage 2:主程序加载配置文件
第二阶段通过 boot loader通过加载所有配置文件和相关的环境参数文件(包括文件系统定义和配置文件grub.cfg),来完成loader的配置。通常,这些配置文件都存储在/boot目录下。在这个阶段,boot loader的主程序会根据配置文件的设定,加载操作系统的内核文件和其他必要的文件,从而完成操作系统的启动过程。这个阶段的完成,就意味着操作系统已经准备好运行了。
grub2配置文件维护/etc/default/grub
/etc/default/grub 主要环境配置文件
[root@localhost ~]# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto spectre_v2=retpoline rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
GRUB_TIMEOUT=5
:设置启动菜单的超时时间为5秒。如果在5秒内没有选择启动项,系统将自动启动默认菜单项。(如果不想等待则输入0)GRUB_TIMEOUT_STYLE(是否隐藏选项)
:这个选项上面没有,可以按需添加,这个选项可以选择的值有 menu、countdown、hidden等。如果没有设置,默认是menu的意思。这个选项主要是在设置不要显示启动选项。如果你不想要让用户看到启动选项,这里可以设置为countdown。那么countdown和hidden有啥区别呢?countdown会在屏幕上显示剩余的等待秒数,而hidden则空空如也,除非你有特定的需求,默认一般设置为menu。GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
:设置GRUB的发行商为系统的发行商。这里通过sed命令获取/etc/system-release文件中的发行商信息,并将其赋值给GRUB_DISTRIBUTOR变量。GRUB_DEFAULT=saved
:指定默认启动的操作系统菜单项编号,编号从0开始计数。默认值为0(saved),即默认启动第一个菜单项。GRUB_DISABLE_SUBMENU=true
:禁用子菜单,将所有启动项都显示在主菜单中。GRUB_TERMINAL_OUTPUT="console"
:这个选项主要的设置有【console、serial、gfxterm、vga_text】等。除非有特定需求一般使用,console:将启动菜单输出到控制台。GRUB_CMDLINE_LINUX="crashkernel=auto spectre_v2=retpoline rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet"
:设置Linux内核启动参数。这里设置了一些常用的参数,如crashkernel、spectre_v2、rd.lvm.lv等。GRUB_DISABLE_RECOVERY="true"
:禁用系统恢复模式,避免意外进入系统恢复模式。
通过修改这些参数的值,可以调整GRUB的启动行为,以适应不同的需求。修改完毕后,需要运行 grub2-mkconfig来重建grub.cfg才行。
例题:
假设你需要(1)启动选项等待40秒;(2)默认用第一个选项启动;(3)选项请显示出来不要隐藏;(4)内核外带【elevator=deadline】的参数值,那么应该如何处理grub.cfg?
- 先编辑主要环境配置文件
[root@localhost ~]# vim /etc/default/grub
GRUB_TIMEOUT=40
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=menu
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto spectre_v2=retpoline rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet elevator=deadline"
GRUB_DISABLE_RECOVERY="true"
[root@localhost ~]# grub2-mkconfig
- 开始重新创建grub.cfg
[root@localhost ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
[root@localhost ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.10.0-1160.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-1160.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-567928b865d945a8a3c7211006b3ba40
Found initrd image: /boot/initramfs-0-rescue-567928b865d945a8a3c7211006b3ba40.img
done
命令说明:
grub2-mkconfig -o /boot/grub2/grub.cfg
是一个用于生成GRUB2启动菜单的命令。这个命令会扫描系统中已安装的操作系统和内核,并根据默认配置文件 /etc/default/grub
中的配置参数生成启动菜单。最终生成的启动菜单会被保存在 /boot/grub2/grub.cfg
文件中。
- 检查看看grub.cfg的内容是否真的改变
[root@localhost ~]# grep timeout /boot/grub2/grub.cfg
set timeout_style=menu
set timeout=40
[root@localhost ~]# grep default /boot/grub2/grub.cfg
# from /etc/grub.d and settings from /etc/default/grub
set default="0"
[root@localhost ~]# grep elevator=deadline /boot/grub2/grub.cfg
linux16 /vmlinuz-3.10.0-1160.el7.x86_64 root=/dev/mapper/centos-root ro crashkernel=auto spectre_v2=retpoline rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet elevator=deadline
linux16 /vmlinuz-0-rescue-567928b865d945a8a3c7211006b3ba40 root=/dev/mapper/centos-root ro crashkernel=auto spectre_v2=retpoline rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet elevator=deadline
加载内核检测硬件与 initramfs 的功能
在计算机启动时,首先会由BIOS(Basic Input/Output System)加载boot loader(引导加载程序),其主要作用是找到并加载操作系统内核文件。当boot loader管理开始读取内核文件后,Linux会将内核解压缩到内存中并利用内核的功能,开始测试与驱动各个周边设备,包括存储设备、CPU、网卡、声卡等。此时Linux内核会以自己的功能重新检测一次硬件,而不一定会使用BIOS检测到的硬件信息
。也就是说,内核此时才开始接管BIOS后的工作。一般情况下,内核文件被放置在/boot目录下,并命名为vmlinuz。
-
ls
是列出目录内容的命令 -
--format=single-column
指定每个文件名单独列出,每行只显示一个 -
-F
会在每个文件名后面加上一个符号,表示该文件的类型。例如,加上斜杠表示该文件是一个目录,加上星号表示该文件是一个可执行文件 -
config-3.10.0-1160.el7.x86_64
是Linux内核的配置文件。 -
efi
是存储UEFI启动文件的目录。 -
grub
是GRUB引导程序的配置目录。 -
grub2
是GRUB2引导程序的配置目录。 -
initramfs-0-rescue-567928b865d945a8a3c7211006b3ba40.img
是包含内核模块和初始化脚本的内存文件系统映像文件。这是一个紧急恢复映像文件,用于修复系统出现问题时的紧急情况。 -
initramfs-3.10.0-1160.el7.x86_64.img
是包含内核模块和初始化脚本的内存文件系统映像文件。 -
initramfs-3.10.0-1160.el7.x86_64kdump.img
是用于内核转储(kdump)的内存文件系统映像文件。 -
symvers-3.10.0-1160.el7.x86_64.gz
是Linux内核符号表。 -
System.map-3.10.0-1160.el7.x86_64
是Linux内核符号表。 -
vmlinuz-0-rescue-567928b865d945a8a3c7211006b3ba40
是一个紧急恢复的Linux内核文件。它用于在系统出现问题时使用。 -
vmlinuz-3.10.0-1160.el7.x86_64
是Linux内核文件。
从上图中的特殊字体,我们可以知道CentOS 7.x 的Linux的内核版本为 3.10.0-1160.el7.x86_64
。
Linux内核是操作系统的核心,可以通过动态加载内核模块来扩展其功能,就像插入一个驱动程序一样。这些内核模块通常存储在/lib/modules/
目录中。由于这些模块存储在根目录下(要记得 /lib 不可以与 / 分别放在不同的硬盘分区),因此在启动时必须要挂载根目录,才能够读取内核模块提供的驱动程序功能。为了保护磁盘内的文件系统,启动过程中根目录通常是以只读方式挂载的,以防止对文件系统的影响
一般来说,目前的Linux发行版都会将非必要的功能且可以编译成未模块的内核功能,编译成为模块,比如USB、SATA、SCSI等磁盘设备的驱动程序通常是以模块的方式存在的。如果Linux安装在SATA磁盘上,启动时BIOS会通过INT 13取得boot loader和内核文件,然后内核会开始接管系统并检测硬件并尝试挂载根目录来取得额外的驱动程序。
在Linux中,内核本身并不认识SATA磁盘,因此需要加载SATA磁盘的驱动程序才能挂载根目录。但是,SATA的驱动程序通常存储在/lib/modules目录中,如果无法挂载根目录,就无法读取/lib/modules
中的驱动程序。这时,就需要通过虚拟文件系统来解决这个问题。
虚拟文件系统通常使用的文件名为/boot/initrd
或/boot/initramfs
,它可以通过boot loader加载到内存中。这个文件会被解压缩,并在内存中模拟出一个根目录。这个内存中的文件系统可以提供一个可执行的程序,用于加载启动过程中所需的内核模块,比如USB、RAID、LVM、SCSI等文件系统和磁盘接口的驱动程序。加载完成后,它会帮助内核重新调用systemd来开始后续的正常启动流程。这样就可以让Linux在启动时顺利地加载所需的驱动程序,从而保证系统的正常运行。
如上图所示,boot loader可以加载 kernel与 initramfs,然后再内存中让 initramfs 解压缩成为根目录,内核(kernel)就能够借此加载适当的驱动程序,最终释放虚拟文件系统,并挂载实际的根目录文件系统,从而开始后续的正常启动流程。
initramfs是一个小型的根目录,它与普通的根目录一样也是通过systemd进行管理。它的作用是在启动系统时提供一些必要的文件和驱动程序,以便让系统能够顺利启动。在启动时,系统会先加载initramfs,然后读入一些硬件检测和内核功能启用的流程,使系统能够正常启动。最后,系统会卸载initramfs,然后挂载真正的根目录,启动完成。