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

Problem with BIOS INT 13H (Read Sectors From Drive)

描述:

为了创建一个简单的独立程序,我在第一个扇区中编写了一个简单的引导加载程序。它的目的是将程序加载到内存中。为此,我使用 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 设置结束,表示失败。

阅读 中的优秀答案 我已经修复了可能导致问题的未指定 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 flags=00000093} dr4=00000000 dr5=00000000

00:00:05.930891 fs={0000 base=0000000000000000 limit=0000ffff flags=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 flags=00000093} dr4=00000000 dr5=00000000

00:00:08.984925 fs={0000 base=0000000000000000 limit=0000ffff flags=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 flags=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 这条语句是制作:

Addressing of Buffer should guarantee that the complete buffer is inside the given segment, i.e. ( BX + size_of_buffer ) <= 10000h.

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

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

问题:

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

如果是,为什么好像是跨越线性地址的问题?

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

感谢您的帮助。

问题是您 "attempted DMA across a 64K boundary"。事实上,您的目标缓冲区从物理地址 0x07E00 跨越到 0x17E00,在 0x10000 处跨越 64K 边界。 (这与段边界无关,因此使用什么段和偏移值到达 0x07E00 物理地址并不重要。)

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

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

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

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