FAT32文件系统的存储机制及其在单片机上的实现.docx

上传人:b****6 文档编号:8386927 上传时间:2023-01-31 格式:DOCX 页数:21 大小:504.44KB
下载 相关 举报
FAT32文件系统的存储机制及其在单片机上的实现.docx_第1页
第1页 / 共21页
FAT32文件系统的存储机制及其在单片机上的实现.docx_第2页
第2页 / 共21页
FAT32文件系统的存储机制及其在单片机上的实现.docx_第3页
第3页 / 共21页
FAT32文件系统的存储机制及其在单片机上的实现.docx_第4页
第4页 / 共21页
FAT32文件系统的存储机制及其在单片机上的实现.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

FAT32文件系统的存储机制及其在单片机上的实现.docx

《FAT32文件系统的存储机制及其在单片机上的实现.docx》由会员分享,可在线阅读,更多相关《FAT32文件系统的存储机制及其在单片机上的实现.docx(21页珍藏版)》请在冰豆网上搜索。

FAT32文件系统的存储机制及其在单片机上的实现.docx

FAT32文件系统的存储机制及其在单片机上的实现

FAT32文件系统的存储机制及其在单片机上的实现

 

FAT32文件系统您一定不会陌生,最多看到它是在windows操作系统里,但在一些嵌入式产品(如手机、MP3、MP4等)中,也能看到它的身影。

从某种意义上来讲,FAT32文件系统是非常成功的,使我们可以脱离底层储存设备驱动,更为方便高效地组织数据。

给单片机系统中的大容量存储器(如SD卡、CF卡、硬盘等)配以FAT32文件系统,将是非常有意义的(如创建的数据文件可以在windows等操作系统中直接读取等)。

FAT32本身是比较复杂的,对其进行讲解的最好方法就是实际演练。

笔者手里持有一张刚以FAT32格式化的SD卡,我们就围绕它来讲解FAT32的实现机理。

FAT32分为几个区域,这里将用实例的方法对它们的结构与在文件存储中的功能进行详细的剖析。

1、实例说明

此实例首先在一张空的SD卡(已被格式化为FAT32格式)上创建一个文本文件,并在其中输入20个字符。

再将它插入到单片机系统中,实现对这个文件的读取,将文件内容输出在调试终端上。

2、实现过程

1)格式化与创建文件

Windows上的磁盘格式化与文件创建就不用多说了。

如下图:

2)DBR(DOSBOOTRECORD操作系统引导记录区)

DBR是我们进军FAT32的首道防线。

其实DBR中的BPB部分才是这一区域的核心部分(第12~90字节为BPB),只有深入详实的理解了BPB的意义,才能够更好的实现和操控FAT32。

关于DBR在FAT32中的地位就不多说了,以下面实际的DBR内图所示:

 

上面的数据看起来杂乱不堪,无从下手,其实对我们有用的数据只不过90个字节(如图中彩色线标记的字节)。

仅仅是这90个字节就可以告诉我们关于磁盘的很多信息,比如每扇区字节数、每簇扇区数、磁道扇区数等等。

对于这些信息的读取,只要遵循DBR中的字段定义即可。

(比如图中紫色字段的两个字节表示这张磁盘的每一个扇区有512个字节,具体的计算方法见下文)字段定义如下表(BPB后面的422个字节对我们的意义不大,表中省略):

字段名称

长度

含义

偏移量

jmpBoot

3

跳转指令

0

OEMName

8

这是字符串,标识了格式化该分区的操作系统的名称和版本号

3

BytesPerSec

2

每扇区字节数

11

SecPerClus

1

每簇扇区数

13

RsvdSecCnt

2

保留扇区数目

14

NumFATs

1

此卷中FAT表数

16

RootEntCnt

2

FAT32为0

17

TotSec16

2

FAT32为0

19

Media

1

存储介质

21

FATSz16

2

FAT32为0

22

SecPerTrk

2

磁道扇区数

24

NumHeads

2

磁头数

26

HiddSec

4

FAT区前隐扇区数

28

TotSec32

4

该卷总扇区数

32

FATSz32

4

FAT表扇区数

36

ExtFlags

2

FAT32特有

40

FSVer

2

FAT32特有

42

RootClus

4

根目录簇号

44

FSInfo

2

文件系统信息

48

BkBootSec

2

通常为6

50

Reserved

12

扩展用

52

DrvNum

1

64

Reserved1

1

65

BootSig

1

66

VolID

4

67

FilSysType

11

71

FilSysType1

8

82

DBR的实现代码:

structFAT32_DBR{

unsignedcharBS_jmpBoot[3];//跳转指令offset:

0

unsignedcharBS_OEMName[8];//offset:

3

unsignedcharBPB_BytesPerSec[2];//每扇区字节数offset:

11

unsignedcharBPB_SecPerClus[1];//每簇扇区数offset:

13

unsignedcharBPB_RsvdSecCnt[2];//保留扇区数目offset:

14

unsignedcharBPB_NumFATs[1];//此卷中FAT表数offset:

16

unsignedcharBPB_RootEntCnt[2];//FAT32为0offset:

17

unsignedcharBPB_TotSec16[2];//FAT32为0offset:

19

unsignedcharBPB_Media[1];//存储介质offset:

21

unsignedcharBPB_FATSz16[2];//FAT32为0offset:

22

unsignedcharBPB_SecPerTrk[2];//磁道扇区数offset:

24

unsignedcharBPB_NumHeads[2];//磁头数offset:

26

unsignedcharBPB_HiddSec[4];//FAT区前隐扇区数offset:

28

unsignedcharBPB_TotSec32[4];//该卷总扇区数offset:

32

unsignedcharBPB_FATSz32[4];//一个FAT表扇区数offset:

36

unsignedcharBPB_ExtFlags[2];//FAT32特有offset:

40

unsignedcharBPB_FSVer[2];//FAT32特有offset:

42

unsignedcharBPB_RootClus[4];//根目录簇号offset:

44

unsignedcharFSInfo[2];//保留扇区FSINFO扇区数offset:

48

unsignedcharBPB_BkBootSec[2];//通常为6offset:

50

unsignedcharBPB_Reserved[12];//扩展用offset:

52

unsignedcharBS_DrvNum[1];//offset:

64

unsignedcharBS_Reserved1[1];//offset:

65

unsignedcharBS_BootSig[1];//offset:

66

unsignedcharBS_VolID[4];//offset:

67

unsignedcharBS_FilSysType[11];//offset:

71

unsignedcharBS_FilSysType1[8];//"FAT32"offset:

82

在程序中我们采用以上的结构体指针对扇区数据指针进行转化,就可以直接读取数据中的某一字段,如要读取BPB_BytesPerSec,可以这样来作:

((structFAT32_DBR*)pSector)>BPB_BytesPerSec

用如上语句就可以得到这一字段的首地址。

心细的读者可能会发现读回来的字节拼在一起,与实际的数据并不吻合。

例如BPB_BytesPerSec读出来的内容是“0002”,在程序中我们把00作为int型变量的高字节,把02作为其低字节,那么这个变量的值为2,而实际的SD卡里的扇区大小为512个字节,这512与2之间相去甚远。

是什么造成这种现象的呢?

这就是大端模式与小端模式在作怪。

上面我们合成int型变量的方法(00为高字节,02为低字节)为小端模式。

而如果我们改用大端模式来进行合成的话,结果就会不同:

将02作高字节,而把00作低字节,变量值就成了0x0200(十进制的512),这样就和实际数据吻合了。

可见FAT32中字节的排布是采用小端模式的。

在我们程序中需要将它转为大端模式的表达方式。

在笔者的程序有这样一个函数lb2bb,专门将小端模式转为大端模式,程序如下:

unsignedlonglb2bb(unsignedchar*dat,unsignedcharlen){

//小端转为大端

unsignedlongtemp=0;

unsignedlongfact=1;

unsignedchari=0;

for(i=0;i

temp+=dat[i]*fact;

fact*=256;

}

returntemp;

}

这样就可以从BPB中读出关于磁盘的各种参数信息,为我们后面的工作作准备。

而这个从BPB中读取参数装入到参数表中以备后用的过程就是FAT32的初始化了。

具体的实现如下:

先定义用来装入从BPB中读取的参数的结构:

structFAT32_Init_Arg{

unsignedcharBPB_Sector_No;//BPB所在扇区号

unsignedlongTotal_Size;//磁盘的总容量

unsignedlongFirstDirClust;//根目录的开始簇

unsignedlongFirstDataSector;//文件数据开始扇区号

unsignedintBytesPerSector;//每个扇区的字节数

unsignedintFATsectors;//FAT表所占扇区数

unsignedintSectorsPerClust;//每簇的扇区数

unsignedlongFirstFATSector;//第一个FAT表所在扇区

unsignedlongFirstDirSector;//第一个目录所在扇区

unsignedlongRootDirSectors;//根目录所占扇区数

unsignedlongRootDirCount;//根目录下的目录与文件数

当然也可以用零散的变量来存储参数,但用结构体更方便管理,也会使程序更为整洁。

FAT32的初始化将向结构中装入参数,实现如下:

voidFAT32_Init(structFAT32_Init_Arg*arg){

structFAT32_BPB*bpb=(structFAT32_BPB*)(FAT32_Buffer);

//将数据缓冲区指针转为structFAT32_BPB型指针

arg>BPB_Sector_No=FAT32_FindBPB();

//FAT32_FindBPB()可以返回BPB所在的扇区号

arg>Total_Size=FAT32_Get_Total_Size();

//FAT32_Get_Total_Size()可以返回磁盘的总容量,单位是兆

arg>FATsectors=lb2bb((bpb>BPB_FATSz32),4);

//装入FAT表占用的扇区数到FATsectors中

arg>FirstDirClust=lb2bb((bpb>BPB_RootClus),4);

//装入根目录簇号到FirstDirClust中

arg>BytesPerSector=lb2bb((bpb>BPB_BytesPerSec),2);

//装入每扇区字节数到BytesPerSector中

arg>SectorsPerClust=lb2bb((bpb>BPB_SecPerClus),1);

//装入每簇扇区数到SectorsPerClust中

arg>FirstFATSector=lb2bb((bpb>BPB_RsvdSecCnt),2)+arg>BPB_Sector_No;

//装入第一个FAT表扇区号到FirstFATSector中

arg>RootDirCount=lb2bb((bpb>BPB_RootEntCnt),2);

//装入根目录项数到RootDirCount中

arg>RootDirSectors=(arg>RootDirCount)*32>>9;

//装入根目录占用的扇区数到RootDirSectors中

arg>FirstDirSector=(arg>FirstFATSector)+(bpb>BPB_NumFATs[0])*(arg>FATsectors);

//装入第一个目录扇区到FirstDirSector中

arg>FirstDataSector=(arg>FirstDirSector)+(arg>RootDirSectors);

//装入第一个数据扇区到FirstDataSector中

}

3)FAT(文件分配表)

FAT表是FAT32文件系统中用于磁盘数据(文件)索引和定位引进的一种链式结构。

可以说FAT表是FAT32文件系统最有特色的一部分,它的链式存储机制也是FAT32的精华所在,也正因为有了它才使得数据的存储可以不连续,使磁盘的功能发挥得更为出色。

FAT表到底在什么地方?

它到底是什么样子的呢?

从第一步从BPB中提取参数中的FirstFATSector就可以知道FAT表所在的扇区号。

其实每一个FAT表都有另一个与它一模一样的FAT存在,并且这两个FAT表是同步的,也就是说对一个FAT表的操作,同样地,也应该在另一个FAT表进行相同的操作,时刻保证它们内容的一致。

这样是为了安全起见,当一个FAT因为一些原因而遭到破坏的时候,可以从另一个FAT表进行恢复。

FAT表的内容如下图所示:

上图就是一个实际的FAT表。

前8个字节“F8FFFF0FFFFFFFFF”为FAT32的FAT表头标记,用以表示此处是FAT表的开始。

后面的数据每四个字节为一个簇项(从第2簇开始),用以标记此簇的下一个簇号。

拿我们创建的那个叫TEST.TXT(大小为20个字节)的文件来说,如果这个文件的开始簇为第2簇的话,那么就到FAT表里来查找,看文件是否有下一个簇(如果文件大小大于一个簇的容量,必须会有数据存储到下一个簇,但下一个簇与上一个簇不一定是连续的),可以看到“簇2”的内容为“FFFFFF0F”,这样的标记就说明这个文件到第2簇就已经结束了,没有后继的簇,即此文件的大小是小于一个簇的容量的。

上面讲了很多,都是围绕簇这样一个词来讲的,簇又是什么?

为什么要将它引入到FAT32里来呢?

磁盘上最小可寻址存储单元称为扇区,通常每个扇区为512个字节。

由于多数文件比扇区大得多,因此如果对一个文件分配最小的存储空间,将使存储器能存储更多数据,这个最小存储空间即称为簇。

根据存储设备(磁盘、闪卡和硬盘)的容量,簇的大小可以不同以使存储空间得到最有效的应用。

在早期的360KB磁盘上,簇大小为2个扇区(1,024字节);第一批的10MB硬盘的簇大小增加到8个扇区(4,096字节);现在的小型闪存设备上的典型簇大小是8KB或16KB。

2GB以上的硬盘驱动器有32KB的簇。

如果对于容量大的存储定义了比较小的簇的话,就会使FAT表的体积很大,从而造成数据的冗余和效率的下降。

需要指出的是,簇作为FAT32进行数据存储的最小单位,内部扇区是不能进一步细分的,即使一个文件的数据写到一个簇中后,簇中还有容量的剩余(里部扇区没有写满),哪怕这个簇只写了一个字节,其它文件的数据也是不能接在后面继续数据的,而只能另外找没有被占用的簇。

我们按照初始化参数表中的SectorsPerClust可以知道一个簇中的扇区数,笔者的SD卡实测簇大小为4个扇区,按照上面的说法,TEST.TXT这样一个只有20个字节的文件,也会占用一个簇的容量,让我们在Windows里看看它的实际占用空间的情况。

如下图:

从上图可以看到文件大小为20个字节,但占用空间却是2048个字节(一

个簇的容量,4个扇区)。

TEST.TXT容量只有20个字节,所以只占用了一个簇,可能FAT表中还看不出链式结构,现在我们再创建一个文件,使它占用26个簇,如下:

 

可以看到图中红色标记的就是文件所占用的26个簇。

从第4簇开始,簇项4的内容为“05000000”(小端模式),说明下一个簇为第5簇,而簇项5的内容为“06000000”,说明下一个簇为第6簇……依此类推,直到内容为“FFFFFF0F”,说明无后继簇,文件数据到此结束。

FAT表中的链式存储结构已经非常明显。

把我们从FAT表中分析的结果与Windows的统计结束进行对比,说明我们的解理是正确的,如下图:

从上面可以看到,当数据结束于某一簇时,FAT32就用“FFFFFF0F”来对其进行标记。

其实还有其实的标记以表达其它的簇属性,如“00000000”表示未分配的簇,“FFFFFFF7”表示坏簇等。

给出一个簇号,计算出它的后继簇号,是实现FAT32的重点,实现如下:

unsignedlongFAT32_GetNextCluster(unsignedlongLastCluster){

unsignedlongtemp;

structFAT32_FAT*pFAT;

structFAT32_FAT_Item*pFAT_Item;

temp=((LastCluster/128)+Init_Arg.FirstFATSector);

//计算给定簇号对应的簇项的扇区号

FAT32_ReadSector(temp,FAT32_Buffer);

pFAT=(structFAT32_FAT*)FAT32_Buffer;

pFAT_Item=&((pFAT>

Items)[LastCluster%128]);

//在算出的扇区中提取簇项

returnlb2bb(pFAT_Item,4);//返回下一簇号

}

那么FAT表有多大呢?

FAT表中每四个字节表示一个簇,所以FAT表的大小由实际的簇数来决定。

从这里也可以看出,如果簇过大,就会则FAT表比较小,但会造成空间的浪费,而如果簇过小,可以减小空间的浪费,但会使FAT表变得臃肿。

FAT表的大小也可以从BPB参数FATsectors读出。

从上面的BPB图可以得知笔者的SD卡的FAT表大小为958个扇区(“BE030000”的大端表示)。

如果这958个扇区每四个字节都表示一个簇项,则它可以表示

(958*512/4)-2=122622个簇(减去2是因为有8个字节的FAT表头标识。

看看我们计算的是否正确呢,下面是Winhex计算出来的簇数:

与Winhex计算的结果是吻合的,我们对FAT表与簇的理解是正确的。

看完上面对FAT表的讲解中,你可能会问:

一个文件数据的首簇号怎样来确定呢?

只有知道了一个文件数据的首簇号才能继续查找下一簇数据的位置,直到数据结束。

下面将要讲到的“根目录区”就可以由一个文件的文件名来查到它的首簇。

4)根目录区

在FAT32中其实已经把文件的概念进行扩展,目录同样也是文件,从根目录的地位与其它目录是相同的,因此根目录也被看作是文件。

既然是文件就会有文件名,根目录的名称就是磁盘的卷标。

如笔者的SD卡在格式会时设置卷标为znmcu,则根目录的名称就为ZNMCU,如下图:

 

 

每一个文件都对应一个描述它属性的结构,定义如下:

FAT32文件目录项32个字节的定义

字节偏移量

字数量

定义

0~7

8

文件名

8~10

3

扩展名

11

1

属性字节

0x00(读写)

0x01(只读)

0x02(隐藏)

0x04(系统)

0x08(卷标)

0x10(子目录)

0x20(归档)

12

1

系统保留

13

1

创建时间的10毫秒位

14~15

2

文件创建时间

16~17

2

文件创建日期

18~19

2

文件最后访问日期

20~21

2

文件起始簇号的高16位

22~23

2

文件的最近修改时间

24~25

2

文件的最近修改日期

26~27

2

文件起始簇号的低16位

28~31

4

表示文件的长度

 

根目录区所在扇区可从BPB参数FirstDirSector获取,从BPB图得

FirstDirSector=FirstFATSector+BPB_NumFATs*FATsectors=2053。

根目录区的初始大小为一个簇,实际的内容如下:

 

图中的记录1描述根目录,前八个字节为文件名“ZNMCU”(长度小于8的部分用空格符补齐),下面的三个字节为扩展名“”(长度小于3的部分用空格符补齐),08表示此文件为卷标,开始簇高字节为0000,低字节为0000,开始簇为0,文件长度为0。

 

记录2描述TEST.TXT文件,文件名为“TEST”,扩展名为“TXT”,20表示此文件为归档,开始簇为3(“00000003”),长度为20。

记录3描述BIGTEST.TXT文件,文件名为“BIGTES~1”,扩展名为“TXT”,开始簇为4,长度为5200字节(0000CB20)。

可以看到FAT32中的文件名都以大写字母表示,长度不足的部分用空格符补齐,所以我们要读取的文件TEST.TXT就变成了“TEST.TXT”,这将有助于文件名的匹配,我们不用去处理不等长文件名所带来的麻烦。

另外,还会发现长度过长的部分会被~1所替换,如果替换后有文件与之重名,则~后面的数字将增加为2。

文件目录项结构的实现如下:

structdirentry{

unsignedchardeName[8];//文件名

unsignedchardeExtension[3];//扩展名

unsignedchardeAttributes;//文件属性

unsignedchardeLowerCase;//系统保留

unsignedchardeCHundredth;//创建时间的10毫秒位

unsignedchardeCTime[2];//文件创建时间

unsignedchardeCDate[2];//文件创建日期

unsignedchardeADate[2];//文件最后访问日期

unsignedchardeHighClust[2];//文件起始簇号的高16位

unsignedchardeMTime[2];//文件的最近修改时间

unsignedchardeMDate[2];//文件的最近修改日期

unsignedchardeLowCluster[2];//文件起始簇号的低16位

unsignedchardeFileSize[4];//表示文件的长度

}

我们最终要实现的是对TEST.TXT文件的读取,须要作到给定文件名后,可以得到相应文件的首簇。

主要的思想就是对根目录区中(本实例只针对根目录中的文件进行读取,至于多级子目录的实现,只须要进行多次首簇定位)的记录进

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1