http://blog.sina.com.cn/s/blog_3edcf6b80100crz1.html
FAT12文件系统之数据存储方式详解
FAT12文件系统共分为四部分:引导扇区、FAT文件分配表1和2、根目录区、用户数据区,在前面的文章中详细介绍了FAT12格式的引导扇区数据结构,详情请浏览:
地址是:http://blog.sina.com.cn/s/blog_3edcf6b80100cr08.html
同时,也编写了一个简单的引导程序来验证了这个引导扇区的数据结构,详情请浏览:
地址是:http://blog.sina.com.cn/s/blog_3edcf6b80100crl4.html
下面我们来详细了解一下FAT文件分配表、根目录、用户数据的数据结构,只有通过详细分析这些数据结构,才能自由的存取FAT12格式的磁盘文件。
我们先来看看文件分配表的数据格式,文件分配表所在的扇区应该是(隐藏扇区+保留扇区)=0+1=第1扇区处,从第1扇区开起到第9扇区结束,第一个文件分配表共占用9个扇区,第二个文件分配表从第10个扇区开始到第18扇区结束,在引导扇区的数据结构中明明确的指出了这些位置。
文件分配表数据结构如下图所示:
在FAT表开始扇区的第1字节是存储介质,0f0h代表软盘,0f8代表硬盘;第2、3这两个字节都是0ffh,代表了FAT文件分配表标识符,从第四个字节开始与用户数据区所有的簇一一对应,应该注意的是,用户数据区的第一个簇的序号是002,而不是000,因为储存介质和标识符占用了这两个序号。
在FAT12格式中用12比特位来代表一个簇的序号,我们知道,每个字节有8位比特,所以每个簇要占用1.5个字节,也就是说,占用了第1字节和第2字节的一半才能表示一个簇的序号,半字节的拆分办法按照下图的方式进行:
例如:在FAT表中开始位置储存的字节内容依次是F0 FF FF FF 4F 00 05 F0 FF,前面三个字节是储存介质和标识符,我们不管它,前面三个字节占用了0和1这两个簇序号,那么就应该从2簇开始了。经过转换得到的簇序号是:0fffh 004h 005h 0fffh,簇号是12位比特,第4字节(1111 1111)的作为第2簇号的低8位(0-7),第5字节(4F)的低4位(1111)作为第2簇号的高4位(8-11),这样就得到了第2簇号的内容为0fffh;然后第5字节的高4位(0100)作为第3簇号的低4位(0-3),第6字节(0000 0000)作为第3簇号的高8位(4-11),这样便得到了第3簇号的内容为004h;第7字节(0000 0101)作为第4簇号的低8位(0-7),第8字节的低4位(0000)作为第4簇号的高4位(8-11),这样可以得到第4簇号的内容为005h;第8字节的高4位(1111)作为第5簇号的低4位(0-3),第9字节(1111 1111)作为第5簇号的高8位(4-11),这样得到第5簇号的内容为0fffh。
这里叙述得非常的繁琐,实际情形是,用汇编来读取双字节(16位),然后执行AND或右移操作就可以得到相应的簇号,非常的简便,后文会讲述到,从这里可以清楚,使用汇编来操作字节或位,非常的简单和功能强大。
最终我们得到了这4个簇号的内容,内容是0ff0-0ff7h代表坏簇,磁道或柱面损坏不可使用,在格式式磁盘时由系统自动填充;内容是0ff8-0fffh代表文件内容结束,到此簇为止;其它的值代表着下一个簇号,接着我们分析一下刚才得到的那4个簇号的内容代表的意义,第2簇号的内容是0fffh,代表这个文件只占用了第2簇,文件的大小在512B(每簇1扇区512字节)之内。第3簇号的内容是004h,代表它的下一个簇是第4簇;第4簇号的内容是005h,代表它的下一个簇是第5簇,第5簇号的内容是0fffh,代表文件内容结束,所以这个文件应该是从第3簇开始到第5簇结束,占据了3簇的空间,文件大小在1536B(每簇1扇区512字节)之内。
这种在簇号内容中储存下一个簇号的存储方式称为簇链,从文件目录项中查找到文件的首簇号,再在FAT文件分配表中查找对应的下一个簇号内容,直到它的内容是0fffh为止,就可以得到整个文件占用的所有簇空间。
文件目录项最开始位于扇区19(隐藏隐藏+保留扇区+FAT文件分配表占用扇区=0+1+9+9)处,它一共占用了14个扇区的空间,在引导扇区的数据结构中偏移17处的字段BPB_RootEntCnt指出了最大根目录数,在FAT12中的默认值是224,每个文件目录项占用了32字节的空间,那么共占用224*32/512=14扇区。
文件目录项的32字节数据结构如下图所示:
每个文件目录项的长度是32字节,偏移0处的8字节长的文件名,必须是大写的ASCII字符串,不足8字节就以空格(20h)来填充,接着是3字节长的文件后缀名,同样是大写的ASCII字符串,不足3字节以空格填充。如果文件名的第1字节是0E5h表示此文件已经删除,第1字节是0表示此目录项可用。
位于偏移11处的是文件属性,使用比特位来设置值:
00000000:普通文件,可随意读写
00000001:只读文件,不可改写
00000010:隐藏文件,浏览文件时隐藏列表
00000100:系统文件,删除的时候会有提示
00001000:卷标,作为磁盘的卷标识符
00010000:目录文件,此文件是一个子目录,它的内容就是此目录下的所有文件目录项
00100000:归档文件
文件的属性可以叠加使用,可以具有多重属性,即设置为只读的时候也可以同时隐藏。
偏移12处的共10字节长的内容没有使用,保留。
偏移22处的是双字节长的文件最后修改时间,使用的方式是分位压缩存储方式,两个字节共16位从高到低分别存储时分秒的数值,其中时占用5位(11-15),值从0-23,代表小时;分占用6位(5-10),值从0-59,代表分钟;秒占用5位(0-4),值为0-29,它的倍数(值*2)就得到秒数。
偏移24处的是双字节长的文件最后修改日期,使用的方式是分位压缩存储方式,两个字节共16位从高到低分别存储年月日的数值,其中年占用7位(9-15),值从0-199,此值加上1980就可以得到年份,代表从1980到2099年;月占用4位(5-8),值从1-12,代表月份;日占用5位(0-4),值从1-31,代表当月天数。
位于偏移26处的是双字节长的文件首簇号,利用此值就可以得到文件内容占用的第一个簇,然后在文件分配表中就可以得到所有的文件簇了。
最后位于偏移28处的双字(4B)长的文件长度(字节)值。
以上据说的扇区都是逻辑扇区,而通过BIOS读取的扇区都是使用CHS参数Int13h功能实现的,逻辑扇区转换成磁盘CHS参数的计算方式如下所示:
根目录所占扇区数=最大根目录文件数*32/每扇区字节数
注意,在FAT12中:0e0h*32/512=14
用户数据区起始扇区=隐藏扇区+保留扇区+FAT表数*FAT表所占扇区+根目录所占扇区
注意,在FAT12中:0+1+2*9+14=33
簇起始线性扇区=用户数据区起始扇区+(簇号-2)*每簇所占扇区-1
例如:第2号簇的起始线性扇区=33+(2-2)*1-1=32,而第6号簇的起始线性扇区=33+(6-2)*1-1=36
这样我们便可以得到具体簇号所在的起始线性扇区了,将线性扇区转换为磁盘CHS参数如下所示:
扇区=线性扇区 MOD 每磁道扇区数+1
注意:MOD为整除取模(余数);如:32 MOD 18+1=14+1=15
磁道=线性扇区/每磁道扇区数/磁头数
注意:除法为取整,在FAT12默认情况中,每磁道扇区数为18,磁头数为2;如:32/18/2=1/2=0
磁头=(线性扇区/每磁道扇区数) MOD 磁头数
注意:除法为取整,MOD为整除取模;如:(32/18) MOD 2=1 MOD 2=1
在文件根目录项和FAT文件分配表中得到具体的簇号后就可以根据上面的公式计算出磁盘CHS参数后,就可以通过BIOS的Int13h功能来进行读取扇区内容的操作了。在后文中,将编写一个能够读取FAT12格式下的一个子目录中的引导加载文件到内存中,以验证这篇文章的有效性,敬请浏览。
附录(小知识):系统如何检测文件系统是FAT12还是FAT16?或者FAT32?
千万不要依靠引导扇区数据结构中的BS_FileSysType字段(在FAT12中位于偏移54处)来比较这个字符串,很多时候这个字段很不准确,或许压根就没有(全是0或空格20h),这个字段存在与否或内容是什么,与文件系统一点关系也没有,采取什么样的文件系统只有一个计算标准:此卷所有簇的数量。
簇总数=(逻辑扇区总数-(隐藏扇区+保留扇区+FAT表数*FAT表所占用扇区+根目录所占用扇区))/每簇扇区数
注:逻辑扇区总数位于引导扇区数据结构偏移19处(字段BPB_TotSec16,长度为2字节)或32处(字段BPB_TotSec32,长度为4字节),例如,在FAT12默认值中:(2880-(0+1+2*9+14))/1=2847
卷中簇的总数小于4085的为FAT12,总数大于或等于4085并且小于65525的为FAT16,总数大于或等于65525的为FAT32。这就是Microsoft操作系统认可的区分文件系统的标准,必须遵循此标准来操作磁盘卷,否则,Microsoft的操作系统将认为此卷损坏导致不可使用。