BIOS INT 13H 问题(从驱动器读取扇区)

Posted

技术标签:

【中文标题】BIOS INT 13H 问题(从驱动器读取扇区)【英文标题】:Problem with BIOS INT 13H (Read Sectors From Drive) 【发布时间】:2020-02-22 04:22:52 【问题描述】:

说明:

为了创建一个简单的独立程序,我在第一个扇区中编写了一个简单的引导加载程序。其目的是将程序加载到内存中。为此,我使用 AH=2 的 INT 13h。代码是:

disk_load:
  push dx           ; Store DX on stack so later we can recall how many sectors were requested to be read,
                    ; even if it is altered in the meantime.
  mov ah, 0x02        ; Bios read sector.
  mov al, dh          ; Read DH sectors.
  mov ch, 0x00        ; Select cylinder 0.
  mov dh, 0x00        ; Select head 0.
  mov cl, 0x02        ; Start reading from second sector (i.e. after the boot sector).
  int 0x13            ; BIOS interrupt.
                      ;  <!----here
  pop dx
  ret

load_software:
  mov bx, 0x7e0
  mov es, bx
  xor bx, bx
  mov dh, 66
  mov dl, [BOOT_DRIVE]
  call disk_load

我在 VirtualBox 5.2.8 中进行了所有操作,并且运行良好。将所有内容移至具有 VirtualBox 6.0.14 的第二台机器上,实验失败。中断结束并设置 CF,表示失败。

阅读Boot loader doesn't jump to kernel code 中的出色答案我已经修复了可能导致问题的未指定 DS 值的潜在问题。如果我在 int 0x13 调用之前停止并转储 CPU 状态,我会在两个 VirtualBox 上获得一致的状态:

00:00:05.930849 eax=00000280 ebx=00007e00 ecx=00000002 edx=00000000 esi=00000000 edi=0000fff0

00:00:05.930857 eip=00007cc8 esp=00007bf9 ebp=00007bff iopl=0 nv up ei pl nz na po nc

00:00:05.930864 cs=0000 base=0000000000000000 limit=0000ffff flags=0000009b dr0=00000000 dr1=00000000

00:00:05.930877 ds=0000 base=0000000000000000 limit=0000ffff flags=00000093 dr2=00000000 dr3=00000000

00:00:05.930884 es=0000 base=0000000000000000 limit=0000ffff 标志=00000093 dr4=00000000 dr5=00000000

00:00:05.930891 fs=0000 base=0000000000000000 limit=0000ffff 标志=00000093 dr6=ffff0ff0 dr7=00000400

00:00:05.930898 gs=0000 base=0000000000000000 limit=0000ffff flags=00000093 cr0=00000010 cr2=00000000

00:00:05.930904 ss=0000 base=0000000000000000 limit=0000ffff flags=00000093 cr3=00000000 cr4=00000000

00:00:05.930910 gdtr=00000000000fe89f:0047 idtr=0000000000000000:ffff eflags=00200246

解析所有值我只能得出结论,中断的所有输入参数都设置正确。转储后的状态有CF设置和错误代码:

00:00:08.984877 eax=00000900 ebx=00000000 ecx=00000002 edx=00000000 esi=00000000 edi=0000fff0

00:00:08.984887 eip=00007cca esp=00007bf9 ebp=00007bff iopl=0 nv up ei pl nz na po cy

00:00:08.984896 cs=0000 base=0000000000000000 limit=0000ffff flags=0000009b dr0=00000000 dr1=00000000

00:00:08.984909 ds=0000 base=0000000000000000 limit=0000ffff flags=00000093 dr2=00000000 dr3=00000000

00:00:08.984917 es=07e0 base=0000000000007e00 limit=0000ffff 标志=00000093 dr4=00000000 dr5=00000000

00:00:08.984925 fs=0000 base=0000000000000000 limit=0000ffff 标志=00000093 dr6=ffff0ff0 dr7=00000400

00:00:08.984934 gs=0000 base=0000000000000000 limit=0000ffff flags=00000093 cr0=00000010 cr2=00000000

00:00:08.984941 ss=0000 base=0000000000000000 limit=0000ffff 标志=00000093 cr3=00000000 cr4=00000000

00:00:08.984948 gdtr=00000000000fe89f:0047 idtr=0000000000000000:ffff eflags=00200247

注意错误代码 AH=9 数据边界错误(尝试 DMA 跨越 64K 边界或 >80h 扇区) 将我带到 https://en.wikipedia.org/wiki/INT_13H 作出此声明的地方:

Buffer 的寻址应该保证完整的缓冲区在给定的段内,即 ( BX + size_of_buffer )

这可以解释我最初的问题,所以我做了另一个修复来设置es=0x7e0bx=0。这是上面显示的代码的状态。但是,即使此代码也因上述状态而失败。

进一步测试表明,我可以成功读取多达 65 个扇区,但 66 个或更多扇区失败。作为一个奇怪的数字,我计算了第 65 个扇区的结尾:0xffff。所以问题变得有点混乱了。

问题:

我的es=0x7e0bx=0 解决方案是否应该避免段交叉(据我所知)?

如果是这样,为什么它似乎是跨越线性地址的问题?

或者可以跨段,但不能跨线性地址中的 0xffff 标记?

感谢您的帮助。

【问题讨论】:

文档似乎不清楚。 BIOS 版本也可能有问题。无论如何,您可能应该在循环中逐个读取扇区以避免出现问题。 您可以按照 Jester 的建议进行操作。一次循环一个扇区并将它们读入内存。您可以使用逻辑块寻址来执行此操作,并将 LBA 转换为 CHS(气缸盖扇区)。我在这个答案中讨论了这种转换:***.com/a/45495410/3857942。在这个答案中:***.com/a/54894586/3857942 我提供了一个 lba_to_chs 函数,我每次循环 1 个扇区以作为引导加载程序的一部分从磁盘读取内核。 如果您一次读取 1 个扇区,您永远不必担心多磁道读取和跨越柱面边界。 【参考方案1】:

问题在于您“尝试 DMA 跨越 64K 边界”。事实上,您的目标缓冲区从物理地址 0x07E00 跨越到 0x17E00,它在 0x10000 处跨越了 64K 边界。 (这与段边界无关,因此您使用什么段和偏移值到达 0x07E00 物理地址无关紧要。)

之所以如此,是因为最初的 IBM PC 的设计成本很低。他们没有使用带有 16 位总线和 16 位支持芯片的 16 位 8086,而是使用更便宜的带有 8 位总线的 16 位 8088 CPU,可以与更便宜的 8 位支持芯片一起使用。特别是the DMA controller they chose 只能寻址 64K 内存,这是 DMA 控制器设计用于的 8 位 CPU 的典型寻址限制。他们通过一个单独的芯片使其工作,该芯片提供 DMA 地址的高四位,允许对 8088 的全部 1024K 地址空间进行寻址。 (在 IBM PC AT 上,这被扩展为提供高 8 位,以便可以访问整个 80286 16M 地址空间。)

不幸的是,这意味着 DMA 地址的高四位在 DMA 操作期间是固定的,这有效地将 1024K 地址空间划分为 16 个 64K 页。任何尝试执行从一个页面到下一个页面、跨越 64K 边界的 DMA 操作的尝试都将绕到页面的开头。虽然 BIOS 可以通过将读取分成两个单独的读取来解决这个问题,每个 64K 页一个,它只是返回一个错误。

请注意,这通常只是软盘访问的问题,因为硬盘接口通常不使用 IBM PC DMA 控制器。

由于在跨越磁道和柱面边界时也存在这样的潜在边界问题,解决此问题的最佳方法是一次只读取一个扇区,就像 Jester 和 Michael Petch 在 cmets 中所说的那样。作为一项简单的工作,您可以移动缓冲区,使其从物理地址 0x10000 开始,但您可能会发现在现实世界的系统中,您只能读取轨道上的剩余扇区。

【讨论】:

谢谢!这解释了我观察到的很多东西。使用上面的建议,我结束了循环所需的扇区并单独加载每个扇区。

以上是关于BIOS INT 13H 问题(从驱动器读取扇区)的主要内容,如果未能解决你的问题,请参考以下文章

读取磁盘:CHS方式

BIOS int 13H,AH=02(读取软盘)和堆栈段

使用 Int 13H 读取磁盘参数

从没有 BIOS 服务的 INT 13 的软盘加载 OS 映像

如何在 16 位 MASM 中使用 int 13h 格式化可移动设备(闪存驱动器)?

从物理驱动器读取扇区