系统启动流程剖析:从按下电源到用户空间
每次按下服务器电源键,到你能敲出第一行命令,中间发生了什么?
我见过不少运维同事,遇到启动抖动问题就一头扎进内核参数里调。其实很多时候,问题出在启动流程的前半段——BIOS/UEFI 阶段或者 Bootloader 加载。你连问题出在哪一层都没搞清楚,调参数就是瞎蒙。
这一章,我带你把整个启动流程走一遍。从按下电源键开始,到 systemd 拉起第一个用户进程结束。每个阶段我会告诉你:这里可能抖什么、怎么查、怎么避坑。
核心观点:启动抖动排查,第一步不是看参数,而是定位抖动发生在哪个阶段。定位不准,后面全是白费功夫。
1. BIOS/UEFI 阶段:硬件说了算
按下电源键,CPU 会去固定地址取第一条指令。这个地址指向的是固件代码——要么是传统的 BIOS,要么是现代的 UEFI。
这个阶段干三件事:POST(上电自检)、硬件枚举、启动设备选择。
- POST:检查 CPU、内存、显卡这些关键部件。如果内存条没插好,这里就会报警。
- 硬件枚举:UEFI 会扫描所有 PCIe 设备、NVMe 盘、网卡等,给它们分配资源。
- 启动设备选择:按优先级列表找可启动设备,找到后把控制权交给它的引导扇区。
我的经验:有一次排查一台服务器启动时间从 30 秒涨到 2 分钟。查了半天,最后发现是 UEFI 里插了一张 RAID 卡,卡上挂了 12 块盘。UEFI 枚举这些盘花了将近 90 秒。解决方案?进固件设置,把不需要的启动设备扫描关掉。
这个阶段能优化的东西不多。但你可以做一件事:进固件设置,关掉不需要的硬件扫描。比如不用的网卡 PXE 启动、多余的存储控制器。
2. Bootloader 加载:GRUB2 的活儿
BIOS/UEFI 找到启动设备后,会把控制权交给 Bootloader。现在主流是 GRUB2。
GRUB2 的工作流程大致是:
- 读取配置文件
/boot/grub/grub.cfg - 显示菜单(如果有配置超时等待)
- 加载内核镜像
vmlinuz和 initramfs - 把控制权交给内核
这里最容易出问题的是 磁盘 I/O 瓶颈。内核镜像和 initramfs 都放在磁盘上,如果磁盘响应慢,加载时间就会拉长。
注意:我曾经遇到过一台机器,每次启动在 GRUB 阶段卡 20 多秒。排查后发现是磁盘的物理扇区出了问题,导致读取 vmlinuz 时反复重试。换了一块盘就好了。
你可以用 systemd-bootchart 或者 dmesg 里的时间戳来估算这个阶段的耗时。如果发现从 GRUB 菜单选择到内核开始打印日志之间有明显的空白期,那多半是磁盘 I/O 的问题。
3. 内核初始化:start_kernel 的旅程
GRUB 把内核加载到内存后,会跳转到内核入口。内核从 start_kernel() 开始,一路初始化各种子系统。
这个阶段的核心步骤包括:
| 步骤 | 说明 | 可能的抖动原因 |
|---|---|---|
| trap_init() | 初始化中断向量表 | 基本无抖动 |
| mm_init() | 内存管理初始化 | 大内存机器(>1TB)可能耗时较长 |
| sched_init() | 调度器初始化 | 基本无抖动 |
| init_IRQ() | 中断控制器初始化 | 多 CPU 机器上可能因 ACPI 表解析变慢 |
| do_initcalls() | 执行所有内置驱动的初始化函数 | 这里是重灾区,驱动加载顺序和耗时差异很大 |
| initramfs 解压 | 解压并挂载 initramfs | 压缩率高的 initramfs 解压慢,磁盘 I/O 也影响 |
我个人习惯在排查启动抖动时,先看 dmesg 里的时间戳。重点关注 do_initcalls 阶段的耗时。你可以用下面这个命令快速定位:
# 查看 dmesg 中每条日志的时间戳(相对启动时间)
dmesg -T | head -100
# 或者直接看 initcall 的耗时
dmesg | grep "initcall" | awk '{print $1, $2, $NF}'
实战技巧:如果发现某个驱动加载花了 2 秒以上,先确认这个驱动是不是必须的。很多服务器上,一些没用的硬件驱动(比如声卡、火线接口)也被编译进了内核。你可以通过内核参数 module_blacklist= 把它们禁掉。
举个例子,我优化过一台机器,dmesg 显示 i2c_hid 驱动加载花了 3.2 秒。问题是这台服务器根本没有触摸板。加上 module_blacklist=i2c_hid 后,启动时间直接少了 3 秒。
4. 用户空间启动:systemd 的时代
内核初始化完成后,会执行 /init(在 initramfs 中)或者直接执行 /sbin/init。现在绝大多数发行版都用 systemd。
systemd 的启动逻辑是:并行启动所有没有依赖冲突的服务。理论上这很快,但实际中经常出问题。
常见的抖动原因有:
- 服务依赖链太长:A 等 B,B 等 C,C 等 D……串行化了。
- 磁盘挂载超时:特别是 NFS、CIFS 这类网络文件系统,网络还没就绪就开始挂载。
- 服务启动脚本写得烂:有些服务在启动脚本里做大量耗时的操作,比如 sleep、轮询。
排查方法:用 systemd-analyze blame 可以看每个服务的启动耗时。我一般先跑这个命令,找出耗时最长的前 5 个服务,然后逐个分析。
# 查看每个服务的启动耗时
systemd-analyze blame
# 查看整个启动过程的耗时分布
systemd-analyze plot > boot.svg
你想想看,如果 systemd-analyze blame 显示某个服务花了 10 秒,但它的功能只是记录日志或者监控硬件温度,那这个服务完全可以延迟启动,或者干脆关掉。
避坑指南:我曾经手快,在生产环境上直接 systemctl disable 了一个看起来没用的服务。结果那个服务是另一个关键服务的依赖,导致那个关键服务启动失败。所以,改之前一定要看 systemctl list-dependencies,确认没有隐藏的依赖关系。
小结:定位抖动,先分阶段
这一章我们把启动流程拆成了四个阶段。每个阶段都有不同的抖动原因和排查方法。
我的建议是:遇到启动抖动,先别急着调参数。用 dmesg 和 systemd-analyze 把耗时最长的阶段找出来。定位准了,后面的优化才有方向。