FAT/FAT32曾经是windows下主流文件格式,虽然FAT已经这么多年了,也见识到一些缺点,但熟悉FAT,还是对文件系统认识有很大帮助。
一般来说,需要具备一些前期知识:
1. 文件存在flash或硬盘里,还是一个个字节进行存储的,存储介子本身不负责具体内容。如果要对硬盘的数据进行识别,必须需要一定格式,还需要一个驱动程序进行识别。
2. 文件格式最主要作用:格式化管理,快速查找文件
还有几个知识点:
扇区:一般为512,最小分割单位,但不是最小管理单位。
簇:管理最小单位,一般是由多个扇区组成,有2、4、8、16、32或64几种情况。如果=16,即8K=16*512,也就是说一个文件最小也得占8K空间
DBR及保留扇区(FAT16不存在)、文件分配表区(FAT1、FAT2)、数据区(DATA区)。
反过来说,一个文件至少占一族,或多簇。最后一簇往往是未存满的,这样肯定会带来空间的浪费。(但也没太好办法,毕竟是要快速查找文件的)
当然,这个可以根据实际情况来设定,毕竟应用场景不一样。
FAT16的根目录区只有32个扇区,计算一下,每个扇区512字节,共32个扇区,而每个文件名至少要占用32个字节(后面会介绍),很显然,根目录最多只能放512个文件了。
FAT16 这里存在致命缺陷的:
例如:我们的文件是存在根目录的,那么最多也只能存放512个文件(假设文件名又很长的话,这个数量就更小了)
FAT32对这方面进行了优化,可以支持更多的文件数量,但格式变得复杂了,查找时也是先从前面扇区往后跳(会根据目录最后指向下一个扇区地址),所以时间上也浪费很多,搜索起来非常慢
这里:FAT表有两个,一般FAT2为镜像。FAT表非常重要,一般如果文件名乱码,或一下子消失了,往往是FAT表坏掉了。
MBR: Main Boot Record 主引导记录区
DBR:Dos Boot Record 操作系统引导记录区
以下,是我对FAT16格式注解:
FAT格式:(FAT16)
FAT32格式:
FAT表格式:
FAT表之后就是目录区:
目录区是由一个个目录项构成,类似于FAT表。其中每一个目录项占用32个字节,可以是代表长文件名目录项、文件目录项、子目录项等。对于短文件名格式的目录项,其参数的含义如表1所示(不会画这种表,从别处引用了一个)[1]:
通过这些信息,也基本知道文件信息,比如创建信息,修改信息等
也可以看到有两个限制:
1. 文件名的长度为8字节,那长一点文件名怎么办?
2.扩展名为3字节,这个虽然是限制,但实际还可以接受。
其实,这个历史原因导致的,原先Dos系统下就根本没考虑中文这种场景(现在windows系统都是基于DOS发展起来)
先看下FAT32对长文件名的处理:
表15 FAT32长文件目录项32个字节的表示定义 |
||||
字节偏移 |
字节数 |
定义 |
||
0x0 |
1 |
属性字节位意义 |
7 |
保留未用 |
6 |
1表示长文件最后一个目录项 |
|||
5 |
保留未用 |
|||
4 |
顺序号数值 |
|||
3 |
||||
2 |
||||
1 |
||||
0 |
||||
0x1~0xA |
10 |
长文件名unicode码① |
||
0xB |
1 |
长文件名目录项标志,取值0FH |
||
0xC |
1 |
系统保留 |
||
0xD |
1 |
校验值(根据短文件名计算得出) |
||
0xE~0x19 |
12 |
长文件名unicode码② |
||
0x1A~0x1B |
2 |
文件起始簇号(目前常置0) |
||
0x1C~0x1F |
4 |
长文件名unicode码③ |
总共这个也是32字节(跟短文件名一致),那么如何区别?关键看0B位置,如果是=0F,那么就是长文件名。
长文件名的实现有赖于目录项偏移为0xB的属性字节,当此字节的属性为:只读、隐藏、系统、卷标,即其值为0FH时,DOS和WIN32会认为其不合法而忽略其存在。这正是长文件名存在的依据。
长文件名中的字符采用unicode形式编码(一个巨大的进步哦),每个字符占据2字节的空间。
从上表中可以获得文件名长度:0x01~0x0A:10字节; + 0x0E~0x19 12字节; + 0x1C~0x1F 4字节;
总共26字节,即13个字符!(被拆七零八落,有点可怜)
那么怎么表达一个长文件名呢?从以上两个表来看,都无法做到兼容
解决办法:短文件名(32)+长文件名(32)....
规则:
(1)、取长文件名的前6个字符(第1个字符改成0x01)加上"~1"形成短文件名,扩展名不变。(放在第1组32字节)
(2)、如果已存在这个文件名,则符号"~"后的数字递增,直到5。(如果是看二进制是可以看到这些奇怪的名称,有时候在文件fat表破坏掉之后也会出来~1这样的奇怪文件)
(3)、如果文件名中"~"后面的数字达到5,则短文件名只使用长文件名的前两个字母。通过数学操纵长文件名的剩余字母生成短文件名的后四个字母,然后加后缀"~1"直到最后(如果有必要,或是其他数字以避免重复的文件名)。
(4)、如果存在老OS或程序无法读取的字符,换以"_"
(5)、从第2组32字节开始,以13字节为一组,分别表示的文件名。如果存在多组,则以倒叙的办法表示,前面的是文件名末尾部分。
假设一个文件,文件名为 ZHUANCHU-12345678-20190910H095830.bin
文件名实际长度=33,那么/13,需要三组长文件名来表示。
3组 | H095830 不足部分用FF替代 |
2组 | 5678-20190910 |
1组 | ZHUANCHU-1234 |
这个格式参考上面的 《长文件名格式》
那么FAT格式文件名大概是这样的:
其中0F表示长文件名的标志。
96表示短文件名校验值(三组都一样)
红色框起来的是文件名的分组。根据长文件名格式定义,被拆分成不同地方。
好了。我们再回到文章前面的,计算下,如果我们的文件名大概都是这样的:“ZHUANCHU-12345678-20190910H095830.bin”,只是数字不一样
那么FAT根目录下最多可以存放多少个这样的文件?(假设全部文件容量要大大小于存储空间)
一个文件名占 3长+1短,共4个坑。总共512个坑,那最多只能放128这样的文件!
这也是,为什么我们存储空间是够的,但存储是失败的原因~~
so,根目录的文件名可不是随意的,建议小于8字节最佳!(这样只要一组短文件名就可以了)~~
搞定了文件名的含义,我们再来看看文件内容存放(实际就是关注起始地址)
文件起始地址(文件内容) = (保留扇区数 + FAT表扇区数 * FAT表个数(2) + (文件起始簇号-2)*每簇扇区数)*每扇区字节数
文件起始簇号:(在短文件名里)高在前
实际计算:感觉文件起始簇号不用-2,OK?
例如下列文件 实验1:
数据区偏移地址:ADDR=(8+256*2+(0x09C3 – 2)*16)* 512= 20,721,664=0x013C3000
根目录有 32*512=0x4000 (固定32个扇区表示目录,内容区)
实际地址=0x013C3000+0x4000=0x013C7000
实验2:
假设 rec/文件夹有一个文件为:888888.txt 里面内容为 “jjw”
来分析下,子文件/rec下一个888888.txt文件(实际跟放在哪里没有关系)
数据区偏移地址:ADDR=(8+256*2+(0x09C4 – 2)*16)* 512= 20,729,856= 0x013C5000
实际地址=0x013C5000+0x4000=0x013C9000
日期时间,可以表示创建信息,修改信息,访问信息
格式都一样,这里以创建信息举例:
0x0E~0x0F |
文件创建时间 |
0x785C = (0111100001011100)(2进制) 即为 15:02:57(注释1) |
0x10~0x11 |
文件创建日期 |
0x4508 = (0100010100001000)(2进制) 即为 2014/8/8(注释2) |
注释1:01111 000010 11100
1)这里高5位代表小时,由于2^5 = 32,足够表示24小时,这边01111(2进制) = 15(10进制);
2)次6位代表分钟,同理2^6 = 64,足够表示60分钟,这边000010(2进制) = 2;
3)低5位表示秒的1/2, 计算结果需要加上毫秒位上的进位,这边11100(2进制) = 28(10进制),所以秒数 = 28*2 = 56,再加上毫秒上的进位1所以结果为57。
注释2:0100010 1000 01000
1)这里高7位代表从1980年开始的年数,笔者计算了下可以到2108年,总之还有90多年可以使用,这边0100010(2进制) = 34,所以年份 = 1980+34 = 2014;
2)次4位代表月份,2^4=16,可以表示12个月份,这边 1000(2进制) = 8(10进制);
3)低5位代表日期,2^5 = 32,可以表示28~31天,这边 01000(2进制) = 8(10进制)。
这些信息,都蛮奇怪了。不过经常接触嵌入式,也就不奇怪了。