一、启动流程概述:BIOS/UEFI、引导加载程序、内核初始化、用户空间启动

各位同学,咱们今天聊聊系统启动这件事。说实话,我做了这么多年性能优化,发现很多工程师对启动流程的理解停留在「开机-转圈-进桌面」这个层面。但你要真想优化启动速度,就得把每个环节掰开了看。

系统启动,说白了就是一段从硬件到软件的接力赛。我习惯把它分成四个阶段:固件阶段(BIOS/UEFI)、引导加载程序、内核初始化、用户空间启动。每个阶段都有各自的瓶颈,咱们一个一个来拆解。

1.1 固件阶段:BIOS vs UEFI

按下电源键的那一刻,CPU 首先执行的是固件代码。这里有两个流派:传统的 BIOS 和现代的 UEFI。

BIOS(Basic Input/Output System),这玩意儿已经服役三十多年了。它启动时有个著名的「16位实模式」限制,说白了就是只能访问 1MB 内存空间。而且它得逐个扫描设备、执行自检(POST),这个过程非常慢。

UEFI(Unified Extensible Firmware Interface),这是后来者。它运行在 32位或64位模式下,能直接访问大内存。最关键的是,UEFI 支持并行初始化设备,不像 BIOS 那样一个个来。

我个人的经验:在项目中遇到过一台老服务器,BIOS 自检就要花 40 秒。换成 UEFI 模式后,同样的硬件,启动时间直接砍到 12 秒。差距就这么大。

这里有个表格,对比一下两者的关键差异:

特性 BIOS UEFI
运行模式 16位实模式 32/64位保护模式
磁盘分区 MBR(最大2TB) GPT(最大9.4ZB)
启动速度 慢(串行检测) 快(并行初始化)
安全性 无 Secure Boot 支持 Secure Boot
用户界面 文本菜单 图形界面+鼠标

避坑指南:我曾经遇到过一台机器,UEFI 模式下死活起不来。查了半天,发现是显卡的 Option ROM 不兼容。解决办法很简单——在 UEFI 设置里开启 CSM(Compatibility Support Module)兼容模式。嗯,这种坑踩过一次就记住了。

1.2 引导加载程序:GRUB 与内核加载

固件阶段结束后,控制权交给了引导加载程序。Linux 世界里最常用的是 GRUB(GRand Unified Bootloader)。

GRUB 的任务很简单:找到内核文件,把它加载到内存,然后跳转执行。但这里有个细节——GRUB 本身也分两个阶段。

  • 第一阶段(boot.img):写在 MBR 或 GPT 的专用分区里。代码量极小,只有 512 字节。它的任务就是加载第二阶段的 core.img。
  • 第二阶段(core.img):这个就大了,包含了文件系统驱动。它能读取 /boot 分区,找到 vmlinuz 内核文件和 initramfs 镜像。

你想想看,为什么要有 initramfs?因为内核刚启动时,连磁盘驱动都没有。initramfs 是一个微型根文件系统,它包含了必要的驱动和工具,让内核能挂载真正的根文件系统。

优化点:我在调优嵌入式设备时发现,GRUB 的等待时间默认是 5 秒。对于服务器来说,这 5 秒完全浪费了。直接改成 timeout=0,能省下不少时间。当然,如果你需要进恢复模式,可以按住 Shift 键强制显示菜单。

1.3 内核初始化:从 start_kernel 到 init 进程

内核被加载到内存后,执行入口是 start_kernel() 函数。这个阶段做的事情非常多,我挑几个关键点说说:

  1. 中断初始化:设置 IDT(中断描述符表),让 CPU 知道怎么处理硬件中断。
  2. 内存管理初始化:建立页表,初始化伙伴系统、slab 分配器。
  3. 调度器初始化:创建 idle 进程(PID=0),然后创建 init 进程(PID=1)。
  4. 设备驱动初始化:枚举 PCI 设备,加载驱动,创建设备节点。

这里有个常见的性能陷阱——驱动加载顺序。内核默认是按编译顺序加载驱动的,但有些驱动之间有依赖关系。如果 A 驱动依赖 B 驱动,而 B 排在后面,内核就得等 A 加载失败后再重试。这一来一回,几十毫秒就没了。

注意:我曾经调试过一个存储服务器,启动时磁盘扫描花了 8 秒。后来发现是内核参数里没加 rootdelay=0,导致内核傻傻地等了 8 秒才去挂载根文件系统。加上这个参数后,启动时间直接缩短到 2 秒。

内核初始化的最后一步,是挂载根文件系统并执行 init 进程。这个 init 进程的 PID 永远是 1,它是所有用户空间进程的祖先。

1.4 用户空间启动:systemd 与并行化

init 进程启动后,系统进入用户空间阶段。传统的 SysV init 是串行启动服务的——一个服务启动完,下一个才能开始。这效率太低了。

现代 Linux 发行版几乎都换成了 systemd。systemd 的核心思想是并行启动。它通过分析服务之间的依赖关系,把没有依赖关系的服务同时启动。

举个例子:网络服务和 SSH 服务没有依赖关系,它们可以同时启动。但 NFS 挂载依赖网络服务,那就得等网络就绪后再启动。

我建议:用 systemd-analyze blame 命令查看每个服务的启动耗时。我见过最夸张的一次,某个服务启动花了 30 秒,结果发现是脚本里写了个 sleep 30。这种低级错误,查出来真想骂人。

下面这张图展示了整个启动流程的脉络:

系统启动流程总览 BIOS/UEFI 硬件自检 设备枚举 GRUB 加载内核 加载 initramfs 内核初始化 start_kernel() 驱动加载 用户空间 systemd 服务并行启动 0s ~5s ~10s ~15s → 时间

从这张图能看出来,整个启动流程是串行的——前一个阶段没完成,后一个阶段就得等着。所以优化启动速度,本质上就是缩短每个阶段的耗时,或者让某些阶段并行起来。

小技巧:用 systemd-analyze plot > boot.svg 可以生成一张启动时间分布图。我每次优化完都会跑一遍这个命令,看看哪个环节还有优化空间。说白了,数据不会骗人。

好了,这一章咱们把启动流程的四个阶段过了一遍。从固件到内核再到用户空间,每个环节都有值得深挖的优化点。下一章我会详细讲怎么用工具测量启动时间,毕竟「没有测量就没有优化」嘛。

可可精品资料库