跳转至

玩转 BIOS:从启动到接管硬件的完整指南

"BIOS 不仅是启动代码,更是硬件的终极控制台。
本文将深入 BIOS 的启动流程、中断服务、硬件检测,
为你揭示如何在实模式下完全掌控 x86 硬件。"

1 引言:BIOS 的现代价值

在 UEFI 时代,许多人认为 BIOS 已经过时。
但对自制操作系统开发者来说,BIOS 仍是最佳的学习起点

  • 简化启动:无需处理复杂的 UEFI 协议
  • 硬件抽象:提供标准中断服务(INT 10h, INT 13h, INT 16h)
  • 调试友好:实模式调试比保护模式简单得多
  • 兼容性好:所有 x86 PC 都支持 BIOS 启动

本文将带你玩转 BIOS,从启动流程到硬件控制,
为自制 OS 构建坚实的启动基础。


2 第一章:BIOS 启动流程深度解析

1.1 上电自检(POST)阶段

2.0.1 BIOS 启动序列

1. 上电 → CPU 复位 → CS:IP = 0xF000:0xFFF0
2. 执行跳转指令 → BIOS 代码段
3. 执行 POST(Power-On Self Test)
4. 检测硬件(内存、显卡、键盘、磁盘)
5. 初始化硬件(设置中断向量表、配置 DMA)
6. 加载启动设备(MBR)
7. 跳转到 MBR(0x7C00)

2.0.2 关键内存布局

地址范围 用途
0x0000-0x03FF 中断向量表(IVT)
0x0400-0x04FF BIOS 数据区(BDA)
0x0500-0x7BFF 可用内存
0x7C00-0x7DFF 引导扇区
0x7E00-0x9FFF 引导程序堆栈
0xA000-0xBFFF 显存
0xC000-0xDFFF BIOS 扩展 ROM
0xE000-0xFFFF BIOS ROM

1.2 中断向量表(IVT)

2.0.3 IVT 结构

; 中断向量表(每个中断 4 字节:偏移 + 段)
; 地址 0x0000:0x0000
dw timer_handler    ; INT 08h - 时钟中断
dw 0x0000
dw keyboard_handler ; INT 09h - 键盘中断  
dw 0x0000
dw video_handler    ; INT 10h - 视频服务
dw 0x0000
dw disk_handler     ; INT 13h - 磁盘服务
dw 0x0000
dw serial_handler   ; INT 14h - 串口服务
dw 0x0000
dw mouse_handler    ; INT 33h - 鼠标服务
dw 0x0000

2.0.4 自定义中断处理

; 安装自定义时钟中断处理程序
install_timer_handler:
    cli                     ; 禁用中断
    mov ax, cs
    mov [0x0020], timer_handler  ; INT 08h 偏移
    mov [0x0022], ax             ; INT 08h 段
    sti                     ; 启用中断
    ret

timer_handler:
    push ax
    push dx

    ; 更新系统时间
    inc word [timer_ticks]

    ; 调用原始 BIOS 中断(可选)
    pushf
    call far [0x0024]       ; 原始 INT 08h

    pop dx
    pop ax
    iret

1.3 BIOS 数据区(BDA)

2.0.5 BDA 关键字段

偏移 大小 用途
0x0410 word 设备列表(bit 7=DMA, bit 6=FDD, bit 5=LPT2, bit 4=LPT1, bit 3=RS232, bit 2=Game, bit 1=Tape, bit 0=FDD)
0x0413 word 基础内存大小(KB)
0x0449 byte 当前视频模式
0x0450 word 当前光标位置(高字节=行,低字节=列)
0x0463 dword BIOS 视频服务入口

2.0.6 读取 BDA 信息

// 读取基础内存大小
uint16_t get_base_memory(void) {
    uint16_t *bda_base_mem = (uint16_t*)0x0413;
    return *bda_base_mem;
}

// 读取视频模式
uint8_t get_video_mode(void) {
    uint8_t *bda_video_mode = (uint8_t*)0x0449;
    return *bda_video_mode;
}

3 第二章:BIOS 中断服务详解

2.1 视频服务(INT 10h)

3.0.1 常用功能

AH 功能 参数
0x00 设置视频模式 AL=模式
0x02 设置光标位置 DH=行, DL=列, BH=页
0x03 读取光标位置 BH=页
0x06 滚动窗口向上 AL=行数, BH=属性, CH=左上行, CL=左上列, DH=右下行, DL=右下列
0x0E 电传打字输出 AL=字符, BH=页, BL=前景色

3.0.2 文本模式操作

; 设置 80x25 文本模式
mov ah, 0x00
mov al, 0x03
int 0x10

; 设置光标到 (10, 20)
mov ah, 0x02
mov bh, 0x00    ; 页 0
mov dh, 10      ; 行 10
mov dl, 20      ; 列 20
int 0x10

; 输出字符 'A'
mov ah, 0x0E
mov al, 'A'
mov bh, 0x00
mov bl, 0x07    ; 白色前景,黑色背景
int 0x10

3.0.3 图形模式操作

; 设置 320x200 256 色模式
mov ah, 0x00
mov al, 0x13
int 0x10

; 写像素 (x=100, y=50, color=15)
mov ah, 0x0C
mov al, 15      ; 颜色
mov cx, 100     ; X 坐标
mov dx, 50      ; Y 坐标
int 0x10

; 读像素
mov ah, 0x0D
mov cx, 100
mov dx, 50
int 0x10
; 返回颜色在 AL

2.2 磁盘服务(INT 13h)

3.0.4 磁盘参数

  • CHS 寻址:柱面(Cylinder)、磁头(Head)、扇区(Sector)
  • 扇区大小:512 字节
  • 最大容量:8.4GB(1024 柱面 × 256 磁头 × 63 扇区 × 512 字节)

3.0.5 读取扇区

; 读取 1 个扇区到 0x7E00
read_sector:
    pusha

    mov ah, 0x02        ; 读取扇区功能
    mov al, 0x01        ; 扇区数
    mov ch, 0x00        ; 柱面
    mov cl, 0x02        ; 扇区(1-63)
    mov dh, 0x00        ; 磁头
    mov dl, 0x80        ; 驱动器(0x80=第一硬盘)
    mov bx, 0x7E00      ; 缓冲区地址
    int 0x13

    jc disk_error       ; CF=1 表示错误

    popa
    ret

disk_error:
    mov si, error_msg
    call print_string
    hlt

error_msg: db "Disk error!", 0

3.0.6 磁盘参数表(DPT)

// 获取磁盘参数
struct disk_parameter_table {
    uint16_t buffer;        // 缓冲区偏移
    uint16_t count;         // 扇区数
    uint16_t sector;        // 起始扇区
    uint16_t cylinder;      // 起始柱面
    uint8_t head;           // 起始磁头
    uint8_t drive;          // 驱动器号
    uint8_t flags;          // 标志
};

// 扩展磁盘服务(INT 13h, AH=48h)
int get_disk_info(uint8_t drive, struct disk_parameter_table *dpt) {
    struct {
        uint16_t size;      // 结构大小
        uint16_t flags;     // 标志
        uint32_t cylinders; // 柱面数
        uint32_t heads;     // 磁头数
        uint32_t sectors;   // 每磁道扇区数
        uint64_t total_sectors; // 总扇区数
        uint16_t bytes_per_sector; // 每扇区字节数
    } buffer;

    buffer.size = sizeof(buffer);

    __asm__ volatile (
        "int $0x13"
        : "=a"(buffer)
        : "a"(0x4800), "d"(drive), "S"(&buffer)
        : "memory"
    );

    return (buffer.flags & 0x01) ? 0 : -1; // 0=成功
}

2.3 键盘服务(INT 16h)

3.0.7 键盘功能

AH 功能 返回
0x00 读取字符 AL=ASCII, AH=扫描码
0x01 检查键盘缓冲区 ZF=1=无字符, AX=字符
0x02 获取移位状态 AL=移位状态

3.0.8 键盘输入处理

; 等待按键
wait_key:
    mov ah, 0x00
    int 0x16
    ; AL = ASCII, AH = 扫描码
    ret

; 检查是否有按键
check_key:
    mov ah, 0x01
    int 0x16
    jz no_key       ; ZF=1 表示无按键

    ; AX = 字符
    ret

no_key:
    xor ax, ax
    ret

3.0.9 扫描码处理

// 扫描码到 ASCII 转换表
static const char scan_to_ascii[128] = {
    0, 0x1B, '1', '2', '3', '4', '5', '6',
    '7', '8', '9', '0', '-', '=', '\b', '\t',
    'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
    'o', 'p', '[', ']', '\n', 0, 'a', 's',
    'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
    '\'', '`', 0, '\\', 'z', 'x', 'c', 'v',
    'b', 'n', 'm', ',', '.', '/', 0, '*',
    0, ' ', 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, '7',
    '8', '9', '-', '4', '5', '6', '+', '1',
    '2', '3', '0', '.'
};

4 第三章:BIOS 硬件检测与配置

3.1 内存检测

4.0.1 INT 15h, E820 内存映射

// E820 内存映射条目
struct e820_entry {
    uint64_t base;      // 基地址
    uint64_t length;    // 长度
    uint32_t type;      // 类型(1=可用, 2=保留, 3=ACPI, 4=NVS)
    uint32_t reserved;
};

// 获取内存映射
int get_memory_map(struct e820_entry *map, int max_entries) {
    uint32_t cont_id = 0;
    int count = 0;

    do {
        __asm__ volatile (
            "int $0x15"
            : "=a"(cont_id), "=c"(map[count].type), "=d"(map[count].reserved)
            : "a"(0xE820), "b"(map[count].base), "d"(map[count].length), "c"(20), "D"(cont_id)
            : "memory"
        );

        if (cont_id == 0) break;

        // 检查是否为可用内存
        if (map[count].type == 1 && map[count].length > 0) {
            count++;
            if (count >= max_entries) break;
        }
    } while (cont_id != 0);

    return count;
}

4.0.2 内存类型说明

类型 说明
1 可用 RAM
2 保留(硬件使用)
3 ACPI 重声明
4 NVS(非易失性存储)
5 有缺陷的内存

3.2 CPU 检测

4.0.3 CPUID 指令

// CPUID 结构
struct cpuid_result {
    uint32_t eax, ebx, ecx, edx;
};

// 执行 CPUID
static inline struct cpuid_result cpuid(uint32_t leaf) {
    struct cpuid_result result;
    __asm__ volatile (
        "cpuid"
        : "=a"(result.eax), "=b"(result.ebx), "=c"(result.ecx), "=d"(result.edx)
        : "a"(leaf)
        : "memory"
    );
    return result;
}

// 检测 CPU 功能
int detect_cpu_features(void) {
    struct cpuid_result result = cpuid(0x00000001);

    int features = 0;
    if (result.edx & (1 << 15)) features |= CPUID_FPU;
    if (result.edx & (1 << 23)) features |= CPUID_MMX;
    if (result.edx & (1 << 25)) features |= CPUID_SSE;
    if (result.ecx & (1 << 0))  features |= CPUID_SSE3;
    if (result.ecx & (1 << 9))  features |= CPUID_SSSE3;

    return features;
}

3.3 PCI 设备检测

4.0.4 PCI 配置空间访问

// PCI 配置空间读取
uint32_t pci_read_config(uint8_t bus, uint8_t slot, uint8_t func, uint8_t offset) {
    uint32_t address = (1 << 31) | (bus << 16) | (slot << 11) | (func << 8) | (offset & 0xFC);

    outl(0xCF8, address);
    return inl(0xCFC + (offset & 0x3));
}

// 扫描 PCI 总线
void pci_scan(void) {
    for (int bus = 0; bus < 256; bus++) {
        for (int slot = 0; slot < 32; slot++) {
            uint16_t vendor = pci_read_config(bus, slot, 0, 0x00);
            if (vendor == 0xFFFF) continue; // 无设备

            uint16_t device = pci_read_config(bus, slot, 0, 0x02);
            uint32_t class_rev = pci_read_config(bus, slot, 0, 0x08);

            printf("PCI %02x:%02x.0 - %04x:%04x (class %06x)\n", 
                   bus, slot, vendor, device, class_rev >> 8);
        }
    }
}

5 第四章:BIOS 启动扇区开发

4.1 引导扇区结构

5.0.1 MBR 格式

偏移 大小 用途
0x0000 446 引导代码
0x01BE 16 分区表项 1
0x01CE 16 分区表项 2
0x01DE 16 分区表项 3
0x01EE 16 分区表项 4
0x01FE 2 签名(0xAA55)

5.0.2 最小引导扇区

; boot.asm
bits 16
org 0x7C00

start:
    ; 设置段寄存器
    cli
    mov ax, 0x07C0
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    sti

    ; 清屏
    mov ah, 0x06
    mov al, 0x00
    mov bh, 0x07
    mov cx, 0x0000
    mov dx, 0x184F
    int 0x10

    ; 输出消息
    mov si, msg
    call print_string

    ; 无限循环
    cli
    hlt
    jmp $

print_string:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    mov bx, 0x0007
    int 0x10
    jmp print_string
.done:
    ret

msg: db "BIOS Bootloader!", 0

; 填充到 510 字节
times 510-($-$$) db 0

; 签名
dw 0xAA55

4.2 磁盘加载器

5.0.3 加载后续扇区

; 加载 32 个扇区到 0x8000
load_kernel:
    mov si, disk_error_msg

    ; 重置磁盘
    mov ah, 0x00
    mov dl, 0x80
    int 0x13
    jc disk_error

    ; 加载扇区
    mov ax, 0x0800    ; 目标段
    mov es, ax
    xor bx, bx        ; 目标偏移
    mov ah, 0x02      ; 读取功能
    mov al, 32        ; 扇区数
    mov ch, 0x00      ; 柱面
    mov cl, 0x02      ; 起始扇区
    mov dh, 0x00      ; 磁头
    mov dl, 0x80      ; 驱动器
    int 0x13
    jc disk_error

    ; 跳转到内核
    jmp 0x0800:0x0000

disk_error:
    call print_string
    hlt

4.3 实模式到保护模式切换

5.0.4 启用 A20 地址线

enable_a20:
    ; 方法 1:键盘控制器
    call a20_wait
    mov al, 0xAD
    out 0x64, al
    call a20_wait
    mov al, 0xD0
    out 0x64, al
    call a20_wait2
    in al, 0x60
    or al, 2
    call a20_wait
    mov al, 0xD1
    out 0x64, al
    call a20_wait
    mov al, al
    out 0x60, al
    call a20_wait
    mov al, 0xAE
    out 0x64, al
    call a20_wait

    ret

a20_wait:
    in al, 0x64
    test al, 2
    jnz a20_wait
    ret

a20_wait2:
    in al, 0x64
    test al, 1
    jz a20_wait2
    ret

5.0.5 设置 GDT 并切换到保护模式

; GDT 描述符
gdt_start:
    dq 0x0                    ; 空描述符

gdt_code:
    dw 0xFFFF                 ; 段界限 0-15
    dw 0x0                    ; 基地址 0-15
    db 0x0                    ; 基地址 16-23
    db 10011010b              ; 类型:代码段,可读
    db 11001111b              ; 粒度:4KB,32位
    db 0x0                    ; 基地址 24-31

gdt_
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10010010b              ; 数据段,可写
    db 11001111b
    db 0x0

gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

; 切换到保护模式
switch_to_pm:
    ; 加载 GDT
    lgdt [gdt_descriptor]

    ; 启用保护模式
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    ; 远跳转刷新 CS
    jmp 0x08:protected_mode_start

[bits 32]
protected_mode_start:
    ; 设置段寄存器
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; 调用 C 代码
    call KERNEL_ENTRY_POINT

6 第五章:BIOS 调试技巧

5.1 实模式调试

6.0.1 使用 Bochs 调试

# bochsrc.txt
megs: 32
romimage: file=/usr/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest
ata0-master: type=disk, path=disk.img, mode=flat
boot: disk
log: bochsout.txt
debug: 1

6.0.2 调试命令

# 启动 Bochs
bochs -f bochsrc.txt

# Bochs 调试器命令
b 0x7c00        # 在引导扇区设置断点
c               # 继续执行
r               # 查看寄存器
xp /10bx 0x7c00 # 查看内存

5.2 硬件模拟调试

6.0.3 QEMU 调试

# 启用调试
qemu-system-i386 -kernel kernel.bin -hda disk.img -s -S

# GDB 连接
gdb kernel.bin
(gdb) target remote :1234
(gdb) set architecture i8086
(gdb) break *0x7c00
(gdb) continue

5.3 BIOS 仿真工具

6.0.4 使用 coreboot 仿真

# 构建 coreboot 仿真
make menuconfig
# 选择 Hardware → Emulation → QEMU
make

# 运行仿真
./build/coreboot.rom

6.0.5 使用 SeaBIOS

# 编译 SeaBIOS
git clone https://github.com/coreboot/seabios.git
make

# 在 QEMU 中使用
qemu-system-i386 -bios out/bios.bin -hda disk.img

7 第六章:BIOS 与现代启动的对比

6.1 BIOS vs UEFI

特性 BIOS UEFI
模式 16 位实模式 32/64 位保护模式
启动速度 慢(POST 详细) 快(快速启动)
磁盘支持 MBR(2TB 限制) GPT(8ZB 限制)
文件系统 无(直接扇区访问) FAT32(可读文件)
驱动 内置(16 位) 模块化(32/64 位)
安全性 Secure Boot

6.2 Legacy Boot 的优势

7.0.1 对自制 OS 开发者的价值

  • 简化启动:无需处理复杂的 UEFI 协议
  • 硬件控制:直接访问硬件端口
  • 调试友好:实模式调试工具成熟
  • 兼容性:所有 x86 PC 支持

7.0.2 何时转向 UEFI

  • 大磁盘支持:>2TB 磁盘
  • 安全启动:Secure Boot 需求
  • 现代硬件:UEFI-only 系统
  • 性能要求:快速启动需求

6.3 混合启动策略

7.0.3 BIOS 兼容模式

// 检测启动方式
int detect_boot_mode(void) {
    // 检查是否在 UEFI 模式下
    if (*((uint32_t*)0x00000400) == 0x55AA) {
        return BOOT_MODE_UEFI;
    } else {
        return BOOT_MODE_BIOS;
    }
}

7.0.4 双重引导扇区

+------------------+
|  MBR (BIOS)      |  // 446 字节引导代码
+------------------+
|  GPT 头部        |  // UEFI 兼容
+------------------+
|  分区 1          |
+------------------+
|  ...             |
+------------------+

8 结论:BIOS 的持久价值

尽管 UEFI 已成为现代标准,
BIOS 仍然是自制操作系统开发者的最佳起点

通过深入理解 BIOS: - 掌握硬件启动流程 - 学会实模式编程 - 理解中断服务机制 - 构建调试调试技能

这些技能不仅适用于 BIOS 环境,
更为理解 UEFI 和现代操作系统奠定坚实基础。

真正的硬件掌控,
始于对 BIOS 的深刻理解。