3. BIOS中断调用与实模式内存布局

任务目标

编写一个引导扇区程序,在实模式下通过BIOS中断 INT 10h 在屏幕上显示字符,并借助BIOS内存检测中断 INT 15h, E820h 读取实模式内存布局,将内存区域信息打印到屏幕。程序将被写入虚拟软盘镜像的首扇区,由QEMU启动执行。

环境与依赖

  • 汇编器:NASM(nasm)
  • 虚拟机:QEMU(qemu-system-x86_64)
  • 操作系统:Linux(本文以Ubuntu/Debian为例)

实现原理

引导扇区代码从 0x7C00 开始执行,此时CPU处于实模式,段寄存器指向BIOS数据区。程序首先通过 INT 10h 的 0x0E 功能(TTY字符输出)显示标题字符串。随后调用 INT 15h 的 E820h 功能遍历系统内存映射,每次调用返回一个内存区域描述符(20字节),包含起始地址、长度和类型。程序将每个区域的起始地址、长度和类型以十六进制格式打印到屏幕,直到返回 0x00 表示遍历结束。

关键完整代码

; boot.asm - BIOS中断调用与实模式内存布局
; 汇编: nasm -f bin boot.asm -o boot.bin
; 运行: qemu-system-x86_64 -fda boot.bin

BITS 16
ORG 0x7C00

start:
    ; 初始化段寄存器
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00

    ; 显示标题
    mov si, msg_title
    call print_string

    ; 使用INT 15h E820h检测内存布局
    mov di, 0x0500        ; 描述符缓冲区(避开栈和代码)
    xor ebx, ebx          ; 首次调用必须为0
    mov bp, di            ; 保存缓冲区地址

detect_loop:
    mov eax, 0xE820
    mov ecx, 20           ; 描述符大小
    mov edx, 0x534D4150   ; 'SMAP'签名
    int 0x15
    jc .error             ; CF=1表示出错

    cmp eax, 0x534D4150   ; 检查签名
    jne .error

    ; 打印当前区域信息
    mov si, msg_addr
    call print_string
    mov eax, [bp]         ; 起始地址低32位
    call print_hex32
    mov si, msg_len
    call print_string
    mov eax, [bp+8]       ; 长度低32位
    call print_hex32
    mov si, msg_type
    call print_string
    mov eax, [bp+16]      ; 类型
    call print_hex32
    call newline

    ; 检查是否还有更多区域
    test ebx, ebx
    jz .done
    add di, 20            ; 指向下一个描述符
    mov bp, di
    jmp detect_loop

.error:
    mov si, msg_error
    call print_string
    jmp halt

.done:
    mov si, msg_done
    call print_string

halt:
    hlt
    jmp halt

; 打印以SI指向的以0结尾的字符串
print_string:
    lodsb
    or al, al
    jz .ret
    mov ah, 0x0E
    int 0x10
    jmp print_string
.ret:
    ret

; 打印换行
newline:
    mov al, 0x0D
    mov ah, 0x0E
    int 0x10
    mov al, 0x0A
    int 0x10
    ret

; 以十六进制打印EAX中的32位值
print_hex32:
    push eax
    shr eax, 16
    call print_hex16
    pop eax
    call print_hex16
    ret

print_hex16:
    push eax
    shr eax, 8
    call print_hex8
    pop eax
    call print_hex8
    ret

print_hex8:
    push eax
    shr eax, 4
    call print_hex4
    pop eax
    call print_hex4
    ret

print_hex4:
    pusha
    and al, 0x0F
    add al, '0'
    cmp al, '9'
    jle .digit
    add al, 7            ; 'A'-'9'-1
.digit:
    mov ah, 0x0E
    int 0x10
    popa
    ret

msg_title db 'BIOS Memory Map:', 0x0D, 0x0A, 0
msg_addr  db '  Addr: 0x', 0
msg_len   db '  Len: 0x', 0
msg_type  db '  Type: 0x', 0
msg_error db 'E820 error!', 0x0D, 0x0A, 0
msg_done  db 'Done.', 0x0D, 0x0A, 0

times 510-($-$$) db 0
dw 0xAA55

编译与运行

nasm -f bin boot.asm -o boot.bin
qemu-system-x86_64 -fda boot.bin

验证方法

QEMU窗口打开后,屏幕应显示以下内容:

  1. 第一行标题 BIOS Memory Map:,证明 INT 10h 字符显示功能正常。
  2. 随后逐行列出内存区域,每行包含 Addr(起始地址)、Len(长度)和 Type(类型),均为十六进制格式。
  3. 最后一行显示 Done.,表示遍历完成。

典型输出中,第一行应为 Addr: 0x00000000 Len: 0x0009FC00 Type: 0x00000001(类型1表示可用内存),最后一行通常为 Addr: 0xFFFC0000 Len: 0x00040000 Type: 0x00000002(类型2表示保留内存)。若程序显示 E820 error!,说明当前BIOS不支持E820功能(QEMU默认支持,此情况极少发生)。

实模式内存布局说明

程序运行时的内存布局如下:

  • 0x00000000 - 0x000003FF:中断向量表(IVT),共1024字节,存放256个中断处理程序入口。
  • 0x00000400 - 0x000004FF:BIOS数据区(BDA),存放键盘状态、显示模式等系统信息。
  • 0x00000500 - 0x00007BFF:可用常规内存,本程序将E820描述符缓冲区放在 0x0500 处。
  • 0x00007C00 - 0x00007DFF:引导扇区加载地址,即当前代码所在位置。
  • 0x000F0000 - 0x000FFFFF:BIOS ROM区域,包含BIOS中断服务例程。

E820返回的内存区域描述符中,类型1为操作系统可用内存,类型2为保留内存,类型3为ACPI可回收内存,类型4为ACPI NVS(非易失性存储)。