FAT12镜像查看
前言:为了搞定OS的Lab2实验——实现一个FAT12镜像查看工具,还是耗费了较多的精力的,以此文记录下收获
准备工作
工具:为了方便较为直观呈现效果,采用vscode + Hex Editor插件
镜像文件:a.img 1.44MB
镜像结构:
FAT12文件系统
FAT
FAT(File Allocation Table)文件配置表。用于记录文件所在位置的表格。
在DOS v1.0时代就已引入,是最基本的文件系统之一。
FAT家族包括:FAT12、FAT16、FAT32、ExFAT、VFAT
FAT12
12位地址,故最大容量为16MB,软盘文件系统使用。
标准以FAT12组织的软盘包括以下设定:
- 两磁头
- 每磁头80个磁道
- 每磁道18扇区
- 每扇区512字节
故FAT12的标准软盘大小为2 * 80 * 18 * 512 = 1.44MB
,故一个标准1.44MB
的FAT12软盘共有2 * 80 * 18 = 2880
扇区
文件系统为了方便组织与管理,会将磁盘分为若干层次:
- 扇区:磁盘上最小数据单元
- 簇:一个或多个扇区,是数据存储的基本单位
- 分区:不同功能区
上述2880
扇区被分为如下5
个不同的分区
由于根目录分区的大小不确定,数据区大小也不能够加以确定。
MBR区
引导扇区,占用一个扇区大小空间。
先前的学习中,我们了解到上电后Bios自检,会检测第0
磁头、第0
磁道、第1
扇区是否以0x55
和0xaa
作结,以此判断该扇区是否为引导扇区。
从第11
个字节开始的25
个字节构成一个较为特殊的结构BPB(Bios Parameter Block)
用一个结构体描述BPB
的结构就可以表示为:
typedef struct BPB { |
可以看出BPB
对于接下来许多操作都具有决定性作用,比如:
BPB_BytsPerSec * BPB_SecPerClus
,这就是一个簇的字节大小
BPB_RsvdSecCnt + BPB_FATSz16 * BPB_NumFATs
,这就是MBR区以及FAT表区所占扇区数,所对应扇区也是根目录区开始扇区号
查看以下1.44MB
的a.img
的第一个扇区
- 扇区以
0x55
0xAA
结尾 - 扇区第
11
到35
字节为00 02 01 01 00 02 E0 00 40 0B F0 09 00 12 00 02 00 00 00 00 00 00 00 00 00
对应于BPB
即有
BPB { |
符合先前
标准以FAT12组织的软盘包括以下设定: |
同时扇区划分,扇区总数,每个簇所对应扇区数,FAT表个数以及每个FAT表所占扇区数等数据均吻合。
FAT表
FAT1
与FAT2
互为备份,所以理论上两张表是一样的。
FAT表被划分为紧密排列的若干表项,每个表项与数据区中一个簇对应,表项序号同时与簇号一一对应。
FAT12
每个表项由12
位组成,故先前说其是12
位地址,最大容量为16MB
。
需要注意的是,由于1.44MB
的软盘上FAT12
前三个字节为固定的0xF0
、0xFF
、0xFF
,用于表示这是一个FAT12
文件系统,所以3
字节,24
位,2
表项被占据,对应的簇0
簇1
就没有存在意义,故数据区起始不是簇0
,而是簇2
。
FAT表项的值的含义:
- 通常情况下代表文件下一簇号
- 值
>= 0xFF8
,该簇已经是文件最后一个簇 - 值
= 0xFF7
,表示一个坏簇
根目录区
根目录区由根目录下的目录项/文件构成,一个目录项占据32
字节
类似的,每个目录项都有固定的结构,可用如下结构体进行模拟:
typedef struct RootEntry { |
通过查询根目录区即可得到文件/目录开始簇号,也就可以去相应簇号读取。那么引入FAT表进行定位意义何在?大文件!
上述方式只适用于文件大小小于一个簇的,鉴于文件的零散分布,就需要FAT表指示文件的下一部分所在簇号,方便加以定位,将散乱的文件整合起来。
由此,归结起来,FAT12访问文件的基本操作为:
- 首先通过根目录文件查找文件名,确定是哪一个条目,接着在条目中访问DIR_FstClus对应的开始簇号
- 当一个簇号访问完后,通过FAT表项查询下一簇号,决定是结束还是继续访问下一簇号,重复第二条
由上面区域划分可知,根目录区始于19扇区
,即19 * 512 = 2600h
先前又可知根目录区最大文件数为224
,故根目录区所占扇区大小为(32 * BPB_RootEntCnt + BPB_BytsPerSec - 1) / BPB_BytsPerSec = (32 * 224 + 512 - 1) / 512 = 14
加上BPB_BytsPerSec-1
保证此公式在根目录区无法填满整个扇区时仍然能够成立
数据区
数据存放区域,包括文件以及根目录下目录对应的文件信息,该信息同根目录区,使用Entry
对目录加以存储。
由于根目录区占据14
扇区,加上先前MBR
以及FAT
占据的19
扇区,计算得出数据区起始于33
扇区,即33 * 512 = 4200h
实例
下面就实际的通过FAT12
对文件进行查找
回顾步骤:
- 首先通过根目录文件查找文件名,确定是哪一个条目,接着在条目中访问DIR_FstClus对应的开始簇号
- 当一个簇号访问完后,通过FAT表项查询下一簇号,决定是结束还是继续访问下一簇号,重复第二条
大致流程也就如上图所示
再看一下目录结构
分别以一个目录和文件举例
查看HOUSE
目录
step1:查看根目录目录项
定位到2600h
HOUSE
目录具有以下结构
RootEntry HOUSE { |
判断是文件还是目录,FAT12下文件属性常见如下:
- 0x01:只读文件
- 0x02:隐藏文件
- 0x04:系统文件
- 0x08:卷标
- 0x10:子目录
- 0x20:存档文件
根据DIR_Attr = 0x10
也能判断出HOUSE
为子目录类型
HOUSE
子目录下还有ROOM
子目录,去数据区HOUSE
开始簇号4
查看
簇号4
应该是定位到(4 - 2) * 512 + 4200h = 0400h + 4200h = 4600h
忽略.
和..
子目录,ROOM
子目录具有以下结构:
RootEntry ROOM { |
去对应的簇号6(4A00h)
处
查看到ROOM
目录下的信息
step2:查询FAT表
HOUSE
起始簇内容全部访问完了,紧接着我们就要去访问与簇号对应的FAT表了,FAT表对应表项为簇号4
FAT1表
起始于1
扇区,即200h
前三个字节为固定的0xF0
、0xFF
、0xFF
,符合FAT12
如何读取12
位FAT表项取值?
可不是照着顺序F0F
FFF
这样,这就涉及到存储的方式了。以下是几张较为直观的示意图:
以我的看法,由于是按字节(8位)
进行存取的,12
位的尴尬取值,就不得不由一个完整的字节与另一字节的一半拼接起来。
以FAT(0)
和 FAT(1)
为例,依据低地址在大端,高地址在小端的顺序,构成的三个字节应该为0xFF-FF-F0
,读取12
字节划分应该为0xFFF-FF0
,FAT(0)
的高位F
为byte2
的低位,FAT(1)
的低位F
为byte2
的高位F
,真是奇怪的读取方式。
用16
位存储关联两字节后获取这12
位的FAT表项
也就得分奇偶进行区分了。粗略有以下写法
if(奇数表项号){ |
所以簇号4
对应FAT表项
值为0xFFF
,>0xFF8
,表明已是文件的最后一个簇(HOUSE
作为目录,对应的首簇能放512 / 32 = 16
条entry
)
查看/NJU/SOFTWARE/SE1.TXT
之所以选择该文件一是目录结构有点复杂,而是文件大小超过512
字节,也就是1
簇,便于理解FAT表
step1:查看根目录目录项
大致与上文一样,定位NJU
目录开始簇号为0x0005 = 5
,定位至4800h
定位SOFTWARE
目录开始簇号为0x0008 = 8
,定位至4E00h
定位SE1.TXT
开始簇号为0x000B = 11
,定位至5400h
step2:查询FAT表
查询表项FAT(11)
,为0x00C = 12
,接着继续去簇12
读取接下来内容,查询表项FAT(12)
,为0x00D = 13
,接着去簇13
读取下一段内容,查询表项FAT(13) = 0x00E = 14
,去簇14
读取剩余内容,查询表项FAT(14) = 0xFFF
,读取完毕。
所以簇14
为最后一个簇,定位到5A00h
,确实如此。
至此已完成了FAT12
文件系统的了解,镜像查看工具的实现依据上面所说文件读取过程进行模拟即可。
参考文章: