Linux 启动过程:从按下电源按钮,到启动内核

这是一篇来自 The Linux Boot Process: From Power Button to Kernel | 0xkato 内容的整理:

[timelines]

第一部分 — 开机到内核启动的故事

你按下电源键,几秒钟后,屏幕上可能出现了厂商的LOGO,也可能是一串闪烁的字符。很快,Linux 的世界就展现在你的眼前。这不是魔法,而是硬件和软件小心协作的成果。

1. 第一条指令

  • 当电流稳定,CPU 苏醒,开始跑它的第一条指令:直接跳转到主板固件设定的复位地址(0xFFFFFFF0)。这里只有很少的空间,工程师放了一个“远跳”指令,把启动权交给了主板固件(BIOS/UEFI)。

2. BIOS和UEFI出场

固件是预装在电路板上的一个小型启动程序。

  • BIOS 模式下,主板固件先做自检(POST),接着寻找启动盘,找到后把启动盘的第一个扇区(512字节)读到内存(0x7C00),并跳过去执行。
  • UEFI 更现代,能直接读硬盘文件系统,加载更大的程序(比如GRUB),也能和操作系统沟通更多硬件细节。

两者是不同的路径,相同的目标:将控制权交给能够加载 Linux 的引导程序。


3. Bootloader 的任务

引导加载程序 Bootloader 就像引路人,引导操作系统进入系统。

  • GRUB 是 PC 上常用的引导加载程序。它读取系统配置,如果安装了菜单则会显示菜单,并将 Linux 内核加载到内存中。Linux 内核文件实际上包含两部分:

4. setup 程序做足准备,创建安全工作空间

  • 这个阶段的内核 setup 程序,负责把寄存器、堆栈都设置妥当,还会清理 BSS 区(存储要从零开始的变量)。如果早期输出(earlyprintk)被启用,还会初始化串口,让工程师能看到最早期的信息。
  • 最后再向主板固件查询可用内存,划分哪些区域是安全可用的。做好这一切,才会跳进第一个 C 语言函数 main,距离正式内核已经很近了。

完成上述步骤后,设置代码会调用它的第一个 C 函数,该函数名为 main 。此时我们仍然处于小型旧式实模式下。下一步是退出这种模式。


第二部分 — 离开实模式,逐步探索 32 位领域,最终进入 64 位领域

现代 PC 上的 Linux 系统运行在长模式(Long Mode)下,也就是 x86_64 架构的 64 位模式。你不能直接从实模式跳转到长模式。正确的路径是:实模式到保护模式,再从保护模式到长模式。

1. 什么是保护模式

  • Linux 真正运行是64位世界,而最初启动在“实模式”(只支持1MB空间)。setup 程序要带着CPU先切进32位保护模式,这一步用到 GDT(内存段定义表)和 IDT(中断分配表),把老模式的限制彻底松绑,实现更强的内存管理。

2. 模式切换细节

  • 切换前会依次关掉可屏蔽中断,屏蔽掉老式中断控制器PIC,打开 A20 线(让内存能突破1MB)。然后安排好浮点协处理器,加载精简版 GDT 和 IDT,设好 CR0 寄存器,开启32位保护模式。

3. 补充:控制寄存器是啥

  • CR0、CR3、CR4 这些寄存器就像硬件开关,决定着 CPU 的运行状态和内存分页、安全等功能,是整个切换过程中最核心的小机关。

4. 为什么还没进入64位环境

  • 光有保护模式还不够,还得开启分页功能,让虚拟内存和物理内存一一对应,然后通过 EFER 寄存器的 LME 位,正式让 CPU 以64位模式运行。这一步要用简单的页表做最原始的映射,确保系统不会出错。

5. 为什么切换特别小心

  • 切换过程中要防止各种中断、地址冲突等风险,所以流程非常谨慎,确保每一步都到位,否则可能会让电脑启动卡死。

第三部分 — 内核“解包”和“搬家”的故事

你看到系统启动的过程,就像搬家和装修,事无巨细,先收拾清理、再搬东西、最后把所有细节安顿好。

1. 先检查环境准备

  • 解压内核的代码(stub)会上来检查自己和即将放置内核的位置是否重叠。如果有冲突,会主动搬到空余、安全的区块,腾出空间让内核顺利解包。与此同时,还会将部分变量区清零,设置临时的中断响应,映射关键内存区。

2. Linux内核“拆包”和“安家”

  • extract_kernel 把压缩的内核文件解压到内存,然后根据 ELF 文件头的“地图”,把代码和数据精确搬到目标位置。如果发生搬迁,还要做“重定位”,修正所有需要用到的地址,确保后续内核运行时一切正常。安顿好以后,就能跳到真正的内核入口,让 Linux 正式“苏醒”。

3. 为什么内核有时要“搬家”

  • 如果 Linux 开启了地址随机化功能(kASLR),每次启动都会根据当前系统情况,把内核安置到不同的内存地址。这么做的好处是提升安全性黑客找不到固定位置更难攻击,让系统更安全。

[/timelines]

常用术语简明解释

  • 十六进制:一种计数方法,包括数字0-9和字母A-F,常用于表示内存地址,比如0x10表示16。
  • 寄存器:CPU内部用于暂存数据的小空间,有很多种类,比如代码段、数据段等。
  • 段和偏移:在实模式下,内存地址由段地址和偏移地址组成。
  • BIOS:传统主板固件,负责自检和加载启动盘。
  • UEFI:新一代主板固件,支持现代硬件和文件系统加载。
  • Bootloader(启动引导程序):负责把操作系统内核装入内存并准备相关参数(常见如GRUB)。
  • 堆栈:一种先进后出的数据区,函数调用时会使用。
  • BSS:存着需要清零的全局变量区域。
  • GDT(全局描述符表):定义内存管理和权限,保护模式下用到。
  • IDT(中断描述符表):存放所有中断处理函数的入口地址。
  • A20线:允许内存访问超过1MB的历史机制。
  • 保护模式:CPU的32位工作模式,支持分页和分段内存管理。
  • 长模式:CPU的64位工作模式,必须开启分页之后才能进入。
  • 分页:实现虚拟地址到物理地址转换的机制。
  • 页表:存储分页映射关系的数据结构。
  • 控制寄存器:如CR0/CR3/CR4,控制CPU的各种关键功能和状态。
  • ELF:Linux内核和程序的文件格式,定义哪些是代码哪块是数据。
  • 重定位:代码或数据实际存放位置和初始设定不一致时,需要纠正所有指针和引用。
  • kASLR:每次开机都随机分配内核位置,提高安全性。
3 个赞