资讯动态

Linux启动全链路解析:从固件到systemd的故障排查与优化

发布时间:2026/10/13 3:49:05 来源:尧图企业网站定制
按下电源键之后屏幕上快速滚过的那些英文日志大部分人都不会细看——能顺利进桌面的时候谁也不关心这些信息。但一旦哪天开机卡住、黑屏、或者进系统要等五分钟这些平时被忽略的启动过程就成了唯一的查找线索。对于任何维护 Linux 系统或嵌入式设备的人来说搞清楚 Linux 系统启动的完整链路是最基础也最救命的一项技能。这篇文章从固件开始一路讲到内核初始化、systemd 接管以及最后的故障排查和速度优化。不管你是刚入门的用户还是已经在用 Linux 工作的开发者和运维顺着这条链路过一遍以后碰到启动相关的问题至少知道该往哪个方向查而不是无条件重装。1. 固件与引导加载程序启动的最初一百米1.1 BIOS 与 UEFI真正的第一行代码在主板里很多人以为按下电源键后 Linux 就开始运行了其实不对。CPU 通电后的第一条指令来自主板上的固件传统上叫 BIOS新机器上基本都是 UEFI。这个阶段的任务很纯粹初始化最基本的硬件、自检内存和外设然后按照启动顺序找到一个可以引导的设备把控制权交给下一段程序。区分 BIOS 和 UEFI 不光是概念问题排障时候非常关键。BIOS 传统模式下固件直接读取硬盘的第一个 512 字节扇区也就是 MBRMBR 里再嵌着引导代码去找 GRUB 的后续部分。UEFI 模式不一样它会读取一个独立的 EFI 系统分区ESP这个分区用 FAT 格式里面存放着 .efi 文件比如\EFI\GRUB\grubx64.efi或者\EFI\systemd\systemd-bootx64.efi。主板固件里有启动条目记录着要去 ESP 里执行哪个文件。我处理过几次装完 Linux 重启直接进 Windows的情况原因几乎都是 UEFI 启动顺序里Windows Boot Manager 排在 GRUB 前面。进主板设置调整启动顺序或者在 Linux 里执行一次efibootmgr把启动条目顺序改掉就能解决。还有一类是开了 Secure Boot 后 GRUB 被拦因为固件在校验引导文件签名的时候不认 GRUB 的版本这时候要么在主板里关闭 Secure Boot要么装签名能被识别的引导程序。固件阶段出问题的典型症状是屏幕完全没有 BIOS 或 Logo 画面或者机器反复断电重启根本走不到 GRUB。遇到这种情况别先怀疑系统优先检查电源、内存、主板设置和启动设备是否正常。记住一个原则启动排查要按阶段来拆固件阶段的问题就留在固件阶段解决不要一头扎进系统层面找原因。1.2 GRUB 加载内核与传递控制权固件把控制权交给 GRUB 之后GRUB 要做的事情有两件从配置文件里读出菜单然后根据用户选择去加载内核和 initramfs 到内存最后跳转到内核入口。GRUB 的配置逻辑很多人搞混。用户真正应该修改的是/etc/default/grub和/etc/grub.d/下的脚本改动之后用grub-mkconfig -o /boot/grub/grub.cfg或者发行版封装好的update-grub生成最终的grub.cfg。直接手改grub.cfg当然能生效但下次一执行生成命令就被覆盖等于白改。所以我一直建议所有对 GRUB 的持久化修改都走改配置 生成这条路手改grub.cfg只用于临时调试。GRUB 里最实用的调试功能是启动时按e进入编辑模式。你可以临时修改内核命令行比如去掉quiet和splash参数让启动日志全量显示或者加上nomodeset绕过显卡驱动加载。这种修改只是本次启动生效不会写进配置文件非常适合一次性诊断。我修复过一台装上新版桌面环境后开机黑屏的机器就是在 GRUB 命令行里临时加nomodeset确认是显卡驱动在启动时崩溃才回头去处理驱动版本问题的。内核命令行是通向整个启动过程的一扇门常用参数列个表供参考参数作用典型场景nomodeset禁用内核显卡模式设置使用保守的显示方式开机黑屏、显卡驱动加载失败single进入单用户模式忘记密码、服务导致启动失败init/bin/bash指定替代的初始程序紧急修复根文件系统loglevel7打开内核调试日志排查内核早期 panicsystemd.unitrescue.target临时进入救援目标临时切换启动级别rd.break在 initramfs 阶段中断进入 shell调试 initramfs 启动问题还有一个非常容易被忽略的细节root参数指定根设备的方式。现代发行版默认用 UUID比如rootUUIDxxxxx原因很简单——硬盘接口插拔、SATA 编号变动时/dev/sda1这种名字可能对不上而 UUID 是文件系统的唯一标识基本不会变。所以排查启动失败时如果看到找不到根设备之类的问题第一反应应该是用blkid查看磁盘的实际 UUID再和 GRUB 里的root参数比对。1.3 initramfs 里到底发生了什么有个常见误解内核启动后能直接挂载根文件系统。实际上如果根分区用了 LVM、LUKS 加密、特殊文件系统或者硬盘控制器驱动没编进内核内核根本看不见根设备。解决方案就是 initramfs——一个临时的最小根文件系统把它打包进内存后由里面的脚本负责加载驱动、识别存储设备、组装 RAID/LVM、解密 LUKS最后挂载真实的根文件系统。initramfs 的内部结构其实不复杂。解包之后能看到bin、lib、sbin等目录里面放着 kernel 模块、必需的工具程序和一堆启动脚本。内核加载 initramfs 后把它作为临时根目录运行脚本按顺序执行完成后再用switch_root把根目录切换到真实磁盘上。这个机制也解释了为什么内核升级后一定要同步更新 initramfs。如果驱动列表是旧的新内核启动时可能缺模块最常见的结果就是启动卡在Waiting for device ...然后被丢弃到 initramfs 的 shell 提示符。重建 initramfs 的命令不同的发行版包装的名字不一样Debian 系是update-initramfs -uRHEL/Fedora 系是dracut -fArch 系是mkinitcpio -P。生成之后我习惯用lsinitrddracut或lsinitramfsDebian看一眼里面的模块列表确认硬盘控制器驱动真的在里面。这种检查每次做完内核升级后花一分钟就够却能避免在最不想看到的时候被拉进救援 shell。2. 内核初始化从汇编入口到真正的系统接管2.1 压缩内核解压与 start_kernelGRUB 把控制权交给内核时内核其实是压缩过的通常就是/boot下的 vmlinuz 文件。内核代码最开始一段是汇编写的它会完成页表初始化、CPU 特性探测、设置基本的段寄存器然后进入 C 语言世界的大门——start_kernel()。启动早期尤其依赖汇编完成环境搭建因为在进入 C 语言之前我们连一个像样的内存管理环境都还没有。start_kernel()是整个内核初始化的中枢。函数一路调下去依次建立内存管理、调度器、中断系统、时间子系统、内核工作队列、虚拟文件系统层。这个过程如果出问题机器根本到不了用户态连日志都来不及写进磁盘。所以内核专门实现了早期控制台在标准驱动初始化完成之前通过 VGA 或串口输出调试信息。早期启动崩溃的排查方式和平时的日志排查很不一样。因为此时磁盘和文件系统都还没准备好journalctl帮不上忙。正确做法是在内核命令行里加上loglevel7配合串口参数consolettyS0,115200用最原始的输出方式来捕获崩溃点。我在虚拟机上排查早期 panic 时经常这么干虚拟串口 loglevel 参数能非常直观地看到内核死在了哪个函数调用之后。2.2 设备探测与驱动模块的加载时机设备探测阶段是内核初始化里既关键又耗时的一环。现代 Linux 把大量设备驱动做成可加载模块放在 initramfs 或根文件系统里由 udev 根据硬件信息动态加载。设备驱动加载后相应的设备节点会出现在/dev下硬件信息则通过 sysfs 暴露在/sys下。这段逻辑是内核态和用户态的第一次协同。内核只负责把设备挂到总线上、创建 kobject 并上报事件真正的用什么驱动加载哪个模块由用户态的 udev 来处理。一旦这个协同链条出问题设备就加载不上或者加载错驱动。我遇到过一个比较典型的案例某块网卡在内核里被识别为另一型号的芯片导致驱动报错启动日志里能看到卸载重载的循环。最后靠modinfo和手工指定驱动别名才解决。启动慢的常见原因也往往藏在这一环驱动在等待一个永远不会出现的设备udev 等待规则排队或者 systemd 在等待某个挂载设备。这类问题最典型的表现就是屏幕上出现A start job is running for ...并开始倒计时。见到这种字样别慌看它等待的设备名十有八九是/etc/fstab里写了已经拔掉的硬盘或交换分区。我处理过的开机白等九十秒的机器原因就是这么简单fstab 里挂着一个不存在的 swap 设备。2.3 根文件系统挂载与 switch_root 的细节initramfs 阶段工作的终点是把真正的根文件系统挂载到某个临时目录然后执行switch_root或老内核的pivot_root把根目录切换过去。为什么是切换而不是直接挂载根因为 initramfs 本身也在根目录上直接覆盖会让已经打开的文件句柄失效而 switch_root 会先把旧根卸载掉、妥善迁移文件描述符再让新根成为根目录。这个环节翻车大多出在参数错误或模块缺失上。rootUUIDxxx写错、文件系统模块没编进内核、磁盘加密没正确解锁都会让切换失败把你丢进 initramfs 的救援 shell。进了这个 shell 不要慌先用blkid查看磁盘 UUID用ls /dev/mapper查看 LVM 或加密设备映射对照 GRUB 里的root参数和fstab检查哪里不一致。绝大多数卡在 initramfs的问题到这里都能定位出来。3. systemd 接管第一进程如何组织整个世界3.1 PID 1 与 systemd 的基本架构内核初始化的最后一步是尝试执行根文件系统里的/sbin/init也就是 PID 1。主流发行版现在都用 systemd 作为 init它在接管之后的核心任务不是直接启动一堆东西而是按照依赖关系把系统上所有服务、挂载点、socket 组织成一张依赖图最终把系统带到某个目标状态。先理解一个底层概念PID 1 不是普通的进程它是所有用户态进程的祖先。如果 PID 1 崩溃或者被杀掉内核会因为找不到进程树的根而直接 panic。所以 systemd 在设计时做了大量的自我保护比如能用 dbus、cgroup 等机制管理服务不会因为某个服务崩溃就整个垮掉。systemd 用单元文件来定义所有可管理的东西。后缀不同含义也不同.service是服务.target是目标状态.socket是套接字激活单元.mount是挂载单元.timer是定时器。每个单元文件里写清楚启动条件、依赖关系、进程行为、资源限制。理解了这套概念你就理解了大半个 systemd。3.2 并行启动机制与依赖声明systemd 最大的特点就是并行启动。传统 SysVinit 是按顺序执行启动脚本上一个不结束下一个不开始。systemd 通过单元文件里的依赖声明构建一张有向无环图某个服务只要它依赖的条件满足了就可以立即启动不用等其他不相关的服务排队。单元文件里最关键的几个字段很多人分不清After只控制启动顺序不建立依赖。写成Afternetwork.target只表示我等网络先启动但不代表网络启动失败就不启动我。Requires硬依赖依赖失败自己也启动不了。Wants软依赖依赖失败不影响自己启动。Before反过来表示我启动之后再启动谁。我调一个需要网络就绪的定时上报服务时一开始只写了Afternetwork-online.target结果服务在网卡还没拿到地址时就启动了连接失败。加了Wantsnetwork-online.target之后才真正解决。这个区别特别容易踩网上很多教程混着用实际排查问题时要仔细。3.3 journald 与启动期日志的读取技巧systemd 自带日志服务 journald把服务和内核的日志统一收集起来。启动排障最常用的命令就那么几条建议直接背下来journalctl -b -1查看上一次启动的完整日志journalctl -b查看本次启动的日志systemctl --failed列出启动失败的服务systemctl status 服务名查看某个服务的详细状态和最近的日志journalctl -u 服务名 -b只看某个服务本次启动的日志这个命令序列基本覆盖了绝大多数启动问题。我处理服务启动失败时从来不会先急着改配置。先看systemctl status里的状态码再看journalctl -u的具体日志找到真正的失败原因再决定是改配置、改依赖还是禁用服务。记住systemctl status看到的Active: failed只是结果原因一定在日志里。日志里常见的错误比如端口被占用、配置文件语法错误、依赖的另一个服务没启动、文件权限不对。每种错误的处理方式完全不同所以日志这一步千万不能跳过。3.4 启动超时服务慢不难查怕的是 timeout启动排障里还有一类非常讨厌的问题服务既不爆红也不报错就是启动过程干等卡在那儿几十秒。这类问题多数和等待某个条件满足有关systemd 默认给每个启动任务一个 timeout到时间还不满足就标记失败。最典型的卡顿源是网络挂载点。/etc/fstab里写了 NFS 或者自动挂载的网络盘而网络在早期还没就绪systemd 就会一直等待这个挂载点直到超时。另一个高发源是等待设备节点的 udev 规则。这些问题的排查方式都一样看到A start job is running for ...时记录它后面跟的单元名称journalctl里搜索定位到 fstab 或对应的.mount单元然后调整挂载选项或删除不需要的挂载。4. 开机时间优化从两分钟到二十秒的实战路径4.1 先用 systemd-analyze 做启动耗时体检优化启动速度前先要知道时间花在哪了。systemd 自带一组分析工具第一步执行systemd-analyze time这条命令会显示固件、内核加载、用户空间启动分别花了多少时间。第二步执行systemd-analyze blame列出每个服务各自的耗时按从高到低排列能直观看到哪些服务是大头。再执行systemd-analyze critical-chain这条命令会列出从启动到终态的关键依赖链把每个环节的耗时标出来一眼就能看出瓶颈在哪几个服务之间。注意critical-chain显示的是最长的关键路径不是所有服务。如果最耗时的服务不出现在这条路径里说明它被其他服务并行覆盖了优化它的优先级可以降低。4.2 找出假慢挂载等待与设备超时看耗时报告时要区分真实的慢和掩盖起来的超时。有的是服务本身初始化慢比如数据库要加载大量数据有的则是白白等一个不会出现的东西典型就是 fstab 里写了不存在的设备。前面提到的A start job is running for dev-disk-by...就是这种假慢。判断假慢的方式很简单如果一个服务耗时接近系统默认的 90 秒超时或者在等待一个和设备、挂载点有关的名字那多半不是正常慢而是在等一个超时周期结束。这种问题修起来也最痛快把 fstab 里错误行删掉或注释掉把不要的挂载单元 mask 掉启动时间立刻下降一大截。排查这种问题时systemctl list-jobs能看到正在等待中的任务很直白。4.3 禁用、延迟与按需启动服务的三种操作优化服务启动有三种不同力度的操作按需选择第一种是禁用服务。systemctl disable 服务名让它在开机时不再启动。这只会禁止自启不会删除服务定义以后手动systemctl start启动不受影响。适合那些明确用不到的服务比如打印服务、蓝牙服务、不必要的 socket 激活。第二种是彻底屏蔽服务。systemctl mask 服务名把服务定义链接到/dev/null服务被彻底锁定连手动启动都做不到。当某个服务总是被其他服务依赖拉起来又反复引发启动问题时mask 比 disable 更有效。第三种是按需启动。systemd 支持 socket 激活和 timer 单元可以让一个服务被访问时才启动。比如远程登录用的 sshd可以配置成收到连接时才拉起进程平时不占资源也不拖慢启动。还有一堆周期性任务可以从 cron 改成 systemd timer让它们按时间计划在系统空闲时运行。我优化一台老旧笔记本时用到的组合是blame找出最耗时的几个服务两个根本不用的直接 disable一个被误设为开机启动的数据库服务改成 socket 激活fstab 里删掉一个不存在的交换分区。启动时间从原来的 70 秒压到了 15 秒左右全是系统层面调整没换硬件。4.4 内核参数与 initramfs 压缩的最后加成系统层面的优化做完之后还能从内核和 initramfs 身上再挤一点时间。内核命令行里去掉quiet不会让启动变快但能看到卡在哪去掉splash能减少图形界面的初始化延迟。如果你的 initramfs 用的是默认的 gzip 压缩可以考虑改成 lz4 或 zstd解压速度快得多但要注意你的发行版是否默认支持。硬件层面同样有优化空间。SSD 的随机读写速度远胜机械盘换 SSD 是最直观的提速。固件设置里有快速启动快速自检之类选项的开启后能减少 POST 阶段的等待。但注意固件快速启动偶尔会引发设备不能被正确探测的问题属于取舍。启动优化有一个总原则不要只看单点耗时要结合critical-chain判断真正的关键路径。优化一个不在关键路径上的服务对整体启动时间几乎没有帮助这个坑我踩过希望你别踩。5. 启动故障排查一次完整的救砖过程5.1 从症状到阶段五步定位法启动排障最重要的一步是先搞清楚卡在哪个阶段这里我把判断逻辑整理成快速对照现象所在阶段排查方向完全无显示机器无响应或反复重启固件电源、内存、主板、启动设备卡在主板 Logo进不了引导加载程序固件到 GRUB启动顺序、ESP 分区、GRUB 安装出现 GRUB 菜单但选择后无反应GRUB 到内核GRUB 配置、内核文件完整性内核日志刷到某处后停住内核初始化设备驱动、根设备、早期 console显示A start job is running...倒计时systemd 用户空间服务启动、挂载单元、超时问题把症状对应到阶段排查范围立刻缩小不会盲目重装或者乱改配置。这条判断逻辑我已经用了很多年每次都能快速定位到正确的层级。5.2 日志是启动排查的第一现场定位到阶段之后接下来就是读日志。内核日志在启动早期会写进内核环形缓冲区进入用户空间后由 journald 接管。如果是上次启动失败的问题重启后第一件事执行journalctl -b -1 -p err只看上次启动的错误级别日志能把大量无效信息过滤掉。如果还是定位不到再执行不带-p err的journalctl -b -1逐段看启动过程在哪个环节停止或大量报错。我碰到过一个很典型的问题机器启动后黑屏只有光标闪烁。进入急救模式后查看日志发现图形驱动的 DRM 模块初始化失败。这时问题性质就变了不是系统坏了是显卡驱动和内核版本不匹配。解决方式是更新或回退驱动或者先加nomodeset参数应急。如果没有日志这个过程就像在黑暗里摸开关全凭运气。5.3 救援模式与 live 环境 chroot 修复如果连正常启动都不行还有两条路救援模式和 live 环境。救援模式通常能从 GRUB 启动菜单进入会停在一个带 root shell 的紧急环境里适合修改配置或重建 initramfs。如果根文件系统损坏到连救援模式都进不去就得准备一张 USB 的 Linux live 盘启动到 live 环境之后把磁盘上的根分区挂载到一个目录再 chroot 进去修复。chroot 的意思是把那个挂载点当作新的根目录运行命令这样就能在坏系统内部执行修复操作。典型的 chroot 修复流程# 1. 确认磁盘结构 lsblk blkid # 2. 挂载根分区 mount /dev/sdXx /mnt # 3. 挂载必要的虚拟文件系统 mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys mount --bind /run /mnt/run # 4. chroot 进入系统 chroot /mnt /bin/bash # 5. 执行修复命令比如重建 initramfs update-initramfs -uchroot 之前一定要用lsblk和blkid把磁盘结构看清楚确认挂载的是根分区而不是数据盘或 boot 分区。如果系统用了 UEFI修复引导程序时还要把 EFI 分区挂载到/mnt/boot/efi不然grub-install会找不到 ESP。5.4 重建 initramfs 和修复 GRUB 的常用命令最后分享两个最常用的修复动作在 chroot 之后执行或者在急救模式下执行。重建 initramfs# RHEL / Fedora dracut -f # Debian / Ubuntu update-initramfs -u # Arch Linux mkinitcpio -P修复 GRUB# Debian/Ubuntu 的 UEFI 系统 grub-install --targetx86_64-efi --efi-directory/boot/efi update-grub # RHEL/Fedora grub2-install --targetx86_64-efi --efi-directory/boot/efi grub2-mkconfig -o /boot/grub2/grub.cfg执行完之后退出 chroot、卸载挂载、重启大部分系统进不去的问题就能恢复。记住重建 initramfs 前先确认当前内核版本和根设备 UUID修复 GRUB 前先确认 ESP 分区挂载位置这两点错了问题只会更严重。还有一个很容易被忽略的场景根分区磁盘空间满了也可能导致启动失败。因为系统在跑的过程中需要写日志、写临时文件磁盘满了之后很多服务直接起不来。急救模式里执行df -h看一眼空间占用清理掉日志和临时文件有时候比重建 initramfs 更关键。我对启动排障的最后一句话是不要遇到启动问题就想着重装。启动链路上的每个阶段都有对应的日志、命令和修复方法。固件阶段查固件引导加载程序阶段查 GRUB 和 ESP内核阶段加 loglevel 查早期日志initramfs 阶段进救援 shell 查根设备和模块systemd 阶段用 journalctl 查服务状态。走完这条链路你已经能处理 90% 以上的启动故障剩下的 10% 也基本都能准确定位到是硬件还是驱动问题解决起来方向明确得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑