自定义Bootloader无法加载超53扇区,需支持加载至1MB及以上
我正在为自研操作系统开发自定义Bootloader,遇到一个问题:无论如何修改,Bootloader都无法从磁盘加载超过53个扇区。以下是Bootloader的相关代码片段:
; Load kernel from disk load_kernel: mov ah, 0x02 ; BIOS read sector function mov al, 128 ; Number of sectors to read (128 * 512 bytes = 64 KB) mov ch, 0 ; Cylinder number mov dh, 0 ; Head number mov cl, 2 ; Sector number (starts from 1, boot sector is 1) mov bx, KERNEL_OFFSET int 0x13 ; BIOS interrupt jc disk_error ; Jump if error (carry flag set) ret
我已将mov al, 128指令修改为加载更多扇区(例如128个扇区以支持64KB内核),但Bootloader仍无法加载超过53个扇区。内核加载至内存地址0x1000,对于较小的内核(最多53个扇区),Bootloader可以正常工作。
我已尝试以下步骤:
- 验证磁盘镜像中包含53个扇区之外的内核数据。
- 增加
Makefile中的填充以确保OS镜像有足够空间:dd if=/dev/zero bs=512 count=4096 >> $(OS_IMAGE) dd if=$(OS_IMAGE) of=$(OS_IMAGE) bs=512 count=4096 conv=notrunc - 检查链接脚本(
link.ld)中的内存布局,确保有足够空间容纳内核。 - 确认BIOS中断
int 0x13调用正确。
尽管做了这些尝试,当我增加扇区数量时,Bootloader要么挂起,要么无法加载内核。是int 0x13存在限制,还是我的实现中有遗漏?
补充信息:
- Bootloader采用汇编编写,运行于实模式。
- 内核为扁平二进制文件,加载至
0x1000。 - 磁盘镜像使用
dd创建,并在QEMU中测试。
问题的原因可能是什么?我该如何修改Bootloader以加载超过53个扇区,甚至支持加载至1MB及以上?
问题根源分析
你遇到的是实模式下BIOS INT 0x13 0x02函数的扇区读取限制,核心原因有两点:
1. CHS寻址的天然限制
INT 0x13 0x02使用传统CHS(柱面-磁头-扇区)寻址,该函数不支持自动跨磁道/磁头/柱面的连续读取。早期BIOS默认一个磁道最多63个扇区,你从扇区2开始读取,当前磁道剩余可读取的扇区数为63 - 2 + 1 = 62,但QEMU对部分磁盘镜像的CHS参数识别可能更严格(比如每磁道54扇区),导致最多只能读取53个扇区,超出后BIOS会直接读取失败。
2. 内存重叠风险(需额外确认)
虽然你将内核加载到0x1000(位于Bootloader默认加载地址0x7C00下方),但需确认KERNEL_OFFSET对应的内存区域未被Bootloader自身的代码、栈或BIOS数据占用,否则会覆盖关键数据导致系统挂起。
解决方案
方案1:循环读取,手动处理跨磁道逻辑
既然单次读取无法跨磁道,可分多次读取,每次读取当前磁道剩余扇区数,手动更新CHS参数:
; 读取多个扇区,支持跨磁道/磁头/柱面 ; 输入: ; ax = 要读取的总扇区数 ; bx = 目标内存地址 ; cx = 起始扇区(CL=扇区, CH=柱面低8位) ; dh = 磁头号 load_kernel: push ax push bx push cx push dx .read_loop: ; 计算当前磁道剩余可读取扇区数 mov al, 63 sub al, cl add al, 1 ; 取剩余扇区数和总待读扇区数的较小值作为本次读取量 cmp ax, 0 je .read_done cmp ax, al jle .set_read_count mov al, ax .set_read_count: mov ah, 0x02 ; INT 0x13读扇区函数 int 0x13 jc disk_error ; 读取失败跳转 ; 更新剩余待读扇区数 sub ax, al ; 更新目标内存地址(每个扇区512字节,左移9位等价于乘512) mov cx, ax shl cx, 9 add bx, cx ; 更新CHS参数 add cl, al cmp cl, 64 ; 扇区数超过63,切换磁头 jl .read_loop mov cl, 1 inc dh cmp dh, 2 ; 磁头超过1(双面软盘),切换柱面 jl .read_loop mov dh, 0 inc ch jmp .read_loop .read_done: pop dx pop cx pop bx pop ax ret
方案2:切换到INT 0x13扩展函数(LBA寻址)
QEMU完全支持INT 0x13 0x42扩展读取函数,它使用LBA(逻辑块寻址),不受CHS限制,单次最多可读取128扇区:
第一步:检查扩展函数支持
; 检查INT 0x13扩展支持 check_extended_int13: mov ah, 0x41 mov bx, 0x55AA int 0x13 jc .no_extended_support cmp bx, 0xAA55 jne .no_extended_support ret .no_extended_support: ; 不支持则降级到CHS循环读取 jmp load_kernel
第二步:使用扩展函数读取内核
; 使用LBA读取内核 load_kernel_extended: pusha ; 定义磁盘地址包(DAP) dap: db 0x10 ; DAP大小 db 0 ; 保留位 dw 128 ; 要读取的扇区数 dw KERNEL_OFFSET ; 目标内存地址低16位 dw 0 ; 目标内存地址高16位(实模式下为0) dq 1 ; LBA起始扇区号(boot sector是0,内核从1开始) mov si, dap mov ah, 0x42 int 0x13 jc disk_error popa ret
方案3:加载到1MB以上(进入保护模式)
若要加载超过1MB的内核,实模式无法直接访问高内存,需:
- 先进入保护模式(或 unreal 模式)
- 使用INT 0x13扩展函数将内核读取到高内存区域
- 或在保护模式下直接操作ATA控制器访问磁盘(复杂度较高)
额外检查点
- 使用
qemu-img info查看磁盘镜像的实际扇区数,确保足够容纳内核 - INT 0x13 0x02执行后,
al寄存器会返回实际读取的扇区数,可与预期值对比排查读取失败原因
内容的提问来源于stack exchange,提问作者Uday Khare

