2、Linux启动流程详解:BIOS/UEFI、Bootloader、内核初始化、init进程、systemd启动
Linux启动,说白了就是一段从按下电源键到出现登录提示符的旅程。很多运维新手觉得这过程黑盒一样,出了问题只会重启。其实搞懂它,你排查启动故障的能力会上一个台阶。
我个人习惯把启动流程拆成五个阶段:固件→引导加载→内核初始化→init进程→用户空间服务。下面我们逐个拆开看。
核心观点:启动日志分析的核心,就是在这五个阶段中找到“断点”。哪个阶段卡住了,日志就停在哪儿。
2.1 第一阶段:BIOS vs UEFI
按下电源键,CPU先执行的是固件代码。传统BIOS和现代UEFI,两者差别不小。
| 对比项 | BIOS | UEFI |
|---|---|---|
| 引导方式 | 读取MBR(主引导记录) | 读取EFI系统分区(ESP) |
| 磁盘分区 | MBR分区表(最大2TB) | GPT分区表(支持大容量) |
| 启动速度 | 较慢,实模式 | 较快,保护模式 |
| 日志输出 | 屏幕显示,无日志文件 | 可记录到EFI事件日志 |
嗯,这里要注意:UEFI模式下,启动日志里会出现“EFI stub”或“EFI framebuffer”字样。如果你看到“Booting from Hard Disk...”这种老式提示,那大概率是BIOS。
实战小技巧:排查“开机黑屏无响应”时,先看主板有没有自检通过(蜂鸣器或指示灯)。如果自检过了,问题才轮到操作系统。
2.2 第二阶段:Bootloader(GRUB2)
固件加载完,控制权交给Bootloader。Linux世界最常用的是GRUB2。
GRUB2的核心任务就两个:加载内核到内存,传递启动参数。它的配置文件在 /boot/grub/grub.cfg,但你别直接改它——改 /etc/default/grub 然后运行 update-grub 才是正解。
我曾经遇到一个案例:服务器重启后卡在GRUB提示符,手动输入 linux 和 initrd 命令才能启动。查了半天,发现是 /boot 分区空间满了,update-grub 没生成完整配置。清理旧内核后问题解决。
避坑指南:我曾经在升级内核后忘记运行 update-grub,结果新内核根本没被加入启动菜单。重启后系统还是老内核。记住:安装新内核后一定要更新GRUB配置。
GRUB2的启动参数里,quiet 和 splash 会隐藏大部分启动信息。排查问题时,我建议在GRUB菜单按 e 编辑启动项,去掉这两个参数,改成 loglevel=7 或 debug,这样内核会输出更详细的日志。
2.3 第三阶段:内核初始化
GRUB把内核和initramfs加载到内存后,内核开始初始化。这个过程分为两步:
- 内核解压并初始化硬件:CPU、内存、中断控制器、块设备驱动等。
- 挂载根文件系统:从initramfs中加载必要驱动,然后切换到真正的根分区。
内核日志可以通过 dmesg 查看,或者直接看 /var/log/kern.log。我排查启动慢的问题时,经常用 dmesg | grep -i "error\|fail\|warn" 快速定位异常。
关键日志位置:内核初始化阶段的日志,在 dmesg 中通常以 [ 0.000000] 开头的时间戳。如果某个驱动加载失败,你会看到类似 [ 3.456789] scsi: ... ERROR 的条目。
你想想看,如果内核找不到根文件系统,它会打印 Kernel panic - not syncing: VFS: Unable to mount root fs。这时候别慌,检查 root= 参数是否正确,或者initramfs里有没有对应的文件系统驱动。
2.4 第四阶段:init进程(PID 1)
内核初始化完成后,启动第一个用户空间进程——init。传统SysV init用 /etc/inittab 定义运行级别,现在基本被systemd取代了。
不过你仍然可能在旧系统或容器里遇到SysV init。它的日志输出比较原始,直接打印到控制台。而systemd则提供了 journalctl 这个强大的日志工具。
| 特性 | SysV init | systemd |
|---|---|---|
| 启动方式 | 串行启动脚本 | 并行启动单元 |
| 依赖管理 | 手动排序 | 自动解析依赖 |
| 日志系统 | 无统一日志 | journald |
| 排查命令 | dmesg + 脚本日志 |
journalctl -b |
实用命令:用 journalctl -b -p err 只看本次启动的错误级别日志,比翻 /var/log/messages 快多了。
2.5 第五阶段:systemd启动与单元管理
systemd作为PID 1,负责启动所有用户空间服务。它的核心概念是单元(unit),每个服务、挂载点、套接字都是一个单元。
启动顺序由依赖关系决定。比如 network.target 必须在 sshd.service 之前启动。如果某个服务启动失败,systemd会尝试重启(取决于 Restart= 策略)。
我记得有一次排查“SSH连不上”的问题,用 systemctl status sshd 看到状态是 failed,再用 journalctl -u sshd.service -b 看到是 Permission denied——原来是 /etc/ssh/sshd_config 权限被改成了777。修复后重启服务就好了。
避坑指南:我曾经手贱改了 /lib/systemd/system/ 下的单元文件,结果系统更新时被覆盖了。正确的做法是:自定义修改放到 /etc/systemd/system/ 目录,它会覆盖默认配置。
systemd的启动分析工具 systemd-analyze 非常实用:
# 查看总启动时间
systemd-analyze
# 查看每个服务的启动耗时
systemd-analyze blame
# 生成启动流程图(SVG格式)
systemd-analyze plot > boot.svg
说白了,systemd-analyze blame 能告诉你哪个服务是启动慢的“罪魁祸首”。我见过一个案例,NetworkManager-wait-online.service 耗时30秒,禁用后启动时间从90秒降到60秒。
2.6 启动流程全景图
下面这张图把整个流程串起来了。你可以对照着看,每个阶段对应哪些日志输出。
2.7 启动日志分析实战思路
搞清楚了流程,分析日志就有方向了。我总结了一个简单的排查思路:
- 看启动是否完成:用
uptime或last reboot确认系统是否正常启动。 - 定位卡住阶段:如果启动卡住,看屏幕最后一行输出。比如卡在
Started Network Manager,那就是网络服务的问题。 - 查看对应日志:用
journalctl -b -p err看错误,或者dmesg -T看内核日志。 - 对比正常启动:如果系统能启动但很慢,用
systemd-analyze blame找出耗时服务。
我的习惯:每次部署新系统,我都会保存一份 dmesg 和 journalctl -b 的输出作为基线。出问题时对比一下,能快速发现异常。