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文件的读取,须要作到给定文件名后,可以得到相应文件的首簇。
主要的思想就是对根目录区中(本实例只针对根目录中的文件进行读取,至于多级子目录的实现,只须要进行多次首簇定位)的记录进