【参考】 实现X86_64架构下的BootLoader(二)文件系统
https://www.cycycd.com/blog/?p=352
上一章中通过使用Boot程序在屏幕上显示出了“start boot”字符串,如果在这个现有程序上启动Loader原理也不难:要么将Loader直接写在这512B中,统一引导启动;要么单独存放Loader,在Boot中读取Loader程序并写入内存,使用跳转指令去执行Loader程序。但是这样有一些弊端,第一种方法限制了Loader程序的大小(因为整个Boot只能占用1个扇区);第二种方法中,Boot程序需要知道Loader程序存放的起始位置以及Loader程序的大小,这样才能保证每次读取都准确无误,若Loader程序频繁被修改,则需要修改对应的Boot读取程序,这样是非常不利于后续代码的编写的,所以我们需要一个简单的文件系统。
对于容量限制为512B的Boot程序,可以使用简单的FAT12文件系统,该文件系统提供简单的文件搜索,按文件读写等功能,满足我们管理和加载Loader的需要。
FAT12文件系统结构由引导程序、FAT表、根目录区和数据区构成。引导程序即之前的Boot程序,固定占用1个扇区,FAT表分为FAT表1和FAT表2,存储完全相同的内容,冗余的一份作为文件恢复用,一个FAT表占用9个扇区;接下来是根目录系统,根目录系统存储文件的基本信息,占用扇区数由引导程序定义的参数计算得出:(根目录最大文件数*单个目录项大小)/每个扇区字节数;再下来是数据区,存储文件的数据内容,长度不定长。下面分别分析每个结构的功能和作用。
引导程序比较简单,除了Boot中需要的一些标志位之外,还需要在数据开始时标记一些Flag,用来标记硬件和文件系统的一些基本信息,固定格式如下:
名称 | 偏移 | 长度 | 内容 | 软盘参考值 |
BS_jmpBoot | 0 | 3 | jmp LABEL_STARTnop | |
BS_OEMName | 3 | 8 | 厂商名 | ‘ForrestY’ |
BPB_BytsPerSec | 11 | 2 | 每扇区字节数 | 0x200(即十进制512) |
BPB_SecPerClus | 13 | 1 | 每簇扇区数 | 0x01 |
BPB_RsvdSecCnt | 14 | 2 | Boot记录占用多少扇区 | 0x01 |
BPB_NumFATs | 16 | 1 | 共有多少FAT表 | 0x02 |
BPB_RootEntCnt | 17 | 2 | 根目录文件数最大值 | 0xE0 (224) |
BPB_TotSec16 | 19 | 2 | 扇区总数 | 0xB40(2880) |
BPB_Media | 21 | 1 | 介质描述符 | 0xF0 |
BPB_FATSz16 | 22 | 2 | 每FAT扇区数 | 0x09 |
BPB_SecPerTrk | 24 | 2 | 每磁道扇区数 | 0x12 |
BPB_NumHeads | 26 | 2 | 磁头数 | 0x02 |
BPB_HiddSec | 28 | 4 | 隐藏扇区数 | 0 |
BPB_TotSec32 | 32 | 4 | 如果BPB_TotSec16是0,由这个值记录扇区数 | 0xB40(2880) |
BS_DrvNum | 36 | 1 | 中断13的驱动器号 | 0 |
BS_Reserved1 | 37 | 1 | 未使用 | 0 |
BS_BootSig | 38 | 1 | 扩展引导标记 | 0x29 |
BS_VolD | 39 | 4 | 卷序列号 | 0 |
BS_VolLab | 43 | 11 | 卷标 | ‘OrangeS0.02’ |
BS_FileSysType | 54 | 8 | 文件系统类型 | ‘FAT12’ |
引导代码 | 62 | 448 | 引导代码、数据及其他填充字符等 | |
结束标志 | 510 | 2 | 0xAA55 |
FAT表、根目录区、数据区之间的关系比较复杂,这里先说FAT表和数据区的关系。FAT表固定占用9个扇区,也就是9*512B=4608B, 其中存储一定数量的FAT表项,每个FAT表项为12bit,则最多有4608/1.5=3072个表项,表项1和表项2保留,所以最多可用表项为3070个。 数据区由许多个簇构成,簇是一个逻辑上的概念,是由引导程序定义,根据偏移量来获取的一片数据区域,在引导程序中可以看到一个簇大小为一个扇区(512B)。FAT表的表项和数据区的簇是一一对应关系,不过由于FAT[0]和FAT[1]另作他用,所以簇的编号直接从2开始。
在磁盘的使用过程中经常需要写操作和删操作,如果直接线性连续的存储整个文件数据,在使用一定时间后,会出现很多碎片化空间,这些碎片化空间不足以存储一个较大的文件,所以引入了簇的概念,这样就使得数据不必在磁盘中连续的存储,而是分割成若干个簇,可以在磁盘中分段存储,例如一个文件大小为600B,那么就会占用两个簇,这两个簇在磁盘中不一定是连续的,那么文件系统怎么标记哪些簇和哪些簇是属于一个文件的呢?这里就要用到簇和FAT表的配合了,FAT表项的存储内容有相关规则:000h表示对应的簇可用,002h~FEFh表示下一个簇的簇号,FF0h~FF6h为保留簇,FF7h为坏簇,FF8h~FFFh表示为文件的最后一个簇。可以看出,簇和FAT表共同实现了一个单向链表的功能,下面用图解来更直观的解析:
以上图为例,上面的表为FAT表,下面的为数据区的簇,现在从3号簇开始,取出“H”,然后查看3号簇对应的FAT表的FAT[3]中为004h,得知下一个簇是4号簇,从4号簇中取出“E”,然后查看4号簇对应的FAT[4]中为006h,得知下一个簇为6号簇…以此类推,最后从10号簇中读出“O”,然后查看10号簇中对应的FAT[10]中为FF8h为结束符(该簇为最后一个簇),整个读取过程结束,读出“HELLO”数据,这就是FAT表和簇实现单向链表来读取数据的过程,同理也可以使用该原理来存储数据。
问题似乎已经解决了,但是在上述过程中还有一个重要的未解决的问题,那就是我们是怎么知道数据是从3号簇也就是“H”开始的?也就是什么在充当链表的表头?答案就是最后一个结构,根目录区。根目录区也是由一条条记录构成,每条数据大小为32Byte,结构如下所示:
可以看到,里面记录了文件开始的簇号,也就是说,根目录区的记录在承担表头的功能;除了簇号之外,还存储了文件的一些基本信息。至此,FAT12文件系统下文件的读写已经很明了了,下面是代码实现:
org 0x7c00
BaseOfStack equ 0x7c00
BaseOfLoader equ 0x1000
OffsetOfLoader equ 0x00
RootDirSectors equ 14
SectorNumOfRootDirStart equ 19
SectorNumOfFAT1Start equ 1
SectorBalance equ 17
;FAT12文件系统标记
jmp short Label_Start
nop
BS_OEMName db 'testboot'
BPB_BytesPerSec 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 'boot loader'
BS_FileSysType db 'FAT12 '
Label_Start:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack
;清屏
mov ax, 0600h
mov bx, 0700h
mov cx, 0
mov dx, 0184fh
int 10h
;显示startboot
mov ax, 0200h
mov bx, 0000h
mov dx, 0000h
int 10h
mov ax, 1301h
mov bx, 000fh
mov dx, 0000h
mov cx, 10
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartBootMessage
int 10h
;搜索Loader文件
mov word [SectorNo], SectorNumOfRootDirStart
Label_Search_In_Root_Dir_Begin:
cmp word [RootDirSizeForLoop], 0
jz Label_No_LoaderBin
dec word [RootDirSizeForLoop]
mov ax, 00h
mov es, ax
mov bx, 8000h
mov ax, [SectorNo]
mov cl, 1
call Func_ReadOneSector
mov si, LoaderFileName
mov di, 8000h
cld
mov dx, 10h
Label_Search_For_LoaderBin:
cmp dx, 0
jz Label_Goto_Next_Sector_In_Root_Dir
dec dx
mov cx, 11
Label_Cmp_FileName:
cmp cx, 0
jz Label_FileName_Found
dec cx
lodsb
cmp al, byte [es:di]
jz Label_Go_On
jmp Label_Different
Label_Go_On:
inc di
jmp Label_Cmp_FileName
Label_Different:
and di, 0ffe0h
add di, 20h
mov si, LoaderFileName
jmp Label_Search_For_LoaderBin
Label_Goto_Next_Sector_In_Root_Dir:
add word [SectorNo], 1
jmp Label_Search_In_Root_Dir_Begin
;未搜索到Loader,显示错误信息
Label_No_LoaderBin:
mov ax, 1301h
mov bx, 008ch
mov dx, 0100h
mov cx, 21
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, NoLoaderMessage
int 10h
jmp $
;搜索到Loader,进行读取
Label_FileName_Found:
mov ax, RootDirSectors
and di, 0ffe0h
add di, 01ah
mov cx, word [es: