理论讲解:Bootloader

1. Bootloader的职责

引导扇区只有512字节,能做的事情非常有限。Bootloader的核心职责是为后续加载内核做准备:

① 初始化段寄存器和栈
        ↓
② 打印启动信息(让用户知道启动中)
        ↓
③ 从磁盘加载更多数据到内存
        ↓
④ 准备进入保护模式(下一章)
        ↓
⑤ 跳转到内核代码

2. 屏幕打印函数

上一章我们逐个字符打印,现在封装成可复用函数:

; 打印以0结尾的字符串
; 输入: SI = 字符串地址
print_string:
    pusha               ; 保存所有通用寄存器
    mov ah, 0x0E        ; BIOS teletype功能
.loop:
    lodsb               ; AL = [SI], SI++
    cmp al, 0           ; 到字符串结尾了吗?
    je .done
    int 0x10            ; 打印字符
    jmp .loop
.done:
    popa                ; 恢复所有寄存器
    ret

pusha 和 popa — 保存/恢复所有通用寄存器。好习惯:函数不应该修改调用者的寄存器。

3. 磁盘读取

BIOS磁盘服务 INT 0x13

INT 0x13, AH=0x02 — 读取磁盘扇区

输入:
  AH = 0x02          功能号(读取)
  AL = 扇区数量      要读取多少个扇区
  CH = 柱面号        CHS寻址的柱面
  CL = 扇区号        CHS寻址的扇区(从1开始)
  DH = 磁头号        CHS寻址的磁头
  DL = 驱动器号      0x00=软驱A, 0x80=第一个硬盘
  ES:BX = 目标地址    数据读取到的内存位置

输出:
  CF = 0 成功, 1 失败
  AH = 状态码
  AL = 实际读取的扇区数

CHS vs LBA

  • CHS(柱面-磁头-扇区): 传统的磁盘寻址方式
  • LBA(逻辑块寻址): 从0开始的线性扇区编号

BIOS INT 0x13 AH=0x02 使用CHS寻址。扇区号从1开始。

磁盘布局

扇区0: 引导扇区(Bootloader)← BIOS已加载到0x7C00
扇区1: 内核代码
扇区2: 内核代码(续)
...

我们的Bootloader需要把扇区1开始的数据加载到内存中。

4. 代码组织

本章将代码拆分为多个文件:

code/
├── boot.asm      主引导程序
├── print.asm     字符串打印函数
├── disk.asm      磁盘读取函数
└── Makefile      构建脚本

使用 %include 指令将多个文件组合:

%include "print.asm"
%include "disk.asm"

5. 内存布局

0x0000 ┌──────────────────┐
       │ 中断向量表        │
0x0400 ├──────────────────┤
       │ BIOS数据区        │
0x0500 ├──────────────────┤
       │                  │
       │ 可用内存(栈区)    │  ← 栈从0x7C00向下增长
       │                  │
0x7C00 ├──────────────────┤
       │ 引导扇区(512B)    │  ← 我们的Bootloader
0x7E00 ├──────────────────┤
       │                  │
       │ 可用内存          │
       │                  │
       │ 内核加载到这里 →   │  ← 0x1000(我们选的地址)
       │                  │
       └──────────────────┘