3. 实模式启动代码分析:header.S 与 setup.bin
当 BIOS 将引导扇区加载到 0x7C00 并跳转执行后,内核的启动流程进入实模式阶段。这一阶段的核心代码位于 arch/x86/boot/header.S 和 arch/x86/boot/setup.bin(由 setup.S 及多个辅助文件编译链接而成)。本章将深入分析这两部分代码在实模式下的执行逻辑、内存布局以及关键的初始化操作。
3.1 header.S:引导扇区与内核头部
header.S 是内核镜像的最前端,它既包含引导扇区代码,也定义了内核的 实模式头部(Real-Mode Header)。这个头部是引导加载程序(如 GRUB、LILO 或直接 BIOS 调用)识别和加载内核的关键数据结构。
3.1.1 实模式头部结构
内核头部位于镜像文件偏移 0x1F1 处,其结构定义在 arch/x86/boot/header.S 中。以下是关键字段及其含义:
| 偏移(十六进制) | 字段名 | 大小(字节) | 描述 |
|---|---|---|---|
| 0x1F1 | setup_sects |
1 | setup.bin 占用的扇区数(默认 4) |
| 0x1F2 | root_flags |
2 | 根文件系统挂载标志 |
| 0x1F4 | syssize |
4 | 压缩内核镜像的大小(以 16 字节为单位) |
| 0x1F8 | ram_size |
2 | 扩展内存大小(已废弃) |
| 0x1FA | vid_mode |
2 | 视频模式选择 |
| 0x1FC | root_dev |
2 | 根文件系统设备号 |
| 0x1FE | boot_flag |
2 | 引导扇区标志(0xAA55) |
| 0x200 | jump |
2 | 跳转到 setup 代码的指令 |
| 0x202 | header |
4 | 头部魔数(HDRS) |
0xAA55 位于偏移 0x1FE,这是 BIOS 识别可引导设备的硬性要求。内核头部魔数 HDRS(0x53726448)用于引导加载程序验证内核镜像的完整性。
3.1.2 引导扇区代码流程
当 BIOS 将第一个扇区(512 字节)加载到 0x7C00 后,执行从 _start 标号开始。以下是核心代码片段及注释:
// arch/x86/boot/header.S (简化)
.section ".bstext", "ax"
.globl _start
_start:
// 1. 设置段寄存器
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $0x7C00, %sp // 栈指针指向当前代码段底部
// 2. 读取 setup.bin 到 0x7E00
movw $0x7E00, %bx // 目标地址
movb setup_sects, %al // 要读取的扇区数
movb $0x02, %ah // BIOS 读扇区功能号
int $0x13 // 调用 BIOS 中断
// 3. 跳转到 setup 代码
ljmp $0x0000, $0x7E00 // 远跳转到 setup.bin 入口
0x0000:0x7C00 对应物理地址 0x7C00,0x0000:0x7E00 对应 0x7E00。
3.2 setup.bin:实模式初始化核心
setup.bin 是内核在实模式下的主要执行体,它负责收集硬件信息、设置基本环境,并为切换到保护模式做准备。其入口点位于 arch/x86/boot/setup.S 中的 start_of_setup 标号。
3.2.1 内存布局与加载地址
在实模式阶段,内核各部分在内存中的布局如下:
3.2.2 初始化操作流程
setup.bin 的执行流程可以分为以下几个关键步骤:
- 设置栈和段寄存器:将栈指针
SP设置为0x8000(向下增长),确保有足够的栈空间供后续调用。 - 检查内核签名:验证
0x7C00处的引导扇区标志0xAA55和内核头部魔数HDRS。 - 收集硬件信息:通过 BIOS 中断获取内存大小(
int 0x15, e820h)、磁盘参数、视频模式等。 - 设置视频模式:根据
vid_mode字段或用户指定的模式初始化显示。 - 移动内核到低地址:将压缩内核从加载地址(通常
0x10000以上)移动到0x10000(64KB)处,为解压做准备。 - 进入保护模式:关闭中断、加载全局描述符表(GDT)、设置控制寄存器
CR0的保护模式位,然后远跳转到 32 位代码。
cli 指令关闭中断,并确保 IDT(中断描述符表)在保护模式下正确设置,否则会导致三重故障(Triple Fault)并重启系统。
3.2.3 关键代码分析:内存探测
内存探测是 setup.bin 最重要的任务之一。以下是通过 int 0x15, e820h 获取内存布局的典型代码:
// arch/x86/boot/memory.c (简化)
int detect_memory_e820(void) {
struct biosregs ireg, oreg;
struct e820entry *desc = &e820_table;
int count = 0;
initregs(&ireg);
ireg.ax = 0xE820;
ireg.cx = sizeof(*desc);
ireg.edx = SMAP; // 签名: 0x534D4150 ('SMAP')
ireg.di = (size_t)desc;
do {
intcall(0x15, &ireg, &oreg);
ireg.ebx = oreg.ebx; // 连续调用索引
desc++;
count++;
} while (ireg.ebx && count < 128);
return count;
}
e820entry 结构,包含内存基地址、长度和类型(1 = 可用内存,2 = 保留,3 = ACPI 可回收,4 = ACPI NVS)。内核利用这些信息构建最终的物理内存映射。
3.3 实模式与保护模式的切换
setup.bin 的最后一步是切换到保护模式。这一过程涉及以下关键操作:
- 加载 GDT:使用
lgdt指令加载临时全局描述符表,其中包含代码段和数据段描述符。 - 设置 CR0:将
CR0寄存器的第 0 位(PE 位)置 1,启用保护模式。 - 远跳转刷新流水线:执行
ljmp指令跳转到 32 位代码段,同时刷新 CPU 指令预取队列。
// arch/x86/boot/pmjump.S (简化)
.section ".text32", "ax"
.code32
jmpl *%eax // 跳转到 32 位内核入口 (startup_32)
__BOOT_CS(代码段)和 __BOOT_DS(数据段)作为初始选择子。
3.4 本章小结
实模式启动代码是 Linux 内核启动流程的基石。header.S 提供了引导扇区和内核头部,确保 BIOS 和引导加载程序能够正确识别和加载内核。setup.bin 则负责在实模式下完成硬件探测、环境初始化,并最终将控制权移交给保护模式下的 32 位内核。理解这一阶段的内存布局和初始化操作,对于后续分析内核解压、重定位以及内存管理至关重要。