mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
4323 字
12 分钟
从系统加电到 Shell
2024-03-06

从系统加电到 Shell#

开发操作系统之前,需要了解底层硬件的基本工作方式。在 [汇编语言:从机器码到现代应用的底层探索](../language/x86 汇编语言入门.md) 中介绍了传统计算机体系结构,在 CPU 工作模式 中提到了 CPU 的三种工作模式。本文以系统加电到进入 Shell 的过程为主线,逐层拆解硬件、固件、引导加载器、内核、用户空间各自完成了什么工作,以及这些设计背后的硬件约束和工程取舍。

第 1 层:固件程序层#

CPU 上电复位后,寄存器被硬连线为预设值:CS 指向烧录在主板 ROM 中的 BIOS 程序入口地址 0xFFFF0(EIP = 0x0000FFF0,CS = 0xF000),处理器处于 16 位实模式、关闭分页(CR0 = 0x60000010),中断停用(EFLAGS = 0x00000002)。这些初始值并非随意设定,x86 架构为了保证向后兼容,必须让上电后的第一条指令从 ROM 中取,因为此时 RAM 中还没有有效数据。0xFFFF0 距离 1MB 地址空间顶部仅 16 字节,BIOS 在这里放一条远跳转指令,跳到 ROM 中真正的固件代码。

目前 BIOS 程序分为 Legacy 和 UEFI 两类,下面分别说明。

Legacy BIOS#

Legacy BIOS 是传统的固件程序,只能运行在 16 位实模式下。这个限制来自 1981 年 IBM PC 的设计,当时 8086 CPU 只有 20 根地址线,最大寻址 1MB,BIOS 的整个接口体系(INT 0x10 屏幕输出、INT 0x13 磁盘访问等中断服务)都建立在 16 位实模式之上,此后 40 年的硬件迭代都无法打破这个兼容性枷锁。

实模式的约束直接决定了 Legacy BIOS 的能力边界:只能使用 1MB 内存;引导记录被限制在 MBR 的 512 字节(446 字节引导代码 + 64 字节分区表 + 2 字节签名 0x55AA),因为 MBR 必须装进一个扇区;分区表只有 4 个条目,最多支持 4 个主分区(或 3 主分区 + 1 扩展分区);使用 32 位 LBA 寻址,磁盘上限 2TB;没有文件系统驱动,只能直接读取磁盘扇区,这意味着引导加载器必须自己实现文件系统解析。

Legacy BIOS 的执行流程如下:首先进行 POST(Power-On Self Test)自检,检查基本输入输出设备,失败时主板发出蜂鸣或闪烁信号灯;然后在物理地址 0x0000 建立中断向量表(IVT),填充 BIOS 中断服务程序;接着读取 CMOS 中的配置参数(启动顺序、硬盘设置等),执行固件初始化,初始化 CPU、内存控制器、芯片组、总线(PCIe、USB、SATA)、时钟等;之后按启动顺序逐个查找引导设备,读取每个设备最前面的 512 字节,如果末尾两个字节是 0x55 和 0xAA 就认为该设备可引导;最后将可引导设备的 MBR 加载到内存 0x7C00 处,将控制权交给 Bootloader。

Note

0x7C00 这个地址并非随意选择。IBM PC 5150 的设计者决定将 DOS 载入到 0x0000 开始的低地址,DOS 最少需要约 32KB 内存,0x7C00 = 32KB - 1024B,刚好给 MBR 留出空间又不与 DOS 冲突。这个地址从此成为事实标准,即使现代系统早已不需要这个约束,仍然沿用至今。

Note

传统意义上的 bootloader(如 GRUB)的一部分实现就位于 MBR 中。

UEFI#

UEFI(统一可扩展固件接口)抛弃了 16 位实模式的兼容性包袱,在极早期就切换到保护模式以使用更大的地址空间。这个设计选择牺牲了与旧 BIOS 中断服务的兼容性(UEFI 程序不能调用 INT 0x10 等),换来的是:支持 GPT 分区表(突破 2TB 限制和 4 分区限制)、内置文件系统驱动(可直接读取 FAT32 上的 EFI 文件)、原生图形输出协议(GOP,不依赖 VGA)、网络协议栈等。

UEFI 将启动过程划分为 7 个阶段,每个阶段有明确的职责边界和状态转换条件:

SEC(Security)阶段进行硬件初始化和验证,相当于 BIOS 的 POST 自检,但增加了固件完整性校验(Secure Boot)。PEI(Pre-EFI Initialization)阶段初始化内存和其他关键资源,此时内存还不可用,PEI 代码只能使用 CPU 寄存器和 ROM 中的临时存储(称为 CAR,Cache-as-RAM)。DXE(Driver Execution Environment)阶段是工作量最大的阶段,初始化大部分硬件驱动和功能,此时内存已可用,可以加载和执行复杂的驱动模块。BDS(Boot Device Selection)阶段确定从哪个设备加载操作系统。TSL(Transition to SMM)阶段准备操作系统加载。RT(Runtime)阶段操作系统已接管。AL(Advanced Launch)阶段涉及灾难恢复或关机。

这种分阶段设计的核心动机是信任链的逐级建立:SEC 验证 PEI,PEI 验证 DXE,DXE 验证 BDS,每一级只信任已验证的下一级代码,未经验证的代码不允许执行。这是 Secure Boot 的基础。

由于 UEFI 在操作系统启动前就已初始化大部分硬件驱动,在加载操作系统前就能完整利用硬件功能,直接识别文件系统、通过显卡显示图形界面、使用网络等。它直接在 ESP(EFI System Partition)中查找特定路径的引导程序(如 /EFI/BOOT/BOOTX64.EFI)进行启动,不再需要 MBR 那种逐扇区扫描的方式。

Note

在 UEFI+GPT 方案中,GRUB 以 EFI 文件形式存在于 ESP 分区,不再依赖 MBR。

第 2 层:引导加载层#

BIOS/UEFI 的最后一项工作是找到引导加载器并将其加载到内存的 0x7C00 处,然后开始调用。常见的引导加载器(Boot Loader)有 GRUB2 和 LILO 等。

这里主要介绍 GRUB2,它可以引导多个操作系统,可直接读取文件系统,拥有图形和文本界面,通过配置文件设置引导项的内核及启动参数等,也可在启动页面通过指令进行动态修改。其主要工作是将选定引导项的内核启动参数传递给内核,将内核和 initramfs(如果存在)加载到内存中,然后转移控制权。

GRUB2 在 BIOS 模式下的启动分为四个阶段:

阶段 1(boot.img):BIOS 开机后执行 MBR(第一个扇区,512 字节),MBR 的前 446 字节由 GRUB2 的 boot.img 占据。由于空间很小,只能初始化最基本的执行环境并加载下一阶段(core.img)。

阶段 1.5(core.img 前半段,包含磁盘驱动模块):MBR 的后续「空闲扇区」中,GRUB 会放置 core.img(远大于 512 字节)。core.img 包含文件系统驱动(ext4、xfs、btrfs 等)、磁盘访问模块、压缩和解压工具。这使得 GRUB 拥有了读取磁盘文件系统的能力,并能从磁盘加载必要的模块和配置以进入下一阶段。

阶段 2(加载 /boot/grub 下的模块和配置文件):core.img 找到 /boot/grub/grub.cfg,加载必要的模块(如 ext2.mod、part_msdos.mod、normal.mod 等),初始化完整的 GRUB2 环境。此时 GRUB 进入 normal mode,可进行图形界面或命令行操作,支持解析配置(内核路径、initrd、内核参数等)以及高级功能(LVM、RAID、加密分区支持)。

阶段 3(加载 Linux 内核与 initrd/initramfs):根据 grub.cfg 的配置,执行类似下面的命令:

linux /boot/vmlinuz-xxx root=/dev/sda1 ro quiet
initrd /boot/initrd-xxx.img
boot

将 Linux 内核(vmlinuz)和 initrd 装入内存,跳转执行内核,内核接管控制权。

UEFI#

UEFI 下没有 MBR 的 512 字节限制,流程更简单:

  1. UEFI 固件从 EFI 系统分区(ESP)加载 grubx64.efi
  2. grubx64.efi 内部包含 GRUB2 的核心功能(类似 BIOS 模式的 core.img)
  3. 加载 /boot/grub/grub.cfg 和其他模块
  4. 显示菜单,加载内核 + initrd,跳转执行
Note

GRUB2 在执行过程中会进入保护模式,但后续内核会覆盖修改 GDT 等相关配置。 initramfs 是进入真正的根文件系统之前的一个垫片,通常包含必要的设备驱动程序、工具、配置文件以及运行这些工具的环境。引导加载器直接将它和内核加载到内存。这样内核可以更加精简(不必将不同格式的磁盘驱动直接编译进内核),并且在挂载真正根文件系统前也可完成加密解密等初始化操作。

这一层也是可以着手编码的层。

第 3 层:内核初始化层#

当引导加载程序(如 GRUB)成功将操作系统的内核映像加载到内存中,并将 CPU 的控制权移交给内核入口点(通常是 _start 或类似符号)时,就进入了内核初始化层。此时内核面对的是一个几乎原始的硬件环境,没有内存管理、中断处理、文件系统,也没有进程等概念。内核初始化层的任务是建立这些子系统,将硬件抽象化,直到准备好执行第一个用户空间进程(通常为 init 或 systemd)。

1. 架构相关初始化(Architecture-Specific Setup)#

  • CPU 模式切换:在 x86 上,从实模式进入保护模式/长模式(64 位),设置段寄存器
  • 内存管理:建立初始页表,启用分页(MMU),保留引导信息中的物理内存布局
  • 中断机制:设置 GDT(全局描述符表)、IDT(中断描述符表),初始化 APIC/IOAPIC
  • CPU 特性检测:检测并启用 CPU 扩展功能(FPU、MMX、SSE 等)
  • 早期输出:初始化 early console 以便 printk() 能输出调试信息
  • 内核解压:bzImage 格式的内核先在低地址执行解压,再把完整内核搬到高地址运行

2. 内核通用初始化(start_kernel())#

进入内核 C 语言入口点 start_kernel(),这里会初始化几乎所有核心子系统:

  • 内存管理:伙伴系统初始化(物理页分配器),虚拟内存子系统建立
  • 进程调度器:sched_init() 初始化调度框架
  • 同步机制:初始化 RCU(Read-Copy Update)等高性能同步机制
  • 时钟和定时器:time_init() 设置系统时钟和定时器中断
  • 内核日志:printk() 正式启用环形缓冲
  • 安全机制:初始化 Linux Security Modules(LSM)
  • 虚拟文件系统:初始化 VFS、挂载内存中的临时根文件系统 rootfs(ramfs/tmpfs)
  • 设备模型:建立总线/设备/驱动模型,准备后续设备探测

3. 关键内核线程和进程#

在 rest_init() 中,内核启动几个关键进程:

  • 0 号进程(idle 或 swapper):最初的内核上下文,最终进入空闲循环
  • 1 号进程(kernel_init):内核初始化线程,后续会尝试执行 /init 或 /sbin/init
  • 2 号进程(kthreadd):内核线程管理器,负责创建其他内核线程

4. initramfs 处理#

如果引导加载器提供了 initramfs,内核会:

  • 解压 initramfs 到内存中的 rootfs
  • 将其作为临时根文件系统使用
  • 尝试执行 initramfs 内的 /init 程序(第一个用户态程序)

如果没有 initramfs,或 /init 执行失败,内核会直接查找并执行指定根文件系统上的 /sbin/init、/bin/init 或 /bin/sh。

initramfs 本质上是一个临时的基于内存的根文件系统,使命是为挂载真实的根文件系统做准备。内核启动 initramfs 后,会运行其中的 /init 脚本或可执行文件,最后切换到真正的用户根文件系统。

第 4 层:用户空间层#

当真正的根文件系统挂载完成后,内核会启动第一个用户空间进程 /sbin/init(现代系统多为 systemd,也可能是 SysV init、OpenRC 等)。它成为 PID 1,是所有用户空间进程的祖先进程。

systemd 启动流程(主流 Linux 发行版)#

systemd 的启动流程按顺序分为四步。

第 1 步:读取配置文件。systemd 读取 /etc/systemd/system.conf(本地覆盖)和 /usr/lib/systemd/system.conf(发行版默认),以及管理单元文件(/etc/systemd/system/ 本地自定义、/usr/lib/systemd/system/ 系统默认)。

第 2 步:确定启动目标(Target)。systemd 根据默认目标(通过 /etc/systemd/system/default.target 符号链接指定)决定启动到哪个模式:

  • graphical.target:图形界面多用户模式
  • multi-user.target:命令行多用户模式
  • rescue.target:单用户维护模式

第 3 步:初始化基础系统。挂载必要的虚拟文件系统(/proc、/sys、/dev、/run 等),设置主机名、时区、本地化、随机数种子,初始化内核模块和网络服务(如 systemd-networkdNetworkManager),启动日志服务 systemd-journald、认证服务 systemd-logind、定时任务服务 systemd-timedated 等,并通过 udev 设备管理为 /dev 创建设备节点、响应热插拔事件。

第 4 步:进入用户工作环境。达到 multi-user.target 时系统进入命令行多用户模式(可登录终端);达到 graphical.target 时启动显示管理器(gdm、lightdm、sddm 等),进入图形桌面环境。

systemd 相比传统 init 的核心改进在于依赖管理和并行启动。服务之间通过 .service、.target、.socket 等单元文件声明依赖关系,systemd 会自动分析依赖关系并尽可能并行启动服务,大幅加快启动速度。它还支持多种按需激活机制:Socket Activation(端口有连接时启动服务)、D-Bus Activation(通过 D-Bus 消息按需启动服务)、Path / Mount Activation(监控路径或挂载事件触发服务)。

SysV init 启动流程(对比 systemd)#

  • 使用 /etc/inittab 定义默认运行级别(0–6)
  • 每个运行级别对应 /etc/rcX.d/,包含启动/关闭脚本链接,顺序执行
  • 串行启动,缺乏依赖关系管理,启动较慢

第 5 层:登录管理器 / Shell#

当 systemd 或 init 启动完目标服务后,系统进入可交互的登录阶段。

文本登录(multi-user.target)#

  • getty:在虚拟终端(tty1–tty6)启动,显示 login: 提示符
  • 认证:用户输入用户名和密码,经由 PAM(Pluggable Authentication Modules) 认证
  • 启动 Shell:认证成功后,执行 /etc/passwd 中指定的登录 shell(如 /bin/bash/bin/zsh
  • 加载配置:shell 执行其初始化配置文件(如 .bashrc.zshrc

图形登录(graphical.target)#

  • 显示管理器(Display Manager, DM):如 gdmlightdmsddm
    • 提供图形化登录界面
    • 通过 PAM 完成用户认证
  • 桌面会话:认证通过后,DM 启动桌面会话(如 GNOME、KDE Plasma、XFCE、Wayland 或 X11 会话)
    • 启动桌面环境核心进程(会话管理器、面板、文件管理器等)
    • 执行用户自定义的自动启动脚本(~/.xinitrc~/.config/autostart/ 等)

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

从系统加电到 Shell
https://blog.souloss.cn/posts/os/from-power-on-to-shell/
作者
Souloss
发布于
2024-03-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时