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。 0X0014 记录3描述BIGTEST.TXT文件,文件名为“BIGTES~1”,扩展名为“TXT”,开始簇为4,长度为52000字节(0000CB20)。 可以看到FAT32中的文件名都以大写字母表示,长度不足的部分用空格符补齐,所以我们要读取的文件TEST.TXT就变成了“TEST .TXT”,这将有助于文件名的匹配,我们不用去处理不等长文件名所带来的麻烦。另外,还会发现长度过长的部分会被~1所替换,如果替换后有文件与之重名,则~后面的数字将增加为2。 文件目录项结构的实现如下: struct direntry { unsigned chardeName[8]; // 文件名 unsigned char deExtension[3]; // 扩展名 unsigned char deAttributes; // 文件属性 unsigned char deLowerCase; // 系统保留 unsigned char deCHundredth; // 创建时间的10 毫秒位 unsigned char deCTime[2]; // 文件创建时间 unsigned char deCDate[2]; // 文件创建日期 unsigned char deADate[2]; // 文件最后访问日期 unsigned char deHighClust[2]; // 文件起始簇号的高16 位 unsigned char deMTime[2]; // 文件的最近修改时间 unsigned char deMDate[2]; // 文件的最近修改日期 unsigned char deLowCluster[2];// 文件起始簇号的低16 位 unsigned char deFileSize[4]; // 表示文件的长度 } 我们最终要实现的是对TEST.TXT文件的读取,须要作到给定文件名后,可以得到相应文件的首簇。主要的思想就是对根目录区中(本实例只针对根目录中的文件进行读取,至于多级子目录的实现,只须要进行多次首簇定位)的记录进行扫描,对记录中的文件名进行匹配。具体的实现如下: struct FileInfoStruct * FAT32_OpenFile(char *filepath) { unsigned char depth=0; unsigned char i,index=1; unsigned long iFileSec,iCurFileSec,iFile; struct direntry *pFile; iCurFileSec=Init_Arg.FirstDirSector; for(iFileSec=iCurFileSec; iFileSec<iCurFileSec+(Init_Arg.SectorsPerClust); iFileSec++) { FAT32_ReadSector(iFileSec,FAT32_Buffer); for(iFile=0; iFile<Init_Arg.BytesPerSector; iFile+=sizeof(struct direntry))//对记录逐个扫描 { pFile=((struct direntry*)(FAT32_Buffer+iFile)); if(FAT32_CompareName(filepath+index,pFile->deName))//对文件名进行匹配 { FileInfo.FileSize=lb2bb(pFile->deFileSize,4); strcpy(FileInfo.FileName,filepath+index); FileInfo.FileStartCluster=lb2bb(pFile->deLowCluster,2)+lb2bb(pFile->deHighClust,2)*65536; FileInfo.FileCurCluster=FileInfo.FileStartCluster; FileInfo.FileNextCluster=FAT32_GetNextCluster(FileInfo.FileCurCluster); FileInfo.FileOffset=0; return &FileInfo; } } } } 这个函数在找到目标文件后,会将此文件的一些参数信息装入到文件结构中,为以后的文件读取作好准备。文件结构如下:struct FileInfoStruct { unsigned char FileName[12]; //文件名 unsigned long FileStartCluster;//文件首簇号 unsigned long FileCurCluster;//文件当前簇号 unsigned long FileNextCluster;//下一簇号 unsigned long FileSize; //文件大小 unsigned char FileAttr; //文件属性 unsigned short FileCreateTime; //文件建立时间 unsigned short FileCreateDate; //文件建立日期 unsigned short FileMTime;//文件修改时间 unsigned short FileMDate;//文件修改日期 unsigned long FileSector; //文件当前扇区 unsigned int FileOffset; //文件偏移量 }; 通过对根目录区的扫描,可以得到TEST.TXT首簇为3,下面就可能以它为起点,来读取文件内容了。 5)文件读取 通过上面的讲解,我们已经得到了TEST.TXT的首簇。现在要做的就是到相应的簇及其后继簇去读取数据了。一直都在说簇,比如第2簇、第3簇等等。那这些簇在磁盘的什么位置呢?从FAT表中可以看到,簇号是从2开始的,而第2簇的位置就在第二个FAT表(一共有两个FAT表,它们即时同步)的后面,即根目录所在的簇就为第2簇。 下面就为本篇教程的最后部分,读TEST.TXT文件的内容。主要思想是这样的:在已各文件首簇的前提下,从首簇开始,对于文件满一簇的数据,就把整簇数据读出(其实还是按扇区来读,只是一次性读出所有扇区),对于文件结尾不足一簇的部分,计算它占用了簇内几个扇区,把占用整个扇区部分直接按扇区读出,而最后很有可能是零散的若干个字节,不足一个扇区,即占用了最后一个此文件最后一个扇区的一部分,对于这部分我们也要将整个扇区读出,截选中有效的数据部分。文件信息结构中的FileOffset参数将时刻记录文件读到的位置,它与文件大小的差就是还未读取的数据数量。 具体的实现如下: void FAT32_ReadFile(struct FileInfoStruct *pstru,unsignedlong len) { unsigned longSub=pstru->FileSize-pstru->FileOffset; unsigned long iSectorInCluster=0; unsigned long i=0; while(pstru->FileNextCluster!=0x0fffffff) //如果FAT中的簇项为0x0fffffff,说明无后继簇 { for(iSectorInCluster=0;iSectorInCluster<Init_Arg.SectorsPerClust;iSectorInCluster++) //读出整簇数据 { FAT32_ReadSector((((pstru->FileCurCluster)-2)*(Init_Arg.SectorsPerClust))+Init_Arg.FirstDataSector +(iSectorInCluster),FAT32_Buffer); pstru->FileOffset+=Init_Arg.BytesPerSector; Sub=pstru->FileSize-pstru->FileOffset; for(i=0;i<Init_Arg.BytesPerSector;i++) { send(FAT32_Buffer); //将数据发送到终端上显示 } } pstru->FileCurCluster=pstru->FileNextCluster; pstru->FileNextCluster=FAT32_GetNextCluster( pstru->FileCurCluster); //这里是FAT簇链的传递 } iSectorInCluster=0; while(Sub>=Init_Arg.BytesPerSector) //处理不足一簇,而足扇区的数据 { FAT32_ReadSector((((pstru->FileCurCluster)-2)*(Init_Arg.SectorsPerClust))+Init_Arg.FirstDataSector +(iSectorInCluster++),FAT32_Buffer); pstru->FileOffset+=Init_Arg.BytesPerSector; Sub=pstru->FileSize-pstru->FileOffset; for(i=0;i<Init_Arg.BytesPerSector;i++) { send(FAT32_Buffer); } } FAT32_ReadSector((((pstru->FileCurCluster)-2)*(Init_Arg.SectorsPerClust))+Init_Arg.FirstDataSector +(iSectorInCluster),FAT32_Buffer); //读取最后一个扇区 for(i=0;i<Sub;i++) //Sub为最后剩余的字节数 { send(FAT32_Buffer);} }
|