3. 引导扇区与MBR:结构解析、加载流程与0x7C00约定
MBR(Master Boot Record,主引导记录)是x86系统加电后第一个被加载并执行的用户可见代码块。它位于硬盘的0号扇区(LBA 0),大小为512字节,以0xAA55作为有效标志。本章深入解析MBR的内部布局、BIOS加载流程以及0x7C00这个历史地址约定的由来与工程含义。
3.1 MBR结构解析
MBR的512字节被划分为三个区域:引导代码区、磁盘签名区、分区表区,以及结束标志。其布局如下表所示:
| 偏移(十六进制) | 长度(字节) | 内容 | 说明 |
|---|---|---|---|
| 0x000 – 0x1B7 | 440 | 引导代码(Boot Code) | 第一阶段引导程序,负责加载后续代码 |
| 0x1B8 – 0x1BB | 4 | 磁盘签名(Disk Signature) | 唯一标识磁盘的32位值,Windows用于识别磁盘 |
| 0x1BC – 0x1BD | 2 | 保留/空(通常为0x0000) | 保留字段,部分实现用于写保护标志 |
| 0x1BE – 0x1FD | 64 | 分区表(Partition Table) | 4个16字节的主分区表项 |
| 0x1FE – 0x1FF | 2 | 引导标志(0x55AA) | 有效MBR标志,BIOS据此判断扇区可引导 |
工程要点: 分区表从偏移0x1BE开始,每个表项16字节,包含引导标志、CHS/LBA地址、分区类型和扇区计数。引导代码必须严格控制在440字节以内,否则会覆盖磁盘签名或分区表。
每个分区表项的结构如下:
| 偏移(字节) | 长度 | 字段 | 说明 |
|---|---|---|---|
| 0 | 1 | 引导标志 | 0x80 = 可引导,0x00 = 不可引导 |
| 1 | 3 | 起始CHS地址 | 磁头/扇区/柱面(旧式寻址) |
| 4 | 1 | 分区类型 | 0x07 = NTFS,0x0C = FAT32 LBA,0x83 = Linux ext |
| 5 | 3 | 结束CHS地址 | 磁头/扇区/柱面 |
| 8 | 4 | 起始LBA地址 | 相对于磁盘起始的扇区偏移(小端序) |
| 12 | 4 | 分区总扇区数 | 该分区占用的扇区数量 |
内核解压缩关联: 在解压缩工程中,MBR引导代码通常只负责加载第二级引导(如boot.img或stage2),真正的解压缩逻辑位于后续扇区。MBR本身不参与解压缩,但它的加载地址0x7C00直接决定了后续代码的链接基址。
3.2 引导代码加载流程
BIOS在POST(加电自检)完成后,按照配置的启动顺序扫描存储设备。对于硬盘,BIOS执行以下步骤:
- 读取0号扇区: BIOS使用INT 13h功能02h,将硬盘的0号扇区(LBA 0)读取到内存地址
0x0000:0x7C00(即线性地址0x7C00)。 - 验证引导标志: 检查读取的512字节的最后两个字节是否为
0x55AA。若不匹配,跳过该设备继续扫描下一个。 - 跳转到0x7C00: BIOS执行远跳转
jmp 0x0000:0x7C00,将控制权交给MBR引导代码。此时CPU处于实模式,CS:IP指向0x0000:0x7C00。 - 引导代码执行: MBR代码开始执行,通常它会读取分区表,找到活动分区(引导标志为0x80),然后加载该分区的卷引导记录(VBR)到内存并跳转。
实模式限制: 在加载MBR时,CPU处于16位实模式,只能访问1MB内存空间。0x7C00位于640KB常规内存的上部,紧邻EBDA(扩展BIOS数据区)。引导代码必须注意不要覆盖0x7C00以下的BIOS数据区(0x400–0x4FF)和中断向量表(0x000–0x3FF)。
3.3 0x7C00地址约定
0x7C00是x86体系中最著名的“魔法地址”之一。这个地址并非随意选择,而是IBM在1981年设计IBM PC/XT时基于以下工程约束确定的:
- 内存布局约束: 实模式下,0x00000–0x003FF为中断向量表(1KB),0x00400–0x004FF为BIOS数据区(256字节),0x00500–0x07BFF为自由内存(约30KB),0x07C00–0x07DFF为MBR加载区(512字节),0x07E00–0x9FFFF为扩展内存(约608KB),0xA0000以上为硬件映射区。
- 最小内存需求: 早期PC最小配置为32KB RAM。0x7C00位于32KB(0x8000)以下,确保即使最小内存也能加载MBR。
- 不与BIOS冲突: 0x7C00–0x7DFF共512字节,正好容纳一个扇区。该区域上方0x7E00开始是自由内存,下方0x7BFF以下是用户程序区。BIOS自身使用0xFE000–0xFFFFF(64KB ROM),与0x7C00无重叠。
工程含义: 在编写MBR引导代码时,链接器脚本必须将代码的起始地址设为0x7C00。例如,使用GNU ld时,需要在链接脚本中指定
. = 0x7C00;。如果代码中使用了绝对地址引用(如变量、函数指针),必须确保它们基于0x7C00重定位,否则跳转或数据访问会指向错误的内存位置。
3.4 引导代码示例与地址重定位
以下是一个极简的MBR引导代码示例,展示如何设置段寄存器、加载后续扇区并跳转。注意代码起始地址为0x7C00:
; boot.asm — 极简MBR引导代码
; 汇编: nasm -f bin boot.asm -o boot.bin
; 起始地址 0x7C00
[org 0x7C00] ; 告诉汇编器代码基址为0x7C00
start:
; 初始化段寄存器
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00 ; 栈指针设在MBR下方,避免覆盖
; 读取第二个扇区(LBA 1)到 0x7E00
mov ah, 0x02 ; INT 13h 功能号:读扇区
mov al, 1 ; 读取1个扇区
mov ch, 0 ; 柱面0
mov cl, 2 ; 扇区号2(LBA 1)
mov dh, 0 ; 磁头0
mov dl, 0x80 ; 第一个硬盘
mov bx, 0x7E00 ; 目标地址
int 0x13
jc disk_error ; 如果进位标志置位,读取失败
; 跳转到加载的代码
jmp 0x0000:0x7E00
disk_error:
; 简单错误处理:死循环
hlt
jmp disk_error
; 填充至510字节,并添加引导标志
times 510-($-$$) db 0
dw 0xAA55
地址重定位关键: 上述代码中
[org 0x7C00]指令告诉NASM所有标签(如start、disk_error)的地址都基于0x7C00计算。如果省略该指令,汇编器会从0x0000开始计算地址,导致jmp和call指令跳转到错误位置。在解压缩工程中,后续的stage2代码也需要类似的重定位处理。
3.5 工程实践:MBR编写注意事项
- 大小严格限制: 引导代码必须≤440字节(0x1B8),否则覆盖磁盘签名。如果需要更多代码,必须在MBR中加载第二级引导。
- 分区表不可修改: MBR中的分区表由操作系统或分区工具维护,引导代码只能读取,不能写入。修改分区表会导致分区丢失。
- INT 13h调用规范: 读取扇区前必须设置正确的
dl(驱动器号),BIOS在跳转到MBR时会将dl设置为启动驱动器号(0x80为第一硬盘,0x00为第一软盘)。 - 栈指针设置: 实模式下栈通常设在0x7C00以下(如0x7BFF),避免与MBR代码重叠。栈向下增长,因此
sp = 0x7C00是安全的。 - 避免使用绝对地址: 如果代码需要访问全局变量或函数指针,必须确保它们基于0x7C00重定位。使用
[org 0x7C00]或链接脚本指定基址。
常见陷阱: 许多初学者在编写MBR时忘记设置
ds段寄存器,导致数据访问使用默认的ds值(可能为0x0000或0x07C0)。在实模式下,ds:offset的物理地址计算公式为ds × 16 + offset。如果ds = 0x07C0,则ds:0x0000指向物理地址0x7C00,与0x0000:0x7C00相同。但混合使用两种段基址会导致混乱,建议统一使用ds = 0x0000,所有地址基于0x7C00偏移。
3.6 本章小结
MBR是x86引导链的起点,其512字节结构包含引导代码、磁盘签名、分区表和0x55AA标志。BIOS通过INT 13h将MBR加载到0x7C00并执行。0x7C00地址是IBM PC设计时基于内存布局和最小配置的工程选择,至今仍是所有x86引导代码的默认加载点。在编写MBR时,必须严格遵守440字节代码限制、正确设置段寄存器、使用[org 0x7C00]进行地址重定位,并注意栈指针和INT 13h调用规范。这些基础是后续实现内核解压缩与地址重定位的基石。