启动流程详解

从按下电源键到看到登录界面,Linux 系统经历了一个复杂而精密的启动过程。本章将详细剖析这个过程的每一个阶段。

启动流程概览

┌─────────────────────────────────────────────────────┐
│  BIOS/UEFI 阶段                                      │
│  ├─ POST (Power-On Self Test)                       │
│  ├─ 检测硬件                                        │
│  └─ 加载 Bootloader                                 │
└───────────────────┬─────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────┐
│  Bootloader 阶段 (GRUB2)                             │
│  ├─ 显示启动菜单                                    │
│  ├─ 加载内核镜像 (vmlinuz)                          │
│  ├─ 加载 initramfs                                  │
│  └─ 传递内核参数                                    │
└───────────────────┬─────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────┐
│  内核初始化阶段                                      │
│  ├─ 解压内核                                        │
│  ├─ 初始化硬件                                      │
│  ├─ 挂载 initramfs                                  │
│  ├─ 初始化进程 (PID 0)                              │
│  └─ 启动 init 进程 (PID 1)                          │
└───────────────────┬─────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────┐
│  Init 系统阶段 (systemd)                             │
│  ├─ 挂载真实根文件系统                              │
│  ├─- 启动系统服务                                   │
│  ├─ 配置网络                                        │
│  └─ 启动登录管理器                                  │
└───────────────────┬─────────────────────────────────┘
                    ↓
                登录界面

BIOS/UEFI 阶段

BIOS(传统方式)

流程

  1. 加电自检(POST)

    检查 CPU、内存、显卡等硬件
    初始化硬件设备
    
  2. 加载 MBR

    读取硬盘第一个扇区(512 字节)
    前 446 字节:Bootloader 代码
    接下来 64 字节:分区表
    最后 2 字节:魔数 0x55AA
    
  3. 执行 Bootloader

    BIOS 跳转到 MBR 中的 Bootloader 代码
    

UEFI(现代方式)

优势

  • 支持 GPT 分区表(突破 2TB 限制)
  • 支持安全启动(Secure Boot)
  • 更快的启动速度
  • 图形化配置界面

流程

1. UEFI 固件初始化
2. 读取 ESP (EFI System Partition)
3. 加载 EFI 应用程序(如 grubx64.efi)
4. 执行 Bootloader

查看启动模式:

# 检查是否为 UEFI 启动
ls /sys/firmware/efi
# 如果目录存在,则是 UEFI;否则是 BIOS

# 查看 ESP 分区
lsblk -f | grep vfat

Bootloader 阶段(GRUB2)

GRUB2 配置

主配置文件/boot/grub/grub.cfg(自动生成,不要手动编辑)

用户配置文件/etc/default/grub

# /etc/default/grub
GRUB_DEFAULT=0                          # 默认启动项
GRUB_TIMEOUT=5                          # 超时时间(秒)
GRUB_DISTRIBUTOR="$(lsb_release -i -s 2> /dev/null || echo Debian)"
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

# 更新 GRUB 配置
sudo update-grub
# 或者
sudo grub-mkconfig -o /boot/grub/grub.cfg

GRUB 启动流程

阶段 1:MBR

- 位于 MBR 的前 446 字节
- 作用:加载阶段 1.5 或阶段 2

阶段 1.5:

- 位于 MBR 后的若干扇区
- 包含文件系统驱动
- 可以读取 /boot 分区

阶段 2:

- 位于 /boot/grub
- 显示启动菜单
- 加载内核和 initramfs

内核参数

常用内核参数:

# 单用户模式(救援模式)
linux /vmlinuz root=/dev/sda1 single

# 调试模式
linux /vmlinuz root=/dev/sda1 debug

# 禁用图形启动画面
linux /vmlinuz root=/dev/sda1 quiet splash

# 指定 init 程序
linux /vmlinuz root=/dev/sda1 init=/bin/bash

# 限制内存
linux /vmlinuz root=/dev/sda1 mem=2G

# 禁用 SELinux
linux /vmlinuz root=/dev/sda1 selinux=0

查看当前内核参数:

cat /proc/cmdline

内核初始化阶段

内核解压和启动

vmlinuz 结构

┌──────────────────┐
│   自解压代码     │  可执行的引导代码
├──────────────────┤
│   压缩的内核     │  gzip/bzip2/lzma 压缩
└──────────────────┘

启动过程

// arch/x86/boot/compressed/head_64.S
// 1. 解压内核
startup_64:
    call extract_kernel

// 2. 跳转到解压后的内核
    jmp *%rax

// init/main.c
// 3. 内核主函数
asmlinkage __visible void __init start_kernel(void)
{
    // 架构相关初始化
    setup_arch(&command_line);
    
    // 初始化 CPU
    setup_per_cpu_areas();
    
    // 初始化调度器
    sched_init();
    
    // 初始化内存管理
    mm_init();
    
    // 初始化 VFS
    vfs_caches_init();
    
    // 创建 init 进程
    rest_init();
}

initramfs(初始内存文件系统)

作用

  • 提供临时根文件系统
  • 包含必要的驱动和工具
  • 用于挂载真实根文件系统

查看 initramfs 内容

# 解压 initramfs
mkdir /tmp/initramfs
cd /tmp/initramfs
zcat /boot/initrd.img-$(uname -r) | cpio -idmv

# 查看结构
tree -L 2

# 重新打包
find . | cpio -o -H newc | gzip > /boot/custom-initrd.img

生成 initramfs

# Debian/Ubuntu
sudo update-initramfs -u

# RHEL/CentOS
sudo dracut --force

init 进程启动

内核代码

// init/main.c
static int __ref kernel_init(void *unused)
{
    // 尝试运行 init 程序
    if (ramdisk_execute_command) {
        run_init_process(ramdisk_execute_command);
    }
    
    // 按优先级尝试
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");
    
    panic("No working init found.");
}

systemd 初始化

systemd 启动流程

systemd (PID 1)
    ↓
default.target (通常链接到 graphical.target)
    ↓
├─ sysinit.target
│  ├─ local-fs.target        挂载本地文件系统
│  ├─ swap.target            启用交换分区
│  └─ cryptsetup.target      解密加密分区
│
├─ basic.target
│  ├─ paths.target           路径监控
│  ├─ slices.target          cgroup 切片
│  ├─ sockets.target         套接字
│  └─ timers.target          定时器
│
├─ multi-user.target
│  ├─ network.target         网络服务
│  ├─ remote-fs.target       远程文件系统
│  └─ 各种系统服务
│
└─ graphical.target
   └─ display-manager.service  图形登录管理器

systemd 单元类型

# Service 单元(服务)
/lib/systemd/system/sshd.service

# Target 单元(目标,类似 runlevel)
/lib/systemd/system/multi-user.target

# Mount 单元(挂载点)
/lib/systemd/system/home.mount

# Timer 单元(定时任务)
/lib/systemd/system/apt-daily.timer

# Socket 单元(套接字激活)
/lib/systemd/system/sshd.socket

分析启动时间

# 查看启动总时间
systemd-analyze

# 查看每个服务的启动时间
systemd-analyze blame

# 绘制启动流程图
systemd-analyze plot > boot.svg

# 查看关键链
systemd-analyze critical-chain

# 验证单元文件
systemd-analyze verify /lib/systemd/system/sshd.service

实践:自定义启动服务

创建 systemd 服务

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target
Requires=network.target

[Service]
Type=simple
User=myuser
Group=mygroup
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/start.sh
ExecStop=/opt/myapp/bin/stop.sh
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

服务管理

# 重新加载配置
sudo systemctl daemon-reload

# 启动服务
sudo systemctl start myapp

# 开机自启
sudo systemctl enable myapp

# 查看状态
sudo systemctl status myapp

# 查看日志
sudo journalctl -u myapp -f

启动故障排查

进入救援模式

方法 1:GRUB 菜单

1. 重启系统
2. 在 GRUB 菜单按 'e' 编辑
3. 在 linux 行末添加:systemd.unit=rescue.target
4. 按 Ctrl+X 启动

方法 2:单用户模式

在 linux 行末添加:single 或 1

方法 3:emergency 模式

在 linux 行末添加:systemd.unit=emergency.target

常见启动问题

1. 文件系统损坏

# 只读挂载根分区
mount -o remount,ro /

# 检查并修复
fsck -y /dev/sda1

# 重新挂载
mount -o remount,rw /

2. 忘记 root 密码

# 1. GRUB 添加:init=/bin/bash
# 2. 进入后重新挂载根分区
mount -o remount,rw /

# 3. 修改密码
passwd root

# 4. 重启
exec /sbin/init

3. 查看启动日志

# 查看所有启动日志
journalctl -b

# 查看上一次启动日志
journalctl -b -1

# 查看内核消息
dmesg | less

# 查看失败的服务
systemctl --failed

启动优化

禁用不必要的服务

# 列出开机自启的服务
systemctl list-unit-files --type=service --state=enabled

# 禁用服务
sudo systemctl disable bluetooth.service
sudo systemctl disable cups.service

# 屏蔽服务(完全禁用)
sudo systemctl mask bluetooth.service

并行启动优化

# /etc/systemd/system.conf
[Manager]
DefaultTimeoutStartSec=30s    # 减少超时时间
DefaultTimeoutStopSec=30s

下一步:学习 系统调用机制 章节。