一台 Linux 服务器在机房断电后重新通电,你 SSH 进去发现一切正常;另一台服务器同样断电后却卡在某个画面,远程连不上、串口也没有任何输出。同样都是 Linux,为什么一台能恢复服务,另一台要人工介入?这时候不少人的第一反应是“硬件坏了”,但真正排查下来,有相当概率是启动链路里某个环节出了问题。
能完整回答“Linux 是如何启动的”的人,面对这类问题不会慌。因为这本质上不是一条看不见摸不着的“玄学链路”,而是可以按阶段拆解、按日志定位、按配置修复的确定性过程。无论你是刚入行的运维、准备面试的后端开发,还是做嵌入式或容器平台的工程师,今天这篇文章我都会按实际启动顺序讲清楚 Linux 从按下电源键到出现登录提示符的完整过程,并给出每个阶段可以用到的验证命令和排障思路。
1. 为什么要理解 Linux 启动过程
先抛一个判断:启动过程不是 Linux 的冷门知识,而是系统管理员和 DevOps 工程师排障时的第一层地图。原因很简单,服务器一旦无法启动,所有上层应用、监控、自动化流程通通不可用,你必须直接从“开机到用户态”这条链路里找问题。而这条链路的每一段都有它独立的日志、配置文件和工具链。
举一个高频场景:你给一台机器新加了内核参数,重启后系统卡在挂载根文件系统之前。如果不了解启动过程,你可能反复重装系统;了解启动过程后,你会先判断是不是内核参数写错,然后从 GRUB 菜单进入 emergency 模式,把参数改回来。
再比如面试中经常被问到“Linux 启动过程包括哪些阶段”,考察的其实不是背诵能力,而是你有没有真正理解 BIOS/UEFI、引导加载程序、内核、init 系统这几层分别承担什么职责、谁会先于谁运行、各自的日志去哪里看。这些问题在实际排障中一定会碰到。
这篇文章不会只列阶段名称。我会从固件到用户态,把每一层的核心机制、关键文件、常用命令和最容易踩的坑都讲清楚。读完你不仅能说清楚 Linux 启动流程,还能照着做一次启动耗时分析,甚至解决一次启动卡住的问题。
2. Linux 启动的整体阶段划分
Linux 的启动过程可以按“谁在运行、谁负责接力”的方式,拆成四个大阶段:
| 阶段 | 运行主体 | 核心职责 | 典型组件 | 常见日志/查看方式 |
|---|---|---|---|---|
| 固件阶段 | BIOS/UEFI | 硬件自检、选择启动设备 | BIOS / UEFI 固件 | 厂商固件界面、串口输出 |
| 引导加载阶段 | 引导加载程序 | 加载内核和初始化内存盘 | GRUB2 | GRUB 菜单、grub.cfg |
| 内核初始化阶段 | Linux 内核 | 驱动初始化、挂载根文件系统 | vmlinuz、initramfs | dmesg、内核日志 |
| 用户态初始化阶段 | PID 1 进程 | 启动系统服务、完成多用户环境 | systemd / SysVinit | systemctl、journalctl |
这四段是层层递进的关系。前一个阶段没有完成,后一个阶段根本不会开始。很多所谓“系统起不来”的问题,其实是在某一棒交接时断掉的。比如内核已经起来但找不到根文件系统,和 systemd 无法启动某个关键服务,表现上都是“无法正常进入系统”,但排查方向完全不同。
总览完这四个阶段后,从下一节开始,我们逐层深入。每一层我都会告诉你:它到底做了什么、有没有办法看到它的输出、出了问题该看哪里。
3. 阶段一:固件与硬件初始化 BIOS/UEFI
3.1 BIOS 和 UEFI 的区别
传统 BIOS 和现代 UEFI 是 Linux 启动的第一棒。很多人认为它们只是“一个设置界面”,实际上它们负责的是最底层的硬件初始化,以及决定“去找哪一个设备上的引导程序”。
传统 BIOS 的工作方式比较古老:它做完全局硬件自检后,按照设定好的启动顺序,逐个去读取启动设备第一个扇区的内容,然后跳转执行。它读取磁盘用的是传统 MBR 分区表,受到 2TB 磁盘上限和 4 个主分区限制。
UEFI 则是现代系统的主场。它本身就是一个小型操作系统,支持从 FAT 格式的 EFI 系统分区(ESP)中读取.efi引导文件,支持 GPT 分区表,磁盘容量限制小得多。更重要的是,UEFI 引入了安全启动(Secure Boot)机制,只允许加载经过签名的引导程序,这在某些服务器发行版上默认开启。
从运维直觉上,要知道自己用的是哪种方式,可以看磁盘分区表:
# 检查磁盘分区表类型,gpt 对应 UEFI 启动,dos 则可能是传统 BIOS lsblk -o NAME,PARTTYPENAME,SIZE sudo fdisk -l /dev/sda | head -20如果看到/dev/sda1之类的小分区且类型是EFI System,基本上就是 UEFI 引导。
3.2 固件阶段最容易卡住的现象
固件阶段虽然没有 Linux 的日志,但它的表现通常很有特征:开机后一直停留在品牌 Logo 界面,或者只有一个光标在闪,又或者直接报“No Boot Device Found”。
遇到这种情况,先不要急着怀疑 Linux 系统坏了。优先检查:
- BIOS/UEFI 中能否识别到硬盘或 NVMe 盘;
- 启动顺序是否正确,是否被重置成了 U 盘启动;
- 是否开启了 Secure Boot 但引导文件签名不匹配;
- 服务器是否开启了 RAID 卡,需要先在 RAID 配置界面里确认逻辑盘状态。
从实际经验看,固件阶段排障的正确姿势是“先看硬件配置界面,再查启动顺序,最后再怀疑引导程序”。因为固件阶段是一个封闭环境,Linux 看不到这里的日志,能依靠的就是硬件厂商的界面输出和串口控制台。
4. 阶段二:引导加载程序 GRUB
4.1 GRUB 的核心职责
固件阶段完成后,控制权交给引导加载程序。绝大多数主流 Linux 发行版使用的是 GRUB2。GRUB 做的事情可以拆成三步:
- 找一个能被它识别的文件系统,加载自己的配置;
- 根据配置展示启动菜单,或者直接读取默认内核;
- 加载内核文件
vmlinuz和初始化内存盘initramfs,把启动参数传给内核。
换句话说,GRUB 是 Linux 内核与固件之间的翻译官。它不负责“启动 Linux”,而是负责“找到 Linux 内核并把它加载起来”。这也是为什么很多人修改了/etc/default/grub后必须重新生成 GRUB 配置,因为实际生效的是/boot/grub2/grub.cfg或/boot/grub/grub.cfg。
4.2 GRUB 配置实例
如果你用的是 RHEL、CentOS、RockyLinux,GRUB 主配置通常生成在/boot/grub2/grub.cfg。我们一般改动的是/etc/default/grub:
# 文件路径:/etc/default/grub GRUB_TIMEOUT=5 GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)" GRUB_DEFAULT=saved GRUB_DISABLE_SUBMENU=true GRUB_TERMINAL_OUTPUT="console" GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet" GRUB_DISABLE_RECOVERY="true"这里的GRUB_CMDLINE_LINUX就是内核启动参数。排障时经常要调整这行,比如去掉rhgb quiet才能看到内核的滚动输出,或者追加systemd.unit=emergency.target进入紧急模式。
修改完要重新生成配置并更新默认启动项:
# RHEL/CentOS 系 sudo grub2-mkconfig -o /boot/grub2/grub.cfg # Debian/Ubuntu 系 sudo update-grub4.3 GRUB 出现故障时的处理
GRUB 阶段最经典的故障是开机进入grub rescue>提示符。这通常意味着 GRUB 核心模块或者/boot下的文件缺失、被误删,或者磁盘分区发生了变化。
grub rescue>环境功能非常有限,它只有几个内建命令。常见的抢救思路是手动指定 prefix 和 root,再加载正常模块:
set root=(hd0,msdos1) set prefix=(hd0,msdos1)/boot/grub insmod normal normal上面的(hd0,msdos1)要换成你自己的分区。这里的hd0表示第一块磁盘,msdos1表示 MBR 分区表的第一个分区。如果能进入正常 GRUB 菜单,再在系统内重新安装 GRUB 到磁盘。如果 GRUB 配置或模块损坏严重,通常需要借助 Live CD 或安装盘 chroot 进入原系统修复。
GRUB 阶段排障的关键判断是:如果你能看到 GRUB 菜单但选内核后卡住,那是内核或 initramfs 的问题;如果你根本看不到菜单,那问题在 GRUB 本身。
5. 阶段三:内核初始化
5.1 内核文件与 initramfs 的作用
当 GRUB 把内核和 initramfs 加载进内存后,Linux 内核开始接管。这里涉及两个文件:
vmlinuz-xxx:压缩过的内核镜像;initramfs-xxx.img:初始化内存文件系统,它是一个包含必要驱动和工具的临时根文件系统。
为什么要先挂一个临时根文件系统?因为内核一开始不一定认识你的根分区。比如根文件系统在 LVM 卷上,或者必须由特定 NVMe 驱动、RAID 驱动才能访问,这些驱动如果直接编译进内核,内核会变得很大。initramfs 解决的就是“先加载驱动,再挂真实根文件系统”的问题。
查看当前系统使用的内核和 initramfs:
uname -r ls -lh /boot/vmlinuz-$(uname -r) ls -lh /boot/initramfs-$(uname -r).img5.2 内核启动阶段到底做了什么
以内核视角看,启动阶段做的事情大致包括:
- 解压自身,建立最初的内存管理结构;
- 枚举总线上的硬件设备,加载必要的驱动程序;
- 挂载 initramfs,执行其中
/init脚本; - 根据内核参数找到真实根文件系统;
- 把根文件系统切换为真实磁盘上的根;
- 执行根文件系统上的第一个用户态程序,即
/sbin/init。
这一步最常出问题的位置是“找不到根文件系统”。对应内核日志里会看到类似Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)。出现这种提示时,不要怀疑 GRUB,而是要看内核参数里root=是否正确、磁盘驱动是否缺失、initramfs 是否需要重新生成。
5.3 如何查看内核阶段日志
内核启动完成后的日志可以用dmesg查看。如果你想看每次开机后的内核环形缓冲区内容,它保存在内存里,必要时也可以写入文件:
# 查看内核日志,关注 hardware、driver、mount 相关 dmesg | grep -i "error\|fail\|usb\|nvme\|sda" | head -50 # 如果 dmesg 被普通用户限制,加 sudo sudo dmesg -T | tail -100这里要区分两个概念:dmesg显示的是内核环形缓冲区,反映的是内核初始化期间的硬件和驱动信息;而journalctl可以看到完整用户态日志。很多启动问题需要通过两者配合才能定位。
从实践上看,当你怀疑是内核阶段问题,可以临时去掉内核参数里的rhgb quiet,这样开机时屏幕上会直接滚动显示内核日志。这个技巧在服务器本地排障时非常常用。
6. 阶段四:systemd 与用户态服务启动
6.1 PID 1 的交接
内核完成根文件系统切换后,会执行根文件系统上的第一个用户态进程。在主流的现代 Linux 里,这个进程就是 systemd,PID 永远是 1。
之所以强调 PID 1,是因为它和普通进程不同:它负责拉起整个用户态服务树,并且直接或间接成为所有其他进程的祖先进程。如果 PID 1 挂掉,内核就会 panic。查看当前系统的 PID 1:
ps -p 1 -o pid,comm,cmd从材料看,现在绝大多数 Linux 发行版都默认使用 systemd,但如果你在做嵌入式Linux项目或维护老系统,可能会遇到 SysVinit 风格。SysVinit 的启动方式依赖/etc/inittab和/etc/rc.d/rc*.d里的脚本,systemd 则全面转向了 target、unit 和并行启动。
6.2 target 与依赖关系
systemd 里没有传统意义上的“运行级别 3、5”,取而代之的是 target。一个 target 可以理解为一组服务的集合。最常见的有:
multi-user.target:对应传统多用户命令行模式;graphical.target:多用户加图形界面;rescue.target:单用户模式,用于修复;emergency.target:紧急模式,只挂载根文件系统,适合底层修复。
查看默认启动 target:
systemctl get-default修改默认启动 target,比如从图形界面切换为命令行,或者反过来:
sudo systemctl set-default multi-user.target sudo systemctl set-default graphical.targetsystemd 在启动时会根据依赖关系并行拉起服务。每个服务都由一个 unit 文件定义。一个典型的 unit 文件长这样:
# 文件路径:/etc/systemd/system/my-demo.service [Unit] Description=My Demo Service After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/my-demo Restart=on-failure User=myapp [Install] WantedBy=multi-user.target其中After=表示在 network-online.target 之后启动,但它不等于依赖它,真正表达依赖的是Wants=或Requires=。要注意 systemd 并不保证After里的服务一定先完成,只是“尽量排序”,这也是新手常误解的地方。
6.3 分析启动耗时
systemd 提供了一组非常实用的启动性能分析命令。我们可以用它们定位“启动到底慢在哪里”:
# 查看总启动耗时 systemd-analyze # 查看每个服务的启动耗时排名 systemd-analyze blame | head -20 # 生成 SVG 启动过程图(虽然我默认不写绘图,但命令可以用) sudo systemd-analyze plot > boot-plot.svg # 查看关键事件时间链 systemd-analyze critical-chain典型输出里,systemd-analyze会分成 firmware、loader、kernel、userspace 四段时间。如果 firmware 或 loader 时间占了大头,那就是固件或 GRUB 层的问题;如果 userspace 时间很长,就去blame里看哪个服务拖了后腿。
这也呼应了本文的核心思路:启动过程中的每一段都有它自己的“计时器”。系统启动慢,并不是笼统的“开机慢”,而是能具体到是内核慢、某个服务等待网络慢,还是某个自定义 unit 脚本里 sleep 太久。
6.4 如何追踪用户态服务启动日志
systemd 管理下的服务日志都集中在 journal 里:
# 查看本次启动的日志 journalctl -b # 查看某个服务的启动日志 journalctl -u sshd -b # 只查看错误级别 journalctl -p err -b如果某个服务启动失败导致不能进入系统,开机时会进入 fallback 状态。你可以通过 systemctl 查看失败服务:
systemctl --failed这个命令列出的就是本次启动中失败的 unit。很多“系统起不来”的真相,最后就是systemctl --failed里某个服务报了一个配置路径不存在或者权限不对。
7. 常见启动问题与排查方法
启动问题虽然种类多,但按阶段分类后,排查路径非常清晰。下面用表格汇总几个高频问题。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 开机卡在 BIOS/UEFI Logo | 磁盘识别失败、启动顺序错误 | 进入固件界面查看磁盘和 Boot Order | 修复 RAID 状态,调整启动顺序 |
进入grub rescue> | GRUB 模块或 /boot 文件丢失 | 手动设置 root、prefix 加载 normal | 修复 GRUB,或进 LiveCD 重装 GRUB |
出现Kernel panic - VFS unable to mount root fs | root 参数错误、磁盘驱动缺失 | 查看内核参数和 dmesg | 修改 root= 参数,重新生成 initramfs |
| 开机后黑屏无登录界面 | graphical.target 相关服务失败 | systemctl get-default,切到 multi-user.target | 修复显示管理器或切换 target |
| 服务一直显示 activating(auto-start) | 服务依赖网络或磁盘等待超时 | systemctl status 服务名,journalctl -u 服务名 | 调整 After/Requires 依赖,优化服务配置 |
| 修改内核参数后无法启动 | 参数写错或模块冲突 | 在 GRUB 菜单按 e 临时编辑启动项 | 移除错误参数后重新生成 grub.cfg |
| 私有自定义 unit 启动失败 | 路径错误、权限不足、Type 不匹配 | systemctl status、journalctl -u | 修正 ExecStart 路径和文件权限 |
一个非常实用的临时启动技巧是:在 GRUB 菜单界面选中内核行,按e编辑启动参数。比如要进入 emergency.target,可以在linux那一行的末尾追加systemd.unit=emergency.target,然后按 Ctrl+X 启动。这比修改配置文件更快,适合一次性排障。
如果是内核阶段根本起不来,优先用dmesg或者临时去掉 quiet 参数看内核输出。如果是用户态起不来,优先用systemctl --failed和journalctl。这个先后顺序几乎是固定套路。
8. 最佳实践与工程建议
理解了启动过程,更重要的是把它应用到真实的系统维护和项目交付中。下面这些建议来自比较常见的线上实践,能帮你少踩很多坑。
8.1 内核参数尽量少改,改前先备份
启动参数是一个典型的“高影响小改动”区域。很多人在/etc/default/grub里随手加入一串参数后重启,系统直接起不来。建议每次修改前先备份:
sudo cp /etc/default/grub /etc/default/grub.bak.$(date +%F)修改后先执行grub2-mkconfig或update-grub生成新配置,再重启。在生产环境,最好先在测试机验证。
8.2 学会使用 GRUB 临时编辑,别急着重装
只要 GRUB 菜单还能出现,系统就没有完全死亡。按e进入临时编辑,可以临时更换内核参数、指定 systemd target,甚至指向旧内核。这种“临时启动”方式不写入磁盘,非常适合试错。确认参数可用后再写入配置文件。
8.3 定期检查内核与 initramfs 是否匹配
如果你手动升级过内核,或者执行过重新打包 initramfs 的操作,要注意内核版本和 initramfs 文件是否能对上。更新内核后没有重新生成 initramfs,是启动失败的高频原因之一。在 RPM/DEB 系发行版上,正常更新会自动执行脚本,但如果手动操作过/boot目录,就需要留意了。
8.4 记录并对比每次启动耗时
把启动耗时变成可追踪的指标,而不是“感觉变慢了”。可以写一个简单的脚本,每次开机后把systemd-analyze的耗时记录到/var/log/boot-time.log:
#!/bin/bash # 文件路径:/usr/local/bin/boot-time-log.sh { echo "=== $(date) ===" systemd-analyze systemd-analyze blame | head -10 } >> /var/log/boot-time.log然后通过 systemd timer 或 rc.local 在开机后执行。一段时间后,你就能看出启动耗时的变化趋势,而不是等到卡死才去排查。
8.5 systemd unit 编写要遵循最小依赖原则
自定义服务时,不要为了“保险”写一堆 Wants/After 依赖,过度依赖往往会拖慢启动。依赖越多,systemd 的调度越复杂,出问题的可能性也越大。建议只声明真正需要的依赖,并且优先使用Wants而不是Requires,避免一个网络依赖把整个服务树拖挂。
8.6 安全的排障氛围:先备份、再操作、记录每一步
这一点专门提醒从事运维或平台开发的同学。在生产环境排查时,先确认当前会话不会因为重启丢失,再修改配置。任何时候执行 reboot 前,都要检查/etc/default/grub、/etc/fstab、关键 unit 文件有没有被改动过。如果有多台机器,先在测试环境复现问题再上生产。
9. 总结与后续学习方向
到这里,一条完整的 Linux 启动链路已经梳理清楚了:从 BIOS/UEFI 固件检测硬件,到 GRUB 找到并加载内核,再到内核通过 initramfs 识别根文件系统,最后交给 systemd 拉起整个用户态服务树。你只需要记住每一阶段的交接标志:
- 固件阶段找启动设备;
- GRUB 阶段加载内核和 initramfs;
- 内核阶段挂载根文件系统,执行 PID 1;
- systemd 阶段按 target 和依赖关系启动服务。
哪个阶段出问题,就在哪个阶段用对应的工具去看。内核之前的阶段重点看硬件界面和 GRUB 菜单,内核阶段重点看dmesg,用户态阶段重点看systemctl --failed和journalctl。能把问题定位到具体阶段,你就已经解决了一大半。
下一步建议你亲自做两件事:一是查看自己这台机器的systemd-analyze输出,看看四个阶段的时间分配;二是在测试机上刻意制造一次引导故障,比如修改默认内核参数,观察启动失败后如何用 GRUB 临时编辑恢复。这种模拟排障比看十篇文章都更有用。
如果你对启动过程中的某一块特别感兴趣,比如 GRUB 是如何从磁盘定位内核的、initramfs 内部到底有什么、或者 systemd 的单元依赖图怎么分析,都可以顺着这篇文章的方向继续深入。掌握启动过程后,你会发现 Linux 的很多“神秘现象”,本质上都只是某一阶段交接时的一个配置细节。