前几节我们介绍了FAT12文件系统,制作了虚拟软盘文件a.img,并在Qt Creater中进行了文件内容的读取实验。那些读取都是使用外部的程序实现的,实际应用中,我们需要用主引导程序来实现文件的读写,主引导程序存在于主引导扇区MBR中,也就是说程序和文件是存在一张盘上的,而且这些主引导程序需要使用汇编语言实现。接下来,我们就来实现具有读取功能的主引导程序。
为了验证文件读取的正确性,我们需要在主引导程序中先实现一个字符串打印函数。BIOS已经将中断向量写到了内存的指定位置处,这其中就有能实现字符串打印的函数,我们需要做的就是配置一些参数,需要配置的参数和配置步骤如下:
1、指定打印参数(AX=0x1301 , BX = 0x0007)
2、指定字符串的内存地址(ES:BP = 字符串地址)
3、指定字符串的长度(CX = 字符串长度)
4、中断调用(int 0x10)
示例如下:
需要用到的汇编语言的知识点如下:
字符串打印的汇编程序如下所示
org 0x7c00
jmp short start
nop
define:
BaseOfStack equ 0x7c00
header:
BS_OEMName db "D.T.Soft"
BPB_BytsPerSec dw 512
BPB_SecPerClus db 1
BPB_RsvdSecCnt dw 1
BPB_NumFATs db 2
BPB_RootEntCnt dw 224
BPB_TotSec16 dw 2880
BPB_Media db 0xF0
BPB_FATSz16 dw 9
BPB_SecPerTrk dw 18
BPB_NumHeads dw 2
BPB_HiddSec dd 0
BPB_TotSec32 dd 0
BS_DrvNum db 0
BS_Reserved1 db 0
BS_BootSig db 0x29
BS_VolID dd 0
BS_VolLab db "D.T.OS-0.01"
BS_FileSysType db "FAT12 "
start:
mov ax, cs
mov ss, ax
mov ds, ax
mov es, ax
mov sp, BaseOfStack
mov ax, MsgStr
mov bp, ax
mov ax, ds
mov es, ax
mov cx, 6
call Print
last:
hlt
jmp last
; es:bp --> string address
; cx --> string length
Print:
mov ax, 0x1301
mov bx, 0x0007
int 0x10
ret
MsgStr db "Hello, DTOS!"
MsgLen equ ($-MsgStr)
Buf:
times 510-($-$$) db 0x00
db 0x55, 0xaa
我们的虚拟软盘文件data.img已经格式化好了,现在要修改的只是第一个扇区即主引导扇区,所以我们要按照FAT12的格式来填充第一个扇区,因此,上面的程序中,第9到27行是FAT12的引导扇区的信息,它前面还有三个字节,其中前两个字节是跳转指令jmp short start,第三个字节为空指令nop。从start开始是我们的可执行程序,BaseOfStack是定义的栈底地址。
汇编程序我们需要编译成二进制可执行程序,然后写入a.img虚拟软盘的第一个扇区中,每次都执行这个过程很麻烦,我们将这个过程写makefile,如下所示:
.PHONY : all clean rebuild
SRC := boot.asm
OUT := boot.bin
IMG := data.img
RM := rm -fr
all : $(OUT) $(IMG)
dd if=$(OUT) of=$(IMG) bs=512 count=1 conv=notrunc
@echo "Success!"
$(IMG) :
bximage $@ -q -fd -size=1.44
$(OUT) : $(SRC)
nasm $^ -o $@
clean :
$(RM) $(IMG) $(OUT)
rebuild :
@$(MAKE) clean
@$(MAKE) all
接下来,我们开始读取文件,假如我们知道了数据所在的扇区,那么怎么将它读出来呢?先来看一下软盘的构造,如下所示:
3.5寸软盘的特性如下:
根据逻辑扇区号计算磁头号、柱面号、物理扇区号的方法如下:
软盘复位和软驱数据读取的参数如下:
整体的读取流程如下:
需要用到的汇编知识点如下:
整体程序的汇编代码如下
org 0x7c00 # 将本程序加载到0x7c00这段物理地址空间上去
define:
BaseOfStack equ 0x7c00 # 设置对应的栈底指针
jmp short start # 主引导程序的前三个字节需要实现主程序的跳转,这条指令占2个字节,nop占一个字节
nop
header: # 主引导程序的文件系统信息
BS_OEMName db "D.T.Soft"
BPB_BytsPerSec dw 512
BPB_SecPerClus db 1
BPB_RsvdSecCnt dw 1
BPB_NumFATs db 2
BPB_RootEntCnt dw 224
BPB_TotSec16 dw 2880
BPB_Media db 0xF0
BPB_FATSz16 dw 9
BPB_SecPerTrk dw 18
BPB_NumHeads dw 2
BPB_HiddSec dd 0
BPB_TotSec32 dd 0
BS_DrvNum db 0
BS_Reserved1 db 0
BS_BootSig db 0x29
BS_VolID dd 0
BS_VolLab db "D.T.OS-0.01"
BS_FileSysType db "FAT12 "
start: # 主程序开始
mov ax, cs # 初始化各个段寄存器为0,sp寄存器为主引导程序的加载地址,向低地址增长
mov ss, ax
mov ds, ax
mov es, ax
mov sp, BaseOfStack
mov ax, 34 # 参数:逻辑扇区的编号
mov cx, 1 # 参数:读取扇区数
mov bx, Buf # 参数:指定读取到的内存地址
call ReadSection # 调用读取函数
mov bp, Buf # 参数:指定需要打印字符串的起始地址
mov cx, 27 # 参数:指定需要打印字符串的长度
call Print # 调用打印函数
jmp last
last:
hlt # 让cpu暂停执行
jmp last
;es:bp --> string address
;cx --> string length
Print:
mov ax, 0x1301 # 这两行指定打印参数
mov bx, 0x007
int 0x10 # 打印所对应的BIOS中断号
ret
;no parameter
ResetFloppy:
push ax # 将对应的寄存器入栈
push dx
mov ah, 0x00
mov dl, [BS_DrvNum] # 驱动器号
int 0x13
pop dx # 恢复对应寄存器入栈前的值
pop ax
ret
; ax --> logic sector number
; cx --> number of sector
; es:bx --> target address
ReadSection:
push bx
push cx
push dx
push ax
call ResetFloppy # 软驱复位
push bx
push cx
mov bl, [BPB_SecPerTrk]
div bl # 将逻辑扇区号除以柱面扇区数
mov cl, ah
add cl, 1 # cl的值为起始扇区号
mov ch, al
shr ch, 1 # 右移指令
mov dh, al
and dh, 1 # 按位与指令,dh的值为磁头号
mov dl, [BS_DrvNum] # dl的值为驱动器号
pop ax # 将cx寄存器出栈,值赋给ax寄存器,ax寄存器的值为要读取的扇区长度
pop bx
mov ah, 0x02
read:
int 0x13 # BIOS读取数据对应的中断号
jc read # 如果读取失败,则继续读取
pop ax
pop dx
pop cx
pop bx
ret
MsgStr db "Hello, DTOS!" # 定义字符串
MsgLen equ ($-MsgStr) # 定义字符串长度,$代表当前指令的起始地址,equ不占用存储空间
Buf:
times 510-($-$$) db 0x00 # $$表示程序的起始地址,从当前程序的起始地址开始,不足510字节的内存空间补0
db 0x55, 0xaa # 主引导程序的结束标志,最后两个字节为0x55aa