当磁盘不是硬盘驱动器时读取磁盘时出错。内部 0x13 啊 0x02

Error reading disk when disk is not a hard drive. Int 0x13 ah 0x02

我正在编写一个简单的操作系统,但在从磁盘读取时遇到了很多问题。我使用 int 0x13 和 ah=0x02 从驱动器读取数据,我收到了几个不同的错误消息。当我 运行 和

$ qemu-system-x86_64 -drive file=os.bin,if=floppy,index=0,media=disk,format=raw

效果很好。当我这样做时

$ qemu-system-x86_64 -drive file=os.bin,format=raw

设置进位标志,ah为0x20。根据 http://www.ctyme.com/intr/rb-0606.htm#Table234,这是一个 "controller failure" 错误。这没有多大意义,因为它 运行 在虚拟机中运行,所以我很确定是我的代码错了。

当我将启动映像写入磁盘(dd 到闪存驱动器上的分区)时,它会启动并成功启动我的程序,但在同一磁盘加载时失败,ah 为 0x01。同一站点说这是一个 "invalid function in AH or invalid parameter" 错误,这进一步证实了问题出在我的代码中。我不得不拼凑出一个糟糕的 print_hex 解决方案来打印啊 X 的数量,因为我没有动力将更好的东西组合在一起。

这是我的 load_disk.asm 文件:

disk_load:
  pusha
  push bx
  mov bx, DISK_START
  call print_string
  pop bx   

  push dx
  mov ah, 0x02
  mov al, dh
  mov cl, 0x02
  mov ch, 0x00
  mov dh, 0x00


  int 0x13
  jc disk_error0
  pop dx
  cmp dh, al
  jne disk_error1

  push bx
  mov bx, DISK_SUCC
  call print_string
  pop bx  
  popa
  ret

disk_error0:
  loopY:
  cmp ah, 0x0
  je cont
  mov bx, STARTING_DISK_ERROR
  call print_string  
  sub ah, 1
  jmp loopY
cont:
; print a nice little counter
  mov bx,NEWLINE
  call print_string 
  mov ah,8 ; 80 character screen, 10 characters
loopS:
  cmp ah,0x0
  je cont2
  mov bx, NUMBERS
  call print_string
  sub ah, 1
  jmp loopS

cont2:
  mov bx,NEWLINE
  call print_string
  mov bx, DISK_ERROR_0
  call print_string
  jmp $

disk_error1:
  mov bx, DISK_ERROR_1
  call print_string
  jmp $

STARTING_DISK_ERROR : db "X",0
NEWLINE: db 10,13,0
NUMBERS: db "1234567890",0
DISK_ERROR_0 : db "Error0",10,13, 0
DISK_ERROR_1 : db "Error1",10,13, 0
DISK_START : db "Startingdisk", 10,13, 0
DISK_SUCC : db "Loadeddisk", 10,13,0

我已经运行整理了我的字符串,以便在 512 字节中为调试代码腾出空间。这段代码是从boot.asm调用的,也就是

[bits 16]
[org 0x7c00]
  jmp 0x0000:main_entry    ; ensures cs = 0x0000

main_entry:
  xor ax, ax
  mov ds, ax
  mov es, ax
  KERNAL_OFFSET equ 0x1000

  mov [BOOT_DRIVE], dl

  mov bp, 0x9000
  mov sp, bp


  mov bx, MSG_REAL_MODE
  call print_string



  call load_kernal
  call switch_to_pm 

  ;this line will never execute
  jmp $

%include "src/print_string.asm"
%include "src/disk_load.asm"
%include "src/gdt.asm"
%include "src/print_string_pm.asm"
%include "src/switch_to_pm.asm"
%include "src/print_hex.asm" ; this is broken, don't use

[bits 16]
load_kernal:
  mov bx, MSG_LOAD_KERNAL
  call print_string

  mov bx, KERNAL_OFFSET

  mov dh, 31            ; load 31 sectors. gives plenty of room
  mov dl, [BOOT_DRIVE]
  call disk_load 
;  mov bx, MSG_LOAD_DISK
;  call print_string
  ret

[bits 32]

BEGIN_PM:
  mov ebx, MSG_PROT_MODE
  call print_string_pm
  call KERNAL_OFFSET 
  mov ebx, 0x5000
  call print_string_pm
  jmp $ ; if the kernal returns, stay here



BOOT_DRIVE      db 0
MSG_REAL_MODE   db "Started in 16-bit", 10, 13, 0
MSG_PROT_MODE   db "Sted in 32-bit", 0
;MSG_SHOULD_NEVER_PRINT db "Frack",10,13, 0
MSG_LOAD_KERNAL db "Loding kernal",10,13, 0
;MSG_LOAD_DISK   db "Loaded disk!", 10,13,0
MSG_KERNAL_EXIT db "kernal has exited",10,13,0



times 510-($-$$) db 0

dw 0xaa55

我一直在寻找 https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf 作为我这个项目的基础。但是,它假定是一张软盘,因此在这种情况下它的帮助有限。

编辑:我以为我得到了所有相关文件,但似乎我没有:( 这里是 print_string.asm:

; prints the string at location BX
print_string:
  push ax
  push bx
  push dx ; NEW
  mov ah, 0x0e
  loop1:
    mov al, [bx]
    int 0x10
    add bx, 1
    mov dl, [bx]
    cmp dl, 0x0
    jne loop1
  pop dx ; NEW
  pop bx
  pop ax
  ret

在评论提到它之后,我向该文件添加了一个 push dx/pop dx。 ah 现在是 12,即 0x0C,即 "unsupported track or invalid media"。

有可能是硬盘驱动器的结构问题或其他问题。我正在使用 cat 到 assemble 我的最终 os.bin 文件,这对我来说没有多大意义。这是我的 Makefile 行(如果有帮助,我可以 post 整个 makefile)

os.bin : build/boot.bin build/kernal.bin
    cat build/boot.bin build/kernal.bin > $@

build/boot.bin 是我在前 512 个字节中加载的所有程序集。 kernal.bin 是我应该从磁盘加载的 C 代码

你没有显示你的内核,但我可以做出一些有根据的猜测。虽然它可能在某些版本的 QEMU 上有所不同,但您会发现,当从磁盘映像作为软盘启动时,您可以读取文件末尾之后的扇区,但作为硬盘驱动器启动则不那么宽容。

您的代码在加载内核时从 CHS(0,0,2) 开始读取 31 个扇区。您没有显示内核 (kernel.bin),但我怀疑它的大小小于 31 个扇区。

当你这样做时:

qemu-system-x86_64 -drive file=os.bin,if=floppy,index=0,media=disk,format=raw

你作为第一张软盘启动。由于 QEMU 通常允许您读取超过软盘映像的末尾,因此 Int 13h/AH=2 成功。

当你这样做时:

qemu-system-x86_64 -drive file=os.bin,format=raw

你作为第一个硬盘启动。 QEMU 可能会抱怨,因为您已请求读取 31 个扇区的数据,但磁盘映像 os.bin 中没有那么多数据。我相信一般规则是,要使 QEMU 的硬盘读取工作,一个扇区中必须至少有 1 个字节的数据才能使读取成功。这意味着至少你必须有一个 os.bin 至少 512 字节(引导扇区)+ 30 * 512 字节(内核)+ 1(在第 31 个扇区中至少 1 个字节)= 15873 个字节。我希望如果您的图像文件小于 15873 字节,从 CHS(0,0,2)/LBA(Logical Block Address)=1 读取 31 个扇区将会失败。这可能就是您收到错误的原因:

unsupported track or invalid media

修复相当简单。确保您的 os.bin 至少有 32 个扇区(引导扇区 + 内核最多 31 个扇区)或文件大小为 32*512=16384。您可以使用 DD 程序构建一个 16384 字节的图像,然后使用 DD 将 boot.binkernel.bin 文件放入其中。

用于构建 os.bin 的 Makefile 条目可能如下所示:

os.bin : build/boot.bin build/kernal.bin    
    dd if=/dev/zero of=$@ bs=512 count=32
    dd if=build/boot.bin of=$@ bs=512 conv=notrunc
    dd if=build/kernal.bin of=$@ bs=512 seek=1 conv=notrunc

第一个命令使用 512 的块大小 (bs) 创建一个名为 os.bin 的零填充文件,并生成一个包含 32 个块的文件。 32 * 512 = 16384。第二个命令将 boot.bin 写入文件的开头以块 0(第一个块)。 conv=notrunc 表示在将 boot.bin 写入 os.bin 之后,我们不希望文件被截断。最后一行类似,但它将 kernal.bin 写入 os.bin,但告诉 DD 在磁盘上寻找块 1 并写入文件,而不是在完成时截断 os.bin

完成此 Makefile 配方后,您应该有一个包含引导加载程序和内核的 16384 字节长的 os.bin 文件。当使用 Int 13h/AH=2 时,无论它是作为软盘还是硬盘映像读取,这都应该让 QEMU 满意。


使用 USB FDD 仿真在真实硬件上启动

在使用软盘驱动器 (FDD) 仿真的真机上作为 USB 启动时,您可能会发现启动加载程序 运行 但似乎无法正常工作。这是因为许多将 USB 介质作为软盘引导的 BIOS 假定存在 Bios 参数块 (BPB),并在引导扇区被读入内存后和控制权转移到物理地址 0x07c00 之前盲目更新驱动器几何字段。如果没有 BPB,这些更改可能会覆盖数据 and/or 指令,导致代码无法按预期工作。有关此现象的更多信息和示例 BPB 可以在我的另一个 Whosebug .

中找到

如果磁盘操作失败,在真实硬件上重试几次也是一个好主意。您可以在再次重试该操作之前调用 BIOS 功能 Int 13h/AH=0(磁盘重置)来完成此操作。如果失败多次,则可以中止。我不认为这是你的问题,但我提到它是为了完整性。


其他观察结果

您的引导加载程序以:

开头
main_entry:
  xor ax, ax
  mov ds, ax
  mov es, ax
  KERNAL_OFFSET equ 0x1000

  mov [BOOT_DRIVE], dl

  mov bp, 0x9000
  mov sp, bp

您的代码仅设置 SP,而不设置 SSSS:SP 组合创建堆栈指针。由于 SS 尚未设置,我们真的不知道堆栈在内存中的位置。无法保证 SS 在进入我们的引导加载程序时为 0(有关更多信息和建议,请参阅我的 Whosebug 答案)。由于您的代码似乎甚至没有使用 BP (通常是堆栈帧指针),因此无需将其设置为 0x9000。只需将 SS:SP 设置为 0x0000:0x9000。代码可能如下所示:

main_entry:
  xor ax, ax
  mov ds, ax
  mov es, ax
  mov ss, ax               ; Set SS to 0
  mov sp, 0x9000           ; Set SP right after SS (see my bootloader tips for reason why) 

  mov [BOOT_DRIVE], dl

  KERNAL_OFFSET equ 0x1000