Linux启动过程全解:从固件到systemd的接力赛排障指南
2. 引言启动只是一场接力赛我第一次真正搞懂Linux启动过程不是靠读源码也不是靠背启动图而是靠一个特别朴素的比喻整个开机过程就是一场四人接力赛。第一棒是固件把系统唤醒第二棒是引导加载器把内核请上场第三棒是内核自己完成硬件到软件的初始化最后一棒是init系统也就是systemd负责把所有用户态服务跑起来。每一棒接力的那一刻都是最容易掉棒的地方也是我们排查启动故障时的核心发力点。这个思路对谁最有用两类人。一类是刚接触Linux运维、天天在虚拟机里折腾、却搞不明白“开机卡住到底卡在哪一步”的新手另一类是已经会用一些命令、但对启动流程的因果链没有整体认知、遇到内核 panic 或者挂载失败就到处乱试的进阶用户。这篇文章不会像教科书那样按时间线把所有细节铺开而是沿着接力棒递进的顺序把每一棒是什么、它们之间怎么衔接、每一棒最容易掉链子的点在哪里讲透再附上我踩过的坑和排查套路。2. 四棒接力先看整张流程图再逐个拆解2.1 启动全程的五个关键节点为了后续讨论方便先把整条链路用“接力棒”的方式标记出来接力点0按下电源键CPU 从固定地址取指令固件BIOS 或 UEFI开始运行。接力点1固件完成硬件自检把控制权交给 Bootloader绝大多数场景下是 GRUB2。接力点2GRUB 加载内核镜像 vmlinuz 和 initramfs并把控制权交给内核。接力点3内核解压、初始化核心子系统挂载 initramfs 中的临时根文件系统然后切换到真正的根文件系统。接力点4内核执行 PID1 的 init 程序即 systemdsystemd 按依赖关系启动所有用户态服务。每一棒都有自己的“跑道”和“交接区”。排查启动问题本质上就是在确认“当前这一棒到底有没有跑完交接失败到底是哪一方的问题”。2.2 为什么用接力赛来类比最贴切接力赛的妙处在于每一棒运动员各司其职但最终成绩取决于交接是否顺畅。Linux 启动也一样——固件把控制权交给 GRUB 的时候GRUB 如果找不到配置文件或者磁盘路径变了就直接掉棒GRUB 把控制权交给内核的时候如果 initramfs 里缺了磁盘驱动内核连根分区都挂不上内核把控制权交给 systemd 时如果默认 target 损坏系统会掉进 rescue 模式而不是正常进入多用户环境。这个类比还有一个实用价值它帮我们建立“分段定位”的排查思路。启动卡住时不用慌先判断卡在哪一个接力区——是在 GRUB 菜单之前还是内核日志停止输出之后还是 systemd 的某个服务超时。分段定位比从头到尾瞎查快十倍。2.3 我建议每个想搞懂启动过程的人都跑一遍的验证方法光看文章不如动手做一次实验。准备一台虚拟机我用的是某开源虚拟化平台的默认配置按以下步骤走一遍整个过程大概半小时开机进入 GRUB 菜单如果是双系统或者有 GRUB 的机器开机时按 Shift 或 Esc 调出菜单。在 GRUB 菜单界面按e编辑启动项在 linux 那一行末尾加上systemd.log_leveldebug然后按 CtrlX 启动。开机后查看journalctl -b输出重点看 systemd 启动阶段的服务顺序和耗时。再正常重启一次对比两次启动日志的差异。这一套操作下来你对“启动过程是分阶段的”这个感知会非常强烈比看十篇文章都管用。3. 第一棒固件阶段——从按下电源键到 GRUB 出场3.1 BIOS 和 UEFI 的分工固件这一棒的核心任务就两个自检和找引导程序。传统 BIOS 的工作方式非常直接——先执行 POST加电自检然后按照 CMOS 里配置的启动顺序逐个扫描磁盘的 MBR 区域如果发现 MBR 的最后两个字节是0x55AA就把这一段 512 字节的代码加载到内存并跳转执行。UEFI 则是另一种玩法了。它不自找磁盘上的引导代码而是直接读取 ESPEFI 系统分区里的.efi文件比如/EFI/BOOT/BOOTX64.EFI。UEFI 固件实际上是一个小型操作系统它提供了更完善的运行时服务也让启动过程更快、更安全安全启动 Secure Boot 可以校验引导程序的签名。从运维排障的角度固件阶段最容易遇到的问题有三类开机直接黑屏连品牌 Logo 都不出现这是硬件级问题和系统无关。能看到固件界面但 GRUB 菜单不出现大概率是引导顺序不对或者 GRUB 装到了别的磁盘。开启 Secure Boot 后 GRUB 提示签名无效需要到 UEFI 设置里关闭 Secure Boot 或给 GRUB 签名。3.2 GRUB 接棒时到底做了什么GRUB2 目前是绝大多数 Linux 发行版默认的 Bootloader它的完整工作流程可以拆成两级第一级是引导镜像boot.img它被装在 MBR 或 GPT 的保护性 MBR 里。这个镜像很小只有一个任务找到核心镜像core.img。第二级core.img通常装在 MBR 和第一个分区之间的空隙里它比 boot.img 大得多包含文件系统驱动。有了它GRUB 才可以真正去识别分区、读取/boot/grub/grub.cfg配置文件。这里有一个很常见的误解有人以为 GRUB 是直接读取内核文件去启动的。其实不是GRUB 是先去读配置文件根据配置里定义的菜单项再去找vmlinuz-xxx内核文件和initramfs-xxx.img镜像文件。grub.cfg里最关键的一条命令是menuentry它定义了菜单项在这个块内部有两行是灵魂linux /vmlinuz-6.5.0-generic root/dev/mapper/vg-root ro quiet initrd /initramfs-6.5.0-generic.imglinux这一行不仅指明内核文件路径还通过root参数告诉内核真正的根分区在哪。如果系统使用 LVM 或加密盘GRUB 还得额外加载对应的模块这就引出了升级内核后必须执行update-grub或grub2-mkconfig的原因——你不更新配置GRUB 就不知道新内核的存在。3.3 实战修复一个“进不去 GRUB 菜单”的机器有一次我拿到一台旧服务器开机直接卡在固件界面提示找不到启动设备。第一反应不是重装系统而是进 UEFI 设置看启动顺序。结果发现启动优先级列表里第一项是 U 盘第二项是另一块没有系统的盘真正的系统盘排到了第三位。调整顺序之后重启GRUB 菜单就回来了。如果连 GRUB 都彻底坏了比如 MBR 被覆盖正确的做法是用发行版安装 U 盘进入救援模式用安装 U 盘启动选择“试用/救援”不同发行版叫法不同。打开终端执行lsblk找到系统盘符比如/dev/sda。在救援模式下用grub-install /dev/sda重新安装引导程序。再执行update-grub重新扫描内核并更新配置。需要注意如果根分区是 LVM 或 LUKS 加密盘在救援环境里要先vgchange -ay激活卷组、或cryptsetup luksOpen解锁加密分区否则grub-install之后还是起不来因为 GRUB 找不到它该找的分区。注意grub-install是写到磁盘引导区域不是写某个分区。目标参数一般写整块磁盘如/dev/sda而不是/dev/sda1。4. 第二棒交接GRUB 把内核交给 initramfs——启动排障的核心战场4.1 内核启动到底做了什么GRUB 把内核镜像加载到内存后会跳转过去执行。此时内核处于一个非常尴尬的状态它自身只包含基于 x86 的通用代码但系统的磁盘控制器、文件系统驱动、LVM 映射关系都还没加载。内核想挂载真正的根分区但可能连硬盘都识别不了。这就是 initramfs初始内存文件系统存在的意义。它是一个打包成 cpio 格式的小型根文件系统里面包含了内核在挂载真正根分区前所需要的一切——磁盘驱动、SCSI 控制器驱动、文件系统模块、LVM 工具、加密解密工具。启动流程的顺序是这样的内核先解压自身然后解压 initramfs 到内存中的临时根目录执行里面的/init脚本或者 systemd 作为 init由它去识别根分区、挂载真正的根然后通过switch_root把根目录切换到真正的根文件系统上释放 initramfs 占用的内存。4.2 怎样快速判断 initramfs 出了问题最常见的一个表现是GRUB 菜单正常选择内核后卡了很久然后跳出一个Kernel panic - not syncing: VFS: Unable to mount root fs...或者在类似弹出提示框。这个信息直接说明了一件事——内核找不到根分区。原因不外乎几个root参数指定的设备路径变了。比如原来用的是/dev/sda1但你加了一块硬盘后设备名变成了/dev/sdb1。initramfs 里缺少必要的驱动模块。比如根分区在 NVMe 固态上但旧 initramfs 没有 nvme 驱动。根分区本身损坏或者 LVM 卷组因异常关机失活。排查并修复的思路如下第一步在 GRUB 菜单按e编辑启动项去掉quiet和splash参数加上rd.shell如果基于 dracut或breakpre-mount如果基于 initramfs-tools。这样启动过程中会直接给你丢一个 shell里面可以做手工排查。第二步在 initramfs 的 shell 里执行lsblk查看磁盘设备名对比/boot/grub/grub.cfg里的root参数是否一致。第三步如果设备名变了直接在 GRUB 编辑界面改root参数临时启动进入系统后再持久化修改。以 Debian/Ubuntu 系为例内核更新后重建 initramfs 的命令是sudo update-initramfs -u -k all如果是基于 RHEL/Fedora 的 dracut 体系sudo dracut --force4.3 一个典型的 LVM 根分区修复案例我印象最深的一次故障是这样的某台虚拟机根分区用的是 LVM某天异常断电后重启时一直提示找不到根卷。通过breakpre-mount进入 initramfs shell执行lvm vgscan发现卷组显示VG not found再用lvm vgchange -ay强制激活系统就能识别了。原因也不复杂LVM 的元数据和磁盘设备名高度绑定异常断电可能导致设备路径变化卷组处于 inactive 状态。解决之后我在系统里做了两个永久性修复——一个是把根分区的root参数改成使用 UUID 而不是设备名另一个是把 LVM 相关工具自动激活配置确认一下。说到这里强烈建议所有人把启动参数里的设备路径改成 UUID。查看 UUID 的方式blkid例如输出PARTUUIDxxxx或UUIDxxxx在 GRUB 的linux行里写rootUUID那个值。UUID 是唯一的和设备名无关不管磁盘顺序怎么变它都不会变这是我从多次磁盘顺序变动事故中总结出来的最实用的经验。5. 第三棒systemd 接管——从内核态跨入用户态的过程5.1 PID 1 的真正职责不只是跑服务内核完成根分区挂载后会启动/sbin/init它会用 symlink 指向/lib/systemd/systemd。对于 systemd 来说它接管之后要做的事情比“启动服务”多得多。它要按依赖顺序拉起 sockets、挂载点、交换分区、系统日志、多用户目标。理解 systemd 的启动机制必须先理解 target 的概念。target是 systemd 里的一种单元代表一组服务的集合可以类比为跑道上的“计时点”。我们最常接触的三个 targetpoweroff.target关机。rescue.target单用户模式只有基础系统相当于 Windows 的安全模式。multi-user.target正常的命令行多用户模式。graphical.target带桌面环境的目标一般依赖 multi-user.target。默认启动到哪个 target是通过软链接决定的ls -l /etc/systemd/system/default.target如果这个软链接指向graphical.target系统开机后就会进入图形界面否则默认进 multi-user。5.2 systemd 的并行化逻辑为什么启动这么快老派 init 是串行的服务一个一个按顺序跑遇到网络等待就全卡住。systemd 的核心优势在于它并行处理相互独立的服务并且通过 socket 激活和 D-Bus 激活把服务真正需要的时候才拉起来。但这引出一个问题并行启动会导致日志顺序看起来“不规律”。比如网络服务和登录管理器可能同时启动日志里看不出严格的先后关系。初学者如果试图从journalctl -b里找“启动顺序”会很容易陷入混乱。实际上 systemd 的启动链分几个阶段第一阶段启动systemd-udevd等设备管理服务等待所有硬件设备被 udev 接管。第二阶段挂载文件系统local-fs.target包括根分区、交换分区、以及/etc/fstab里声明的挂载点。第三阶段启动基础服务sysinit.target、basic.target包括系统日志、D-Bus、用户工具等。第四阶段进入 multi-user.target按需启动网络、SSH、cron 等服务。5.3 用 systemd-analyze 给启动过程“掐表”如果你想知道系统启动到底花了多少时间、到底哪一步浪费了时间不需要猜直接看数据systemd-analyze会输出类似 “Kernel 12.3s, Userspace 5.1s” 的结果。其中内核部分是指从 GRUB 到内核切换到用户态的时间用户空间部分是从 systemd 启动到达到默认 target 的时间。进一步看每个服务的耗时systemd-analyze blame它会按耗时从长到短列出所有服务的启动时间。我遇到过好几次启动慢的问题都是通过这个命令定位的比如某个服务启动没问题但 timeout 设置过长导致失败后还要白白等 90 秒。再看关键链路上的时间消耗systemd-analyze critical-chain这条命令会展示一条从默认 target 反向回溯的依赖链每一行的 时间是相对上一层的偏移时间 是服务本身的消耗时间。如果你发现某个服务在链条上被标记为很久以前且巨大基本就能锁定是它拖慢了整个启动。小技巧某服务启动失败后 systemd 会等待一段时间再决定是否继续这个时间默认是 90 秒timeout。如果某服务在 blam 列表里稳居第一大概率不是因为它真的很慢而是它挂了之后 systemd 在傻等超时。5.4 卡在“却到哪里了”的日志解读法启动过程中如果屏幕上没有出现图形界面或者登录提示符很多人会慌。其实只需要灵活切换视角先按 Ctrl Alt F2或 F3 等尝试切换到其他虚拟控制台。如果能看到登录提示说明系统已经起来了只是显示服务的问题。如果按了没反应说明系统还在初始化阶段或已经损坏。用journalctl -b查看本次启动日志配合-p err只看错误级别journalctl -b -p err --no-pager这条命令会列出本次开机以来所有 error 级日志绝大多数启动问题的关键线索都会在大概 10 行之内出现。如果你的系统卡在 systemd 初始化某个服务可以在 GRUB 里临时追加systemd.log_leveldebug启动日志会变得非常详细但务必结合systemd-analyze critical-chain来对照否则 debug 日志量大到会淹没有效信息。6. 启动排查工具箱常用命令、应急方案和经典故障速查6.1 每次排查都少不了的命令清单启动过程相关的问题绕来绕去就那么几个工具整理成速查表建议收藏命令/操作用途使用时机dmesg或journalctl -k -b查看内核日志内核阶段卡死、驱动报错journalctl -b查看本次启动完整日志任何用户态启动问题systemd-analyze blame统计服务耗时启动慢systemd-analyze critical-chain查看关键链路定位依赖链阻塞lsblk查看磁盘设备确认设备名和挂载点blkid查看 UUID修改启动参数前必备df -h查看磁盘空间/boot满了导致内核更新失败update-grub/grub2-mkconfig重新生成 GRUB 配置安装新内核或修改配置后update-initramfs -u/dracut --force重建 initramfs内核模块变动、根分区变多systemctl rescue进入救援模式正常系统但想去排查依赖问题6.2 启动卡在内核阶段的排查技巧启动过程完全没有任何输出屏幕一片黑这时候要大胆怀疑是内核阶段卡住了。首先要做的是去掉quiet和splash启动参数让内核把所有启动消息打印到屏幕上。GRUB 菜单按e进入编辑把 quiet 和 splash 删掉再加上loglevel7最高日志级别然后 CtrlX 启动。如果屏幕一直停在内核日志的某一行注意这一行的最后内容。常见情况停在task xxxxx blocked for more than 120 seconds.说明某个内核线程在等待 IO极大概率是磁盘出问题或 NFS 挂载卡死。停在BUG: soft lockup说明 CPU 上某个内核线程死循环优先怀疑驱动问题。停在Out of memory说明内存不足和启动参数或硬件配置有关。6.3 根文件系统变成只读的典型场景与修复启动过程中最让新手困惑的另一个问题是系统能启动但提示根文件系统变成只读Read-only file system。这通常是因为系统检测到文件系统异常为了安全自动以只读方式挂载。在只读环境下直接执行fsck可能不生效因为文件系统是挂载状态。正确流程是重启在 GRUB 菜单按e在 linux 行末尾加single或1以进入单用户模式。如果单用户模式和 multi-user 一样繁忙可以再加一个emergency参数。在紧急模式下先以只读方式挂载根分区mount -o remount,ro /然后再执行文件系统检查fsck /dev/mapper/你的根分区这里有个我踩过的坑如果根分区是 LVM不能对/dev/mapper/vg-root直接执行fsck时跳过它可能提示不认识 LVM 设备名要看清楚实际物理分区路径。执行修复后重启之前务必确认文件系统已经没有错误否则修复不彻底会再次变成只读。6.4 我常用的应急复盘法一次起不来的经历有一次我更新内核后重启时卡在 GRUB 之后没有任何输出。我当时判断是 initramfs 受损因为 update 过程被中断了。处理方式很朴素在 GRUB 菜单选择高级选项进入旧内核上一版内核启动然后重新执行sudo update-initramfs -u sudo update-grub重启后新内核恢复正常。这里的关键经验是更新内核之后不要立刻删除旧内核至少保留一个已知能用的版本。这就是行业里常说的“保留可回滚点”原则它在启动排障中救过我至少三次。6.5 如果一切方法都无效最终兜底方案万不得已时可以用 Live CD 或安装 U 盘启动系统挂载根分区来做修复。步骤从 Live 环境启动lsblk找到根分区。挂载根分区和 boot 分区如果独立mount /dev/sda2 /mnt mount /dev/sda1 /mnt/boot挂载必要的虚拟文件系统mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/syschroot进入系统chroot /mnt /bin/bash在 chroot 环境内重新安装 GRUBgrub-install /dev/sda update-grub这套流程几乎能解决 95% 的引导类故障也是系统救援的标准姿势。7. 实战侧写从按下电源键到登录界面的完整时间线前面讲的是阶段和原理来串一遍完整的实战时间线把前面的知识点串成一个完整闭环。假设一台普通的 UEFI systemd LVM 根分区的机器0.000s按下电源键CPU 跳转到 UEFI 固件代码。0.5sUEFI 完成自检加载 X64 引导器扫描 ESP 分区。1.0sGRUB2 加载显示菜单如果配置了等待时间。1.5s选择默认内核GRUB 读取 vmlinuz 和 initramfs 到内存。2.0s内核接管硬件设备开始枚举initramfs 被解压。3.0sudev 加载存储驱动LVM 卷组被激活根分区准备挂载。3.5sswitch_root完成真正的根文件系统挂载。4.0ssystemd 启动进入 basic.target。4.5ssystemd-journald 启动日志记录开始。6.0s网络服务启动DHCP 获取地址。7.0sSSH 服务监听端口。8.0sgraphical.target 或 multi-user.target 达成显示登录提示。如果你用systemd-analyze去测量会发现优秀配置的系统从固件到 login 界面 10 秒内完成非常正常。这也说明了 systemd 并行化的效果确实是实打实的。8. 我对启动过程的理解一套固定的排查思路搞懂 Linux 启动过程并不是为了背流程而是为了在面对一台永远起不来的机器时心里有一个清晰的“接力棒地图”。机器卡在哪一步、哪一步的信息源去哪查、错误特征的背后是什么每一样都有对应方法这比碎星式地搜索错误码效率高得多。我个人在实际操作中最受益的一点是戒掉了“重启大法”的瘾。以前遇到启动问题第一反应是重装现在第一反应是按 Esc/Shift 进 GRUB、看一眼引导参数、进一次 initramfs shell、查一遍 journal。绝大多数问题都能定位到某一棒上。也建议所有刚接触 Linux 的朋友找一台不重要的虚拟机故意破坏几次引导、改错几次启动参数亲手把它救回来。把这个流程走通你对 Linux 的掌控感会上一个台阶。最后一次强调那个最容易被忽略但最重要的习惯拿到一台新机器后第一时间记录根分区的 UUID、确认 GRUB 配置里用的是 UUID 而非设备名、确保 initramfs 是完整且最新的。这三件事做好你大概率永远不需要使用这篇文章里的应急手段。