-
Linux操作系统开机启动的过程–使用GRUB引导
在Linux操作系统中,开机启动过程涉及多个关键步骤,从主板的启动方式到Linux内核的加载,再到用户空间进程的初始化。这篇文章详细探讨了两种主流的主板启动方式( [[GRUB]] 和[[UEFI]]),以及它们如何引导[[GRUB]]来加载[[Linux内核]]。随后,我们会深入讨论GRUB加载内核后,Linux内核如何初始化根文件系统以及使用
systemd
或SysVinit
完成系统的启动。
-
2种不同的主板启动方式: BIOS和UEFI
目前,计算机硬件主要使用两种启动固件:BIOS(Basic Input/Output System)和UEFI(Unified Extensible Firmware Interface)。这两种方式在加载GRUB和启动Linux系统时具有显著差异。
-
BIOS:传统的BIOS固件通过读取主引导记录(MBR)来查找启动代码。BIOS启动时会读取MBR的前512字节(其中包括启动引导扇区),此扇区中包含的代码指向存储设备的引导加载程序。
-
UEFI:与BIOS不同,UEFI引导模式依赖于EFI分区(ESP),该分区是一个独立的FAT文件系统分区,通常标记为“EFI System Partition”。UEFI能够直接读取存储设备上的GRUB EFI文件,允许更灵活的启动配置和更多的存储空间。
从这两种方式加载GRUB之后,GRUB引导Linux内核的流程则基本一致,差别主要体现在引导时的初始加载机制和存储格式上。
-
通过BIOS加载GRUB
在BIOS模式下,启动过程分为多个细致的阶段,从系统上电到启动GRUB再到引导Linux内核,每一步都有其特定的流程和角色。下面将详细描述这些阶段:
-
- BIOS上电自检(POST):系统通电后,BIOS(Basic Input/Output System)固件首先会执行电源自检(Power-On Self Test,POST)流程。POST初始化CPU、内存、图形卡、硬盘、键盘和其他外设,确保硬件处于正常状态。POST完成后,BIOS固件会定位系统的引导设备(通常在BIOS设置中指定),如硬盘、光盘或USB驱动器,并尝试从该设备启动操作系统。
-
- 读取MBR:一旦BIOS确认引导设备(例如硬盘),它会尝试读取该设备的第一个扇区(即主引导记录,MBR)。MBR占据硬盘的前512字节,其中包含了以下关键内容:
- 启动引导程序:MBR的前446字节包含一小段启动引导代码,它负责识别并启动操作系统的引导程序。BIOS会将该部分代码加载到内存中并执行。
- 分区表:MBR中接下来的64字节为分区表,存储硬盘上的分区信息。
- 魔数:最后2字节是一个魔数(55aa),用于验证MBR的完整性。如果魔数无效,BIOS会判断MBR已损坏,从而无法继续引导。
-
- 加载GRUB阶段1:MBR中的启动引导代码包含的程序十分简单,大小限制在446字节左右,因此通常只包含最基础的指令。在GRUB的多阶段设计中,这一阶段的代码被称为GRUB的第一阶段(stage 1)。此时,MBR中的代码将加载并启动GRUB的下一阶段。由于空间有限,stage 1代码只负责查找并加载更大的stage 1.5或stage 2文件。
-
- GRUB的阶段1.5:GRUB通常还包含一个称为stage 1.5的中间阶段,位于MBR之后的若干扇区(称为“引导扇区”或MBR gap)中。stage 1.5在加载时会尝试加载必要的文件系统驱动,使GRUB能够访问文件系统,便于加载配置文件和更大的引导程序。如果系统支持并安装了stage 1.5阶段代码,GRUB可以在此阶段加载其配置文件和必要的文件系统模块。
-
- 加载GRUB阶段2:一旦stage 1.5完成引导准备,GRUB将继续加载stage 2阶段代码(完整的GRUB程序)。在stage 2中,GRUB开始显示用户可见的启动菜单。此阶段不仅可以从文件系统中加载内核映像文件,还支持内核参数设置和引导配置。
-
- 进入GRUB菜单:在GRUB的阶段2加载后,用户会看到GRUB启动菜单界面,提供多项选择:
- 内核选项:用户可以选择不同的内核版本,或者通过菜单条目指定内核参数,例如单用户模式或恢复模式。
- 高级配置:用户还可以手动编辑GRUB配置或进入命令行模式以调整启动配置。
- 内核启动:选择启动项后,GRUB通过
boot
命令加载并启动内核,同时传递内核启动参数(如根文件系统路径等)。
-
通过UEFI加载GRUB
相比于BIOS引导,UEFI引导流程更加灵活,也更适合现代硬件设备。UEFI使用EFI系统分区(ESP)来管理和加载启动文件,不再依赖MBR格式,从而支持更大的硬盘容量和更多分区数量。
- UEFI固件初始化:系统上电后,UEFI固件执行硬件自检和初始化(类似于BIOS的POST流程)。在硬件初始化完成后,UEFI固件会自动查找并加载EFI系统分区(ESP)中的启动文件。ESP分区是一个独立的FAT文件系统分区,通常包含各种启动项文件和配置文件。UEFI会根据配置或默认路径查找启动项,优先加载ESP分区中的可执行文件。
- 加载GRUB EFI文件:在ESP分区中,UEFI能够直接读取和执行可执行的EFI文件,如
grubx64.efi
。该文件是GRUB为UEFI引导而编译的可执行程序,通常位于/EFI/boot/
目录下。UEFI加载并执行grubx64.efi
文件后,将控制权移交给GRUB EFI文件,从而初始化GRUB引导加载器。在UEFI设置中,用户可以手动配置启动顺序、指定GRUB EFI文件的路径,甚至选择不同的操作系统引导项。 - GRUB的初始化与进入启动菜单:GRUB EFI文件加载后,GRUB引导加载器开始执行其初始化代码,并显示启动菜单。此时,用户可以通过菜单界面选择操作系统,指定Linux内核和内核参数,或进入GRUB命令行模式手动输入引导配置。后面就与BIOS一样了。
- GRUB加载速度与扩展支持:由于UEFI支持读取FAT文件系统并直接访问ESP分区,GRUB的加载速度相比传统BIOS模式有所提升。此外,UEFI模式下GRUB的启动选项和配置项更多,可以处理超过2TB的硬盘分区,支持更多文件系统格式,使其适用于现代大型存储设备。
-
GRUB加载Linux内核
在BIOS或UEFI加载完成后,GRUB的主要任务是启动Linux内核并传递必要的启动参数。这个过程可以通过
grub.cfg
配置文件来了解具体加载步骤。grub.cfg
通常位于/boot/grub
或/boot/efi/EFI/<distro>/
目录中,负责定义GRUB的启动项、内核路径和其他配置项。下面详细说明GRUB加载Linux内核的过程。 -
- 加载启动配置(grub.cfg):GRUB引导时会读取
grub.cfg
文件,该文件包含系统的启动配置。每个启动项通常定义如下指令:
- menuentry:定义启动菜单项的名称。此处包含不同操作系统或内核版本的描述。例如:
menuentry 'Ubuntu 20.04 LTS' { linux /boot/vmlinuz-5.4.0-42-generic root=UUID=<UUID> ro quiet splash initrd /boot/initrd.img-5.4.0-42-generic }
- linux /vmlinuz-:指定内核文件的路径和版本。此行将加载具体的Linux内核映像文件,并可以在这一行中附加启动参数。
- initrd /initrd.img-:指定临时根文件系统的路径(如initrd或initramfs),提供启动过程中需要的模块和驱动程序。
- 加载启动配置(grub.cfg):GRUB引导时会读取
-
- 内核参数传递:在GRUB菜单中,每个
menuentry
的配置中通常包括linux
指令,这一行用于加载Linux内核映像并指定启动参数。例如:
-
linux /vmlinuz-5.4.0-42-generic root=/dev/sda1 ro quiet splash
-
root:指定根文件系统的位置(如
/dev/sda1
),告知内核应挂载哪个设备为根文件系统。 -
ro:表示根文件系统在启动时以只读模式挂载。
-
quiet和splash:这些参数用于控制启动时的信息输出(如静音启动和加载启动画面)。
这些内核参数在将控制权交给内核时传递给内核,以帮助内核初始化和加载正确的配置。
- 内核参数传递:在GRUB菜单中,每个
-
- 加载initrd/initramfs:在一些系统中,GRUB还需要加载一个临时根文件系统
initrd
(或initramfs
)。该文件系统通常指定在initrd
指令中,例如:
-
initrd /initrd.img-5.4.0-42-generic
- initrd或initramfs文件中包含了启动所需的驱动程序和模块,用于识别并初始化硬件组件(如文件系统、磁盘驱动器等)。
- initrd/initramfs文件可以帮助内核加载必要的模块,例如硬件驱动和网络配置,确保在系统最终加载根文件系统之前已经完成关键的硬件初始化。
- 加载initrd/initramfs:在一些系统中,GRUB还需要加载一个临时根文件系统
-
- 启动内核:一旦内核映像和initrd/initramfs被加载完毕,GRUB通过
boot
命令启动内核。这一命令将控制权移交给内核,从而进入内核的初始化流程:
-
在此阶段,内核会从GRUB接收控制权并根据传递的参数进行启动配置。在内核初始化过程中,会挂载根文件系统,加载必要的设备驱动,初始化系统管理进程boot
init
(通常为systemd
或SysVinit
),随后逐步启动用户空间环境。
- 启动内核:一旦内核映像和initrd/initramfs被加载完毕,GRUB通过
-
Linux内核加载根文件系统
当GRUB将控制权移交给Linux内核后,内核开始执行自检和初始化过程,并完成根文件系统的挂载和初始化。这个过程中包括了从硬件检测、驱动加载到启动第一个用户进程
init
的多个步骤。 -
- 驱动加载与初始化:
- 初始化硬件驱动:在内核启动初期,内核会从initrd或initramfs加载预定义的硬件驱动和模块。initramfs提供了一个临时的内存根文件系统,包含启动所需的关键驱动和模块,如存储设备、文件系统和网络接口等硬件模块。
- 模块自动检测:现代内核支持模块自动检测(如
udev
服务),会根据当前系统硬件状况动态加载所需的内核模块。这样可以在尽量减少冗余模块加载的同时,确保对硬件的全面支持。 - 初始化设备节点:加载硬件驱动后,内核通过
/dev
目录创建设备节点,设备节点使用户空间的程序可以访问硬件设备。
-
- 挂载根文件系统:
- 找到根分区:在启动参数中,GRUB会指定根文件系统的位置,通常通过
root=UUID=<UUID>
或root=/dev/sdX
的方式,帮助内核找到对应的根分区。内核在此阶段会依据该参数确定具体的分区,并准备挂载根文件系统。 - 切换根文件系统:一旦识别并加载根文件系统,内核将从临时文件系统(initramfs)切换到真实的根文件系统,并将其设定为系统的主要操作环境。此时,内核会使用真实的根文件系统覆盖原来的initramfs根文件系统,执行
pivot_root
操作,使系统正式进入独立的根文件系统环境。 - 卸载initramfs:切换完成后,内核会卸载并释放initramfs的资源,以节省内存并确保系统只使用一个根文件系统。
-
- 启动init进程:
- 查找init进程:内核完成根文件系统的挂载后,会启动第一个用户空间进程,即
/sbin/init
。init
进程是所有用户空间进程的祖先,被赋予PID 1。 - 初始化系统管理(systemd或SysVinit):
- 如果使用
systemd
,/sbin/init
通常是一个指向systemd
的符号链接。systemd
负责启动和管理系统的服务,依赖系统配置启动目标(如multi-user.target
或graphical.target
)定义的服务列表来管理启动顺序。 - 如果使用传统的
SysVinit
,则/sbin/init
执行SysVinit脚本,在/etc/inittab
文件中根据不同的运行级别启动不同的服务。
- 如果使用
- 加载用户空间环境:
init
启动后,系统正式进入用户空间环境,此时init
会启动各种用户服务、后台进程以及登录服务。接着,系统进入正常操作状态,用户可以开始交互式操作系统。
-
systemd方式启动
在现代Linux发行版中,
systemd
是主要的系统和服务管理工具,负责系统启动、服务管理、系统资源配置和日志记录等功能。相比传统的SysVinit,systemd
在启动流程中引入了并行化服务加载和更细致的依赖管理,从而显著提升了启动效率。以下是systemd
启动Linux系统的主要步骤:- 加载启动目标(Target):
systemd
的启动目标类似于传统SysVinit的运行级别(runlevel),通过定义不同的启动目标来控制系统的启动模式。常见的目标有:- multi-user.target:加载命令行模式,适合多用户环境但不启动图形界面。通常等同于SysVinit的“运行级别3”。
- graphical.target:启动图形界面(如GNOME、KDE等),适用于桌面环境用户,相当于传统的“运行级别5”。
- rescue.target:启动到救援模式,通常仅加载基础服务和系统命令行,用于系统维护。
- emergency.target:仅加载最少的服务和shell,用于系统修复和紧急情况。
- 当系统启动时,
systemd
根据配置文件(如/etc/systemd/system/default.target
)指定的默认启动目标,决定进入的系统模式,并根据目标加载必要的服务和依赖进程。用户也可以在引导菜单中通过参数(如systemd.unit=multi-user.target
)临时指定启动目标。
-
- 并行加载服务与依赖解析:
-
并行服务启动:
systemd
的一个显著特性是支持并行启动服务。传统的SysVinit采用顺序加载,导致系统启动时需要等待服务依次启动,而systemd
能够分析服务的依赖关系,按需并行加载,提高启动速度。例如,网络服务可以与日志服务并行加载,而不必依赖特定的顺序。 -
依赖解析:
systemd
通过依赖链管理服务启动顺序。每个服务单元(Unit)文件可以包含启动前的依赖项(Requires
和After
),systemd
会根据这些依赖关系决定启动顺序。例如,network.service
可能会在sshd.service
之前启动,以确保SSH服务有网络支持。单元类型包括服务(
service
)、挂载点(mount
)、设备(device
)、路径(path
)、socket等。systemd
会根据这些单元文件的依赖关系,按最优顺序加载所需服务和挂载文件系统,确保启动的效率和可靠性。
-
- 启动用户空间并进入登录界面:
- 服务就绪检测:当所有核心系统级服务和依赖项被加载后,
systemd
会检查是否满足启动目标的所有条件,以确保系统达到稳定状态。例如,在graphical.target
模式下,需要等待图形服务(如gdm.service
)启动完毕。 - 启动显示管理器和登录界面:在桌面环境中,
systemd
会启动显示管理器(如GDM、LightDM或SDDM),以提供图形化登录界面。对于命令行模式,systemd
则会启动getty
服务,提供控制台登录提示符。 - 用户会话管理:当用户登录后,
systemd
启动一个单独的用户会话管理实例(systemd --user
),管理用户会话中的服务和应用程序。用户会话的独立管理有助于资源隔离、自动重启崩溃的用户级服务,并为多用户提供稳定的环境。
-
SysVinit方式启动
在较老版本的Linux系统和经典发行版中,
SysVinit
是主要的初始化系统。它通过定义运行级别和依次执行启动脚本来控制系统启动。SysVinit
启动过程较为简单,但缺乏现代systemd
的并行和依赖管理能力,因此启动速度和灵活性有限。以下是SysVinit的启动流程:- 加载运行级别(Runlevel):
SysVinit
通过/etc/inittab
文件来配置系统的启动模式,每个运行级别(runlevel)定义了一组特定的启动服务和资源限制,允许系统在不同环境下启动。常见的运行级别包括:
- 0:关机
- 1:单用户模式(用于维护和故障排查,不启动网络和多用户服务)
- 2:多用户模式(不启动网络文件系统)
- 3:完整多用户模式,启用网络支持
- 5:图形用户界面模式,通常是桌面环境的运行级别
- 6:重启
SysVinit
启动时会从/etc/inittab
中读取默认的运行级别,并根据该级别加载相应的服务。例如,默认运行级别为3(多用户模式),SysVinit
会进入对应的多用户环境,并加载必要的网络服务。
- 加载运行级别(Runlevel):
-
- 顺序启动服务:
SysVinit
的核心是按照预定义顺序执行启动脚本,启动流程较为严格的顺序控制。启动服务的具体流程如下:- 启动脚本位置:每个运行级别对应一个目录(如
/etc/rc.d/rc3.d
),该目录下包含一系列按字母或数字顺序命名的启动脚本符号链接。文件名通常以S
或K
开头,后接数字序号,如S10network
、S20sshd
,分别表示启动网络和SSH服务。 - 顺序执行脚本:当进入某个运行级别时,
SysVinit
会按照编号顺序执行脚本。例如,在rc3.d
目录下,文件名S10network
表示网络服务将在编号较低的服务(如S05serviceA
)之后启动,而在S20sshd
之后启动的服务则需等待SSH服务准备就绪。 - 服务停止:若切换到不同的运行级别(如从3切换到0关机),
SysVinit
会执行以K
开头的脚本,按编号顺序依次关闭不再需要的服务,如K20sshd
用于停止SSH服务。
-
- 进入用户空间:
- 启动登录界面:服务加载完成后,
SysVinit
会根据运行级别决定是否启动控制台或图形登录界面。在多用户模式(运行级别3),SysVinit
会启动控制台登录进程(getty
),等待用户通过终端登录。在图形模式(运行级别5),则启动图形显示管理器(如GDM、LightDM)以提供桌面环境的登录界面。 - 用户会话:用户登录后,系统进入用户空间,所有的用户进程在内核的支持下独立运行。此时,用户可以启动应用程序和服务,开始正常的操作。