第三天 能读取软盘了!
先介绍一个BOIS磁盘服务中断:INT 13H。因为我们只需要读软盘数据,所以只重点介绍2号读扇区子功能。
参数:
AH 功能号=02H
AL 需要读取的扇区数
CH 起始磁道数
CL 起始扇区数
DH 盘面号
DL 驱动器号
ES:BX 目标地址,读取出数据保存到内存的地址。
返回值:当CF=0时,操作成功。AH=0,AL为已读取的扇区数。操作失败时AH为错误码。
每个软盘都有正反两面,所以盘面号只能为0或1,每一面又分为80个磁道,就像80个同心圆一样,每个磁道又分为18个扇区。如图所示!
2(盘面)* 80(磁道)* 18(扇区)* 512(每扇区字节数)=1474560 Byte ≈1.44M Byte
这就是为什么总叫1.44M软盘的原因。
[课外知识]
软盘映像文件的数据就是对应软盘的实际位置。
数据组织顺序为:512字节→ 扇区,18扇区→ 盘面,2盘面→ 1磁道。
即理解为:每512个字节组成一个扇区,每个磁道有2个盘面,1个磁道每个盘面上有18个扇区,一个软盘共有80个磁道。有一点大家要注意,在映像文件中,每个磁道数据是以36个扇区数据来组织的,并不是一般理解的一个盘面的数据完了后再是另一个盘面的数据,而是每个磁道都以正反的扇区数据在一起。
读取软盘上的文件数据流程如下:
1、 计算目录区所在扇区号;
2、 读取目录区的一个扇区数据,如果已经到目录区尽头则跳转至10;
3、 在读取的数据中查找是否有目标文件名,如果没有则返回流程2;
4、 保存目标文件名的开始簇号,并计算数据区的开始扇区号;
5、 转换簇号为总扇区号;
6、 读取该扇区数据到指定内存,读取错误则跳转至10;
7、 读取FAT区的二个扇区数据,如果已经到FAT区尽头则跳转至10;
8、 取下一簇号,如果没有结束则跳转至5;
9、 结束;
10、 打印错误信息并结束;
因为我们要经常用到读取某个扇区的数据到指定内存,所以先写一个读取软盘扇区数据的子程序,以方便我们调用。
;##########################################################################
; 函数名: ReadSector
; 参数 : ax 需读取开始位置的总扇区号,这时的参数的总扇区号从0开始编号,以便统一。
; cl 需读取的扇区数
; es:bx 读取取的数据保存位置
; 返回值: 无
; 可能改变的寄存器有: AX , BX , CX , DX
; 用到的变量: [BPB_SecPerTrk] 每磁道扇区数,
; [BS_DrvNum] 当前驱动器号
;----------------------------------------------------------------------------
; 作用: 从第 ax 个扇区开始, 读取 cl 个扇区数据到内存 es:bx
;----------------------------------------------------------------------------
; -----------------------------------------------------------------------
; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 磁道号, 起始扇区, 盘面号)
; -----------------------------------------------------------------------
; 设总扇区号为 x
; ┌ 磁道号 = y >> 1
; x ┌ 商 y ┤
; -------------- => ┤ └ 盘面号 = y & 1
; 每磁道扇区数 │
; └ 余 z => 起始扇区号 = z + 1
; -----------------------------------------------------------------------
ReadSector:
push bp ; 保存指针
mov bp, sp ;
sub sp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2],即为局部变量。
mov byte [bp-2], cl ; 保存需读取的扇区数,后面计算时需要用到CL寄存器。
;-------------------------------
push bx ; 保存 bx,下面的计算会占用BX寄存号。
mov bl, [BPB_SecPerTrk] ; bl: 除数 , 每磁道扇区数
div bl ; y 在 al 中, z 在 ah 中
; 此时AX为开始的扇区号,bl为每磁道扇区数,除出来的结果商:al 余数:ah
inc ah ; z++, 余数加1 ,得到在磁道中的开始扇区号,因为中断扇区号是从1开始编号。
mov cl, ah ; cl <- 起始扇区号, 将在磁道中开始扇区号保存到 cl 中
; 下面计算磁道号与盘面号
mov dh, al ; dh <- y 将余数存入 dh .
shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) 右移一位,等于除以2
mov ch, al ; ch <- 磁道号
and dh, 1 ; dh & 1 = 盘面号
pop bx ; 恢复 bx
;-------------------------------
; 至此, "磁道号, 起始扇区, 盘面号" 全部得到 - 磁道号: ch 盘面号: dh 开始扇区号: cl
mov dl, [BS_DrvNum] ; 取出启动的驱动器号,(0 表示 A 盘)
.GoOnReading:
mov ah, 2 ; 读取功能
mov al, byte [bp-2] ; 读 al 个扇区
int 13h ; 调用中断,进行磁盘扇区读取.
jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止
;--------------------------------------------------------------------
add sp, 2 ; 释放局部变量
pop bp ; 恢复环境并退出
ret
;==================================================================
上面这段子程序的参数要比服务中断的参数少,而且更好理解了。
代码中BPB_SecPerTrk、BS_DrvNum均在FAT头数据中有定义,BPB_SecPerTrk、BS_DrvNum为数据标号,标号加方括号为引号该标号所代码的内存中储存的数据。
子程序先保存必须数据,然后计算开始扇区号、磁道与盘面号,再调用中断,最后恢复环境数据并退出。
参数总扇区号(从0开始编号)除以每磁道扇区数(一般为18)所得余数为该磁道的偏移扇区数;
偏移扇区数加一得到中断所需的开始扇区号;
除出来的商再除以2所得商为磁道号,余数为盘面号。
提示: .GoOnReading:——为子标号,在标号前加小数点,代表该标号为上一个正常标号的子标号,一般用于子程序中的标号,方便局部引用,各子程序之间的子标号可以重复。关于更多的相关知识请查看NASM使用文档。
今天能把上面这段代码看懂就可以了。