引言:理解Linux启动链的意义
每当按下电源键,计算机从一片死寂中苏醒,到呈现出熟悉的登录界面,背后经历了一段精密而复杂的旅程。Linux系统的启动过程涉及硬件初始化、引导加载、内核解压、设备驱动加载、文件系统挂载以及用户空间初始化等多个阶段。对于运维工程师和内核开发者来说,理解这条启动链不仅有助于排查启动故障,更能深入把握操作系统与硬件的交互机制。
本文将从按下电源键的那一刻起,逐层剖析Linux系统启动的每一个关键阶段,结合实际代码和配置,带你完整走一遍从硬件自检到PID 1诞生的全过程。
第一阶段:硬件自检(POST)与固件初始化
BIOS vs UEFI:两种固件架构
计算机通电后,CPU的第一个指令地址被硬件固定为
1 | 0xFFFFFFF0 |
(32位BIOS)或
1 | 0xFFFFFFF0 |
附近的复位向量。这个地址映射到主板上的固件ROM,由此开始POST(Power-On Self-Test)自检流程。
传统的BIOS(Basic Input/Output System)和现代的UEFI(Unified Extensible Firmware Interface)在工作方式上有本质区别:
| 特性 | BIOS | UEFI |
|---|---|---|
| 启动模式 | 16位实模式 | 32/64位保护模式 |
| 磁盘支持 | MBR(最大2TB) | GPT(最大9.4ZB) |
| 启动程序大小 | 512字节(MBR) | 无硬性限制 |
| 安全启动 | 不支持 | Secure Boot |
| Shell环境 | 无 | UEFI Shell |
| 驱动支持 | INT 13h中断 | UEFI驱动模型 |
POST自检过程会依次检查CPU、内存、显卡、存储设备等关键硬件。如果自检失败,主板会通过蜂鸣器发出不同模式的报警声(Award BIOS的1长2短代表显卡错误,AMI BIOS的3短代表内存错误等)。在UEFI系统上,还可以通过
1 | efibootmgr |
工具查看和管理启动项:
1
2
3
4
5
6
7
8 # 查看UEFI启动项
efibootmgr -v
# 添加新的启动项
efibootmgr -c -d /dev/sda -p 1 -L "My Linux" -l '\EFI\mylinux\shimx64.efi'
# 设置启动顺序
efibootmgr -o 0001,0002,0000
固件完成自检后,根据启动顺序查找可引导设备,将控制权交给引导加载程序。
第二阶段:引导加载程序(Bootloader)
GRUB2:Linux最主流的引导加载器
GRUB2(GRand Unified Bootloader version 2)是绝大多数Linux发行版的默认引导加载器。它的启动过程分为三个阶段:
- boot.img:位于MBR的前440字节(BIOS模式)或UEFI的
1grubx64.efi
,负责加载core.img
- core.img:包含diskboot.img和GRUB内核,负责识别文件系统并加载GRUB配置
- GRUB正常模式:读取
1grub.cfg
配置文件,显示启动菜单
GRUB2的配置文件路径为
1 | /boot/grub2/grub.cfg |
(RHEL系)或
1 | /boot/grub/grub.cfg |
(Debian系),但不应直接编辑此文件,而应修改
1 | /etc/default/grub |
后重新生成:
1
2
3
4
5
6
7
8
9
10
11 # /etc/default/grub 关键配置
GRUB_TIMEOUT=5 # 启动菜单超时(秒)
GRUB_DEFAULT=0 # 默认启动项
GRUB_CMDLINE_LINUX="rhgb quiet" # 内核启动参数
GRUB_DISABLE_SUBMENU=true # 禁用子菜单
# 重新生成grub.cfg(BIOS模式)
grub2-mkconfig -o /boot/grub2/grub.cfg
# 重新生成grub.cfg(UEFI模式)
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
GRUB如何加载内核
GRUB在
1 | grub.cfg |
中定义了每个菜单项的加载命令,一个典型的Linux启动项如下:
1
2
3
4
5
6
7
8
9 menuentry 'CentOS Linux (5.14.0-284.11.1.el9_2.x86_64)' --class centos {
load_video
set gfxpayload=keep
insmod gzio
insmod part_msdos
insmod xfs
linux /vmlinuz-5.14.0-284.11.1.el9_2.x86_64 root=/dev/mapper/cs-root ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/cs-swap rd.lvm.lv=cs/root rd.lvm.lv=cs/swap
initrd /initramfs-5.14.0-284.11.1.el9_2.x86_64.img
}
其中
1 | linux |
命令加载内核镜像(vmlinuz),
1 | initrd |
命令加载初始RAM磁盘(initramfs)。GRUB通过BIOS INT 13h或UEFI Block I/O Protocol将这些文件读入内存,然后跳转到内核入口点,将控制权交给Linux内核。
第三阶段:内核初始化
内核解压与实模式启动
现代Linux内核以压缩格式存储(vmlinuz = vm-linux-gzip),内核入口代码首先进行自解压。解压流程位于
1 | arch/x86/boot/compressed/ |
目录:
1
2
3
4
5
6
7
8 // arch/x86/boot/compressed/head_64.S(简化)
startup_64:
// 1. 检测CPU是否支持长模式(64位)
// 2. 初始化临时页表(identity mapping)
// 3. 将内核解压到目标地址
// 4. 跳转到解压后的内核入口
call extract_kernel
jmp *%rax // 跳转到解压后的startup_64
内核解压后进入
1 | arch/x86/kernel/head_64.S |
的正式入口,开始执行内核初始化的C代码
1 | start_kernel() |
,这是内核启动的核心函数。
start_kernel():内核启动的核心
1 | start_kernel() |
定义在
1 | init/main.c |
中,它负责初始化内核的所有子系统。这是一个超过300行的函数,按顺序执行约80个初始化调用。以下是关键步骤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 // init/main.c(简化)
void __init start_kernel(void)
{
// 1. 架构相关初始化
setup_arch(&command_line); // 内存布局、NUMA、SMP等
// 2. 内存管理初始化
mm_core_init(); // 页表、slab分配器
mem_init(); // 释放未使用内存
kmem_cache_init_late(); // SLAB后期初始化
// 3. 进程调度器
sched_init(); // 初始化CFS/EEVDF调度器
// 4. 中断与时钟
init_IRQ(); // 中断描述符表
time_init(); // 时钟源和tick设备
timer_init(); // 高精度定时器
// 5. 设备驱动模型
driver_init(); // sysfs、设备树、platform bus
// 6. 控制台
console_init(); // 早期printk输出
// 7. 内核线程
kernel_thread(kernel_init, ...); // PID 1的起源
cpu_startup_entry(CPUHP_ONLINE); // 进入idle循环
}
在
1 | start_kernel() |
的最后,内核创建了第一个内核线程
1 | kernel_init |
,这个线程最终会成为PID 1的init进程。同时,主CPU进入idle循环,等待调度器分配工作。
第四阶段:initramfs——早期用户空间
为什么需要initramfs
Linux内核本身只是一个压缩的ELF二进制文件,它并不包含根文件系统所需的磁盘驱动和文件系统模块。比如,如果根文件系统在LVM逻辑卷上,内核需要先加载LVM模块和设备映射器才能访问。这就是initramfs(initial RAM filesystem)的作用——它是一个临时的根文件系统,包含了挂载真正根文件系统所需的一切。
initramfs使用
1 | cpio |
归档格式,通过
1 | gzip |
压缩,在内核启动时被解压到内存中的
1 | rootfs |
(tmpfs)。它的核心文件是
1 | /init |
脚本,由内核作为PID 1执行。
initramfs内部结构
1
2
3
4
5
6
7
8
9
10
11 # 查看initramfs内容
lsinitrd /boot/initramfs-$(uname -r).img
# 典型结构
/init # 启动脚本(通常指向/sbin/init)
/sbin/modprobe # 内核模块加载工具
/sbin/dracut-init # dracut框架初始化
/etc/modprobe.d/ # 模块配置
/usr/lib/modules/$kernel/ # 内核模块
/usr/lib/udev/ # udev规则
/sysroot/ # 真正根文件系统的挂载点
init脚本的执行流程
以dracut生成的initramfs为例,
1 | /init |
脚本的执行流程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 #!/usr/bin/sh
# /init(简化版)
# 1. 挂载虚拟文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
# 2. 加载必要的内核模块
modprobe ext4 # 根文件系统驱动
modprobe dm-mod # 设备映射器(LVM)
modprobe dm-crypt # 磁盘加密(如需要)
# 3. 运行udev处理设备事件
/usr/lib/systemd/systemd-udevd --daemon
udevadm trigger --type=subsystems --action=add
udevadm settle
# 4. 激活LVM/Crypt等块设备
lvm vgchange -ay # 激活卷组
# 5. 挂载真正的根文件系统
mount /dev/mapper/cs-root /sysroot
# 6. 切换到真正的根文件系统
exec switch_root /sysroot /sbin/init
1 | switch_root |
是关键的系统调用——它删除当前initramfs的所有内容(释放内存),将
1 | /sysroot |
挂载为新的根文件系统,然后执行
1 | /sbin/init |
(PID 1),完成从早期用户空间到真正用户空间的过渡。
调试initramfs问题
当系统启动卡在initramfs阶段时,可以在GRUB启动项中添加调试参数:
1
2
3
4
5
6
7 # 在grub.cfg的内核参数中添加
rd.shell=1 # 进入紧急shell
rd.debug=1 # 输出详细调试信息
rd.break=premount # 在挂载根文件系统前中断
rd.break=mount # 在挂载根文件系统时中断
# 也可以在GRUB菜单中按e编辑启动项,手动添加
第五阶段:systemd——PID 1与用户空间初始化
systemd作为PID 1的职责
当
1 | switch_root |
执行
1 | /sbin/init |
后,systemd作为PID 1开始运行。在绝大多数现代Linux发行版中,
1 | /sbin/init |
是systemd的符号链接:
1
2
3
4
5
6 # 验证
$ ls -la /sbin/init
lrwxrwxrwx 1 root root 22 /sbin/init -> ../lib/systemd/systemd
$ readlink /sbin/init
../lib/systemd/systemd
作为PID 1,systemd承担三个核心职责:
- 信号转发:内核发送的信号(如SIGINT、SIGTERM)由PID 1接收并转发给其他进程
- 孤儿进程回收:父进程死亡的子进程会被PID 1收养,避免产生僵尸进程
- 服务管理:按依赖关系并行启动系统服务
systemd启动目标与依赖图
systemd使用
1 | target |
单元来组织启动阶段,每个target代表一个启动状态,类似于SysVinit的运行级别:
| Target | 对应运行级别 | 说明 |
|---|---|---|
| sysinit.target | — | 基础系统初始化 |
| basic.target | — | 基础服务就绪 |
| multi-user.target | runlevel 3 | 多用户命令行模式 |
| graphical.target | runlevel 5 | 图形界面模式 |
| rescue.target | runlevel 1 | 救援模式 |
| emergency.target | — | 紧急模式(最小化) |
查看当前默认target和启动耗时:
1
2
3
4
5
6
7
8
9
10 # 查看默认启动目标
systemctl get-default
# 设置默认启动目标
systemctl set-default multi-user.target
# 查看启动耗时分析
systemd-analyze
systemd-analyze blame
systemd-analyze critical-chain
systemd并行启动原理
与传统的SysVinit串行启动不同,systemd的核心优势在于并行启动。其实现依赖三个机制:
- socket激活:systemd提前创建服务所需的socket,服务在启动时直接继承已创建的socket,无需等待依赖服务完全就绪
- D-Bus激活:当客户端通过D-Bus请求某个服务时,systemd按需启动该服务
- 显式依赖声明:通过
1After=
、
1Requires=、
1Wants=等声明依赖,systemd据此构建有向无环图(DAG),并行执行无依赖关系的单元
一个典型的服务单元文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 # /etc/systemd/system/nginx.service
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=notify
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
第六阶段:用户登录与会话初始化
登录管理器的启动
在
1 | graphical.target |
下,systemd会启动
1 | display-manager.service |
(如GDM、SDDM、LightDM),它负责显示图形登录界面。在
1 | multi-user.target |
下,则由
1 | getty@.service |
在各个TTY上提供文本登录提示:
1
2
3
4
5
6
7
8
9
10 # 查看getty实例
systemctl list-units 'getty@*'
# 手动启动一个getty
systemctl start getty@tty3.service
# 配置自动登录(修改 /etc/systemd/system/getty@tty1.service.d/override.conf)
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin username --noclear %I $TERM
登录后的会话初始化
用户成功登录后,shell启动过程中会依次读取一系列配置文件,建立完整的用户环境:
| 文件 | 登录Shell | 交互式非登录Shell | 作用 |
|---|---|---|---|
| /etc/profile | ✓ | ✗ | 系统级环境变量和PATH |
| /etc/profile.d/*.sh | ✓ | ✗ | 系统级脚本片段 |
| ~/.bash_profile | ✓ | ✗ | 用户级登录配置 |
| ~/.bashrc | 间接 | ✓ | 用户级交互配置 |
| /etc/bashrc | 间接 | ✓ | 系统级交互配置 |
理解这个加载顺序对于配置环境变量、别名和启动脚本至关重要。常见的错误是在
1 | ~/.bash_profile |
中定义环境变量但忘记
1 | source ~/.bashrc |
,导致交互式shell中环境变量丢失。
启动故障排查实战
常见启动故障与解决方案
以下列出几个最常见的启动故障场景和排查方法:
1. GRUB rescue模式(grub rescue >)
当GRUB找不到
1 | normal.mod |
或配置文件时会进入rescue模式。这通常发生在分区调整或GRUB安装损坏后:
1
2
3
4
5
6
7
8
9
10
11 # 在grub rescue中修复
grub rescue> ls # 列出所有分区
grub rescue> ls (hd0,gpt2)/boot/grub2 # 找到GRUB目录所在分区
grub rescue> set prefix=(hd0,gpt2)/boot/grub2
grub rescue> set root=(hd0,gpt2)
grub rescue> insmod normal
grub rescue> normal # 进入正常模式
# 进入系统后重新安装GRUB
grub2-install /dev/sda # BIOS模式
grub2-install # UEFI模式
2. 内核panic——根文件系统无法挂载
内核panic输出
1 | VFS: Unable to mount root fs |
时,通常是因为initramfs中缺少必要的驱动或根设备路径错误:
1
2
3
4
5 # 重新生成initramfs(包含所需驱动)
dracut --force --add-drivers "ext4 dm-mod" /boot/initramfs-$(uname -r).img $(uname -r)
# 或在Debian系
update-initramfs -u -k all
3. systemd紧急模式
如果关键文件系统挂载失败或fsck检查不通过,systemd会进入emergency.target。此时可以手动挂载和修复:
1
2
3
4
5 # 在emergency shell中
mount -o remount,rw /
fsck -y /dev/sda2
mount -a
systemctl default # 尝试回到正常模式
启动过程性能优化
使用
1 | systemd-analyze |
工具链来识别和优化启动瓶颈:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 # 生成启动时间SVG图
systemd-analyze plot > boot_analysis.svg
# 查看最耗时的服务
systemd-analyze blame | head -20
# 查看关键路径
systemd-analyze critical-chain
# 禁用不必要的服务
systemctl disable NetworkManager-wait-online.service
systemctl mask plymouth-start.service
# 设置服务延迟启动
# 在unit文件的[Unit]段添加
DefaultDependencies=no
After=basic.target
完整启动链全景图
将上述所有阶段串联起来,Linux启动的完整流程可以总结为:
- POST → 固件自检,检测硬件
- 固件 → UEFI/BIOS查找引导设备
- GRUB → 加载vmlinuz和initramfs到内存
- 内核解压 → 自解压到正确内存地址
- start_kernel() → 初始化CPU、内存、调度器、驱动
- kernel_init线程 → 解压initramfs到rootfs
- /init → 加载驱动、挂载根文件系统
- switch_root → 切换到真正的根文件系统
- /sbin/init (systemd) → 作为PID 1启动
- sysinit.target → 基础系统初始化
- basic.target → 核心服务就绪
- multi-user/graphical.target → 达到目标运行状态
- getty/display-manager → 提供登录界面
- 用户登录 → Shell初始化,用户环境就绪
理解这条完整的启动链,不仅能帮助你快速定位启动故障,更能在需要定制系统启动行为时(如裁剪嵌入式系统、优化云服务器启动速度、配置双系统引导)做到心中有数、手中有招。
汤不热吧