第 12 讲:从上电复位到内核启动

启动最令人困惑的地方是:加载程序需要已经在运行的程序,但机器刚上电时还没有任何软件环境。解决方法不是“一步启动完成”,而是把一小段必然可执行的代码固化在预定位置,再一级级加载能力更强的程序。

启动链的一张文字图

上电/复位
  ↓ CPU 从架构规定的复位向量取指
固件(MIPS 上的 ROM/U-Boot stage 1,x86 上的 BIOS/UEFI)
  ↓ 建立最小硬件环境,找可启动介质
Bootloader 后续阶段(U-Boot stage 2、GRUB 等)
  ↓ 识别内核映像,装入 RAM,准备参数
架构相关内核入口(head.S 等)
  ↓ 建栈、页表、异常向量,解压/移动内核
架构无关初始化(Linux start_kernel)
  ↓ 初始化内存、调度、中断、驱动、VFS
init/用户空间

每一级只做当时条件允许的事,然后把“可用环境”扩大一点。课件将其称为逐步释放系统灵活性。

Bootloader 的两个任务

Bootloader 一词包含:

  • Booter:把 CPU、时钟、内存控制器、串口等从复位态带到可用态;
  • Loader:找到 OS 内核映像,装入内存,把控制权跳转给内核入口。

它与硬件强相关:即使 CPU 相同,Flash、DRAM 控制器和板级连线不同,也需要移植。U-Boot 通常拆成两阶段:

  1. stage 1 体积小、多用汇编,先建立栈和基本 DRAM 环境;
  2. stage 2 已能运行 C 代码,初始化更多设备,读取环境变量,选内核和启动参数。

MIPS:为什么上电时要用 kseg1

32 位 MIPS 将 4 GiB 虚拟地址空间分区。课件关心的是:

  • kuseg 是用户地址,需要 MMU/TLB 转换;
  • kseg0 是内核直接映射且可缓存的区域;
  • kseg1 也直接映射低端物理地址,但不经 Cache;
  • 其他内核区域需要 TLB 支持。

刚复位时 TLB 和 Cache 尚未配置,因此不能把启动绑定在它们上。MIPS 复位向量落在与 ROM/Flash 对应的 kseg1 地址,CPU 可以立即稳定取指。

MIPS + U-Boot 的接力

  1. 从 Flash 复位入口执行少量汇编;
  2. 初始化 CPU 相关状态、时钟和 DRAM;
  3. 建立栈,进入 board_init_f 等 C 代码;
  4. 初始化 timer、环境变量、串口等;
  5. 规划 RAM,搬迁 U-Boot 自身,建立堆栈;
  6. 把 Linux 内核复制到 RAM,跳到内核入口。

Linux 的架构相关入口 head.S 先完成早期映射、堆栈与异常环境,然后进入 start_kernel。后者才是大量架构无关子系统初始化的起点。

x86 BIOS/MBR 传统链路

1. 复位向量与 BIOS

x86 复位后从架构规定的固件入口取指;课件以 0xFFFF0 为例。BIOS 随后:

  • 执行 POST 硬件自检;
  • 初始化基本设备;
  • 根据启动顺序选硬盘、U 盘或网络;
  • 读取启动介质的首扇区。

BIOS 背负实模式、16 位代码等历史兼容包袱,这正是 UEFI 试图用模块化、C 风格接口和更强可扩展性替代它的原因。

2. MBR 为什么只有 512 B

传统 BIOS 从磁盘首扇区读取 MBR,常见布局是:

区域大小用途
引导代码及数据446 B很小的第一阶段加载器
分区表 DPT64 B4 个 16 B 分区项
结束标志2 B引导扇区签名

空间这么小,根本放不下一个懂文件系统、懂网络、有菜单的完整加载器。因此 GRUB 也要分阶段:MBR 中的 stage 1 只负责找到更强的后续阶段,后者再解析配置和文件系统。

MBR 同时限制了四个主分区。“活动分区”和扩展分区都是这条历史链上的兼容机制,不应当成所有现代 UEFI/GPT 系统都必然遵循的步骤。

3. GRUB 与内核映像

GRUB 后续阶段能够:

  • 提供交互菜单和命令行;
  • 识别文件系统,按路径找内核;
  • 选择多个 OS 或多个内核版本;
  • 准备内核命令行、初始 RAM 文件系统等参数;
  • 把内核映像放到约定内存位置并跳转。

内核映像不是可直接随意跳入的普通用户程序。其引导头、早期设置代码和压缩内核有规定的内存布局与协议。

Linux 接管后的顺序

start_kernel 附近的主线可以理解为:

  1. 先让 CPU 和内核自身的基本数据结构稳定;
  2. 在完整中断子系统准备好前保持中断关闭;
  3. 建立内存管理、调度、定时器和中断框架;
  4. 初始化设备与文件系统;
  5. 建立第一批内核线程,最终启动用户空间 init。

顺序不能乱:驱动申请中断前,中断框架要存在;挂载根文件系统前,存储设备与 VFS 要可用。启动不是一张运行清单,而是一张依赖图的拓扑排序。

遇到启动题怎么判断

先问当前阶段已经有什么能力:

  • 还没有 RAM:代码不能假设有正常栈和堆;
  • 还没有页表/TLB:只能使用架构保证的直接地址;
  • 还没有文件系统:只能按固定块位置或借助更早固件服务读取;
  • 还没有驱动:不能假设通用 I/O 接口已存在。

然后再问它想把哪个下一级加载到哪里、如何验证、最后跳到哪个入口。这比死背 BIOS 的第几步更不容易混。

评论