我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Linux开机启动流程全解析:从BIOS到systemd的完整链路

Linux开机启动流程全解析:从BIOS到systemd的完整链路 按下电源键之后Linux 到底经历了什么才最终出现登录界面“Linux 开机启动流程”这个词我在排查服务器无法启动、调优系统启动速度和带新人入门时反反复复地讲。它看似是一张大学操作系统课的目录表实际上却是一张排查路线图从固件到引导程序从内核到 init每一环都是一道关卡任何一环出问题机器就卡在那里不动。这篇文章把我这些年在真实故障中验证过的启动流程完整写一遍适合刚开始接触 Linux 运维的读者也适合做嵌入式开发或想彻底搞懂 systemd 的同学。1. 整个启动流程的大局观1.1 启动是一个接力赛而不是单步执行Linux 开机启动流程如果用一句话概括就是“接力棒依次从硬件传到固件、引导程序、内核、init 进程”的过程。完整链条可以拆成四个主要阶段阶段主要角色关键产物职责固件阶段BIOS / UEFI启动设备选择初始化硬件寻找可引导设备引导阶段GRUB2 / GRUB Legacy加载内核与 initramfs解析启动配置把控制权交给内核内核阶段Linux Kernelinitramfs / rootfs硬件驱动初始化挂载根文件系统init 阶段systemd / SysVinit系统服务运行启动所有用户态服务进入目标运行级别我常用一个生活化的类比来理解这四个阶段固件像是酒店前台它只负责确认你现在能出现在房间里并告诉你电梯在哪引导程序是礼宾员知道你最终要去哪一层还帮你安排了路线内核是酒店的工程部负责把所有房间的电路、水路、网络全部打通init 则是运营团队把这些资源分配给各个业务单元让客人真正住进来。这个类比能解释一个关键点越靠前的阶段能访问的硬件和资源越少。固件甚至不需要认识文件系统它只认固定的设备地址和启动文件引导程序才开始具备“读取配置文件”的能力而内核则拥有全部的硬件访问权限。理解这个递进关系后面排查问题时会非常有帮助。1.2 为什么不能一步到位直接加载内核很多人会问为什么固件不能直接把内核读进内存还要中间夹一层引导程序答案在于工程上的“职责分离”和“实现限制”。固件BIOS 或 UEFI在设计时只负责最底层的硬件初始化和启动设备选择它没有文件系统驱动的概念也不关心你装的是 Linux 还是其他系统。而引导程序是各个操作系统自己维护的天然能解析自己生态内的配置格式。如果让固件直接加载内核固件就必须适配每一种内核的格式和特性这对上下游都不是一件好事。另一方面嵌入式系统和桌面系统的需求差异也决定了必须有这一层。嵌入式设备经常直接让固件加载内核省去 bootloader以换取启动速度和简化设计而桌面服务器需要多系统选择、内核参数调整、故障恢复等能力引导程序就不得不存在。所以“直接加载”不是做不到而是设计取舍的结果。1.3 各发行版的差异没有想象中那么大不同 Linux 发行版的启动流程大体一致固件阶段基本相同区别主要出现在引导程序和 init 的实现上。RHEL/Fedora/CentOS 系用 GRUB2 加 systemdDebian/Ubuntu 系也是 GRUB2 加 systemdArch 同样Gentoo 可能使用 GRUB2、Syslinux 或 EFISTUB嵌入式场景则可能用 U-Boot 加自定义 init。发行版/场景引导程序init 系统RHEL / CentOS / FedoraGRUB2systemdDebian / UbuntuGRUB2systemdArch / openSUSEGRUB2systemdGentooGRUB2 / Syslinux / 自定义systemd / OpenRC嵌入式 / YoctoU-Boot自定义 init / systemd也就是说只要掌握了 GRUB2 和 systemd 这两大件就能覆盖绝大多数实际运维场景。SysVinit 在如今的新系统里基本绝迹碰到 CTDB、LXC 等老环境才会用到原理上差异也比较大。2. 固件阶段BIOS 与 UEFI 的交接2.1 POST 自检与启动设备选择按下电源键后的第一件事是 CPU 开始执行固件中被映射到固定地址的代码。这里有一段所有 PC 工程师都绕不过去的流程先执行 POST通电自检检查 CPU、内存、关键控制器是否存在并能正常工作。POST 通过后会进入启动设备选择阶段固件会按照设定的顺序逐个检查可引导设备。BIOS 时代的逻辑是从设备的第一个扇区读取引导代码。具体是读取硬盘 MBR主引导记录的前 446 字节检查最后的 0x55AA 标志位然后把控制权交给这段代码。MBR 的局限性很明显分区表只有 64 字节只支持 4 个主分区而且由于使用 32 位逻辑块地址极限只能处理约 2TB 的磁盘。这也是为什么现在的 PC 和服务器全面转向 UEFI 的重要原因。UEFI 的逻辑完全不同。它支持从 FAT32 格式的 ESPEFI System Partition分区中读取.efi文件通过 NVRAM 中保存的启动项来决定加载哪个文件。启动项不仅包含路径还包含可选的参数比如efibootmgr可以查看和修改启动顺序efibootmgr -v输出会列出编号、启动标签、设备路径等信息。如果机器启动时直接进入 UEFI Shell 而不是 GRUB 界面大概率是 NVRAM 启动项丢失或者损坏重建启动项是比较基础的应急操作。2.2 Secure Boot 与内核签名UEFI 还有一个绕不开的机制叫 Secure Boot。它通过固件内置的密钥库来校验启动文件的数字签名防止恶意程序在系统启动早期注入。这本来是一件好事但和 Linux 主线的自编译内核却天生有矛盾自编译内核没有签名的密钥Secure Boot 会拒绝加载。我踩过这个坑。之前在对应发行版上编译了一个带特定驱动的内核重启后直接提示Verification failed: (0x1A) Security Violation屏幕定格在固件界面完全不给进系统的机会。解决方式有三种方案适用场景操作方式在固件中关闭 Secure Boot自用开发机开机进入 UEFI 设置关闭 Secure Boot使用发行版自带签名内核生产环境直接安装发行版仓库内的内核注册自己的 MOK 密钥需要保留 Secure Bootmokutil --import重启后按提示登记如果你是个人电脑自编译内核直接关闭 Secure Boot 最简单。如果是工作用的服务器建议走 MOK 密钥这条路不要轻易动固件安全设置。2.3 固件阶段常见故障速查固件阶段的故障现象往往在画面还停留在固件 logo 时就能察觉。碰到过以下几类典型问题UEFI 找不到 ESP 分区通常表现为启动时白屏或者提示No bootable device在固件设置里确认 SATA/NVMe 硬盘是否被发现再做一次启动项探测。NVRAM 启动项失效多见于重装 Windows 后 Windows Boot Manager 把 Linux 的启动项顶掉用 Live USB 进系统后efibootmgr -c重新添加。Secure Boot 拒绝加载自编译内核或第三方驱动导致的签名校验失败按上一小结的 MOK 方案处理。这阶段排查的思路和其他阶段不太一样能操作的工具非常少基本只能靠固件设置界面。所以经验是遇到启动问题先判断点是落在固件还是更后面判断方法很粗暴如果画面完全停在固件 logo 或直接提示没有设备那基本都是固件层如果固件 logo 消失后出现了 GRUB 界面或者内核日志那已经进入了下一关。3. 引导程序阶段GRUB2 的配置与实战操作3.1 GRUB2 在启动链条中的真实职责GRUB2 是当前 Linux 世界使用最广泛的引导程序它的核心职责可以压缩成三件事加载内核加载 initramfs传递内核参数。所谓“引导”真不是把文件读进内存那么简单它还需要解析文件系统上的配置文件把内核和数据块从根分区或 /boot 分区中读出来然后以正确的协议把控制权交给内核。GRUB2 相比老 GRUB 有革命性的变化模块化设计和运行时命令解析。它可以在启动时动态加载文件系统驱动识别 ext4、xfs、btrfs 等格式甚至读取 LVM 逻辑卷。这意味着只要你的根分区格式在 GRUB2 支持的范围内它不需要像老系统那样依赖裸扇区读写就能启动内核。也正因为这个原因改 GRUB2 的配置文件后需要重新生成主配置因为主配置文件grub.cfg本质上是脚本生成的结果。直接手改grub.cfg能生效但下次更新内核时就会被覆盖所以规范做法是改/etc/default/grub和/etc/grub.d/下的模板再运行生成命令。3.2 /etc/default/grub 的常用配置项/etc/default/grub是最常修改的配置文件里面的变量会被/etc/grub.d/中的脚本读取并嵌入生成的grub.cfg。下面是我常用的配置说明GRUB_DEFAULT0 GRUB_TIMEOUT5 GRUB_CMDLINE_LINUX_DEFAULTquiet splash GRUB_CMDLINE_LINUXGRUB_DEFAULT默认启动第几个菜单项从 0 计数。也可以填菜单项的唯一 id 字符串比如GNULinux。GRUB_TIMEOUT菜单等待时间设置为 0 直接启动默认项设置为-1则会一直等待手动选择。GRUB_CMDLINE_LINUX_DEFAULT正常启动时附加的内核参数比如quiet splash、net.ifnames0等。GRUB_CMDLINE_LINUX所有启动模式都附加的内核参数包括恢复模式。修改完配置后需要生成新的grub.cfg。RHEL 系是grub2-mkconfigDebian 系是grub-mkconfig注意命令不同grub-mkconfig -o /boot/grub/grub.cfg如果你在 UEFI 系统上生成后可能还需要安装更新引导项grub-install --targetx86_64-efi --efi-directory/boot/efi --bootloader-idGRUB跳过重新安装这一步只跑 mkconfig 在绝大多数场景下也能生效但如果修改了分区布局、换了磁盘这一条就很有必要。3.3 开机时临时修改内核参数这是我最依赖的排查手段之一。当系统无法正常启动时在 GRUB 菜单界面按e进入编辑模式找到以linux开头的行在行尾追加你想临时加的内核参数然后按CtrlX或F10启动。这个修改只对本次启动生效不会写进配置文件。典型应用场景场景追加参数效果进入紧急救援模式systemd.unitrescue.target进入单用户模式绕过损坏的显示驱动nomodeset使用基本帧缓冲强制使用指定显卡驱动modprobe.blacklistnouveau屏蔽开源驱动找回 rootfs 挂载失败的原因rd.break在 initramfs 中中断进入 shell我处理过一次服务器在更新内核后无限重启的问题就是因为显卡驱动在 kernel 5.x 上发生了冲突。重启后在 GRUB 编辑界面加nomodeset才顺利进入系统之后再用工具把错误驱动从 initramfs 里摘掉。这种“先临时修复再永久修复”的套路比直接改配置要安全得多。还要提醒一句GRUB 编辑模式下按CtrlX或F10启动不同机器的按键提示会显示在屏幕底部实在找不到就看屏幕最后一行。第一次操作时建议在虚拟机里练一遍因为生产机上手抖改坏一个参数虽然不会损坏数据但会平白多花几分钟重启时间。3.4 initramfs 与内核文件的读取顺序GRUB2 在解析grub.cfg中的menuentry时会依次执行多个命令。典型的一段配置大致如下menuentry GNU/Linux { load_video set gfxpayloadkeep insmod gzio insmod part_gpt insmod ext2 set roothd0,gpt2 if [ x$feature_platform_search_hint xy ]; then search --no-floppy --fs-uuid --setroot --hint-bioshd0,gpt2 6a8e... fi linux /vmlinuz-linux rootUUID6a8e... ro quiet splash initrd /initramfs-linux.img }这里有几个容易踩坑的细节set root是 GRUB 自己的根设备概念不是 Linux 内核的根文件系统search --fs-uuid用 UUID 来定位分区避免因盘符变化导致找不到文件linux和initrd两行分别指定内核和 initramfs 文件。如果内核文件路径写错会在 GRUB 界面直接报file not found如果 initramfs 路径写错内核能启动但会在加载根文件系统时卡住两种现象要区分开。4. 内核阶段与 initramfs 的秘密4.1 内核自解压与核心初始化GRUB2 把vmlinuz文件加载到内存后控制权从引导程序转移到内核。这看起来只是一次跳转但内核本身是一个压缩过的可执行文件。内核开始执行时会先用一段非常精简的汇编代码完成自身解压然后切到保护模式调用start_kernel()进入 C 语言世界。start_kernel()之后做的最重要的事情是按顺序初始化各个子系统内存管理、调度器、中断、定时器、驱动模型、网络协议栈等。这些初始化代码不是挨个写死的而是通过initcall机制将初始化函数整理成多个层级按优先级依次调用。这也是为什么内核启动日志里能看到大量[ OK ]风格的初始化消息但顺序非常有规律。这段初始化过程对普通运维来说不可见因为它发生在 init 进程启动之前。你能观察到的就是内核日志被刷屏比如[ 1.234567] ACPI: ...、[ 1.567890] EXT4-fs: ...等。建议在排查启动问题时导出这份日志保存下来因为一旦进入 init 阶段后旧日志就可能被滚出 systemd journal 的窗口。4.2 initramfs 为什么必不可少现代 Linux 发行版的根文件系统可能存在于 LVM 逻辑卷、加密分区、软件 RAID 甚至是网络文件系统上。内核自身的驱动模块体积太大不可能全部编入内核映像而是在系统安装时生成的临时根文件系统中也就是 initramfs早期也叫 initrd。initramfs 的本质是压缩过的 cpio 归档内部包含的内核模块确保根文件系统的驱动能在启动早期被加载。启动时内核先挂载这个零时根运行其中的 init 脚本加载所需的驱动然后通过switch_root切换到真正的根文件系统。整个过程对用户是透明的但如果你遇到根文件系统相关的启动失败需要深入到这里面排查。Debian/Ubuntu 系查看和使用 initramfs 的命令是lsinitramfs /boot/initrd.img-$(uname -r)RHEL/CentOS 系对应的是lsinitrd /boot/initramfs-$(uname -r).img如果在 initramfs 里找不到某个驱动可以使用dracut --add-drivers xxx --forceRHEL 系或update-initramfs -u -k allDebian 系重新生成。注意重新生成 initramfs 时一定要指定正确的内核版本避免生成之后引导的还是旧内核。4.3 rd.break 调试 initramfs当你怀疑根文件系统挂载失败时进入 initramfs 的现场 shell 是最直接的排查方式。在 GRUB 编辑界面给linux行追加rd.break内核会在 initramfs 阶段停下来给你一个包含完整工具集的 shell。在 shell 里可以做以下事情# 查看哪些块设备被识别 ls /dev/sd* ls /dev/nvme* # 查看 LVM 状态 lvm vgscan lvm vgchange -ay # 查看当前挂载情况 mount # 检查根文件系统分区 fdisk -l /dev/sda这个 shell 里内核模块已经加载了大部分能直接确认是驱动缺失、LVM 无法激活、还是 root 参数写错。比如有一次我发现系统卡在挂载根文件系统的阶段rd.break进去检查后发现是磁盘在 RAID 卡上而 initramfs 里没有对应的驱动模块。重新生成 initramfs 时指定--add-drivers megaraid_sas后恢复正常。没有这个调试手段这种问题只能靠盲猜。还有一个比rd.break更简单的排查方式在启动参数里去掉quiet和splash让内核把完整的启动信息打出来。很多发行版默认这两个参数把日志屏掉了去掉它们就能看见卡在哪个驱动或哪个挂载点信息量非常直观。5. systemd 接管从内核到用户态服务5.1 /sbin/init 与 systemd 的默认 target内核完成初始化并挂载真实根文件系统后会执行/sbin/init。在 systemd 系统中这是指向 systemd 的符号链接而 systemd 启动后的第一个任务就是确定它的默认启动目标target。在 systemd 中target 相当于 SysVinit 的运行级别。常用 target 有Target对应行为poweroff.target关机rescue.target单用户救援模式multi-user.target多用户文本模式graphical.target多用户图形界面模式可以通过内核参数直接指定要进入的 target例如在 GRUB 编辑界面加systemd.unitrescue.target就能进入救援模式。这个技巧不需要记住任何 systemctl 命令开机时敲一下就行。查看当前默认 targetsystemctl get-default修改默认 targetsystemctl set-default multi-user.target如果误改了默认目标导致系统无法进入图形界面在 GRUB 编辑界面临时指定systemd.unitgraphical.target就能恢复然后再把默认设置改回来。5.2 单元依赖与并行启动模型systemd 启动系统的方式和传统脚本完全不同。传统 SysVinit 以固定顺序执行启动脚本一行一行跑慢得离谱systemd 则是用单元unit描述依赖关系找出可以并行的部分同时启动。systemd 的依赖以三种关键字描述Requires强依赖如果依赖的单元启动失败本单元也不启动。Wants弱依赖依赖的单元启动失败不阻止本单元启动。After排序依赖只决定启动顺序不决定是否启动。举例说明网络服务单元network.service可能声明Afternetwork.target意味着系统不会在网络目标完成前启动它但这个声明本身不会去激活网络目标而要激活网络目标则需要某个单元声明Wantsnetwork.target。理解这三者的区别是理解 systemd 启动行为的关键排查服务启动顺序问题时要重点看服务的 unit 文件。并行启动带来的直观效果是启动速度大大提升但也带来了一个隐藏问题你无法再用“按时间顺序读日志”的方式理解系统状态因为日志可能来自多个并行单元。因此启用了 systemd 以后排查服务启动问题的手段完全变了要依赖systemctl status、journalctl -u等命令。5.3 systemd-analyze 与启动耗时分析如果你想优化开机速度systemd-analyze是最快入门的工具。它从内核挂载根文件系统开始计时直到到达默认 target给出各部分耗时。可以直接使用以下命令# 查看总耗时 systemd-analyze time # 按耗时排列所有启动单元 systemd-analyze blame # 生成矢量时序图 systemd-analyze plot boot-plot.svgblame的输出能帮我们找出启动耗时最高的单元比如某个服务等待硬件稳定等待了 30 秒那它就是这个阶段启动慢的元凶。有次我优化一台服务器的启动速度blame显示systemd-udev-settle.service等待了 50 秒原因是这个老服务在等待某个不存在的设备。排查后确认该设备已从硬件清单中移除直接 mask 掉这个服务开机时间立刻缩短到原来的三分之一。再补充一个实际经验systemctl list-dependencies default.target可以列出默认 target 下所有依赖单元里面的Wants列表是可以做减法的候选对象。但不是所有单元都能随手禁用建议按“是否影响业务”为边界逐步验证关闭。6. 启动过程排障实践从现象到根因6.1 日志的正确打开方式启动失败的排查第一步一定是收集日志。systemd 环境下的日志查询优先级非常高开机阶段的输出虽然打印在屏幕上但很多信息在进入用户态后就被覆盖了需要靠 journald 来复盘。# 查看本次启动的日志 journalctl -b 0 # 查看上次启动的日志当前启动失败时看 -1 journalctl -b -1 # 只看错误级日志 journalctl -b 0 -p err # 查看某个单元的启动日志 journalctl -u network.service -b 0 # 查看内核启动环形缓冲区 dmesg有一个处理内核早期启动问题的技巧在 GRUB 里去掉quiet让内核打印完整日志同时也可以在/etc/default/grub里的GRUB_CMDLINE_LINUX中加loglevel7让内核日志输出到控制台。这个参数会显著增加启动信息量排查完了记得改回来不然日常启动满屏滚动也挺闹心。6.2 常见故障对照表下面这份对照表是我在实际维护中反复用到的问题定位汇总按故障现象分类。故障现象可能原因排查命令/操作开机卡在 GRUB 菜单或grub提示符grub.cfg 损坏、内核文件丢失用 Live USB 进入系统重新生成 grub.cfg内核启动后卡在Kernel panic - not syncing内核模块冲突、根文件系统无法识别查看 panic 之前最后的日志行用rd.break检查卡在Failed to mount /sysroot根文件系统挂载失败、LVM 未激活rd.break后手动vgchange -ay修复 fstabsystemd 启动超时服务卡在started后无反应单元依赖循环或者设备等待systemctl list-jobs查看等待中的任务开机进入emergency mode关键本地文件系统挂载失败查看/etc/fstab是否有无效条目注释测试图形界面黑屏但系统能通过 SSH 访问显示管理器或显卡驱动问题切换到multi-user.target修复图形栈Kernel panic时屏幕上最后几行信息是最值钱的它通常会明确写清楚是哪个驱动或哪个函数触发的问题。比如常见的VFS: Unable to mount root fs说明根文件系统没有被正确挂载多半是 initramfs 没包含对应磁盘驱动或者 root 参数指向错误。6.3 优化启动速度的几个实测建议做完排障后顺便聊聊优化这项工作和排障相辅相成。通过systemd-analyze blame找到耗时大的单元后按以下优先级处理不存在的设备等待服务systemd-udev-settle.service和systemd-networkd-wait-online.service是最常见的元凶。确认不再需要的设备或网络栈后systemctl mask掉它。手动启动的旧服务有些第三方软件安装后会自动设为开机自启但实际不需要systemctl list-unit-files --stateenabled检查清单逐项确认能否 disable。fstab 中无效条目/etc/fstab里指向不存在分区的条目会导致进入 emergency mode删掉无效行是启动提速最容易被忽视的手段。夜间定时任务无关的扫描服务例如某些备份软件开机扫描确认业务不需要时禁用。用 mask 可以杜绝服务被任何方式启动而 disable 只是不让它开机启动区别是 mask 使用了- /dev/null的符号链接强制阻断。对排查时确认不该存在的单元习惯用 mask一劳永逸。还有一个细节systemd-analyze critical-chain default.target可以查看到达默认 target 的关键路径。它会把耗时累计最长的链条高亮标注沿着这条链优化的收益是最高的比盲目禁服务高效得多。启动流程的第一步永远是判断故障落在哪一环。固件阶段的问题在固件界面上就能判断引导程序阶段会停在 GRUB 界面或提示找不到文件内核阶段会看到 panic 或挂载失败systemd 阶段则表现为服务超时或某些 target 无法到达。每次处理启动故障时我习惯先打开dmesg和journalctl -b -1把启动过程的完整日志导出再从中找异常而不是盯着屏幕上最后几行乱猜。在我个人的实操中最有价值的三个排查技巧其实很简单一是去掉quiet让日志全部打在屏幕上二是用rd.break进入 initramfs 现场看设备识别情况三是在 GRUB 里临时改内核参数而不是急着改配置文件。这三个技巧覆盖了我遇到的大部分启动故障场景省下了大量重启试错的时间。这套流程今天看来基本稳定但每次遇到新硬件、新内核版本时仍然值得重新走一遍完整的启动链路因为驱动冲突、固件行为差异、systemd 单元变化总是在你想不到的地方冒出来。
返回列表