在《自己动手写操作系统》的第四章中作者给出了这个函数。作者说这个函数的输入是扇区号,输出是其对应的fat项的值。我觉得说函数的输入是扇区号让人会产生疑惑,说函数的输入是Sector在FAT中的序号更恰当,在源代码中作者也是这么注释的。还有就是函数中有区别对待ax是奇数还是偶数。ax的值是Sector在FAT中的序号。这个函数的目的是根据这个序号,得到该序号所对应的fat项的值。一个fat项占1.5个字节。Sector在FAT中的序号是从0开始的。
当Sector在FAT中的序号是偶数时,假设为0(其实序号0在FAT是不会用的,但为了说明简单,其它偶数也是一样的)。下面给出一张图,图中fat0表示序号为0的fat项,ax是16位的,它存放了两个字节,而fat值是1.5个字节,即12位的,所以我们要把其它的四位置为0,当序号是偶数时,其它四位是ax中的高四位(源代码中是and ax,0fffh).
图中被红线划掉的部分表示不需要的4位。
当Sector在FAT中的序号为奇数时,假设为1(其实序号1在FAT是不会用的,但为了说明简单,其它奇数也是一样的)。下面给出一张图,图中fat1表示序号为1的fat项,ax是16位的,它存放了两个字节,而fat值是1.5个字节,即12位的,所以我们要把其它的四位置为0,当序号是奇数时,其它四位是ax中的低四位(源代码中是shr ax,4).
图中被红线划掉的部分表示不需要的4位。
下面是该函数的源代码
;----------------------------------------------------------------------------
; 函数名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
GetFATEntry:
push es
push bx
push ax
mov ax, BaseOfLoader ; ┓
sub ax, 0100h ; ┣ 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
mov es, ax ; ┛
pop ax
mov byte [bOdd], 0
mov bx, 3
mul bx ; dx:ax = ax * 3
mov bx, 2
div bx ; dx:ax / 2 ==> ax <- 商, dx <- 余数
cmp dx, 0
jz LABEL_EVEN
mov byte [bOdd], 1 ;表示ax为奇数
LABEL_EVEN:;偶数
xor dx, dx ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
mov bx, [BPB_BytsPerSec]
div bx ; dx:ax / BPB_BytsPerSec ==> ax <- 商 (FATEntry 所在的扇区相对于 FAT 来说的扇区号)
; dx <- 余数 (FATEntry 在扇区内的偏移)。
push dx
mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h
add ax, SectorNoOfFAT1 ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号
mov cl, 2
call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区
pop dx
add bx, dx
mov ax, [es:bx]
cmp byte [bOdd], 1 ;比较是否为奇数,如果不是则转到LABEL_EVEN_2
jnz LABEL_EVEN_2
shr ax, 4 ;为奇数时
LABEL_EVEN_2: ;为偶数时
and ax, 0FFFh
LABEL_GET_FAT_ENRY_OK:
pop bx
pop es
ret
;----------------------------------------------------------------------------