Linux 启动全流程 — 从通电到登录提示符 / The Linux Boot Process from Power-On to Login
📅 创建时间:2026-06-17 🏷️ 标签:#Linux #启动 #GRUB #内核 #initramfs #systemd 📚 前置知识:[[02-firmware-bios-uefi]], [[03-partition-table]], [[05-filesystem-formats]]
📋 本章目标
- 追踪 Linux 从通电到登录提示符的完整六阶段流程
- 理解 GRUB 的工作原理——如何从 446 字节发展到加载内核
- 理解 Linux 内核镜像(bzImage)的自解压过程
- 理解 initramfs——为什么需要它,里面放了什么
- 理解 systemd 作为 PID 1 的启动编排
- 能够解释 GRUB 菜单里每个选项背后的技术含义
第1部分:完整链路速览
┌─────────────────────────────────────────────────────────────┐
│ Linux 启动六阶段全链路 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 按下电源键 │
│ ↓ │
│ ╔══════════════════════════════════════════════════════╗ │
│ ║ 阶段1: 固件 (UEFI/BIOS) ║ │
│ ║ 通电 → CPU复位 → 固件运行 → 发现启动设备 ║ │
│ ║ 详见 [[02-firmware-bios-uefi]] ║ │
│ ╚══════════════════════════════════════════════════════╝ │
│ ↓ │
│ ╔══════════════════════════════════════════════════════╗ │
│ ║ 阶段2: Bootloader (GRUB) ║ │
│ ║ 加载 grub.cfg → 显示菜单 → 加载内核+initramfs ║ │
│ ╚══════════════════════════════════════════════════════╝ │
│ ↓ │
│ ╔══════════════════════════════════════════════════════╗ │
│ ║ 阶段3: 内核加载 (bzImage 自解压) ║ │
│ ║ 解压 → 初始化硬件 → 挂载 initramfs 为临时根目录 ║ │
│ ╚══════════════════════════════════════════════════════╝ │
│ ↓ │
│ ╔══════════════════════════════════════════════════════╗ │
│ ║ 阶段4: initramfs(早期用户空间) ║ │
│ ║ 加载驱动 → 发现并挂载真正的根文件系统 ║ │
│ ╚══════════════════════════════════════════════════════╝ │
│ ↓ │
│ ╔══════════════════════════════════════════════════════╗ │
│ ║ 阶段5: systemd (PID 1) ║ │
│ ║ 切换到真实根 → 启动所有服务 → 到达登录提示 ║ │
│ ╚══════════════════════════════════════════════════════╝ │
│ ↓ │
│ ╔══════════════════════════════════════════════════════╗ │
│ ║ 阶段6: getty → login → shell ║ │
│ ║ 显示登录提示符 → 你输入密码 → 进入 shell ║ │
│ ╚══════════════════════════════════════════════════════╝ │
│ ↓ │
│ $ ← 你在这里 │
│ │
└─────────────────────────────────────────────────────────────┘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
30
31
32
33
34
35
36
37
38
39
40
第2部分:GRUB — Bootloader 的工作
2.1 GRUB 的多阶段设计
为什么 GRUB 需要多个阶段?答案:446 字节根本不够用。还记得 MBR 的引导代码只有 440 字节吗?
┌─────────────────────────────────────────────────────────────┐
│ GRUB Legacy Boot (BIOS 模式) 的三阶段 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Stage 1: boot.img (446 字节) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存在: MBR (LBA 0 的前 446 字节) │ │
│ │ │ │
│ │ 功能: 只做一件事 │ │
│ │ → 加载 LBA 1 处的 core.img 的第一个扇区 │ │
│ │ │ │
│ │ 为什么这么小: 因为 MBR 只有 440 字节给代码 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ Stage 1.5: core.img (约 32KB) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存在: MBR 之后、第一个分区之前的空隙 (post-MBR gap) │ │
│ │ 这个区域在现代磁盘上大约是 1MiB-512B │ │
│ │ │ │
│ │ 功能: 包含文件系统驱动 │ │
│ │ → 能直接读取 ext4/btrfs 里的 /boot/grub/ 目录 │ │
│ │ → 加载真正的 GRUB 模块 │ │
│ │ │ │
│ │ 为什么需要这步: Stage 1 连文件系统都不懂 │ │
│ │ Stage 1.5 给它读文件的能力 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ Stage 2: /boot/grub/x86_64-efi/normal.mod + grub.cfg │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 存在: /boot/grub/ 目录下 (普通文件, 不是扇区了) │ │
│ │ │ │
│ │ 功能: │ │
│ │ 1. 读取 grub.cfg → 显示启动菜单 │ │
│ │ 2. 加载用户选择的内核 (vmlinuz-linux) │ │
│ │ 3. 加载 initramfs (initramfs-linux.img) │ │
│ │ 4. 设置内核命令行参数 │ │
│ │ 5. 跳转到内核入口点 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ UEFI 模式的简化: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ UEFI 固件能直接读 FAT32 → 直接执行 grubx64.efi │ │
│ │ grubx64.efi 已经包含文件系统驱动 → 相当于传统 Stg 1+1.5│ │
│ │ → 直接从 Stage 2 开始 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2.2 GRUB 配置文件详解
# /boot/grub/grub.cfg (简化版)
# 这个文件通常由 grub-mkconfig 自动生成
# 手动编辑在 /etc/default/grub
# 加载必要的模块
insmod gzio # gzip 解压 (内核镜像是 gz 压缩的)
insmod part_gpt # GPT 分区表支持
insmod btrfs # btrfs 文件系统支持
# 设置根文件系统(GRUB 视角的"根")
set root='hd0,gpt3' # 第一块磁盘, GPT, 第3个分区
# GRUB 用 (hd磁盘编号, 分区类型+编号) 标识设备
# 默认启动项
set default=0 # 第 0 个菜单项为默认
set timeout=5 # 5 秒超时
# Linux 启动菜单项
menuentry 'Arch Linux' {
# 加载内核 (vmlinuz = bzImage)
linux /boot/vmlinuz-linux root=UUID=xxx-xxx rw quiet
# 加载 initramfs
initrd /boot/initramfs-linux.img
}
# 如果装了双系统, 这里会有 Windows 菜单项
menuentry 'Windows Boot Manager' {
# 搜索 ESP 分区里的 Windows Boot Manager
search --fs-uuid --set=root <ESP-UUID>
chainloader /EFI/Microsoft/Boot/bootmgfw.efi
# ↑ chainload = 把控制权交给另一个 .efi 文件
}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
30
31
32
33
2.3 内核命令行参数
┌─────────────────────────────────────────────────────────────┐
│ 你 GRUB 里看到的内核命令行参数 │
├─────────────────────────────────────────────────────────────┤
│ │
│ linux /boot/vmlinuz-linux root=UUID=xxx rw quiet │
│ │ │ │ │
│ │ │ └── quiet: 减少启动日志输出 │
│ │ └── rw: 以读写模式挂载根文件系统 │
│ └── root=UUID=xxx: 告诉内核根文件系统在哪个分区 │
│ │
│ 这是你 Arch 安装时必须填对的一个参数! │
│ 错了 → 内核找不到根 → kernel panic → 启动失败 │
│ │
│ 其他常见参数: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ init=/bin/bash 启动到 bash 而不是 systemd │ │
│ │ 用于修复系统时的救援模式 │ │
│ │ │ │
│ │ single 进入单用户维护模式 │ │
│ │ │ │
│ │ nomodeset 禁用内核图形模式设置 │ │
│ │ 显卡驱动问题时用 │ │
│ │ │ │
│ │ rootflags=subvol=@ 告诉内核挂载 btrfs 的哪个 subvol │ │
│ │ 你的 Arch 就是这么启动的 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘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
第3部分:内核加载 — bzImage 的秘密
3.1 bzImage 格式
┌─────────────────────────────────────────────────────────────┐
│ bzImage 的内部结构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ bzImage = setup header + 压缩的内核 + 解压代码 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ Setup Header │ 实模式启动代码 │ │
│ │ │ (约 20KB) │ 设置 32/64 位模式 │ │
│ │ │ │ 检测内存布局 │ │
│ │ └──────────────────┘ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ 解压代码 │ 自解压器 (decompressor) │ │
│ │ │ (misc.c) │ 一小段 C 代码 │ │
│ │ └──────────────────┘ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 压缩的内核 │ gzip/bzip2/lz4/xz/zstd 压缩 │ │
│ │ │ (vmlinux) │ 解压后约 30-50MB │ │
│ │ │ │ 这是真正执行的内核代码 │ │
│ │ │ │ │ │
│ │ └──────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ GRUB 加载 bzImage → 跳转到 setup header │
│ → setup header 做模式切换 → 跳转到 解压代码 │
│ → 解压代码把压缩内核解压到正确的位置 → 跳转到内核入口点 │
│ │
│ "bz" = "big zImage" (不是 bzip2!) │
│ 这是一个历史命名——早期的 zImage 必须放在 1MB 以下的物理内存 │
│ bzImage 可以放在任意位置 │
│ │
└─────────────────────────────────────────────────────────────┘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
30
31
32
33
34
35
36
3.2 内核初始化——start_kernel()
// 内核入口: arch/x86/kernel/head_64.S
// → 最终调用 init/main.c 中的 start_kernel()
asmlinkage __visible void __init start_kernel(void)
{
// 1. 基础设置
setup_arch(&command_line); // 架构特定初始化 (CPU/内存/设备树)
setup_command_line(); // 保存内核命令行参数
// 2. 内存管理初始化
mm_init(); // 建立页表、初始化内存分配器
// 3. 调度器初始化
sched_init(); // 初始化进程调度器
// 4. 中断和定时器
init_IRQ(); // 中断子系统
init_timers(); // 定时器
// 5. VFS 和 文件系统
vfs_caches_init(); // 虚拟文件系统缓存
// 6. 最后一步: 启动 init 进程
arch_call_rest_init();
// → 创建第一个内核线程 → 加载 initramfs → 执行 /init
}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
关键点:start_kernel() 结束时,第一个进程(PID 1)被创建。它做的第一件事就是尝试执行 initramfs 里的 /init。
第4部分:initramfs — 临时根文件系统
4.1 为什么需要 initramfs?
┌─────────────────────────────────────────────────────────────┐
│ 鸡生蛋蛋生鸡问题 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 内核需要: │
│ • 挂载根文件系统 → 需要读磁盘 │
│ • 读磁盘 → 需要磁盘驱动 │
│ • 磁盘驱动 → 在根文件系统里 (/lib/modules/...) │
│ │
│ 但我们还没挂载根文件系统! │
│ │
│ 这就是 initramfs 的用途: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ initramfs = 一个微型 Linux 系统, 打包成 cpio 归档 │ │
│ │ │ │
│ │ UEFI 固件 → GRUB→内核→挂载initramfs为临时的 '/' │ │
│ │ │ │ │
│ │ initramfs 里有: │ │
│ │ • NVMe/SATA 驱动 │ │
│ │ • btrfs/ext4 驱动 │ │
│ │ • 设备检测工具 (blkid) │ │
│ │ • 挂载脚本 (mount) │ │
│ │ • 一个简化的 init 程序 │ │
│ │ │ │ │
│ │ 加载驱动 → 发现 nvme0n1 │ │
│ │ → mount 真实的 / │ │
│ │ → 切换到真实根 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 没有 initramfs, 内核只能挂载那些"编译进内核的驱动所支持 │
│ 的设备"。但把所有可能的磁盘驱动编译进内核 → 内核太大。 │
│ │
│ initramfs 的设计哲学: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 内核保持精简, 驱动放进 initramfs │ │
│ │ 一个 initramfs 可以在所有机器上工作 —— │ │
│ │ 只要包含足够多的驱动 (现代 initramfs 通常 20-50MB) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘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
30
31
32
33
34
35
36
37
38
39
40
41
4.2 initramfs 内部
$ lsinitcpio /boot/initramfs-linux.img
# 输出 (简化):
# 目录结构
/
├── init ← 内核执行的第一个程序
├── bin/
│ ├── busybox ← 瑞士军刀: sh/mount/cat/ls 全在一个文件
│ └── blkid ← 检测文件系统 UUID
├── lib/
│ └── modules/
│ └── 6.x.x-arch1-1/ ← 内核模块
│ ├── kernel/drivers/nvme/ ← NVMe 驱动
│ ├── kernel/drivers/ata/ ← SATA 驱动
│ └── kernel/fs/btrfs/ ← btrfs 驱动
├── etc/
│ └── fstab ← 临时的挂载配置
└── usr/lib/systemd/
└── systemd-udevd ← 设备管理器
# 你的 Arch 用 mkinitcpio 生成 initramfs
# mkinitcpio 的配置在 /etc/mkinitcpio.conf
# HOOKS=(base udev autodetect modconf block filesystems fsck)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4.3 从 initramfs 切换到真实根
┌─────────────────────────────────────────────────────────────┐
│ pivot_root / switch_root 操作 │
├─────────────────────────────────────────────────────────────┤
│ │
│ initramfs 的 /init 做的最后一步: │
│ │
│ 1. 挂载真实根文件系统到 /new_root: │
│ mount /dev/nvme0n1p3 /new_root │
│ │
│ 2. 切换根目录: │
│ exec switch_root /new_root /sbin/init │
│ │
│ switch_root 做的事: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 把 /new_root 变成新的 '/' │ │
│ │ • 把旧的 initramfs 文件移走 (释放内存) │ │
│ │ • exec 执行新根里的 /sbin/init (通常是 systemd) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 从这一刻起, initramfs 的使命结束 │
│ 控制权交给 systemd (PID 1) │
│ │
└─────────────────────────────────────────────────────────────┘2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
第5部分:systemd — PID 1 的编排
5.1 systemd 的核心职责
┌─────────────────────────────────────────────────────────────┐
│ systemd 的启动编排 │
├─────────────────────────────────────────────────────────────┤
│ │
│ systemd 作为 PID 1 (init 进程) 负责: │
│ │
│ 1. 挂载剩余的文件系统 → 读取 /etc/fstab │
│ mount /home (btrfs subvolume @home) │
│ mount /boot (ESP, FAT32) │
│ │
│ 2. 启动服务 → 按依赖关系并行启动 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ │ │
│ │ network.target │ │
│ │ │ │ │
│ │ ├── NetworkManager.service │ │
│ │ │ └→ 配网 │ │
│ │ │ │ │
│ │ ├── sshd.service │ │
│ │ │ └→ SSH 服务 │ │
│ │ │ │ │
│ │ └── bluetooth.service │ │
│ │ │ │
│ │ multi-user.target │ │
│ │ │ │ │
│ │ └── display-manager.service │ │
│ │ └→ 启动登录管理器 (GDM/SDDM/LightDM) │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 3. 启动 getty → 在 tty 上显示登录提示 │
│ systemd 为每个虚拟终端 (tty1-tty6) 启动一个 getty │
│ │
│ 4. 管理运行级别 (target) │
│ rescue.target → 救援模式 │
│ multi-user.target → 多用户文本模式 │
│ graphical.target → 带图形界面 │
│ │
│ 你可以看到 systemd 的分析: │
│ # systemd-analyze │
│ Startup finished in 3.456s (kernel) + 2.123s (userspace) │
│ │
│ # systemd-analyze blame │
│ 1.234s NetworkManager.service │
│ 0.567s systemd-journald.service │
│ ... │
│ │
└─────────────────────────────────────────────────────────────┘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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
第6部分:getty → login → shell
┌─────────────────────────────────────────────────────────────┐
│ 最后一公里: 从 getty 到 shell │
├─────────────────────────────────────────────────────────────┤
│ │
│ systemd 为每个虚拟终端启动一个 getty: │
│ │
│ getty@tty1.service: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 打开 /dev/tty1 (虚拟终端设备) │ │
│ │ 2. 显示: "Arch Linux 6.x.x (tty1)" │ │
│ │ 3. 显示: "hostname login: " │ │
│ │ 4. 等待用户输入用户名 │ │
│ │ │ │
│ │ 用户输入 "Jaten" + <Enter> │ │
│ │ │ │
│ │ 5. getty exec → /bin/login │ │
│ │ login 程序: 显示 "Password: " │ │
│ │ 验证 /etc/shadow 里的密码哈希 │ │
│ │ │ │
│ │ 6. 验证通过 → login exec → /bin/bash (或 zsh/fish) │ │
│ │ bash 读取 ~/.bashrc → 设置环境 │ │
│ │ → 显示提示符: [Jaten@arch ~]$ │ │
│ │ │ │
│ │ 此时你登录的 shell 是 getty → login → bash │ │
│ │ 的子进程链. PID 1 (systemd) 是它们的祖先. │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 这个链条可以用 pstree 验证: │
│ $ pstree -p │
│ systemd(1)───systemd-logind(456) │
│ ├──getty(789)───login(790)───bash(791) │
│ │
│ 注意: 每次 exec 替换进程镜像, PID 保持不变. │
│ getty(PID 789) → login(PID 789) → bash(PID 789) │
│ │
└─────────────────────────────────────────────────────────────┘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
30
31
32
33
34
35
36
37
38
核心总结
总结1:六阶段完整链
UEFI → grubx64.efi → 加载 vmlinuz + initramfs → 内核自解压
→ initramfs 挂载真实根 → switch_root → systemd → getty → login → shell2
总结2:为什么需要 initramfs
内核需要驱动才能挂载根 → 但驱动在根里 → initramfs 打破循环
GRUB 加载内核 + initramfs → 内核用 initramfs 里的驱动挂载真实根2
总结3:哪些环节你可以干预
1. UEFI 设置 → 改变启动顺序、开关 Secure Boot
2. GRUB 命令行 → 临时修改内核参数 (按 e 编辑)
3. /etc/default/grub → 永久修改内核参数
4. /etc/mkinitcpio.conf → 控制 initramfs 里放什么
5. systemd 服务 → systemctl enable/disable2
3
4
5
章节测试
测试1:GRUB 的阶段
GRUB 为什么设计成多阶段(boot.img → core.img → normal.mod)?
测试2:initramfs 的必要性
如果没有 initramfs,内核在一个使用 NVMe SSD + btrfs 的系统上能否成功启动?为什么?
测试3:pivot_root
switch_root 做了什么?为什么要"切换根"?
测试4:内核参数
root=UUID=xxx 这个参数是给谁看的?作用是什么?
测试5:系统分析
运行 systemd-analyze 看到的 "kernel" 时间和 "userspace" 时间分别指什么阶段?
参考答案
测试1答案
因为 MBR 引导代码只有 440 字节,放不下文件系统驱动。Stage 1(boot.img,446B)只能跳到 Stage 1.5(core.img,~32KB),后者包含文件系统驱动才能读 /boot/grub/ 目录加载 Stage 2。这是受限于硬件的渐进式加载设计。UEFI 模式下固件直接执行 grubx64.efi(≈Stage 1+1.5),简化了这个过程。
测试2答案
不能。内核本身不包含 NVMe 驱动和 btrfs 驱动(作为可加载模块放在 /lib/modules/)。要挂载 btrfs 格式的根文件系统,必须先加载这两个模块。但模块在根文件系统里——这就陷入循环。initramfs 包含了这两个模块,在挂载真实根之前把它们加载到内核。
测试3答案
switch_root 做三件事:(1) 把挂载了真实根文件系统的 /new_root 变成新的 /,(2) 释放旧的 initramfs 占用的内存,(3) 执行新 / 里的 /sbin/init(systemd)。它的核心意义是将系统从"临时急救系统"切换到"真正的操作系统"。
测试4答案
root=UUID=xxx 是给内核看的。在 initramfs 阶段,init 脚本读取这个参数,用 blkid 找到对应 UUID 的分区(如 /dev/nvme0n1p3),然后挂载它作为新的根文件系统。这个参数是 initramfs 和内核命令行之间的关键约定。
测试5答案
- kernel 时间:从 GRUB 跳转到内核入口点到
start_kernel()执行完毕、挂载 initramfs 并调用 initramfs 内/init的这段时间。包括内核自解压、硬件初始化。 - userspace 时间:从 initramfs 的
/init开始执行,经过 switch_root、systemd 启动服务,到graphical.target或multi-user.target就绪的这段时间。
相关笔记
- [[02-firmware-bios-uefi]] - 启动的第一步:固件
- [[07-windows-boot]] - Windows 的启动链,对照阅读
- [[14-dual-boot]] - GRUB 链式加载 Windows 的实现原理
下一步学习
- [ ] 阅读 07 - Windows 启动全流程
学习状态:🟡 开始学习