1. 从按下电源到屏幕亮起:一次开机旅程的宏观图景
每次按下电脑的电源键,看着屏幕上开始滚动那些熟悉的字符,你有没有想过,这台机器究竟是如何从一堆冰冷的硬件,一步步苏醒,最终变成一个可以让你敲命令、跑程序的完整操作系统的?对于Linux用户,尤其是运维、开发或者任何想深入理解系统底层的人来说,搞懂开机启动流程,就像拿到了系统的“地图”。它不仅是面试时的经典考题,更是你排查“系统起不来”、“服务没启动”、“卡在某个黑屏界面”这类问题的根本依据。今天,我就结合自己这些年折腾服务器和桌面的经验,带你走一遍Linux系统从通电到登录提示符出现的完整旅程,我会尽量把每个阶段“为什么这么做”以及“可能在哪里翻车”讲清楚。
整个流程可以粗略地分为几个大的阶段:首先是硬件自己上电自检和初始化,这个阶段操作系统还没介入;然后,一个小而精的引导程序被加载,它的唯一任务就是找到并启动真正的操作系统内核;内核接过控制权后,开始初始化计算机的核心环境,并挂载根文件系统;最后,由用户空间的初始化进程拉起所有我们需要的系统服务和应用。听起来步骤清晰,但魔鬼藏在细节里。无论是传统的BIOS+MBR组合,还是现代的UEFI+GPT组合,亦或是
systemd
、
SysV init
等不同的初始化系统,都会让这条路径产生微妙而重要的变化。我们这就开始。
2. 固件初始化阶段:BIOS与UEFI的战场
当你按下电源按钮,CPU的复位引脚被触发,从一个预设的物理地址(对于x86架构,是
0xFFFF0
)开始执行第一条指令。这个地址指向主板上一块特殊的ROM芯片——这就是固件(Firmware)的地盘。目前主流的固件有两种:传统的
BIOS
和现代的
UEFI
。理解它们的区别,是理解后续所有步骤的基础。
BIOS 的工作方式相对“古老”和直接。它进行完基础的硬件自检后,就会按照你在BIOS设置里定义的顺序(比如先U盘、再硬盘、再网络),去每个存储设备的第一个扇区(512字节)寻找一段特殊的代码——主引导记录。这里有个关键点:BIOS本身不认识文件系统。它只会傻傻地读取硬盘的0柱面、0磁头、1扇区,也就是第一个512字节。因此,引导程序必须把自己压缩在这512字节内,这极大地限制了引导程序的复杂度和功能。
UEFI
则是一个更先进的微型操作系统。它本身支持文件系统和驱动,因此不再需要去搜索一个特定的扇区。UEFI固件会直接读取一个特殊的分区——
EFI系统分区
。这个分区通常是FAT32格式,UEFI固件能直接识别并遍历其中的文件。启动时,UEFI会根据其内部变量(NVRAM)中存储的启动项,直接去ESP分区里找到对应的
.efi
可执行文件并运行它。这个
.efi
文件就是我们的引导程序,比如
grubx64.efi
。因为不受512字节限制,UEFI引导程序可以做得非常强大和复杂。
注意:很多人在新硬件上安装Linux时,会遇到“无法找到可引导设备”的错误,这十有八九是因为安装介质是BIOS模式(MBR格式)制作的,而你的电脑在UEFI模式下运行。或者反过来。确保安装介质的模式(UEFI还是Legacy)与你的BIOS/UEFI设置一致,是成功的第一步。
那么,如何知道你的系统是用哪种方式启动的呢?一个简单的方法是检查是否存在
/sys/firmware/efi
目录。如果存在,你就是UEFI启动;如果不存在,你很可能就是传统的BIOS启动。另一个方法是使用
efibootmgr
命令,如果它能列出启动项,那肯定是UEFI环境。
# 检查是否为UEFI启动
ls /sys/firmware/efi
# 查看UEFI启动项 (仅UEFI模式下有效)
sudo efibootmgr -v
对于“老显卡改BIOS支持UEFI启动”这类网络热词,其背景是:一些较老的显卡其显卡BIOS(VBIOS)不支持UEFI的GOP协议,只支持传统的BIOS的VBIOS协议。当主板运行在纯UEFI模式(关闭了CSM兼容性支持模块)时,就无法初始化这块老显卡,导致黑屏。所谓的“改BIOS”,就是给老显卡的VBIOS添加UEFI GOP模块,使其能在纯UEFI环境下被识别。这是一个高风险操作,普通用户不建议尝试。
3. 引导加载程序:GRUB2如何找到内核
固件找到了引导程序,并把控制权交给了它。在Linux世界,这个引导程序的绝对霸主是 GRUB2 。它的任务很明确:加载操作系统内核镜像和初始内存盘,然后把控制权交给内核。但这个过程并不简单。
在BIOS+MBR的世界里,由于MBR只有512字节,根本放不下完整的GRUB2。所以采用了 分阶段加载 的策略:
- Stage 1 : 这512字节的MBR里,前446字节是引导代码,它唯一的作用是找到并加载 Stage 1.5 。
- Stage 1.5 : 位于MBR之后的扇区(大概是第2到第63扇区)。这个阶段包含了识别常见文件系统(如ext4, xfs)的驱动。因为Stage 1的代码太简单,不认识文件系统,而Stage 1.5认识。它就能去文件系统里找到 Stage 2 了。
-
Stage 2
: 这才是GRUB2的主体,通常存放在
/boot/grub2/目录下。它会读取配置文件(如grub.cfg),显示那个我们熟悉的图形化或命令行菜单,让你选择要启动的内核版本。
在UEFI+GPT的世界里,事情就简单多了。GRUB2的主体直接被编译成一个
.efi
可执行文件(如
grubx64.efi
),存放在ESP分区里(比如
/EFI/ubuntu/
)。UEFI固件直接加载并执行这个文件,它自己就包含了所有需要的功能,不需要分阶段。
GRUB2的核心工作是解析其配置文件
grub.cfg
。这个文件通常不是直接编辑的,而是由
grub2-mkconfig
命令根据
/etc/default/grub
和
/etc/grub.d/
目录下的脚本自动生成。我们来看看一个典型的
grub.cfg
中,一个启动项的关键部分:
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-xxxxx' {
recordfail
load_video
gfxmode $linux_gfx_mode
insmod gzio
insmod part_gpt
insmod ext2
set root='hd0,gpt2'
linux /boot/vmlinuz-5.4.0-xx-generic root=UUID=xxxx ro quiet splash
initrd /boot/initrd.img-5.4.0-xx-generic
}
-
set root='hd0,gpt2': 告诉GRUB,内核文件在哪个设备哪个分区。hd0表示第一块硬盘,gpt2表示GPT分区表下的第二个分区。 -
linux /boot/vmlinuz-... root=UUID=xxxx ...: 这是最关键的一行。linux命令加载内核镜像文件vmlinuz,并将后续参数作为 内核启动参数 传递给内核。其中root=UUID=xxxx指定了根文件系统所在分区的UUID,这是内核接下来要挂载为“/”的地方。ro表示先以只读方式挂载(后续由初始化进程重新挂载为读写),quiet splash是控制启动画面的参数。 -
initrd /boot/initrd.img-...: 加载初始内存盘。这个文件至关重要,我们下一节详细讲。
实操心得:很多时候系统启动失败,卡在
grub>命令行界面,就是因为grub.cfg损坏或指向的root设备不对。此时,你可以在grub>命令行下手动输入上述set root、linux、initrd三条命令来临时引导系统。进入系统后,再修复GRUB配置。记住ls命令在GRUB命令行下可以列出设备,帮你找到正确的分区。
4. 内核初始化与initrd的桥梁作用
GRUB将控制权、内核镜像(
vmlinuz
)以及初始内存盘(
initrd
或
initramfs
)加载到内存后,就功成身退了。内核开始解压自己并执行。但这里马上遇到一个问题:内核镜像为了保持精简,默认只包含了访问根文件系统所
必须
的驱动(比如你编译内核时选定的文件系统驱动、磁盘控制器驱动)。而你的根文件系统可能在一个非常复杂的存储方案上,比如
软RAID
、
LVM逻辑卷
、
加密分区
或者
网络挂载
。
内核在启动初期,根本没有加载这些复杂存储方案所需的驱动和工具。这就产生了一个“先有鸡还是先有蛋”的矛盾:要挂载根文件系统,需要驱动和工具;但这些驱动和工具又存放在根文件系统里。
初始内存盘
就是为了解决这个矛盾而生的。它是一个临时的根文件系统,在真正的根文件系统被挂载之前,被加载到内存中运行。你可以把它想象成一个装在内存里的“急救包”。这个急救包里包含了挂载真实根文件系统所需的所有驱动、内核模块、工具(如
cryptsetup
用于解密,
lvm
用于激活卷组,
nfs
客户端用于网络挂载)以及一个简单的脚本
/init
。
内核启动的最后一步,就是执行这个
initrd
中的
/init
脚本。这个脚本通常用
BusyBox
工具集写成,它的任务非常明确:
-
初始化必要的设备(如
/dev)。 -
加载复杂存储方案所需的驱动模块(从
initrd自身加载)。 -
发现真正的根文件系统设备(通过解析内核参数
root=,可能涉及解密、激活LVM等操作)。 -
将真正的根文件系统挂载到
/sysroot目录下。 -
最后,执行一个**
switch_root**操作。这是一个关键动作:它清理当前的内存环境,将/sysroot切换为新的根目录/,然后去新的根文件系统里执行/sbin/init程序(现在是systemd或sysvinit)。
至此,内核的使命完成,用户空间的初始化进程开始接管。你可以通过查看
/boot
目录下的文件来了解你系统使用的
initrd
,并使用
lsinitramfs
或
dracut -l
等工具来查看
initrd
里面具体包含了哪些模块和文件。
# 查看initrd内容示例 (Ubuntu/Debian)
lsinitramfs /boot/initrd.img-$(uname -r) | head -20
# 查看initrd内容示例 (RHEL/CentOS/Fedora)
sudo dracut -l /boot/initramfs-$(uname -r).img
踩坑记录:我遇到过服务器重启后卡在“Loading initial ramdisk”之后,提示找不到根设备。原因是在内核升级后,新生成的
initrd没有包含硬盘控制器(如megaraid_sas)的驱动。这是因为生成initrd的工具(如update-initramfs或dracut)在自动探测依赖时漏掉了。解决办法是手动将驱动模块名添加到配置中,然后重新生成initrd。例如,在Ubuntu上,编辑/etc/initramfs-tools/modules文件,加入megaraid_sas,然后运行sudo update-initramfs -u -k all。这个坑告诉我们,对于使用特殊硬件驱动的系统,不能完全依赖自动化工具。
5. 用户空间的起点:systemd的崛起与统治
当
initrd
的
/init
脚本执行
switch_root
后,系统的根目录已经切换到了硬盘上真正的根文件系统。接下来,系统会寻找并执行这个新根文件系统下的
/sbin/init
程序(历史上,这是PID为1的进程)。在过去的十多年里,大多数Linux发行版使用的是
SysV init
系统,它依靠运行级别(Runlevel)和一堆放在
/etc/rc.d/
目录下的启动脚本来管理服务。
然而,现在几乎所有的现代主流发行版(RHEL 7/8/9, CentOS 7/8, Fedora, Ubuntu 16.04+, Debian 8+, openSUSE, Arch等)都切换到了
systemd
。
/sbin/init
现在通常是一个指向
/lib/systemd/systemd
的符号链接。
systemd
的目标不仅仅是初始化系统,它更是一个庞大的系统和服务管理器,负责管理从启动到关机的整个生命周期。
为什么是
systemd
?因为它解决了SysV init的几个核心痛点:
-
并行启动
:SysV init是严格的串行执行脚本,速度慢。
systemd通过依赖关系分析,最大限度地并行启动服务,显著加快启动速度。 -
精确的依赖管理
:SysV init的依赖写在脚本注释里,脆弱且不精确。
systemd使用明确的单元文件(Unit File)定义服务、套接字、挂载点等,并声明它们之间的依赖(如Requires=,After=),管理更清晰可靠。 -
统一的管理接口
:使用
systemctl命令可以统一地启动、停止、重启、查看状态、启用开机自启所有类型的单元,体验一致。 -
日志集成
:
systemd自带日志系统journald,所有进程的标准输出和错误都会被收集,使用journalctl命令可以方便地查看,再也不用到处找/var/log/messages,syslog了。
systemd
启动过程的核心是它的“目标”(target)。你可以把target理解为一组需要达到的状态的集合,类似于SysV init的运行级别,但更灵活。默认的启动目标通常是
graphical.target
(带图形界面)或
multi-user.target
(多用户命令行)。
systemd
的启动流程大致如下:
-
systemd作为PID 1进程启动。 -
执行默认target(例如
multi-user.target)及其所有依赖的target(如basic.target,sysinit.target)。 -
每个target定义了需要启动的单元(units)。
systemd根据单元文件中定义的依赖关系,并行启动这些单元。 -
单元包括服务(
.service)、挂载点(.mount)、设备(.device)、套接字(.socket)等。例如,它会先挂载/etc/fstab中列出的文件系统(.mount),然后启动网络服务(network.service),最后启动sshd.service等应用服务。
你可以使用
systemctl
命令来查看系统启动耗时和各个服务的启动情况:
# 查看系统启动耗时
systemd-analyze blame
# 查看启动流程的瀑布图
systemd-analyze critical-chain
# 查看某个服务的详细状态
systemctl status sshd.service
对于网络热词“trying to remove systemd which is protected”,这通常发生在一些用户试图用
apt remove systemd
或
yum remove systemd
时。几乎所有的核心系统组件(网络、日志、登录)都深度依赖于
systemd
,强行移除它会破坏系统的完整性,所以包管理器会阻止这个操作。如果你真的不想用
systemd
(比如想用
openrc
或
runit
),那应该在安装发行版时就选择不支持
systemd
的衍生版(如Devuan),而不是在一个基于
systemd
的发行版上暴力移除它。
6. 启动过程中的常见故障排查实战
理解了流程,排查问题就有了路线图。当你的Linux系统无法启动时,不要慌,根据卡住的阶段,采用不同的方法。
阶段一:硬件/固件阶段故障
- 现象 :按下电源键,风扇转,但屏幕无任何显示,或者直接进入BIOS/UEFI设置界面。
-
排查
:
- 检查硬件连接:内存、显卡、硬盘线是否插紧。
- 听主板蜂鸣器报警声(如果有),对照主板手册判断。
- 进入BIOS/UEFI设置,检查启动设备顺序是否正确,硬盘是否被识别。
- 对于“BIOS中开启了虚拟化但任务管理器中显示已禁用”的问题,这通常不是Linux启动问题,而是Windows下的情况。可能的原因有:a) 需要在BIOS中同时开启VT-x和VT-d(或AMD-V);b) 某些品牌机(如联想)有“Windows Hypervisor Platform”选项也需要开启;c) 保存BIOS设置后没有完全断电重启(需拔掉电源线等待10秒)。
阶段二:GRUB引导阶段故障
-
现象
:屏幕黑屏,左上角光标闪烁;或者直接显示
grub>救援命令行;或者报错“error: no such partition”, “error: file/boot/grub/i386-pc/normal.modnot found”。 -
排查
:
-
GRUB菜单丢失
:通常是因为GRUB配置文件损坏或MBR/ESP分区中的GRUB核心文件损坏。解决方案是使用Live CD/USB启动,挂载原系统的根分区和
/boot分区(如果需要),然后chroot进去重新安装和配置GRUB。 -
GRUB无法找到内核
:在
grub>命令行下,使用ls命令列出所有磁盘和分区,尝试手动设置root并加载内核。例如:
如果能成功启动,进入系统后务必修复GRUB配置。grub> ls # 查看所有设备,如(hd0) (hd0, gpt1)... grub> set root=(hd0,gpt2) # 假设你的/boot在第二个分区 grub> linux /boot/vmlinuz-5.4.0-xx-generic root=/dev/sda3 # 根据实际情况 grub> initrd /boot/initrd.img-5.4.0-xx-generic grub> boot
-
GRUB菜单丢失
:通常是因为GRUB配置文件损坏或MBR/ESP分区中的GRUB核心文件损坏。解决方案是使用Live CD/USB启动,挂载原系统的根分区和
阶段三:内核与initrd阶段故障
- 现象 :卡在“Loading Linux x.x.x ...”, “Loading initial ramdisk ...”, 或内核panic,提示“Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)”。
-
排查
:
-
最常见原因是
initrd镜像损坏或缺少必要的驱动(如磁盘控制器、文件系统、RAID、LVM模块)。可以在GRUB菜单编辑启动参数,在linux行末尾添加init=/bin/bash或rd.break(RHEL系)来进入紧急shell,检查/dev下是否有你的根磁盘设备(如sda3,dm-0等)。 -
如果进入紧急shell后能看到根设备,但挂载失败,可能是文件系统损坏。可以尝试
fsck修复。 -
如果根本看不到根设备,那一定是
initrd的问题。需要从Live环境chroot修复,方法同GRUB修复,重点是重新生成initrd。
-
最常见原因是
阶段四:systemd初始化阶段故障
- 现象 :系统卡在某个服务启动画面(如“Started Update UTMP about System Runlevel Changes”),或者直接进入紧急模式(emergency mode),提示“You are in emergency mode.”
-
排查
:
-
查看日志
:紧急模式下,首先运行
journalctl -xb查看从本次启动开始的详细日志,错误信息通常会高亮显示。-x提供更多解释信息,-b限定本次启动。 -
检查依赖
:使用
systemctl list-dependencies --reverse unit-name查看是哪个单元失败导致目标无法达成。 -
常见原因
:
-
/etc/fstab文件错误:比如UUID写错,或者网络文件系统(NFS)在网络未就绪时尝试挂载。可以在启动参数中添加nofail选项。 - 文件系统检测(fsck)失败 :需要手动输入root密码进行修复。
-
服务启动失败
:例如某个
.service文件配置错误。可以在启动参数中加入systemd.unit=multi-user.target(或rescue.target)来跳过图形界面,进入命令行模式,然后使用systemctl status和journalctl -u unit-name来排查具体服务。 -
对于“systemd 放启动脚本一会就服务关闭”,这通常是因为服务进程自己退出了。检查服务单元文件中的
Type配置(应该是forking或simple),并确保Restart策略设置正确(如on-failure)。更重要的是查看服务的日志journalctl -u your-service,看进程退出前输出了什么错误信息。
-
-
查看日志
:紧急模式下,首先运行
掌握这套从固件到服务的分层排查思路,大部分启动问题都能找到突破口。核心就是:看屏幕提示、查日志(
journalctl
)、在救援环境(Live CD或紧急Shell)下进行操作。
7. 高级话题:内核参数、启动目标与救援模式
除了基本的流程,还有一些高级技巧可以让你更自如地控制系统启动。
内核启动参数
:在GRUB菜单界面,按
e
键可以编辑当前启动项。在
linux
开头的行末尾,你可以添加或修改参数。这些参数直接影响内核和
systemd
的行为。
-
single或1: 直接进入单用户模式(救援模式),获得一个root shell,用于系统修复。 -
systemd.unit=rescue.target: 让systemd启动到救援目标(类似单用户模式,但更干净)。 -
systemd.unit=multi-user.target: 跳过图形界面,直接进入命令行多用户模式。 -
quiet和splash: 分别控制是否显示详细启动信息和启动动画。删除它们可以看到详细的启动过程,便于调试。 -
nomodeset: 一个非常有用的参数,告诉内核在启动阶段不要设置显卡显示模式。对于某些显卡(尤其是NVIDIA独显)驱动兼容性问题导致的黑屏、花屏,加上这个参数往往能让你先进入系统,然后再安装合适的驱动。 -
root=/dev/sdXY或root=UUID=xxx: 手动指定根文件系统设备,覆盖GRUB配置中的设置。
修改默认启动目标 :如果你想让系统默认启动到命令行界面(节省资源,适合服务器),可以这样做:
sudo systemctl set-default multi-user.target
要改回图形界面:
sudo systemctl set-default graphical.target
使用救援模式(Recovery Mode) :很多发行版在GRUB菜单里提供了一个“Recovery Mode”选项。这个选项通常会:
- 以只读方式挂载根文件系统。
- 启动一个最小化的系统,并直接给你一个root shell。
-
网络通常是关闭的。
这是一个非常安全的修复环境,因为根文件系统是只读的,避免了误操作。你可以在里面进行
fsck、重新安装GRUB、修改密码(passwd)、修复软件包等操作。操作完成后,记得执行mount -o remount, rw /将根文件系统重新挂载为读写模式,才能保存更改。
关于“适用于 linux 的 windows 子系统必须更新到最新版本”
:这个WSL2的提示与物理机Linux启动流程无关。它是Windows 10/11内置的Linux子系统在启动时,检测到其虚拟化组件版本过旧。解决方法是在Windows中以管理员身份打开PowerShell或CMD,执行
wsl --update
命令来更新WSL内核,或者运行提示中给出的
wsl.exe --update
命令。
理解Linux启动流程,就像掌握了系统的生命线。从固件自检到服务就绪,每一步都环环相扣。无论是为了优化启动速度,还是为了在系统崩溃时能冷静地修复,这份知识都极其宝贵。下次再遇到启动问题,不妨按照这个流程,一步步定位,你会发现,黑屏背后的世界,其实清晰可见。

364

被折叠的 条评论
为什么被折叠?



