TA的每日心情 | 奋斗 2017-1-16 20:03 |
---|
签到天数: 116 天 [LV.6]常住居民II
|
本帖最后由 冰琥珀 于 2015-10-19 13:38 编辑
上一个帖子:http://www.ihonker.org/thread-6625-1-1.html
上个帖子我们讲述了如何搭建开发环境,同时也写了个简单的"Hello, OS",这次我们来学学引导扇区的编写,确切的说应该是MBR(主引导记录)的编写,它是磁盘的第一个扇区,当我们按下电源键,BIOS完成一些初始化之后,就将MBR读取到0x7c00处,并将控制权转交给MBR来执行,由MBR来完成内核的加载。由于MBR最多只有512个字节(1个扇区),所以加载内核的工作不可能在MBR中完成,那么MBR又是干啥的呢。
其实MBR只是在磁盘中找到Loader并将其读入内存,然后把控制权转交给Loader,让它来完成接下来的工作。所以MBR的工作就只是在磁盘中找到Loader。
因为我们现在还没有文件系统,所以一切都只是用的软盘,后期我们有自己的文件系统了,我们会把软盘改为硬盘。在我们接下来的实验中,软盘都格式化为FAT12,所以我们要对FAT12文件系统有所了解。先来看看FAT12文件系统格式
从上图可以看到,引导扇区之后就是FAT1和FAT2(FAT2是FAT1的备份),每个FAT表占9个扇区,FAT后紧跟着的是根目录区,大小在引导扇区的BPB中有设定,根目录区之后是数据区。我们这次要找到Loader,就要从根目录入手,先在根目录中找到Loader的文件名,然后在根据根目录结构中的簇号到FAT表中去找到相应的项,再根据FAT来读取Loader的数据,并将其加载到内存中。说了这么多,我们先看引导扇区的结构,如下图
从上图可以看出,在引导扇区开始的3个字节是一个跳转指令,然后是OEM字符串,它必须是一个8字节长的字符串标,标识了格式化此磁盘的操作系统的名称和版本号
接下来是每扇区字节数,一般情况下,这个值是512
接着是每簇扇区数,在这里我们填1
接着是保留扇区数,这里默认也为1
接着是FAT表个数,这里有两个,分别是FAT1和FAT2,所以这个值为2
然后是最大根目录数,用来记录根目录区有多少条目录项
再下来是逻辑扇区总数,用来记录整个磁盘的扇区数
接着是磁盘类型标识符,使用0f0h表示3.5寸高密码软盘,用0f8h来表示硬盘
然后是每FAT扇区数,记录每个FAT表中有多少个扇区
接着是每磁道扇区数,记录每个磁道中有多少个扇区
然后是双字节长的磁头数,磁头数指的是磁盘面数,每面都有一个磁头,软盘都是2面的,所以在FAT12格式下此字段固定为2
接下来是的位于偏移28处类型为双字(4B)长的隐藏扇区数,指的在引导扇区之前的隐藏扇区,在FAT12格式上此字段默认为0,即不隐藏任何扇区
偏移32处的是类型为双字(4B)长的逻辑扇区总数,如果此分区或磁盘的逻辑扇区总数大于65536则用这个字段来表示逻辑扇区总数,否则设置此字段为0
偏移36处的是物理驱动器号,类型是字节长,它与BIOS物理驱动器相关,在磁盘中断Int13h相关的操作中使用,第一个软盘驱动器设置为0,第一个硬盘驱动器设置为80h,第二个硬盘驱动器设置为81h,以此类推
位于偏移37处的字节没有使用,保留并设置为0
位于偏移38处的是扩展引导标识,类型是字节,操作系统用它来识别引导信息,值可以是28h或29h
接下来的是位于偏移39处的卷标号,类型是双字(4B)长,在格式化磁盘时所产生的一个随机序号,有助于区分磁盘,可以为0
然后是位于偏移43处的卷标,长度必须是11字节长(不足以空格20h填充)
最后是位于偏移54处的是长度为8字节的文件系统类型标识符,不足8字节则以空格20h来填充
接下来就是引导代码了,最后的两个字节要填入0xaa55,表示引导扇区结束
引导记录结构介绍了之后,我们来看FAT表,相比之下,FAT表的结构就简单的多了,一句话描述就是每个FAT项(FATEntry)长度位12Bit,即一个半字节,FAT表中每3个字节存放了2个FATEntry,这里也不再贴出它的结构图了,下面看根目录的结构,如下图
可以看到,在根目录中,每个目录项占32个字节,其中,前11个字节是文件名(文件名占8字节,拓展名占3字节),接下来是文件属性(占1个字节),然后是保留位(占10个字节),然后是写入时间和日期(各占2个字节,总共占4个字节),接下来是文件在FAT表中的簇号(占2字节),然后是文件大小(占4字节),总共32个字节。在上面的这些项中,我们不需要关心那么多,只要关注文件名和文件在FAT表中的簇号即可。首先通过文件名的对比来找到相应的目录项,其次再从目录项中读取处文件在FAT表中的簇号,然后根据这个簇号在FAT表中找出相应的项,再根据其来读取文件数据,现在我们就先写代码实现在根目录区中找到Loader,代码如下
[AppleScript] 纯文本查看 复制代码 org 0x7c00
jmp LABEL_START ;跳转
nop
BS_OEMName db 'FreeDos ' ;OEM string,8个字节
BPB_BytePerSec dw 512 ;每扇区字节数
BPB_SecPerClus db 1 ;每簇扇区数
BPB_RsvdSecCnt dw 1 ;保留扇区数
BPB_NumFATs db 2 ;FAT表数
BPB_RootEntCnt dw 224 ;根目录文件数最大值
BPB_TotSec16 dw 2880 ;逻辑扇区总数
BPB_Media db 0xf0 ;媒体描述符
BPB_FATsz16 dw 9 ;每FAT扇区数
BPB_SecPerTrk dw 18 ;每磁道扇区数
BPB_NumHeads dw 2 ;磁头数
BPB_HideSec dd 0 ;隐藏扇区数
BPB_TotSec32 dd 0 ;如果BPB_TotSec16为0,这里记录扇区总数
BS_DrvNum db 0 ;中断13的驱动器号
BS_Reserved1 db 0 ;未使用
BS_BootSig db 0x29 ;扩展引导标记
BS_VolID dd 0 ;卷序列号
BS_VolLab db 'Orange OS ' ;卷标,11个字节
BS_FileSysType db 'FAT12 ' ;文件系统类型
LABEL_START:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack
; 清屏
mov ax, 0600h ; AH = 6, AL = 0h
mov bx, 0700h ; 黑底白字(BL = 07h)
mov cx, 0 ; 左上角: (0, 0)
mov dx, 0184fh ; 右下角: (80, 50)
int 10h ; int 10h
;显示Booting
mov dh, 0
call DispStr
;软驱复位
xor ah, ah
xor dl, dl
int 0x13
mov word [wRootCurSecNum], RootDirSecNum
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootSecCnt], 0
jz LABEL_SEARCH_IN_ROOT_DIR_END ;这里跳转表示整个根目录都搜索完毕,没有找到Loader.bin
dec word [wRootSecCnt]
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
mov ax, word [wRootCurSecNum]
mov cl, 1
call ReadSector ;读取扇区数据
mov di, OffsetOfLoader
cld
mov dl, 0x10 ;每个扇区中目录个数为16
LABEL_CMP_NEXT_FILE_NAME:
mov si, LoaderFileName
mov cl, 0x0b ;文件名长度为11个字节
LABEL_CMP_FILE_NAME:
test cl, cl
jz LABEL_FOUND_LOADER
dec cl
lodsb
test al, al
jz LABEL_CMP_NAME_END ;文件名比较完毕
cmp al, [es:di]
jz LABEL_GO_ON ;从根目录中读取到的文件名字符和目标文件名字符相等,则继续比较下一个字符
jmp LABEL_CMP_NAME_END
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILE_NAME
LABEL_CMP_NAME_END:
dec dl
jz LABEL_GO_TO_NEXT_SECTOR ;16组目录都比较完毕,则读取下一个扇区
and di, 0xffe0 ;这里把di指针重置到当前目录的起始处
add di, 0x20 ;指针指向下一组目录(每组目录长度为32个字节)
jmp LABEL_CMP_NEXT_FILE_NAME ;比较下一组文件名
LABEL_GO_TO_NEXT_SECTOR:
inc word [wRootCurSecNum]
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
LABEL_SEARCH_IN_ROOT_DIR_END: ;没找到LOADER.BIN
mov dh, 2
call DispStr
jmp $
LABEL_FOUND_LOADER: ;找到LOADER.BIN文件
jmp $
ReadSector:
push bp
mov bp, sp
sub sp, 2
mov byte [bp - 2], cl
push bx
mov bl, [BPB_SecPerTrk]
div bl ;计算出当前扇区所处的磁道
inc ah ;除得的商放在al,余数放在ah(al中放的是磁道号,ah中放的是扇区号)
mov cl, ah
mov dh, al
shr al, 1 ;磁道号除以2得到磁头号
mov ch, al
and ch, 1
pop bx
mov dl, [BS_DrvNum]
.GoOnReading:
mov ah, 2 ;读扇区
mov al, byte [bp - 2] ;要读的扇区数
int 0x13
jc .GoOnReading ;读扇区失败则继续读
add sp, 2
pop bp,
ret
DispStr:
mov ax, MessageLength
mul dh
add ax, BootMessage
mov bp, ax
mov ax, ds
mov es, ax
mov cx, MessageLength
mov ax, 0x1301
mov bx, 0x0007
mov dl, 0
int 0x10
ret
BaseOfStack equ 0x7c00
BaseOfLoader equ 0x9000
OffsetOfLoader equ 0x100
RootDirSecNum equ 19 ;根目录起始扇区号=FAT扇区数(2个) + 引导扇区数
wRootSecCnt dw 14 ;根目录所占的扇区数
wRootCurSecNum dw 0
wFileNameCnt db 0
LoaderFileName db 'LOADER BIN', 0 ;LOADER.BIN (文件名)
MessageLength equ 9
BootMessage: db "Booting "
Message1: db "Ready. "
Message2: db "No LOADER"
times 510-($-$$) db 0
dw 0xaa55
代码看着有点长,我们一点一点来分析。这段代码因为是写在引导扇区的,所以要按照引导扇区的格式来写。在上面我们已经看到了引导扇区最开始的3个字节是一个跳转指令,所以在程序的开头我们写入
jmp LABEL_START
这里只占两个字节,剩余的一个字节用nop填充,接下来就是一堆的数据,具体的上面有介绍,我们直接看跳转到的代码
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack
; 清屏
mov ax, 0600h ; AH = 6, AL = 0h
mov bx, 0700h ; 黑底白字(BL = 07h)
mov cx, 0 ; 左上角: (0, 0)
mov dx, 0184fh ; 右下角: (80, 50)
int 10h ; int 10h
;显示Booting
mov dh, 0
call DispStr
;软驱复位
xor ah, ah
xor dl, dl
int 0x13
这里就是一些初始化的工作,设置段寄存器,设置栈,清屏,显示字符串,复位软盘等,真正的算法在下面。我们先来整理思路,要在根目录中找到Loader,那个我们就先要确定根目录的起始偏移,前面我们有介绍过,根目录位于FAT表之后,每个FAT表占9个扇区,两个FAT表就占18个扇区,再加上一个引导扇区,所以根目录的起始扇区是19,既然要在根目录中查找,那就要有个范围,我们已经得到了起始值为19,根目录总的扇区数=根目录文件数最大值 * 32 / 512,这里得出的结果是14,所以我们就从19号扇区开始找,最大找满14个扇区,代码如下
mov word [wRootCurSecNum], RootDirSecNum ;RootDirSecNum存放的是根目录的起始扇区号
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootSecCnt], 0 ;wRootSecCnt里存放的是根目录总的扇区数,为0表示根目录所有的扇区都已经查找了
jz LABEL_SEARCH_IN_ROOT_DIR_END ;这里跳转表示整个根目录都搜索完毕,没有找到Loader.bin
上面的代码只是检测根目录是否已经查找完毕,如果查找完毕就跳转到后面显示没找到,没找到后的代码如下
LABEL_SEARCH_IN_ROOT_DIR_END: ;没找到LOADER.BIN
mov dh, 2 ;2表示显示第三组字符串(BootMessage开始的3组字符串),即No LOADER
call DispStr
jmp $ :程序停在这
如果没找到,我们就让程序先停在这,下面我们看根目录查找没有结束的情况,如果没有结束,我们就要读取数据,一次读取一个扇区,代码如下
dec word [wRootSecCnt]
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
mov ax, word [wRootCurSecNum]
mov cl, 1
call ReadSector ;读取扇区数据
上面先是进行一些设置,比如要读取哪个扇区,读取的扇区数,读取到哪个位置等。数据读取出来了之后,我们就要查找Loader的文件名了,这里我们要找的文件名是"LOADER BIN",查找的方法就是取出一个目录项,然后一个字节一个字节的比较文件名,在上面我们介绍过目录项的结构,开始的11个字节就是文件名,所以我们只比较11个字节,如果全部相等,表示找到,否则取出下一个目录项进行比较,代码如下
mov di, OffsetOfLoader
cld
mov dl, 0x10 ;每个扇区中目录个数为16
LABEL_CMP_NEXT_FILE_NAME:
mov si, LoaderFileName
mov cl, 0x0b ;文件名长度为11个字节
LABEL_CMP_FILE_NAME:
test cl, cl
jz LABEL_FOUND_LOADER
dec cl
lodsb
test al, al
jz LABEL_CMP_NAME_END ;文件名比较完毕
cmp al, [es:di]
jz LABEL_GO_ON ;从根目录中读取到的文件名字符和目标文件名字符相等,则继续比较下一个字符
jmp LABEL_CMP_NAME_END
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILE_NAME
[es:di]中存放的是从根目录中读取到的数据,si中存放的是目标文件名,这里用lodsb从si中取出一个字节代al中,然后用个字节和[es:di]中的进行比较,如果相等就比较下一个(jz LABEL_GO_ON),否则就表示这个目录项比较结束,程序跳转到LABEL_CMP_NAME_END,如果全部比较完成后,发现都相等则跳转到LABEL_FOUND_LOADER,找到后我们先让程序停在那,我们现在看如果当前目录项不是我们要找的,又怎么处理,代码如下
LABEL_CMP_NAME_END:
dec dl ;目录项数减1
jz LABEL_GO_TO_NEXT_SECTOR ;16组目录都比较完毕,则读取下一个扇区
and di, 0xffe0 ;这里把di指针重置到当前目录的起始处
add di, 0x20 ;指针指向下一组目录(每组目录长度为32个字节)
jmp LABEL_CMP_NEXT_FILE_NAME ;比较下一组文件名
LABEL_GO_TO_NEXT_SECTOR:
inc word [wRootCurSecNum]
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
如果当前目录项不是我们要找的,那么我们就跳转到下一个,这时dl中记录的当前扇区目录项的总数要减1,如果dl为0,表示当前扇区的所有目录项都查找完毕,那么我们就要去读取下一个扇区(jz LABEL_GO_TO_NEXT_SECTOR),否则就跳到下一个目录项进行比较,依次循环。
到了这里,我们这个帖子的目标也达到了,就是在根目录中查找Loader的文件名,至于找到之后该咋办,或者没找到又该怎么处理,我们下个帖子再作处理。 |
|