启动流程详解
从按下电源键到看到登录界面,Linux 系统经历了一个复杂而精密的启动过程。本章将详细剖析这个过程的每一个阶段。
启动流程概览
┌─────────────────────────────────────────────────────┐
│ BIOS/UEFI 阶段 │
│ ├─ POST (Power-On Self Test) │
│ ├─ 检测硬件 │
│ └─ 加载 Bootloader │
└───────────────────┬─────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Bootloader 阶段 (GRUB2) │
│ ├─ 显示启动菜单 │
│ ├─ 加载内核镜像 (vmlinuz) │
│ ├─ 加载 initramfs │
│ └─ 传递内核参数 │
└───────────────────┬─────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 内核初始化阶段 │
│ ├─ 解压内核 │
│ ├─ 初始化硬件 │
│ ├─ 挂载 initramfs │
│ ├─ 初始化进程 (PID 0) │
│ └─ 启动 init 进程 (PID 1) │
└───────────────────┬─────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Init 系统阶段 (systemd) │
│ ├─ 挂载真实根文件系统 │
│ ├─- 启动系统服务 │
│ ├─ 配置网络 │
│ └─ 启动登录管理器 │
└───────────────────┬─────────────────────────────────┘
↓
登录界面
BIOS/UEFI 阶段
BIOS(传统方式)
流程:
加电自检(POST)
检查 CPU、内存、显卡等硬件 初始化硬件设备加载 MBR
读取硬盘第一个扇区(512 字节) 前 446 字节:Bootloader 代码 接下来 64 字节:分区表 最后 2 字节:魔数 0x55AA执行 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
下一步:学习 系统调用机制 章节。