Skip to content

开机流程

开机流程涉及硬件态向内核态的转化,操作系统是运行在硬件上的一个程序,其加载运行也是一个复杂的过程,一旦操作系统运行成功,应用层的软件便可以由操作系统加载和运行,乘上操作系统的快车,利用操作系统提供的 API 便捷地使用硬件完成业务需求。

供电

在硬件平台通电时,主板等周边设备上的小型专用处理器将优先工作。其中挂载在 CPU 总线上的小型控制器 EC(Embedded Controller)负责计算机的电源管理工作。EC 控制电源供应单元为硬件系统供电,系统各部分进入各自的供电流程,包括 CPU、内存、南北桥芯片等。

周边设备的控制器芯片不承担主要的计算任务,但会帮助协调各个硬件之间的工作。

各个硬件的供电顺序有所先后,一般顺序是:CPU、内存、南北桥、扩展卡和外围设备、启动存储等。电源控制单元在确保电源供应一切顺利后,向 EC 发出信号表示供电完毕。供电成功后,EC 通知主板上的各个芯片组,正式开启计算机的启动流程。

首先,南北桥进行交互:

  1. 南桥向北桥发出正常信号
  2. 北桥收到南桥信号,并向 CPU 发送正常信号
  3. CPU 开始工作

供电单元可以进行交流电到直流电的转化,并保持电源电压的稳定。

BIOS/UEFI 寻找 Boot Loader

CPU 开始工作后的第一个程序是 BIOS/UEFI 程序,它是主板上的固件。BIOS 程序会:

  1. 扫描和检查设备
  2. 初始化硬件系统(主板、内存、CPU、显卡等)
  3. 确保设备能够正常工作(POST - Power-On Self Test)
  4. 为内存条和 MMIO 设备分配地址空间
  5. 将地址空间连成连续的数组空间(地址位数取决于机器位宽,通常为 32 位或 64 位)

在硬件检查完毕后,BIOS/UEFI 程序尝试依次从多个外部存储设备中加载 Boot Loader 程序到内存中。如果某个设备中没有找到,则尝试从下一个存储设备加载。一旦加载成功,计算机的执行权就移交给 Boot Loader。

BIOS 加载 Boot Loader 时,从外部存储设备的启动扇区(MBR - Master Boot Record)加载,并将其放置到内存中的约定区域。BIOS 查找 Boot Loader 程序的顺序可能如下:

  • 硬盘
  • USB
  • CDROM
  • 网卡(pxe 启动)

BIOS 通常提供基于终端的配置界面,允许用户自定义引导过程,例如修改启动介质的优先级。

一些硬件层面的功能(如网卡的 SR-IOV 功能)不一定会被启用。如需启用,需要重启机器并提前修改 BIOS 配置。

Boot Loader 加载操作系统

Boot Loader 是存放在操作系统镜像盘中约定区域的一段程序。它被 BIOS 程序加载并执行,负责:

  1. 把操作系统的文件加载到内存中
  2. 初始化操作系统
  3. 将计算机的执行权移交给操作系统

Boot Loader 执行时,CPU 处于 Real 模式,只能访问 1MB 的内存空间,没有内存保护。Boot Loader 利用硬盘的分区表、文件系统信息和操作系统核心文件,实现从实模式到保护模式的切换,以及从硬盘到内存的数据传输。

Boot Loader 根据 MBR 中的磁盘分区信息,找到活动分区(操作系统文件所在的分区),然后:

  1. 找到操作系统可执行文件
  2. 加载操作系统到指定的内存区域
  3. 跳转 CPU 到该区域,开始执行操作系统

常见的 Boot Loader 实现包括:

  • GRUB(广泛用于 Linux 系统)
  • LILO
  • NTLDR
  • BOOTMGR

在 Linux 文件系统中,Boot Loader 通常位于/boot目录下。

操作系统启动

当 Boot Loader 将操作系统内核加载到内存并将控制权移交后,操作系统的启动过程正式开始。

linux 第一行代码是与体系结构相关的汇编代码,位于 arch 文件夹中,主要执行硬件初始化工作和 C 语言运行时初始化工作,以屏蔽不同的硬件结构来启动 C 语言的能力,接着解压和加载内核镜像,跳转执行内核代码。

  • 验证 Bootloader 环境:检查 boot_params,确保协议兼容。
  • 设置初始栈和清零 BSS:提供栈和初始化全局变量。
  • 切换到保护模式:初始化 GDT,禁用中断。
  • 调用早期 C 代码:解析硬件信息,准备跳转。
  • 解压内核镜像:解压 vmlinuz,设置 vmlinux 入口。
  • 切换到长模式:启用 64 位,设置页表。
  • 初始化 64 位环境:设置 GDT、IDT、栈。
  • SMP 初始化:唤醒多核。
  • 调用 x86_64_start_kernel(在 x86 上):进入 C,调用 start_kernel 函数

init/main.c/start_kernel 函数是内核主体逻辑的入口函数,在该函数中会启动核心的管理子系统,包括进程管理、内存管理、文件系统、设备驱动等。这些子系统为上层应用提供了统一的接口和服务,屏蔽了底层硬件的复杂性。各个子系统的初始化大致遵循如下先后顺序(简化版):

  1. 早期内核初始化(init_task、锁调试)。
  2. 内存管理初始化(物理内存检测、页表建立、内存分配器初始化)
  3. 中断系统初始化(设置中断向量表、注册 ISR、初始化中断控制器)
  4. 定时器/时钟子系统初始化(初始化系统定时器,为后续调度和时间管理做准备)
  5. 调度器和进程管理初始化(初始化调度器、创建内核线程、进程表等)
  6. 设备驱动和文件系统初始化(初始化块设备、字符设备、挂载根文件系统等)
  7. 显式使能中断(如执行 sti 指令,允许 CPU 响应中断)
  8. 启动用户空间第一个进程(如 init 或 systemd)

一号进程启动

接下来,内核会启动第一个用户空间进程(在 Linux 系统中通常是 init 或 systemd),该进程负责进一步启动系统服务和用户环境。

一号进程(PID 1)在 Linux 操作系统中具有极其特殊的地位。它是用户空间启动的第一个进程,由内核直接创建,并且始终拥有进程号 1。由于其特殊的启动顺序和地位,一号进程承担着整个系统用户空间初始化的重任,负责启动系统服务、守护进程以及用户登录环境,决定了系统的运行模式和服务框架。