1. 启动流程概述
各位同学,咱们今天聊聊系统启动这件事。说实话,我做了这么多年性能优化,发现很多工程师对启动流程的理解停留在「开机-转圈-进桌面」这个层面。但你要真想优化启动速度,就得把每个环节掰开了看。
系统启动,说白了就是一段从硬件到软件的接力赛。我习惯把它分成四个阶段:固件阶段(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() 函数。这个阶段做的事情非常多,我挑几个关键点说说:
- 中断初始化:设置 IDT(中断描述符表),让 CPU 知道怎么处理硬件中断。
- 内存管理初始化:建立页表,初始化伙伴系统、slab 分配器。
- 调度器初始化:创建 idle 进程(PID=0),然后创建 init 进程(PID=1)。
- 设备驱动初始化:枚举 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。这种低级错误,查出来真想骂人。
下面这张图展示了整个启动流程的脉络:
从这张图能看出来,整个启动流程是串行的——前一个阶段没完成,后一个阶段就得等着。所以优化启动速度,本质上就是缩短每个阶段的耗时,或者让某些阶段并行起来。
小技巧:用 systemd-analyze plot > boot.svg 可以生成一张启动时间分布图。我每次优化完都会跑一遍这个命令,看看哪个环节还有优化空间。说白了,数据不会骗人。