汇编中如何迭代所有磁盘扇区?

5
在学习汇编语言编程的过程中,我正在编写一个操作系统。我已经成功地编写了必要的代码,将第二个512字节扇区附加到初始的512字节启动程序中。
%define KBDINT  0x16
%define VIDINT  0x10
%define DISKINT 0x13
%define TTYOUT  0x0E
%define VIDMODE 0x0000
%define NUL 0x00
%define CR  0x0D
%define LF  0x0A
%define START   0x7C00

%macro  PRINT   1
    mov si, %1
    call    print
%endmacro

    bits    16          ; 16 bit real mode
    org START           ; loader start in memory
start:  jmp main

print:  jmp .init
.loop:  mov bx, VIDMODE
    mov ah, TTYOUT
    int VIDINT
    inc si
.init:  mov al, [si]
    cmp al, NUL
    jne .loop
    ret

main:   cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    sti
    PRINT   welcome

    mov ah, NUL
    int DISKINT
    mov al, 0x01        ; sector count
    mov ah, 0x02        ; read function
    mov bx, kernel
    mov cl, 0x02
    mov ch, 0x00        ; cylinder number
    mov dh, 0x00        ; head number
    int DISKINT
    jc  fail
    jmp kernel

fail:   PRINT   failure
;   jmp halt

halt:   PRINT   halting
    cli
    hlt
    PRINT   imprbbl
    jmp halt

    welcome db "moose os", CR, LF, NUL
    failure db "failed disk load", CR, LF, NUL
    halting db "halting", CR, LF, NUL
    imprbbl db "but that's impossible!", CR, LF, NUL

times 0x0200 - ($ - $$) - 2 db 0x00
    end dw 0xAA55

kernel: PRINT   yay
    yay db "kernel", CR, LF, NUL
    jmp halt

times 0xFFFF db 0x00

我用以下命令编译文件:nasm -f bin -o boot.bin boot.asm && qemu boot.bin,然后执行二进制文件。如下图所示:

binary execution

我很好奇磁头和柱面是如何使用的:
  • 磁道是如何遍历的?
  • 在模拟和直接执行之间如何不同?

1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Jester
好的,至少你发布了足够的信息让我们发现它 ;) - Jester
@Jester 我觉得整体问题仍然适用。我已经更新了它。 - motoku
1
请参考此处:http://wiki.osdev.org/ATA_in_x86_RealMode_%28BIOS%29#Converting_LBA_to_CHS 。调用INT13h AH=8 BIOS函数以获取“驱动器几何信息”。然后迭代至最大值。 - zx485
1
最好将jmp halt放在yay db "kernel", CR, LF, NUL之前。现在你执行的是垃圾代码,这是一件危险的事情! - Fifoernik
显示剩余5条评论
1个回答

12

•如何遍历扇区?

使用CHS柱面头扇区表示法来遍历多个扇区,首先需要检索这些参数的实际限制。BIOS在int 13h上有函数08h,它给出了最大值以及我们现在不需要的一些额外信息。

CL中的扇区号范围从1到63。
DH中的磁头号范围从0到255,尽管很少使用255。
CL中的柱面号范围从0到1023。由于这不能存储在一个字节中,因此这个10位数的最高2位存储在CL寄存器的位6和7中!

迭代工作原理

将CHS符号视为一种数字,其中C是最重要的部分,S是最不重要的部分。
要找到磁盘上的下一个扇区,我们从其最不重要的端开始递增这个数字。
如果通过递增S部分而溢出其范围,则将其重置为其最小值(1),并开始递增下一个更重要的部分,即此处的H
如果通过递增H部分而溢出其范围,则将其重置为其最小值(0),并开始递增最重要的部分,即此处的C
如果通过递增C部分而溢出其范围,则将其重置为其最小值(0)。这将导致磁盘上的环绕。如果输入给出了正确的SectorCount,则通常在此时阅读将停止。

; INPUT:  DL=Drive
;         CH=Cylinder
;         DH=Head
;         CL=Sector
;         AX=SectorCount
;         ES:BX=Buffer
; OUTPUT: CF=0 AH       = 0
;              CH,DH,CL = CHS of following sector
;         CF=1 AH       = Error status
;              CH,DH,CL = CHS of problem sector
ReadDiskSectors:
  push    es
  push    di
  push    bp
  mov     bp,sp           ;Local variables:
  push    ax              ;[bp-2]=SectorCount
  push    cx              ;[bp-4]=MaxSector
  push    dx              ;[bp-6]=MaxHead
  push    bx              ;[bp-8]=MaxCylinder

  push    es
  mov     ah,08h
  int     13h             ;ReturnDiskDriveParameters
  pop     es
  jc      NOK
  mov     bx,cx           ;10-bit cylinder info -> BX
  xchg    bl,bh
  shr     bh,6
  xchg    [bp-8],bx       ;Store MaxCylinder and get input BX back
  movzx   dx,dh           ;8-bit head info -> DX
  xchg    [bp-6],dx       ;Store MaxHead and get input DX back
  and     cx,003Fh        ;6-bit sector info -> CX
  xchg    [bp-4],cx       ;Store MaxSector and get input CX back

ReadNext:
  mov     di,5            ;Max 5 tries per sector
ReadAgain:
  mov     ax,0201h        ;Read 1 sector
  int     13h             ;ReadDiskSectors
  jnc     OK
  push    ax              ;Save error status byte in AH
  mov     ah,00h
  int     13h             ;ResetDiskSystem
  pop     ax
  dec     di
  jnz     ReadAgain
  stc
  jmp     NOK
OK:
  dec     word ptr [bp-2] ;SectorCount
  jz      Ready
  call    NextCHS
  mov     ax,es           ;Move buffer 512 bytes up
  add     ax,512/16
  mov     es,ax
  jmp     ReadNext

Ready:
  call    NextCHS         ;Return useful CHS values to support reads
  xor     ah,ah           ; -> CF=0 ... that are longer than memory
NOK:
  mov     sp,bp
  pop     bp
  pop     di
  pop     es
  ret

NextCHS:
  mov     al,cl            ;Calculate the 6-bit sector number
  and     al,00111111b
  cmp     al,[bp-4]        ;MaxSector
  jb      NextSector
  cmp     dh,[bp-6]        ;MaxHead
  jb      NextHead
  mov     ax,cx            ;Calculate the 10-bit cylinder number
  xchg    al,ah
  shr     ah,6
  cmp     ax,[bp-8]        ;MaxCylinder
  jb      NextCylinder
DiskWrap:
  mov     cx,1             ;Wraparound to very first sector on disk
  mov     dh,0
  ret
NextCylinder:
  inc     ax
  shl     ah,6             ;Split 10-bit cylinder number over CL and CH
  xchg    al,ah
  mov     cx,ax
  mov     dh,0
  inc     cl
  ret
NextHead:
  inc     dh
  and     cl,11000000b
NextSector:
  inc     cl
  ret

关于扇区大小的注释

虽然拥有不是512字节长度的扇区是完全可以的,但通常它们都是这个大小。 经过几十年的编程,我从来没有见过任何没有512字节扇区的硬盘。
如果你坚持要支持不同的大小,你可以查看从BIOS函数ReturnDiskDriveParameters中通过ES:DI获得的指针所指向的DisketteParameterTable的第4个字节。

•在仿真和直接执行之间迭代有什么不同?

我想你理解的直接执行是真实的硬件。
对于真实的硬件,BIOS将以CHS符号返回几何形状并且扇区存在…因为它们是真实的!
在仿真下,仿真器将尽力提供这些几何值,但你需要确保足够数量的扇区存在于相关驱动器中。这正是@Jester问你:“在镜像文件中是否有该扇区?”时所说的。你通过使用times 0xFFFF db 0x00扩大镜像文件解决了这个问题。

一些建议

你没有设置栈。由于你将内核加载到引导扇区以上的7C00h,我建议你将SS:SP初始化为0000h:7C00h,以使栈位于引导扇区下方。

main:
  cli
  xor ax, ax
  mov ds, ax
  mov es, ax
  mov ss, ax
  mov sp, 7C00h
  sti
  PRINT   welcome

正如 @Fifoernik 评论所说,你最好将 jmp halt 放在 yay db "kernel", CR, LF, NUL 之前,以防止执行这些数据!

kernel:
  PRINT   yay
  jmp halt
  yay db "kernel", CR, LF, NUL

2
实际上,今天销售的几乎所有硬盘都使用4096字节扇区。尽管如此,它们都假装有512字节扇区,因为几十年来程序员一直认为它们不能是其他长度。 - Ross Ridge
我记得有一台古老的IBM-PC 8英寸驱动器,我相信它是1024字节。从CP/M迁移文件非常方便。 - Michael Petch
2
当然,int 13h/08h将受到支持的硬盘大小的限制。如果您想支持更大的硬盘、光盘、USB闪存驱动器,您可能需要检查是否支持扩展磁盘BIOS功能,然后使用int 13h/42h。 - Michael Petch
1
@MichaelPetch 我记得30年前在工作中看到过那些非常大的软盘,但即使那时它们也不再旋转了。它们曾被用于打孔带驱动的铣床上,是后来数控机床的祖先。 - Sep Roland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接