2. 案例2

任务目标

本案例实现x86平台Bootloader从实模式(16位)切换到保护模式(32位)的完整流程。核心工作包括:构建全局描述符表(GDT)并加载、启用A20地址线、修改CR0寄存器控制位,最终跳转到32位代码段执行。代码以可引导的Bootloader扇区形式呈现,可在QEMU中直接验证。

环境与依赖

  • 汇编器:NASM(≥2.14)
  • 虚拟机:QEMU(≥5.0)
  • 目标平台:x86(IA-32)

核心实现

以下代码为完整的Bootloader实现,包含实模式入口、GDT定义、A20启用、CR0切换及保护模式下的显存输出验证。代码经nasm -f bin编译后可直接作为引导扇区运行。

;--------------------------------------------------------
; boot.asm - 实模式到保护模式切换
; 汇编: nasm -f bin boot.asm -o boot.bin
; 运行: qemu-system-i386 -drive format=raw,file=boot.bin
;--------------------------------------------------------
[bits 16]
org 0x7c00

start:
    ; 保存引导驱动器号
    mov [boot_drive], dl

    ; 关闭中断(切换过程不允许被打断)
    cli

    ; 启用A20地址线(使用快速门控端口0x92)
    in al, 0x92
    or al, 0x02
    and al, 0xFE        ; 保留bit0(禁止复位)
    out 0x92, al

    ; 等待A20生效(简单延时)
    mov cx, 0x100
a20_wait:
    loop a20_wait

    ; 加载GDT描述符
    lgdt [gdt_desc]

    ; 修改CR0的PE位(bit0),切换到保护模式
    mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax

    ; 清空流水线并跳转到32位代码段
    jmp 0x08:protected_mode

[bits 32]
protected_mode:
    ; 初始化数据段寄存器为GDT数据段选择子(0x10)
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; 设置栈顶(位于1MB以下,避免与显存冲突)
    mov esp, 0x90000

    ; 验证:向显存写入字符 'P'(绿色)
    mov byte [0xb8000], 'P'
    mov byte [0xb8001], 0x0A

    ; 死循环,保持CPU在保护模式
halt:
    hlt
    jmp halt

;--------------------------------------------------------
; GDT定义
;--------------------------------------------------------
align 8
gdt_start:
    ; 空描述符(必须为全0)
    dd 0x00000000
    dd 0x00000000

    ; 代码段描述符(选择子0x08)
    ; base=0x0, limit=0xFFFFF, G=1, D/B=1, S=1, P=1, DPL=0, TYPE=0xA(可执行/可读)
    dw 0xFFFF        ; limit[15:0]
    dw 0x0000        ; base[15:0]
    db 0x00          ; base[23:16]
    db 0x9A          ; P=1, DPL=0, S=1, TYPE=0xA
    db 0xCF          ; G=1, D/B=1, limit[19:16]=0xF
    db 0x00          ; base[31:24]

    ; 数据段描述符(选择子0x10)
    ; base=0x0, limit=0xFFFFF, G=1, D/B=1, S=1, P=1, DPL=0, TYPE=0x2(可读/写)
    dw 0xFFFF        ; limit[15:0]
    dw 0x0000        ; base[15:0]
    db 0x00          ; base[23:16]
    db 0x92          ; P=1, DPL=0, S=1, TYPE=0x2
    db 0xCF          ; G=1, D/B=1, limit[19:16]=0xF
    db 0x00          ; base[31:24]
gdt_end:

gdt_desc:
    dw gdt_end - gdt_start - 1   ; GDT长度减1
    dd gdt_start                 ; GDT基地址

;--------------------------------------------------------
; 数据区
;--------------------------------------------------------
boot_drive: db 0x00

; 填充至510字节并写入引导签名
times 510-($-$$) db 0
dw 0xAA55

编译与运行

nasm -f bin boot.asm -o boot.bin
qemu-system-i386 -drive format=raw,file=boot.bin

验证方法

运行QEMU后,观察屏幕左上角是否出现绿色字符P。若出现,说明切换成功:

  1. 实模式入口:BIOS将引导扇区加载至0x7C00并跳转执行;
  2. A20启用:通过端口0x92的bit1置位,解除地址线回绕;
  3. GDT加载:lgdt指令加载包含代码段(0x08)和数据段(0x10)的描述符表;
  4. CR0切换:置位PE位后,CPU进入保护模式;
  5. 远跳转刷新流水线:jmp 0x08:protected_mode加载代码段选择子并跳转至32位代码;
  6. 段寄存器初始化:将DS/ES/SS等加载为数据段选择子;
  7. 显存输出:向0xB8000写入字符P及其属性字节(绿色),验证32位模式下内存访问正常。

若屏幕无输出或QEMU报错,请检查:GDT描述符的基地址/界限是否正确、A20端口是否被虚拟机支持(QEMU默认支持)、CR0修改后是否立即执行远跳转。

常见问题

  • 屏幕无输出:确认GDT中代码段描述符的TYPE位为0xA(可执行),且跳转选择子0x08与描述符索引1对应。
  • 三声报警后重启:通常因GDT加载失败或CR0切换后未正确跳转。检查gdt_desc中的基地址是否为gdt_start的实际线性地址(实模式下为CS:IP组合,此处使用org 0x7c00确保正确)。
  • A20未生效:部分环境需通过键盘控制器(端口0x64/0x60)启用,但QEMU支持0x92快速门控,本实现已覆盖。