You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Bootloader无法加载超53扇区,需支持加载至1MB及以上

问题:实模式Bootloader无法加载超过53个扇区

我正在为自研操作系统开发自定义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的内核,实模式无法直接访问高内存,需:

  1. 先进入保护模式(或 unreal 模式)
  2. 使用INT 0x13扩展函数将内核读取到高内存区域
  3. 或在保护模式下直接操作ATA控制器访问磁盘(复杂度较高)

额外检查点

  • 使用qemu-img info查看磁盘镜像的实际扇区数,确保足够容纳内核
  • INT 0x13 0x02执行后,al寄存器会返回实际读取的扇区数,可与预期值对比排查读取失败原因

内容的提问来源于stack exchange,提问作者Uday Khare

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:04:55