2. Linux内核启动流程全景:从BIOS/UEFI到init进程的宏观路线图

理解Linux内核启动的完整流程,是深入分析KASLR(内核地址空间布局随机化)机制的前提。KASLR的随机化决策并非凭空产生,而是深深嵌入在启动路径的特定阶段。本章将为你绘制一张从硬件上电到用户空间init进程启动的宏观路线图,并标注KASLR的介入时机。

核心观点: 整个启动流程可划分为固件阶段 → 引导加载器阶段 → 内核解压与初始化阶段 → 用户空间初始化阶段。KASLR的随机化决策发生在引导加载器与内核早期解压代码之间。
Linux内核启动流程全景图(含KASLR介入点) 阶段1:BIOS/UEFI 硬件自检(POST) 选择启动设备 阶段2:Boot Loader GRUB2 / UEFI stub 加载内核与initramfs 阶段3:内核初始化 解压/重定位 start_kernel() 阶段4:用户空间 /sbin/init 启动 systemd / SysV init 🔑 KASLR 介入点 内核解压前选择随机基址 💡 说明 UEFI可直接加载内核(stub模式) 绕过传统Boot Loader

2.1 阶段一:固件初始化(BIOS/UEFI)

当按下电源键,CPU首先执行的是固化在主板ROM中的固件代码。现代系统主要使用UEFI(统一可扩展固件接口)取代传统的BIOS。

特性 传统BIOS UEFI
接口模式 16位实模式 32/64位保护模式
分区表 MBR(主引导记录) GPT(GUID分区表)
启动设备选择 扫描MBR中的引导代码 读取EFI系统分区(ESP)中的.efi文件
内核加载方式 必须通过Boot Loader链式加载 支持直接加载内核(EFI stub)
KASLR关联: UEFI固件在ExitBootServices()调用前,会为内核提供随机数种子。这个种子是KASLR早期随机熵的重要来源之一。如果固件熵不足,KASLR的随机化质量会下降。

2.2 阶段二:引导加载器(Boot Loader)

引导加载器是固件与内核之间的桥梁。在x86_64架构上,GRUB2是最主流的实现。它的核心任务是:加载内核映像(vmlinuz)与initramfs到内存,并设置好启动参数。

GRUB2 核心工作流程

  1. 读取配置文件:从/boot/grub/grub.cfg中获取内核路径与启动参数。
  2. 加载内核:将vmlinuz(压缩后的内核+解压代码)读入内存。
  3. 加载initramfs:将初始根文件系统读入内存,用于挂载真正的根文件系统前驱动加载。
  4. 设置启动参数:构造内核命令行(如quiet splash),并传递nokaslr或kaslr等参数。
  5. 跳转至内核入口:通过boot命令将控制权交给内核的startup_64(64位)或startup_32(32位)入口。
KASLR关键参数: 内核命令行中的 nokaslr 会完全禁用地址随机化;kaslr 是默认开启状态。调试时常用 nokaslr 来固定内核地址。
GRUB2 加载内核与KASLR参数传递 GRUB2 读取grub.cfg 内核命令行 "quiet kaslr" vmlinuz 压缩内核+解压代码 内核入口点 startup_64 / startup_32

2.3 阶段三:内核早期启动与解压

这是KASLR发挥作用的核心阶段。内核入口代码(位于arch/x86/boot/compressed/head_64.S)首先执行,完成从实模式到保护模式、再到长模式的切换,然后进行内核解压。

KASLR在此阶段的关键步骤

  • 步骤1:收集随机熵 —— 从CPU的RDRAND/RDSEED指令、UEFI随机数种子、以及TSC(时间戳计数器)中获取随机数。
  • 步骤2:计算随机偏移 —— 根据物理内存布局(E820表),在可用内存范围内选择一个随机的基地址。
  • 步骤3:应用重定位 —— 将解压后的内核镜像移动到随机选择的地址上,并修正所有符号引用。
  • 步骤4:跳转到随机化后的内核 —— 执行位于新地址的start_kernel()函数。
安全警告: 如果攻击者能够通过侧信道攻击(如Prefetch攻击)探测到内核的随机基址,KASLR的保护效果将被削弱。因此,内核社区持续在增强熵源质量和减少信息泄露方面进行改进。

2.4 阶段四:内核初始化(start_kernel)

解压完成后,控制权交给C语言编写的start_kernel()函数(位于init/main.c)。这是内核初始化的核心,负责初始化所有子系统。

初始化顺序 子系统 关键操作
1 trap_init() 设置中断描述符表(IDT),初始化异常处理
2 mm_init() 建立内核页表,初始化内存管理器
3 sched_init() 初始化进程调度器
4 init_IRQ() 初始化中断控制器(APIC)
5 time_init() 初始化系统时钟与定时器
6 console_init() 初始化控制台,开始输出内核日志
7 vfs_caches_init() 初始化虚拟文件系统(VFS)缓存
8 rest_init() 创建kernel_init线程和kthreadd线程
KASLR关联: 在start_kernel()执行期间,KASLR的随机化已经生效。所有后续初始化的子系统(如内存管理、中断处理)都运行在随机化的地址空间中,攻击者无法预测关键数据结构的地址。

2.5 阶段五:用户空间初始化(init进程)

rest_init()函数创建的第一个用户空间进程是kernel_init(PID=1)。它负责挂载根文件系统,然后执行用户空间的init程序。

init进程的演变

  • 传统SysV init:按顺序执行/etc/inittab中的脚本,启动串行化,速度较慢。
  • Upstart:基于事件驱动的init系统,Ubuntu曾使用。
  • systemd:现代Linux发行版的标准init系统,并行启动服务,使用socket和D-Bus激活。

至此,Linux内核启动流程全部完成,系统进入用户空间管理阶段。KASLR的使命在阶段三已经完成,但其保护效果贯穿整个系统运行周期。

本章小结: Linux启动流程是一个从硬件到软件的层层递进过程。KASLR在引导加载器传递参数 → 内核解压前选择随机基址这一关键窗口期发挥作用。理解这个全景图,是后续深入分析KASLR实现细节的基础。