文件系统(File System)是计算机系统必不可少的组成部分,可以说除了部分结构简单的单片机系统之外,文件系统是支撑每一个计算机系统运行的最重要的支撑,无论是操作系统、应用程序、文档还是音视频都是基于文件系统的。所以由此可见文件系统在计算机上的重要地位。
嵌入式系统上有很多场合也需要使用大容量存储设备,这时候可以直接使用存储设备的读写API来进行数据的保存和读取,但是这样做的很大不足是无法和电脑系统的文件兼容,如果想将电脑系统上的一张图片或者txt文档存入嵌入式系统使用的存储器中去就会很麻烦,但是如果这个嵌入式存储器使用的是兼容FAT文件系统的存储格式那么气与个人电脑的文件交换就会大大方便。例如一般场合使用的TF卡,如果直接调用读和写函数进行TF的存取也不是不可以,但是如果这个TF卡上有FAT文件系统的话就可以使用读卡器从电脑上直接拷贝文件,大大方便了文件的交互。
鉴于文件系统的重要性,有必要好好分析一下文件系统的组成原理,下面以当前主流的windows文件系统FAT32为对象,分析一下文件系统的存储机制。
下面是对FAT32文件系统的一个简要介绍:FAT32 文件系统您一定不会陌生,最多看到它是在 windows 操作系统里,但在一些嵌入式产品(如手机、MP3、MP4 等)中,也能看到它的身影。从某种意义上来讲,FAT32文件系统是非常成功的,使我们可以脱离底层储存设备驱动,更为方便高效地组织数据。给单片机系统中的大容量存储器(如 SD 卡、CF 卡、硬盘等)配以 FAT32 文件系统,将是非常有意义的(如创建的数据文件可以在 windows 等操作系统中直接读取等)。
我手上有一张512MB的TF卡将其插在电脑上使用二进制查看软件打开TF卡,下面是拷贝的前512字节的数据进行解说:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 EB 58 90 4D 53 44 4F 53 35 2E 30 00 02 08 74 18 ëX.MSDOS5.0...t.
00000010 02 00 00 00 00 F8 00 00 3F 00 FF 00 00 00 00 00 .....ø..?.ÿ.....
00000020 00 34 0F 00 C6 03 00 00 00 00 00 00 02 00 00 00 .4..Æ...........
00000030 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 80 01 29 22 BE A5 F6 4E 4F 20 4E 41 4D 45 20 20 €.)"¾¥öNO NAME
00000050 20 20 46 41 54 33 32 20 20 20 33 C9 8E D1 BC F4 FAT32 3ɎѼô
00000060 7B 8E C1 8E D9 BD 00 7C 88 56 40 88 4E 02 8A 56 {ŽÁŽÙ½.|ˆV@ˆN.ŠV
00000070 40 B4 41 BB AA 55 CD 13 72 10 81 FB 55 AA 75 0A @´A»ªUÍ.r..ûUªu.
00000080 F6 C1 01 74 05 FE 46 02 EB 2D 8A 56 40 B4 08 CD öÁ.t.þF.ë-ŠV@´.Í
00000090 13 73 05 B9 FF FF 8A F1 66 0F B6 C6 40 66 0F B6 .s.¹ÿÿŠñf.¶Æ@f.¶
000000A0 D1 80 E2 3F F7 E2 86 CD C0 ED 06 41 66 0F B7 C9 Ñ€â?÷â†ÍÀí.Af.·É
000000B0 66 F7 E1 66 89 46 F8 83 7E 16 00 75 39 83 7E 2A f÷áf‰Føƒ~..u9ƒ~*
000000C0 00 77 33 66 8B 46 1C 66 83 C0 0C BB 00 80 B9 01 .w3f‹F.fƒÀ.».€¹.
000000D0 00 E8 2C 00 E9 A8 03 A1 F8 7D 80 C4 7C 8B F0 AC .è,.é¨.¡ø}€Ä|‹ð¬
000000E0 84 C0 74 17 3C FF 74 09 B4 0E BB 07 00 CD 10 EB „Àt.<ÿt.´.»..Í.ë
000000F0 EE A1 FA 7D EB E4 A1 7D 80 EB DF 98 CD 16 CD 19 î¡ú}ëä¡}€ëߘÍ.Í.
00000100 66 60 80 7E 02 00 0F 84 20 00 66 6A 00 66 50 06 f`€~...„ .fj.fP.
00000110 53 66 68 10 00 01 00 B4 42 8A 56 40 8B F4 CD 13 Sfh....´BŠV@‹ôÍ.
00000120 66 58 66 58 66 58 66 58 EB 33 66 3B 46 F8 72 03 fXfXfXfXë3f;Før.
00000130 F9 EB 2A 66 33 D2 66 0F B7 4E 18 66 F7 F1 FE C2 ùë*f3Òf.·N.f÷ñþÂ
00000140 8A CA 66 8B D0 66 C1 EA 10 F7 76 1A 86 D6 8A 56 ŠÊf‹ÐfÁê.÷v.†ÖŠV
00000150 40 8A E8 C0 E4 06 0A CC B8 01 02 CD 13 66 61 0F @ŠèÀä..̸..Í.fa.
00000160 82 74 FF 81 C3 00 02 66 40 49 75 94 C3 42 4F 4F ‚tÿ.Ã..f@Iu”ÃBOO
00000170 54 4D 47 52 20 20 20 20 00 00 00 00 00 00 00 00 TMGR ........
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001A0 00 00 00 00 00 00 00 00 00 00 00 00 0D 0A 44 69 ..............Di
000001B0 73 6B 20 65 72 72 6F 72 FF 0D 0A 50 72 65 73 73 sk errorÿ..Press
000001C0 20 61 6E 79 20 6B 65 79 20 74 6F 20 72 65 73 74 any key to rest
000001D0 61 72 74 0D 0A 00 00 00 00 00 00 00 00 00 00 00 art.............
000001E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001F0 00 00 00 00 00 00 00 00 AC 01 B9 01 00 00 55 AA ........¬.¹...Uª
下面讲讲最重要的一个DBR(DOS BOOT RECORD 操作系统引导记录区)。DBR 是我们进军 FAT32 的首道防线。其实 DBR 中的 BPB 部分才是这一区域的核心部分(第12~90 字节为 BPB),只有深入详实地理解了 BPB 的意义,才能够更好地实现和操控 FAT32。关于 DBR 在 FAT32 中的地位就不多说了,上面的数据中的前90个字节是BPB的主要部分。BPB的C语言结构体如下所示:
struct FAT32_DBR
{
unsigned char BS_jmpBoot[3]; //跳转指令 offset: 0
unsigned char BS_OEMName[8]; //原始制造商 offset: 3
unsigned char BPB_BytesPerSec[2]; //每扇区字节数 offset:11
unsigned char BPB_SecPerClus[1]; //每簇扇区数 offset:13
unsigned char BPB_RsvdSecCnt[2]; //保留扇区数目 offset:14
unsigned char BPB_NumFATs[1]; //此卷中FAT表数 offset:16
unsigned char BPB_RootEntCnt[2]; //FAT32为0 offset:17
unsigned char BPB_TotSec16[2]; //FAT32为0 offset:19
unsigned char BPB_Media[1]; //存储介质 offset:21
unsigned char BPB_FATSz16[2]; //FAT32为 0 offset:22
unsigned char BPB_SecPerTrk[2]; //磁道扇区数 offset:24
unsigned char BPB_NumHeads[2]; //磁头数 offset:26
unsigned char BPB_HiddSec[4]; //FAT区前隐扇区数 offset:28
unsigned char BPB_TotSec32[4]; //该卷总扇区数 offset:32
unsigned char BPB_FATSz32[4]; //一个FAT表扇区数 offset:36
unsigned char BPB_ExtFlags[2]; //FAT32特有 offset:40
unsigned char BPB_FSVer[2]; //FAT32特有 offset:42
unsigned char BPB_RootClus[4]; //根目录簇号 offset:44
unsigned char FSInfo[2]; //保留扇区FSINFO扇区数 offset:48
unsigned char BPB_BkBootSec[2]; //通常为6 offset:50
unsigned char BPB_Reserved[12]; //扩展用 offset:52
unsigned char BS_DrvNum[1]; //offset:64
unsigned char BS_Reserved1[1]; //offset:65
unsigned char BS_BootSig[1]; //offset:66
unsigned char BS_VolID[4]; //offset:67
unsigned char BS_FilSysType[11]; //offset:71
unsigned char BS_FilSysType1[8]; //"FAT32 " offset:82
};
每个字段的大小和含义都有详细的注释,解析这些数据使是们进行文件系统探秘的首要任务。需要注意的是在BPB中数据是以小端模式存储的,所以51单片机的大端模式下需要进行大小端的转换,而一般ARM的默认方式就是小端模式,所以无需进行转换操作。
简单介绍DBR的BPB之后还需要介绍一下FAT表(文件分配表)的概念。什么是文件分配表呢?顾名思义,就是给文件分配存储空间的表,这里面存放的不是文件的数据而是文件所在的簇的信息,具体下面会给出解释。
FAT 表是 FAT32 文件系统中用于磁盘数据(文件)索引和定位引进的一种链式结构。可以说 FAT 表是 FAT32 文件系统最有特色的一部分,它的链式存储机制也是 FAT32 的精华所在,也正因为有了它才使得数据的存储可以不连续,使磁盘的功能发挥得更为出色。
那么FAT 表到底在什么地方?它到底是什么样子的?这时就要回到刚才的BPB部分了,在BPB中有一个字段叫做BPB_RsvdSecCnt,这个字段是表示保留山区数目,其实就是保留给BPB使用的空间的扇区数量,也就是说这个数值表示的就是FAT表前面的空间大小,那么FAT表的地址就是这个字段的数值了。其实在文件系统中为了保证正确性和稳定性胡设置两个完全相同的FAT表,并且两个FAT是同步的,也就是说对一个 FAT 表的操作, 同样地, 也应该在另一个 FAT表进行相同的操作,时刻保证它们内容的一致。这样是为了安全起见,当一个FAT 因为一些原因而遭到破坏的时候,可以从另一个 FAT 表进行恢复。
FAT 表的内容如下图所示:
上图就是一个实际的 FAT 表。前 8 个字节“F8 FF FF 0F FF FF FF FF”为FAT32 的 FAT 表头标记,用以表示此处是 FAT 表的开始。后面的数据每四个字节为一个簇项(从第 2 簇开始) ,用以标记此簇的下一个簇号。
上面讲了很多, 都是围绕簇这样一个词来讲的, 簇又是什么?为什么要将它引入到 FAT32 里来呢?磁盘上最小可寻址存储单元称为扇区, 通常每个扇区为 512 个字节。 由于多数文件比扇区大得多, 因此如果对一个文件分配最小的存储空间, 将使存储器能存储更多数据,这个最小存储空间即称为簇。根据存储设备(磁盘、闪卡和硬盘)的容量,簇的大小可以不同以使存储空间得到最有效的应用。在早期的 360KB 磁盘上,簇大小为 2 个扇区(1,024 字节);第一批的 10MB 硬盘的簇大小增加到8个扇区(4,096字节); 现在的小型闪存设备上的典型簇大小是8KB或 16KB。2GB 以上的硬盘驱动器有 32KB 的簇。如果对于容量大的存储定义了比较小的簇的话,就会使 FAT 表的体积很大,从而造成数据的冗余和效率的下降。
需要指出的是,簇作为 FAT32 进行数据存储的最小单位,内部扇区是不能进一步细分的, 即使一个文件的数据写到一个簇中后, 簇中还有容量的剩余(里部扇区没有写满) ,哪怕这个簇只写了一个字节,其它文件的数据也是不能接在后面继续数据的,而只能另外找没有被占用的簇。
那么 FAT 表有多大呢?FAT 表中每四个字节表示一个簇,所以 FAT 表的大小由实际的簇数来决定。从这里也可以看出,如果簇过大,则 FAT 表比较小,但会造成空间的浪费,而如果簇过小,可以减小空间的浪费,但会使FAT 表变得臃肿。FAT 表的大小可以从 BPB 参数 BPB_FATSz32读出。
现在知道了FAT表的地址和FAT表的大小,也知道有两个FAT表就可以计算出第一个文件夹也就是根文件夹的位置,他的位置就在FAT表的正后方,计算方法如下:
FirstDirSector = FirstFATSector + BPB_NumFATs[0] * FATsectors
其中FirstFATSector 表示FAT表的位置, BPB_NumFATs[0] 表示FAT表的数量,FATsectors 表示的是FAT表占用的扇区数量。其实根目录所在的簇就是第2号簇。计算出了根文件夹的位置就可以从根文件夹中读取数据。在 FAT32 中其实已经把文件的概念进行扩展,目录同样也是文件,根目录的地位与其它目录是相同的, 因此根目录也被看作是文件。 既然是文件就会有文件名,根目录的名称就是磁盘的卷标。
下面所以说根目录文件的内容。首先目录也是文件,它可以看做是特殊文件,这个文件使用来存放其他文件或目录的信息,例如文件名、扩展名、属性、创建时间、最后修改时间、文件起始簇号、文件长度等等。所以我要读取一个文件信息的时候首先要做的就是从目录中读取文件的信息数据。根据上面说的我们已经获取到了根目录的地址,那么就可以从根目录中读取根目录下的文件信息。每个文件/目录在目录文件中使用32个字节的数据表示,具体字段如下所示:
这样就可以从根目录下读取各个文件,我们作为试验不设计嵌套目录的结构,只在根目录下进行文件查看、读写试验。所以在我的512M的TF卡上根目录下有一个test.txt文件,根目录的数据如下所示:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00400000 42 20 00 49 00 6E 00 66 00 6F 00 0F 00 72 72 00 B .I.n.f.o...rr.
00400010 6D 00 61 00 74 00 69 00 6F 00 00 00 6E 00 00 00 m.a.t.i.o...n...
00400020 01 53 00 79 00 73 00 74 00 65 00 0F 00 72 6D 00 .S.y.s.t.e...rm.
00400030 20 00 56 00 6F 00 6C 00 75 00 00 00 6D 00 65 00 .V.o.l.u...m.e.
00400040 53 59 53 54 45 4D 7E 31 20 20 20 16 00 23 BB 58 SYSTEM~1 ..#»X
00400050 7E 49 7E 49 00 00 BC 58 7E 49 03 00 00 00 00 00 ~I~I..¼X~I......
00400060 54 45 53 54 20 20 20 20 54 58 54 20 18 23 C0 58 TEST TXT .#ÀX
00400070 7E 49 81 49 00 00 5A 57 7E 49 05 00 13 20 00 00 ~I.I..ZW~I... ..
00400080 54 41 4E 47 51 55 41 4E 20 20 20 08 00 00 00 00 TANGQUAN .....
00400090 00 00 00 00 00 00 DA 58 7E 49 00 00 00 00 00 00 ......ÚX~I......
004000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
我们可以看到在根目录下的0x60到0x7F处表示的就是根目录下的text.txt文件,0x80到0x9F处则表示的是根目录。前面的两个文件信息表示的可能是一些隐藏的文件,具体我也没了解是什么。反正至此已经可以从FAT32文件系统中成功获取根目录下的文件的信息,下一步就是根据这个文件的信息寻找文件存储的位置,读取文件内容。
32个字节的文件信息已经给得很明确了,确定了文件的其实簇号之后就可以进入FAT表中寻找那个簇以及文件的簇链,根据文件长度字段可以知道文件占用多少个簇,其实根据簇的链式存储结果就可以知道文件占用的簇数目,但是并不知道文件在最后一个簇中占用了多少个字节,所以文件长度字段还是很有意义的。找到对应的簇之后从对应的簇中读取数据即可,easy is'nt it?
原文地址:
https://blog.csdn.NET/tq384998430/article/details/53414142