2. x86 实模式基础:内存模型、段寄存器与物理地址计算、BIOS 中断服务

在 x86 体系结构中,实模式(Real Mode)是处理器上电或复位后首先进入的工作模式。它是 16 位模式的始祖,也是内核解压缩与地址重定位工程中必须跨越的第一道门槛。本章将深入剖析实模式的内存模型、段寄存器与物理地址计算机制,以及 BIOS 中断服务的调用规范,为后续的汇编启动代码编写奠定坚实的硬件基础。

2.1 实模式内存模型

实模式下的内存模型直接继承自 Intel 8086 架构,具有以下核心特征:

  • 地址总线宽度:20 位,可寻址 1 MB(220 = 1,048,576 字节)的物理内存空间。
  • 数据总线宽度:16 位,每次可读写 2 字节。
  • 寄存器宽度:通用寄存器、段寄存器、指令指针寄存器均为 16 位。
  • 内存分段:将 1 MB 空间划分为多个 64 KB 的段(Segment),通过“段基址:偏移量”的方式访问。
关键概念:实模式没有内存保护机制,任何程序都可以访问整个 1 MB 空间(包括系统保留区和硬件设备映射区)。这也是内核解压缩代码在启动初期必须谨慎操作的原因。

下表列出了实模式内存空间的典型布局(以 IBM PC/AT 兼容机为例):

地址范围(十六进制) 大小 用途
0x00000 – 0x003FF 1 KB 中断向量表(IVT)
0x00400 – 0x004FF 256 字节 BIOS 数据区(BDA)
0x00500 – 0x9FBFF 约 638 KB 常规内存(用户程序、操作系统)
0x9FC00 – 0x9FFFF 1 KB 扩展 BIOS 数据区(EBDA)
0xA0000 – 0xBFFFF 128 KB 视频显示内存(VGA 帧缓冲)
0xC0000 – 0xDFFFF 128 KB 扩展 ROM(网卡、SCSI 卡等)
0xE0000 – 0xEFFFF 64 KB 系统 BIOS 扩展
0xF0000 – 0xFFFFF 64 KB 系统 BIOS(包含 POST 和启动代码)
工程提示:内核解压缩代码通常被加载到 0x100000(1 MB)以上的扩展内存中,但启动阶段(实模式)的引导扇区代码必须位于 0x7C00 附近的常规内存区域。理解这个内存布局,是避免数据覆盖和地址冲突的前提。

2.2 段寄存器与物理地址计算

实模式下,物理地址的计算公式为:

物理地址 = (段基址 << 4) + 偏移量

其中,段基址存储在 16 位的段寄存器中,偏移量由通用寄存器或指令指针提供。由于段基址左移 4 位(相当于乘以 16),因此段基址与偏移量的有效组合可以覆盖 1 MB 地址空间。

x86 架构提供了 4 个主要的段寄存器:

段寄存器 默认用途 典型关联指令
CS(代码段) 存放当前执行的指令 JMP、CALL、RET
DS(数据段) 存放程序使用的数据 MOV、LODS、STOS
ES(附加段) 字符串操作的目标段 MOVS、SCAS、CMPS
SS(堆栈段) 管理堆栈操作 PUSH、POP、CALL

下面通过一个具体的例子来演示物理地址的计算过程:

; 假设 CS = 0x07C0, IP = 0x0000
; 物理地址 = (0x07C0 << 4) + 0x0000
;          = 0x7C00 + 0x0000
;          = 0x7C00
; 这正是引导扇区被 BIOS 加载后的入口地址。
常见陷阱:同一个物理地址可以通过不同的“段基址:偏移量”组合来表示。例如,物理地址 0x7C00 也可以由 CS=0x07C0, IP=0x0000 或 CS=0x0000, IP=0x7C00 访问。这种“段重叠”特性在地址重定位时经常被利用,但也容易导致代码逻辑混乱。

下面的 SVG 图展示了实模式下物理地址的计算流程与段寄存器的关系:

实模式物理地址计算与段寄存器关系图 实模式物理地址计算与段寄存器关系图 段寄存器组 CS (代码段) DS (数据段) ES (附加段) SS (堆栈段) 16 位段基址 偏移量来源 IP (指令指针) SP/BP (堆栈指针) SI/DI (变址寄存器) BX (基址寄存器) 16 位偏移量 地址加法器 物理地址 = (段基址 << 4) + 偏移量 20 位物理地址 段基址 (左移 4 位) 偏移量 内存 说明:段基址左移 4 位(乘以 16)后与 16 位偏移量相加,生成 20 位物理地址。 最大寻址范围:0x00000 – 0xFFFFF(1 MB)。

2.3 BIOS 中断服务

在实模式下,BIOS(Basic Input/Output System)提供了一套标准的中断服务例程(Interrupt Service Routines, ISRs),允许程序员通过软件中断指令 INT 调用底层硬件功能。这些服务是内核解压缩工程中与硬件交互的主要手段。

2.3.1 中断向量表(IVT)

中断向量表位于物理地址 0x00000 – 0x003FF(1 KB),共包含 256 个中断向量,每个向量占用 4 字节(段基址:偏移量)。当 CPU 执行 INT n 指令时,会自动查找 IVT 中第 n 个向量,并跳转到对应的中断处理程序。

; 中断向量表结构(每个向量 4 字节)
; 偏移量 0-1: 中断处理程序的偏移地址(IP)
; 偏移量 2-3: 中断处理程序的段基址(CS)
; 例如:INT 0x13 的向量位于 0x0000:0x004C – 0x0000:0x004F

2.3.2 常用 BIOS 中断服务

下表列出了内核解压缩工程中常用的 BIOS 中断服务:

中断号 功能 典型用途
INT 0x10 视频服务 设置显示模式、打印字符、读取屏幕信息
INT 0x13 磁盘服务 读取/写入磁盘扇区、获取磁盘参数
INT 0x15 系统服务 获取内存大小(E820 调用)、开启 A20 地址线
INT 0x16 键盘服务 读取键盘输入、检测按键状态
INT 0x1A 时间服务 读取系统时钟、设置计时器

2.3.3 调用 BIOS 中断的规范

调用 BIOS 中断需要遵循严格的寄存器约定:

  1. 设置输入参数:将功能号(Function Number)放入 AH 寄存器,其他参数按规范放入 AL、BX、CX、DX 等寄存器。
  2. 执行中断:使用 INT n 指令触发中断。
  3. 检查返回值:中断返回后,检查 CF(进位标志)判断操作是否成功(通常 CF=0 表示成功,CF=1 表示失败),并从指定寄存器读取返回数据。
代码示例:使用 INT 0x13 读取磁盘扇区到内存。
; 功能:读取磁盘扇区
; 输入:AH = 0x02 (读扇区功能)
;       AL = 扇区数 (1-128)
;       CH = 柱面号 (低 8 位)
;       CL = 扇区号 (位 0-5) | 柱面号高 2 位 (位 6-7)
;       DH = 磁头号
;       DL = 驱动器号 (0x00=软盘A, 0x80=硬盘C)
;       ES:BX = 目标缓冲区地址
; 输出:CF = 0 成功,CF = 1 失败
;       AH = 状态码
;       AL = 实际读取的扇区数

mov ah, 0x02        ; 功能号:读扇区
mov al, 1           ; 读取 1 个扇区
mov ch, 0           ; 柱面号 0
mov cl, 2           ; 扇区号 2 (从第 2 个扇区开始)
mov dh, 0           ; 磁头号 0
mov dl, 0x80        ; 第一个硬盘
mov bx, 0x1000      ; 目标段基址
mov es, bx          ; ES = 0x1000
mov bx, 0x0000      ; 目标偏移量 (ES:BX = 0x1000:0x0000)
int 0x13            ; 调用磁盘中断
jc disk_error       ; 如果 CF=1,跳转到错误处理
; 读取成功,数据已加载到 0x10000 处
工程陷阱:BIOS 中断服务在实模式下运行,但某些中断(如 INT 0x13 的扩展读功能)会切换到保护模式再返回。在编写内核解压缩代码时,必须确保中断调用前后段寄存器的状态一致,否则可能导致系统崩溃。

下面的 SVG 图展示了 BIOS 中断调用的完整流程:

BIOS 中断调用流程图 BIOS 中断调用流程图 1. 设置输入参数 AH=功能号,其他寄存器 2. 执行 INT n CPU 查找 IVT 并跳转 3. BIOS 执行服务 操作硬件并返回结果 4. 检查返回状态 CF=0 成功,CF=1 失败 5. 错误处理 读取 AH 状态码并处理 说明:BIOS 中断调用是实模式下与硬件交互的标准方式,内核解压缩工程中常用于磁盘读取和内存探测。

2.4 实模式下的地址重定位基础

地址重定位(Address Relocation)是指将代码或数据从一个内存位置移动到另一个位置,并修正其中的绝对地址引用。在实模式下,由于段寄存器的存在,地址重定位可以通过调整段基址来实现,而不需要修改代码中的偏移量。

实模式地址重定位的两种常见策略:

  • 段基址调整法:保持代码中的偏移量不变,仅修改 CS、DS 等段寄存器的值。适用于代码内部使用相对寻址或段内短跳转的情况。
  • 位置无关代码(PIC):编写代码时全部使用相对寻址(如 JMP SHORT、CALL NEAR),避免绝对地址引用。这样代码可以在任意位置执行,无需修正。
工程实践:在编写内核解压缩的启动代码时,建议优先采用位置无关代码(PIC)策略。这可以避免在解压缩过程中进行复杂的地址修正,降低代码的复杂度。只有在需要访问固定硬件地址(如 VGA 帧缓冲)时,才使用段基址调整法。