系统启动流程剖析:从按下电源到用户空间

每次按下服务器电源键,到你能敲出第一行命令,中间发生了什么?

我见过不少运维同事,遇到启动抖动问题就一头扎进内核参数里调。其实很多时候,问题出在启动流程的前半段——BIOS/UEFI 阶段或者 Bootloader 加载。你连问题出在哪一层都没搞清楚,调参数就是瞎蒙。

这一章,我带你把整个启动流程走一遍。从按下电源键开始,到 systemd 拉起第一个用户进程结束。每个阶段我会告诉你:这里可能抖什么、怎么查、怎么避坑。

核心观点:启动抖动排查,第一步不是看参数,而是定位抖动发生在哪个阶段。定位不准,后面全是白费功夫。

Linux 系统启动流程总览 Linux 系统启动流程总览 BIOS / UEFI POST + 硬件初始化 Bootloader GRUB2 / 内核加载 内核初始化 start_kernel → init 用户空间启动 systemd / 服务启动 💡 说明: 每个阶段都可能产生启动抖动。BIOS/UEFI 阶段看硬件自检耗时,Bootloader 阶段看磁盘 I/O 和内核解压, 内核初始化看驱动加载和中断处理,用户空间看 systemd 的并行化效率和服务依赖。 时间轴 → ~1-5s ~0.5-2s ~1-10s ~5-30s

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 的工作流程大致是:

  1. 读取配置文件 /boot/grub/grub.cfg
  2. 显示菜单(如果有配置超时等待)
  3. 加载内核镜像 vmlinuz 和 initramfs
  4. 把控制权交给内核

这里最容易出问题的是 磁盘 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 把耗时最长的阶段找出来。定位准了,后面的优化才有方向。