下载

3下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

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

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

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

wflb826
2010-05-13 0人阅读 举报 0 0 暂无简介

简介:本文档为《FAT32文件系统的存储机制及其在单片机上的实现pdf》,可适用于IT/计算机领域

FAT文件系统的存储机制及其在单片机上的实现FAT文件系统您一定不会陌生最多看到它是在windows操作系统里但在一些嵌入式产品(如手机、MP、MP等)中也能看到它的身影。从某种意义上来讲FAT文件系统是非常成功的使我们可以脱离底层储存设备驱动更为方便高效地组织数据。给单片机系统中的大容量存储器(如SD卡、CF卡、硬盘等)配以FAT文件系统将是非常有意义的(如创建的数据文件可以在windows等操作系统中直接读取等)。FAT本身是比较复杂的对其进行讲解的最好方法就是实际演练。笔者手里持有一张刚以FAT格式化的SD卡我们就围绕它来讲解FAT的实现机理。FAT分为几个区域这里将用实例的方法对它们的结构与在文件存储中的功能进行详细的剖析。、实例说明此实例首先在一张空的SD卡(已被格式化为FAT格式)上创建一个文本文件并在其中输入个字符。再将它插入到单片机系统中实现对这个文件的读取将文件内容输出在调试终端上。、实现过程)格式化与创建文件Windows上的磁盘格式化与文件创建就不用多说了。如下图:)DBR(DOSBOOTRECORD操作系统引导记录区)DBR是我们进军FAT的首道防线。其实DBR中的BPB部分才是这一区域的核心部分(第~字节为BPB)只有深入详实的理解了BPB的意义才能够更好的实现和操控FAT。关于DBR在FAT中的地位就不多说了以下面实际的DBR内图所示:上面的数据看起来杂乱不堪无从下手其实对我们有用的数据只不过个字节(如图中彩色线标记的字节)。仅仅是这个字节就可以告诉我们关于磁盘的很多信息比如每扇区字节数、每簇扇区数、磁道扇区数等等。对于这些信息的读取只要遵循DBR中的字段定义即可。(比如图中紫色字段的两个字节表示这张磁盘的每一个扇区有个字节具体的计算方法见下文)字段定义如下表(BPB后面的个字节对我们的意义不大表中省略):字段名称长度含义偏移量jmpBoot跳转指令OEMName这是一个字符串标识了格式化该分区的操作系统的名称和版本号BytesPerSec每扇区字节数SecPerClus每簇扇区数RsvdSecCnt保留扇区数目NumFATs此卷中FAT表数RootEntCntFAT为TotSecFAT为Media存储介质FATSzFAT为SecPerTrk磁道扇区数NumHeads磁头数HiddSecFAT区前隐扇区数TotSec该卷总扇区数FATSzFAT表扇区数ExtFlagsFAT特有FSVerFAT特有RootClus根目录簇号FSInfo文件系统信息BkBootSec通常为Reserved扩展用DrvNum-Reserved-BootSig-VolID-FilSysType-FilSysType-DBR的实现代码:structFATDBR{unsignedcharBSjmpBoot跳转指令offset:unsignedcharBSOEMNameoffset:unsignedcharBPBBytesPerSec每扇区字节数offset:unsignedcharBPBSecPerClus每簇扇区数offset:unsignedcharBPBRsvdSecCnt保留扇区数目offset:unsignedcharBPBNumFATs此卷中FAT表数offset:unsignedcharBPBRootEntCntFAT为offset:unsignedcharBPBTotSecFAT为offset:unsignedcharBPBMedia存储介质offset:unsignedcharBPBFATSzFAT为offset:unsignedcharBPBSecPerTrk磁道扇区数offset:unsignedcharBPBNumHeads磁头数offset:unsignedcharBPBHiddSecFAT区前隐扇区数offset:unsignedcharBPBTotSec该卷总扇区数offset:unsignedcharBPBFATSz一个FAT表扇区数offset:unsignedcharBPBExtFlagsFAT特有offset:unsignedcharBPBFSVerFAT特有offset:unsignedcharBPBRootClus根目录簇号offset:unsignedcharFSInfo保留扇区FSINFO扇区数offset:unsignedcharBPBBkBootSec通常为offset:unsignedcharBPBReserved扩展用offset:unsignedcharBSDrvNumoffset:unsignedcharBSReservedoffset:unsignedcharBSBootSigoffset:unsignedcharBSVolIDoffset:unsignedcharBSFilSysTypeoffset:unsignedcharBSFilSysType"FAT"offset:}在程序中我们采用以上的结构体指针对扇区数据指针进行转化就可以直接读取数据中的某一字段如要读取BPBBytesPerSec可以这样来作:((structFATDBR*)pSector)>BPBBytesPerSec用如上语句就可以得到这一字段的首地址。心细的读者可能会发现读回来的字节拼在一起与实际的数据并不吻合。例如BPBBytesPerSec读出来的内容是“”在程序中我们把作为int型变量的高字节把作为其低字节那么这个变量的值为而实际的SD卡里的扇区大小为个字节这与之间相去甚远。是什么造成这种现象的呢?这就是大端模式与小端模式在作怪。上面我们合成int型变量的方法(为高字节为低字节)为小端模式。而如果我们改用大端模式来进行合成的话结果就会不同:将作高字节而把作低字节变量值就成了x(十进制的)这样就和实际数据吻合了。可见FAT中字节的排布是采用小端模式的。在我们程序中需要将它转为大端模式的表达方式。在笔者的程序有这样一个函数lbbb专门将小端模式转为大端模式程序如下:unsignedlonglbbb(unsignedchar*dat,unsignedcharlen)小端转为大端{unsignedlongtemp=unsignedlongfact=unsignedchari=for(i=i<leni){temp=dati*factfact*=}returntemp}这样就可以从BPB中读出关于磁盘的各种参数信息为我们后面的工作作准备。而这个从BPB中读取参数装入到参数表中以备后用的过程就是FAT的初始化了。具体的实现如下:先定义用来装入从BPB中读取的参数的结构:structFATInitArg{unsignedcharBPBSectorNoBPB所在扇区号unsignedlongTotalSize磁盘的总容量unsignedlongFirstDirClust根目录的开始簇unsignedlongFirstDataSector文件数据开始扇区号unsignedintBytesPerSector每个扇区的字节数unsignedintFATsectorsFAT表所占扇区数unsignedintSectorsPerClust每簇的扇区数unsignedlongFirstFATSector第一个FAT表所在扇区unsignedlongFirstDirSector第一个目录所在扇区unsignedlongRootDirSectors根目录所占扇区数unsignedlongRootDirCount根目录下的目录与文件数}当然也可以用零散的变量来存储参数但用结构体更方便管理也会使程序更为整洁。FAT的初始化将向结构中装入参数实现如下:voidFATInit(structFATInitArg*arg){structFATBPB*bpb=(structFATBPB*)(FATBuffer)将数据缓冲区指针转为structFATBPB型指针arg>BPBSectorNo=FATFindBPB()FATFindBPB()可以返回BPB所在的扇区号arg>TotalSize=FATGetTotalSize()FATGetTotalSize()可以返回磁盘的总容量单位是兆arg>FATsectors=lbbb((bpb>BPBFATSz),)装入FAT表占用的扇区数到FATsectors中arg>FirstDirClust=lbbb((bpb>BPBRootClus),)装入根目录簇号到FirstDirClust中arg>BytesPerSector=lbbb((bpb>BPBBytesPerSec),)装入每扇区字节数到BytesPerSector中arg>SectorsPerClust=lbbb((bpb>BPBSecPerClus),)装入每簇扇区数到SectorsPerClust中arg>FirstFATSector=lbbb((bpb>BPBRsvdSecCnt),)arg>BPBSectorNo装入第一个FAT表扇区号到FirstFATSector中arg>RootDirCount=lbbb((bpb>BPBRootEntCnt),)装入根目录项数到RootDirCount中arg>RootDirSectors=(arg>RootDirCount)*>>装入根目录占用的扇区数到RootDirSectors中arg>FirstDirSector=(arg>FirstFATSector)(bpb>BPBNumFATs)*(arg>FATsectors)装入第一个目录扇区到FirstDirSector中arg>FirstDataSector=(arg>FirstDirSector)(arg>RootDirSectors)装入第一个数据扇区到FirstDataSector中})FAT(文件分配表)FAT表是FAT文件系统中用于磁盘数据(文件)索引和定位引进的一种链式结构。可以说FAT表是FAT文件系统最有特色的一部分它的链式存储机制也是FAT的精华所在也正因为有了它才使得数据的存储可以不连续使磁盘的功能发挥得更为出色。FAT表到底在什么地方?它到底是什么样子的呢?从第一步从BPB中提取参数中的FirstFATSector就可以知道FAT表所在的扇区号。其实每一个FAT表都有另一个与它一模一样的FAT存在并且这两个FAT表是同步的也就是说对一个FAT表的操作同样地也应该在另一个FAT表进行相同的操作时刻保证它们内容的一致。这样是为了安全起见当一个FAT因为一些原因而遭到破坏的时候可以从另一个FAT表进行恢复。FAT表的内容如下图所示:上图就是一个实际的FAT表。前个字节“FFFFFFFFFFFFFF”为FAT的FAT表头标记用以表示此处是FAT表的开始。后面的数据每四个字节为一个簇项(从第簇开始)用以标记此簇的下一个簇号。拿我们创建的那个叫TESTTXT(大小为个字节)的文件来说如果这个文件的开始簇为第簇的话那么就到FAT表里来查找看文件是否有下一个簇(如果文件大小大于一个簇的容量必须会有数据存储到下一个簇但下一个簇与上一个簇不一定是连续的)可以看到“簇”的内容为“FFFFFFF”这样的标记就说明这个文件到第簇就已经结束了没有后继的簇即此文件的大小是小于一个簇的容量的。上面讲了很多都是围绕簇这样一个词来讲的簇又是什么?为什么要将它引入到FAT里来呢?磁盘上最小可寻址存储单元称为扇区通常每个扇区为个字节。由于多数文件比扇区大得多因此如果对一个文件分配最小的存储空间将使存储器能存储更多数据这个最小存储空间即称为簇。根据存储设备(磁盘、闪卡和硬盘)的容量簇的大小可以不同以使存储空间得到最有效的应用。在早期的KB磁盘上簇大小为个扇区(,字节)第一批的MB硬盘的簇大小增加到个扇区(,字节)现在的小型闪存设备上的典型簇大小是KB或KB。GB以上的硬盘驱动器有KB的簇。如果对于容量大的存储定义了比较小的簇的话就会使FAT表的体积很大从而造成数据的冗余和效率的下降。需要指出的是簇作为FAT进行数据存储的最小单位内部扇区是不能进一步细分的即使一个文件的数据写到一个簇中后簇中还有容量的剩余(里部扇区没有写满)哪怕这个簇只写了一个字节其它文件的数据也是不能接在后面继续数据的而只能另外找没有被占用的簇。我们按照初始化参数表中的SectorsPerClust可以知道一个簇中的扇区数笔者的SD卡实测簇大小为个扇区按照上面的说法TESTTXT这样一个只有个字节的文件也会占用一个簇的容量让我们在Windows里看看它的实际占用空间的情况。如下图:从上图可以看到文件大小为个字节但占用空间却是个字节(一个簇的容量个扇区)。TESTTXT容量只有个字节所以只占用了一个簇可能FAT表中还看不出链式结构现在我们再创建一个文件使它占用个簇如下:可以看到图中红色标记的就是文件所占用的个簇。从第簇开始簇项的内容为“”(小端模式)说明下一个簇为第簇而簇项的内容为“”,说明下一个簇为第簇……依此类推直到内容为“FFFFFFF”说明无后继簇文件数据到此结束。FAT表中的链式存储结构已经非常明显。把我们从FAT表中分析的结果与Windows的统计结束进行对比说明我们的解理是正确的如下图:从上面可以看到当数据结束于某一簇时FAT就用“FFFFFFF”来对其进行标记。其实还有其实的标记以表达其它的簇属性如“”表示未分配的簇“FFFFFFF”表示坏簇等。给出一个簇号计算出它的后继簇号是实现FAT的重点实现如下:unsignedlongFATGetNextCluster(unsignedlongLastCluster){unsignedlongtempstructFATFAT*pFATstructFATFATItem*pFATItemtemp=((LastCluster)InitArgFirstFATSector)计算给定簇号对应的簇项的扇区号FATReadSector(temp,FATBuffer)pFAT=(structFATFAT*)FATBufferpFATItem=((pFAT>Items)LastCluster)在算出的扇区中提取簇项returnlbbb(pFATItem,)返回下一簇号}那么FAT表有多大呢?FAT表中每四个字节表示一个簇所以FAT表的大小由实际的簇数来决定。从这里也可以看出如果簇过大就会则FAT表比较小但会造成空间的浪费而如果簇过小可以减小空间的浪费但会使FAT表变得臃肿。FAT表的大小也可以从BPB参数FATsectors读出。从上面的BPB图可以得知笔者的SD卡的FAT表大小为个扇区(“BE”的大端表示)。如果这个扇区每四个字节都表示一个簇项则它可以表示(*)=个簇(减去是因为有个字节的FAT表头标识。看看我们计算的是否正确呢下面是Winhex计算出来的簇数:与Winhex计算的结果是吻合的我们对FAT表与簇的理解是正确的。看完上面对FAT表的讲解中你可能会问:一个文件数据的首簇号怎样来确定呢?只有知道了一个文件数据的首簇号才能继续查找下一簇数据的位置直到数据结束。下面将要讲到的“根目录区”就可以由一个文件的文件名来查到它的首簇。)根目录区在FAT中其实已经把文件的概念进行扩展目录同样也是文件从根目录的地位与其它目录是相同的因此根目录也被看作是文件。既然是文件就会有文件名根目录的名称就是磁盘的卷标。如笔者的SD卡在格式会时设置卷标为znmcu则根目录的名称就为ZNMCU如下图:每一个文件都对应一个描述它属性的结构定义如下:FAT文件目录项个字节的定义字节偏移量字数量定义~文件名~扩展名x(读写)x(只读)x(隐藏)x(系统)x(卷标)x(子目录)属性字节x(归档)系统保留创建时间的毫秒位~文件创建时间~文件创建日期~文件最后访问日期~文件起始簇号的高位~文件的最近修改时间~文件的最近修改日期~文件起始簇号的低位~表示文件的长度根目录区所在扇区可从BPB参数FirstDirSector获取从BPB图得FirstDirSector=FirstFATSectorBPBNumFATs*FATsectors=。根目录区的初始大小为一个簇实际的内容如下:图中的记录描述根目录前八个字节为文件名“ZNMCU”(长度小于的部分用空格符补齐)下面的三个字节为扩展名“”(长度小于的部分用空格符补齐)表示此文件为卷标开始簇高字节为低字节为开始簇为文件长度为。记录描述TESTTXT文件文件名为“TEST”扩展名为“TXT”表示此文件为归档开始簇为(“”)长度为。记录描述BIGTESTTXT文件文件名为“BIGTES~”扩展名为“TXT”开始簇为长度为字节(CB)。可以看到FAT中的文件名都以大写字母表示长度不足的部分用空格符补齐所以我们要读取的文件TESTTXT就变成了“TESTTXT”这将有助于文件名的匹配我们不用去处理不等长文件名所带来的麻烦。另外还会发现长度过长的部分会被~所替换如果替换后有文件与之重名则~后面的数字将增加为。文件目录项结构的实现如下:structdirentry{unsignedchardeName文件名unsignedchardeExtension扩展名unsignedchardeAttributes文件属性unsignedchardeLowerCase系统保留unsignedchardeCHundredth创建时间的毫秒位unsignedchardeCTime文件创建时间unsignedchardeCDate文件创建日期unsignedchardeADate文件最后访问日期unsignedchardeHighClust文件起始簇号的高位unsignedchardeMTime文件的最近修改时间unsignedchardeMDate文件的最近修改日期unsignedchardeLowCluster文件起始簇号的低位unsignedchardeFileSize表示文件的长度}我们最终要实现的是对TESTTXT文件的读取须要作到给定文件名后可以得到相应文件的首簇。主要的思想就是对根目录区中(本实例只针对根目录中的文件进行读取至于多级子目录的实现只须要进行多次首簇定位)的记录进行扫描对记录中的文件名进行匹配。具体的实现如下:structFileInfoStruct*FATOpenFile(char*filepath){unsignedchardepth=unsignedchari,index=unsignedlongiFileSec,iCurFileSec,iFilestructdirentry*pFileiCurFileSec=InitArgFirstDirSectorfor(iFileSec=iCurFileSeciFileSec<iCurFileSec(InitArgSectorsPerClust)iFileSec){FATReadSector(iFileSec,FATBuffer)for(iFile=iFile<InitArgBytesPerSectoriFile=sizeof(structdirentry))对记录逐个扫描{pFile=((structdirentry*)(FATBufferiFile))if(FATCompareName(filepathindex,pFile>deName))对文件名进行匹配{FileInfoFileSize=lbbb(pFile>deFileSize,)strcpy(FileInfoFileName,filepathindex)FileInfoFileStartCluster=lbbb(pFile>deLowCluster,)lbbb(pFile>deHighClust,)*FileInfoFileCurCluster=FileInfoFileStartClusterFileInfoFileNextCluster=FATGetNextCluster(FileInfoFileCurCluster)FileInfoFileOffset=returnFileInfo}}}}这个函数在找到目标文件后会将此文件的一些参数信息装入到文件结构中为以后的文件读取作好准备。文件结构如下:structFileInfoStruct{unsignedcharFileName文件名unsignedlongFileStartCluster文件首簇号unsignedlongFileCurCluster文件当前簇号unsignedlongFileNextCluster下一簇号unsignedlongFileSize文件大小unsignedcharFileAttr文件属性unsignedshortFileCreateTime文件建立时间unsignedshortFileCreateDate文件建立日期unsignedshortFileMTime文件修改时间unsignedshortFileMDate文件修改日期unsignedlongFileSector文件当前扇区unsignedintFileOffset文件偏移量}通过对根目录区的扫描可以得到TESTTXT首簇为下面就可能以它为起点来读取文件内容了。)文件读取通过上面的讲解我们已经得到了TESTTXT的首簇。现在到作的就是到相应的簇及其后继簇去读取数据了。一直都在说簇比如第簇、第簇等等。那这些簇在磁盘的什么位置呢?从FAT表中可以看到簇号是从开始的而第簇的位置就在第二个FAT表(一共有两个FAT表它们即时同步)的后面即根目录所在的簇就为第簇。下面就为本篇教程的最后部分读TESTTXT文件的内容。主要思想是这样的:在已各文件首簇的前提下从首簇开始对于文件满一簇的数据就把整簇数据读出(其实还是按扇区来读只是一次性读出所有扇区)对于文件结尾不足一簇的部分计算它占用了簇内几个扇区把占用整个扇区部分直接按扇区读出而最后很有可能是零散的若干个字节不足一个扇区即占用了最后一个此文件最后一个扇区的一部分对于这部分我们也要将整个扇区读出截选中有效的数据部分。文件信息结构中的FileOffset参数将时刻记录文件读到的位置它与文件大小的差就是还未读取的数据数量。具体的实现如下:voidFATReadFile(structFileInfoStruct*pstru,unsignedlonglen){unsignedlongSub=pstru>FileSizepstru>FileOffsetunsignedlongiSectorInCluster=unsignedlongi=while(pstru>FileNextCluster!=xfffffff)如果FAT中的簇项为xfffffff说明无后继簇{for(iSectorInCluster=iSectorInCluster<InitArgSectorsPerClustiSectorInCluster)读出整簇数据{FATReadSector((((pstru>FileCurCluster))*(InitArgSectorsPerClust))InitArgFirstDataSector(iSectorInCluster),FATBuffer)pstru>FileOffset=InitArgBytesPerSectorSub=pstru>FileSizepstru>FileOffsetfor(i=i<InitArgBytesPerSectori){send(FATBufferi)将数据发送到终端上显示}}pstru>FileCurCluster=pstru>FileNextClusterpstru>FileNextCluster=FATGetNextCluster(pstru>FileCurCluster)这里是FAT簇链的传递}iSectorInCluster=while(Sub>=InitArgBytesPerSector)处理不足一簇而足扇区的数据{FATReadSector((((pstru>FileCurCluster))*(InitArgSectorsPerClust))InitArgFirstDataSector(iSectorInCluster),FATBuffer)pstru>FileOffset=InitArgBytesPerSectorSub=pstru>FileSizepstru>FileOffsetfor(i=i<InitArgBytesPerSectori){send(FATBufferi)}}FATReadSector((((pstru>FileCurCluster))*(InitArgSectorsPerClust))InitArgFirstDataSector(iSectorInCluster),FATBuffer)读取最后一个扇区for(i=i<Subi)Sub为最后剩余的字节数{send(FATBufferi)}})最终的实现在主函数中对磁盘驱动及以上函数进行正确合理的调用就可以达到我们要实现的效果了。主函数如下:#include<regh>#include<stringh>#include<stdioh>#include<mathh>#include<uarth>#include<fath>voidmain(){delay()UARTInit()串口初始化用以向调试终端发送数据sends("yahoo!!!")发送一个测试字符串MMCInit()SD卡初始化delay()MMCgetvolumeinfo()获得SD卡相关信息输出到终端FATInit(InitArg)FAT文件系统初始化装入参数Printf("BPBSectorNo",InitArgBPBSectorNo)Printf("TotalSize",InitArgTotalSize)Printf("FirstDirClust",InitArgFirstDirClust)Printf("FirstDataSector",InitArgFirstDataSector)Printf("BytesPerSector",InitArgBytesPerSector)Printf("FATsectors",InitArgFATsectors)Printf("SectorsPerClust",InitArgSectorsPerClust)Printf("FirstFATSector",InitArgFirstFATSector)Printf("FirstDirSector",InitArgFirstDirSector)以上几个语句用以输出参数值到终端Printf("FATOpenFile",(FATOpenFile("TESTTXT"))>FileSize)打开根目录下的TESTTXT文件并输出文件大小FATReadFile(FileInfo)读取文件数据输出到终端while()}最终实现的效果如下图所示:至此对于FAT文件系统根目录下的文件读取就已经实现了至于多级子目录结构可以像查找文件的首簇一样查找某一级目录名的首簇然后再到此簇下去找下一级目录的首簇直到最终的文件。

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/19

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

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利