2024年1月6日发(作者:)
振南的znFAT单片机上的FAT32文件系统----单片机上的第四章统揽全局三分胜算上一章中我们详细介绍了几种把SD卡格式化为FAT32文件系统的方法。其实,格式化SD卡的时候,计算机就已经将一些与这张SD卡上的FAT32文件系统相关的参数写到了专门的扇区中了。这些参数是我们进行文件操作的根本依据。比如扇区大小、簇扇区数、根目录首簇等等。由于这些参数极为重要,而且在各种文件操作中都要用到,所以我们称之为全局性重要参数。这些参数基本上把FAT32文件系统整体的轮廓勾画出来了,让我们可以对其有一个全局的认识。本章会让您看到如何解读这些参数,以及它们的具体作用。同时从这一章开始我们就开始看到代码了,这需要您有一定的C语言基础。好,我们来看详细内容。第一节解读MBR(主引导记录)1、解析DPT中的记录我们要解读的第一个部分就是MBR(主引导记录)。如果我们照搬FAT32文件系统协议的定义,估计将会使我们的讲述非常无趣。我们的一切研究均从实际出发,所以我们来直接看SD卡的MBR部分的数据,当然我们是用WinHex软件来查看了,这在前面已经讲过。那么MBR在SD卡的哪个扇区呢?这是一个绝对的答案:如果它存在,那么它就一定在0扇区!(这里为什么说“如果它存在”,到后面讲解
DBR的时候您就明白了)如图4.1所示,SD卡的0扇区。图4.1SD卡的0扇区上图中我们看到的就是SD卡的0扇区中的数据,也就是所谓的MBR(主引导记录)。那么这些数据表达了怎样的意义呢?这就需要
我们仔细分析了。这512字节的数据,前面446字节我们不用关心,重点在于图中蓝色标出的64字节。这64字节其实是4条记录,每条记录是16字节,用来记录SD卡上的分区信息。FAT32文件系统称之为DPT(磁盘分区表)。如图4.2,MBR扇区示意图。图4.2DBR扇区示意图我们先取出第一个记录来进行分析:80:表明这是一个有效的分区。如果这里是00,则说明此分区无效。01:开始磁头。0100:开始扇区与开始柱面。0B:系统ID。1F:结束磁头。
FFD8:结束扇区与结束柱面。3F000000:分区的开始扇区。A14C1E00:总扇区数。我们可以看到其实对参数的解析非常简单,FAT32就是把很多参数依次进行存放,我们只需要将这些字节拼在一起就可以得到相应的参数了。比如我们要获得分区的总扇区数,那么我们将这4个字节:A14C1E00拼成一个unsignedlong型的变量,它表达的就是这个参数的值。解析完第一个记录之后,我们再来看后面的三个记录。它们同样也用来描述分区,如果这张SD卡被分成了多个区,那么后面的记录就会分别记录相应分区相关参数。你可能会问,后面只剩下三个记录了,难道FAT32只支持4个分区?这一点显然与我们实际应用中看到的不相符。我们的磁盘被分为4、5个分区是很常见的事情。其实这是因为有扩展分区的机制在起作用。这部分深入进去也是较为复杂的,我们只研究SD卡上仅有一个分区的情况(其实通常情况下我们很少在SD卡上进行分区,尤其是在嵌入式应用中),所以我们看到后面的记录都是0。综上所述,我们要解析DPT中的记录,可使用这样的结构体。structPartRecord{//DPT:分区记录结构如下
UINT8Active;UINT8StartHead;//0x80表示此分区有效//分区的开始磁头UINT8StartCylSect[2];//开始柱面与扇区UINT8PartType;UINT8EndHead;//分区类型//分区的结束头UINT8EndCylSect[2];//结束柱面与扇区UINT8StartLBA[4];UINT8Size[4];};当然,MBR的结构体我们也可以轻松得到。//分区的第一个扇区//分区的大小structPartSector{UINT8PartCode[446];//MBR的引导程序structPartRecordPart[4];//4个分区记录UINT8BootSectSig0;//0x55UINT8BootSectSig1;//0xAA};到后面,我们就可以看到znFAT中是如何使用这些结构体来进行相关参数的计算和提取的。2、“大端”与“小端”(Big-Endian与Little-Endian)在讲解什么是“大端”与“小端”前,让振南先给您讲一个故事,
这也是这两个名词的来历。Endian这个词来源于JonathanSwift在1726年写的讽刺小说“Gulliver‘sTravels”(《格利佛游记》)。该小说在描述Gulliver畅游小人国时碰到了如下的一个场景。在小人国里的小人因为非常小(身高6英寸)所以总是碰到一些意想不到的问题。有一次因为对水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开的争论而引发了一场战争,并形成了两支截然对立的队伍:支持从Big-End剥开的人Swift就称作Big-Endians而支持从Little-End剥开的人就称作Little-Endians(后缀ian表明的就是支持某种观点的人)。Endian这个词由此而来。为什么要引出这样的两个名词,往后看您就会明白了。其实细心的读者已经发现了一些问题。上面获取分区总扇区数的例子中,将A14C1E00拼为一个unsignedlong型的变量,即0xa14c1e00=2706120192,也就是说这张容量为1G的SD卡上唯一的分区总扇区数为2706120192,这样算出来的容量为1290G。这是怎么回事,起码这个值要与1G差不多?!其实这里在计算上我们犯了一个错误,从根本上来讲,也不能算是错误,只是因为FAT32文件系统中的参数字节排列顺序与单片机上不同。这也就是所谓的“大端小端”问题。我们举例来说:拿一个unsignedlong型的变量来说,如果它的值为0x12345678,那么构成这个变量的4个字节的值分别为0x12、0x34、0x56、0x78。这个变量必然被存储在内存之中,那么不同的CPU,在
存储方式上有所不同。如果是在PC上(PC上的CPU,通常是CISC的,为小端模式),那么这个变量的最高字节0x12被存储在高地址上,最低字节0x78被存储在低地址上,最终排列为0x780x560x340x12;而如果是在51上(单片机的内核CPU通常是RISC的,为大端模式),则截然相反,排列为0x120x340x560x78。当然,如果将大端与小端混淆了,就会使变量的值大相径庭。这里要点明的是,FAT32文件系统最初为PC设计,所以它遵循小端模式的存储规则。上面的总扇区数其实应该是0x001e4ca1=1985697,也就是SD卡的分区容量约为977M,也就是1016676864字节。让我们看看是不是与WinHex分析的相吻合(WinHex以逻辑方式打开磁盘,就可以对磁盘进行文件系统级的分析)。如图4.2,WinHex分析得到的总容量。图4.2WinHex分析得到的总容量可以看到,我们计算的结果与WinHex分析的结果是完全吻合的。这说明我们的理解是正确的。在后面对参数的计算中也同样采用这种方式。因此,我们有必要写一个函数,用来把小端转为大端。UINT32LE2BE(UINT8*dat,UINT8len){
,fact=1;UINT32temp=0temp=0,UINT8i=0;for(i=0;i 图4.4SD卡的DBR扇区上面就是SD卡的DBR扇区数据,其中蓝色标记的部分就是BPB(79字节)。很多重要的参数都记录在其中。到底都有哪些参数,它们分别占用了哪些字节,我们来看这样一张图。图4.5,DBR扇区示意图。 图4.5DBR扇区示意图可以看到,BPB中的参数比DPT中要复杂得多,这是因为绝大部分的全局重要参数都在这里,所以BPB是极为重要的。对BPB的解析是否正确,直接关系到FAT32文件系统实现成功与否。我们依照图中的标注及WinHex中得到的DBR扇区数据来仔细分析一下这些参数。0002:每扇区的字节数(0x0200=512)08:每簇扇区数(0x08=8)2000:保留扇区数(0x0020=32)02:FAT表数(0x02=2)0000:FAT32固定为00000:FAT32固定为0F8:存储介质类型 0000:FAT32固定为03F00:磁道扇区数00FF:磁头数3F000000:FAT区前隐扇区数(0x0000003F=63)A14C1E00:总扇区数(0x001E4CA1=1985697)90070000:FAT表所占扇区数(0x00000790=1936)0000:FAT32特有0000:FAT32特有02000000:第一个目录的簇号(0x00000002=2)0100:FSINFO扇区数0600:通常为612个00:用于扩展00:驱动器号00:保留29:扩展引导标签7A160E90:分区序列4E4F204E414D4520202020:卷标46420:系统ID按照上面的定义,我们可以构造出这样一个结构体了(结构体中除了BPB部分,还包含了前面的一小段数据,这是为了便于数据解析)。structFAT32_BPB//FAT32中对BPB的定义如下 {UINT8BS_jmpBoot[3];UINT8BS_OEMName[8];//跳转指令//UINT8BPB_BytesPerSec[2];//每扇区字节数UINT8BPB_SecPerClus[1];//每簇扇区数UINT8BPB_RsvdSecCnt[2];//保留扇区数目UINT8BPB_NumFATs[1];//此卷中FAT表数UINT8BPB_RootEntCnt[2];//FAT32为0UINT8BPB_TotSec16[2];UINT8BPB_Media[1];UINT8BPB_FATSz16[2];UINT8BPB_SecPerTrk[2];//FAT32为0//存储介质//FAT32为0//磁道扇区数UINT8BPB_NumHeads[2];//磁头数UINT8BPB_HiddSec[4];UINT8BPB_TotSec32[4];UINT8BPB_FATSz32[4];UINT8BPB_ExtFlags[2];UINT8BPB_FSVer[2];UINT8BPB_RootClus[4];UINT8FSInfo[2];//FAT区前隐扇区数//该卷总扇区数//一个FAT表扇区数//FAT32特有//FAT32特有//根目录簇号//保留扇区FSINFO扇区数UINT8BPB_BkBootSec[2];//通常为6UINT8BPB_Reserved[12];//扩展用 UINT8BS_DrvNum[1];UINT8BS_Reserved1[1];UINT8BS_BootSig[1];UINT8BS_VolID[4];UINT8BS_FilSysType[11];UINT8BS_FilSysType1[8];};看了上面的参数解析,你一定会说:“BPB中的参数确实是太复杂了!”。表面上来看是这样的,这也是初学FAT32文件系统不好入门的地方,刚开始就被繁多的参数阻拦,使我们无法前行,但如果你了解了这些参数是用来作什么的,对FAT32文件系统有什么意义之后,就会觉得思路畅通,水到渠成了。参数虽多,但实际上并不是每一个我们都用得到的,我们需要将那些重要的参数提取出来。0002:每扇区的字节数(0x0200=512)08:每簇扇区数(0x08=8)2000:保留扇区数(0x0020=32)02:FAT表数(0x02=2)90070000:FAT表所占扇区数(0x00000790=1936)02000000:第一个目录的簇号(0x00000002=2)为什么单单把这几个参数提出来?在了解了FAT32文件系统的整体结构之后,您就明白了! 其实相信很多人在看本节的时候心里一直带着一个疑问:DBR扇区在哪里?仔细看了第一节的人就会知道,MBR中的DPT中有一个字段叫作“分区的第一个扇区”,它就是所谓的DBR了(该字段为3F000000,计算得到的值为63,即MBR在63扇区)。但如果您有印象,我们还说过一句话“如果MBR存在,那么它在0扇区”,言外之意是MBR是有可能不存在的(MBR存在与否决定于格式化软件)。如果MBR不存在,那么0扇区就是DBR,而判断的依据就是扇区数据的第一个字节是否是0xEB。所以在定位DBR时,程序在处理上就要更灵活一些,以下就是znFAT中定位DBR的函数。UINT32FAT32_Find_DBR(){UINT32sec_dbr;FAT32_ReadSector(0,FAT32_Buffer);//读0扇区到缓冲区if(FAT32_Buffer[0]!=0xeb)//如果缓冲区第一个字节不是0xeb,说明MBR存在{sec_dbr=LE2BE(((((structPartSector*)(FAT32_Buffer))->Part[0]).StartLBA),4);//从MBR的DPT中解析出DBR的扇区地址}else {sec_dbr=0;//否则说明MBR不存在,0扇区就是DBR}returnsec_dbr;//返回DBR所在的扇区地址}在这段程序中,我们可以看到它使用了上面我们定义的MBR的结构体,采用指针的强制类型转换的方法直接定位到分区开始扇区的字节序列,并调用LE2BE函数将其转为大端模式,最终得到实际的值。这种对扇区数据的某些字段进行解析提取的方法,我们在后面的程序中会经常见到,如果您在理解上仍有困难,请翻阅C语言的相关教程,先把C语言学扎实!另外就是FAT32_ReadSector这个函数也是第一次出现,顾名思义,它用来读取存储设备的扇区。与FAT32_WriteSector一起构成了znFAT的底层存储设备驱动接口。如图4.6,znFAT的底层存储设备驱动接口。图4.6znFAT的底层存储设备驱动接口这两个函数其实是两个空壳,需要根据具体的存储设备进行编写,这也就使得znFAT可以适用于很多的存储设备,比如SD卡、CF 卡、MS卡等等。下面就是以SD卡为设备存储的底层接口函数的具体实现。UINT8FAT32_ReadSector(UINT32addr,UINT8*buf){returnSD_Read_Sector(addr,buf);//SD卡扇区读函数}UINT8FAT32_WriteSector(UINT32addr,UINT8*buf){returnSD_Write_Sector(addr,buf);//SD卡扇区写函数}可以看到这两个函数的实现是很简单的,都是直接调用存储设备的扇区读写函数(关于SD卡驱动方法我们在后面会有专门章节进行讲解)。到了后面,我们在这个底层接口上会翻出更多花样,从而使znFAT实现多设备功能,此为后话,暂且不提。4、对FAT32文件系统各部分的定位解析全局参数的目的在于对FAT32各部分进行定位,比如前面讲到的MBR、DBR,再如后面将要讲到的FAT表、首目录簇等。对各部分在SD卡上的位置进行准确定位,意义是极其重大的。想像一下,我们连根目录都找不到,又何谈对操作文件呢?连FAT表在哪里都不晓得,怎能知道文件数据的分布情况?此时,我们有必要对FAT32文件系统的整体结构有一个了解了,随后我们再用上面我们提取出来的 参数来计算这些部分的具体位置。我们先来看这样一张图,图4.7,FAT32整体结构。图4.7FAT32整体结构对图中各个部分的具体功能和定义我们会在专门的章节进行讲解,这里我们要作的是计算它们的具体位置,也就是它们分别从哪个扇区开始,到哪个扇区结束。MBR与DBR就不用说了。DBR后面是保留区,我们提取出来的参数中有保留扇区数,值为32。这说明这部分占用了32个扇区。再后面就是第一个FAT表,它占用了1936个扇区,这样可以轻松计算得到第一个FAT表以及紧随其后的第二个FAT表的开始扇区。第一个FAT表开始扇区=DBR扇区+保留扇区数(95)第二个FAT表开始扇区=第一个FAT表开始扇区+FAT表所占扇区数(2031) 最后是首目录簇(通常是第2簇),它是我们访问任何文件或目录的入口,也就是平常所说的根目录!而且从这里开始后面的数据都以“簇”为单位(簇的概念会在后面讲到,它也是极为重要的,这里只需要知道簇是由若干扇区组成的即可),在其之前,数据都以扇区为单位。那么首目录簇的开始扇区如何计算呢?其实道理是一样的。首目录簇的开始扇区=第一个FAT表的开始扇区+2*FAT表所占扇区数(3967)其实得到了首目录簇,即第2簇的开始扇区,我们也就知道了任何一个簇的开始扇区。第n簇开始扇区=(n-首目录簇)*簇所占扇区数+首目录簇开始扇区我们来看一下我们解析计算出来的参数值是否与WinHex中相吻合。如图4.8,WinHex中首目录簇的开始扇区。图4.8WinHex中首目录簇的开始扇区 图中WinHex中的首目录簇的开始扇区为3967(在逻辑方式下,逻辑扇区是以DBR所在扇区为0扇区的,所以与物理扇区相差63),这与我们算出来的值是一致的,说明我们的理解是正确的。这样,我们就已经知道这张SD卡的FAT32文件系统的各个部分的准确位置的了,再加上前面解析出来的一些参数,它们在我们后面的程序中都会被经常用到,所以我们构造出这样一个结构体,用来装载这些信息,在文件系统的运行周期内,结构体内数据都是一直存在的。structFAT32_Init_Arg{UINT8BPB_Sector_No;//BPB所在扇区号UINT32FirstDirClust;//首目录簇UINT32BytesPerSector;//每个扇区的字节数UINT32FATsectors;//FAT表所占扇区数UINT32SectorsPerClust;//每簇的扇区数UINT32FirstFATSector;//第一个FAT表开始扇区UINT32FirstDirSector;//首目录簇开始扇区}; 下面我们就通过这个函数装参数装入到这个结构体中。voidFAT32_Init(){structFAT32_BPB*bpb;bpb=(structFAT32_BPB*)(FAT32_Buffer);//将数据缓冲区指针转为structFAT32_BPB型指针pArg->BPB_Sector_No=FAT32_Find_DBR();//FAT32_FindBPB()可以返回BPB所在的扇区号pArg->FATsectors=LE2BE((bpb->BPB_FATSz32),4);//装入FAT表占用的扇区数到FATsectors中pArg->FirstDirClust=LE2BE((bpb->BPB_RootClus),4);//装入根目录簇号到FirstDirClust中pArg->BytesPerSector=LE2BE((bpb->BPB_BytesPerSec),2);//装入每扇区字节数到BytesPerSector中pArg->SectorsPerClust=LE2BE((bpb->BPB_SecPerClus),1);//装入每簇扇区数到SectorsPerClust中 pArg->FirstFATSector=LE2BE((bpb->BPB_RsvdSecCnt),2)+pArg->BPB_Sector_No;//装入第一个FAT表扇区号到FirstFATSector中pArg->FirstDirSector=(pArg->FirstFATSector)+(bpb->BPB_NumFATs[0])*(pArg->FATsectors);//装入第一个目录扇区到FirstDirSector中}这个函数就是znFAT的第一个应用接口函数----文件系统初始化。现在,这只用来装载文件系统相关参数,在后面我们将对其进行扩展,加入更多的变量,比如为了实现多设备功能而加入的设备号,来源于FSInfo的新簇参考值等等。本章到此将尽,我们已经了解了FAT32文件系统的大概轮廓,知道了各个部分位置的计算方法。然而这些部分是用来作什么的?它们具体的定义是怎样的?这些都是后面我们要讲解的内容。在下章中,我们将会在SD卡上拷入一个TXT文本文件,振南会告诉你如果找到这个文件的数据以及解析出这个文件的相关信息。


发布评论