- UID
- 1
- 精华
- 积分
- 76361
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
现在可以详细说明一下软盘FAT12的文件系统结构了。
FAT12只存在于软盘,因此这篇帖子也是在说明如何对软盘中的文件进行操作。
软盘有如下五个部分:
1、DBR扇区
2、FAT表1
3、FAT表2
4、根目录区
5、数据区
DBR扇区的结构是这样的:(以下所说的软盘都是指的是3.5英寸软盘,不是别的软盘)
资料:http://www.0xaa55.com/thread-60-1-1.html
偏移 | 长度 | 描述 | 0x000 | 3 | JMP跳转指令 | 0x003 | 8 | OEM信息(比如操作系统名什么的) | 0x00B | 2 | 每扇区字节数 | 0x00D | 1 | 每簇扇区数 | 0x00E | 2 | 保留扇区数 | 0x010 | 1 | FAT表的数目 | 0x011 | 2 | 根目录表最大项数 | 0x013 | 2 | 分区扇区总数 | 0x015 | 1 | 介质类型 | 0x016 | 2 | 每个FAT表的扇区数 | 0x018 | 2 | 每磁道扇区数 | 0x01A | 2 | 磁头数 | 0x01C | 4 | 隐藏扇区数 | 0x020 | 4 | 分区扇区总数(32位) | 0x024 | 1 | 驱动器号 | 0x025 | 1 | 当前磁头号 | | 0x026 | 1 | BPB扩展引导标识 | 0x027 | 4 | 分区序列号(一般为随机数) | 0x02B | 11 | 卷标 | 0x036 | 8 | 文件系统类型 | | 0x03E | 448 | 引导指令 | 0x1FE | 2 | 可引导标识0xAA55 |
共0x200(512)字节
下面进行详细的注解。
- 跳转指令:如果是一个短跳转(指令只占用两个字节)那么第三个字节就用NOP填充。总之要填充3个字节。
- OEM信息:只是指示了DBR扇区的建立者而已,8字节字符。
- 每扇区字节数:和硬件相关。因为是软盘,每扇区字节数为512字节。
- 每簇扇区数:FAT文件系统存储文件的最小单位是簇,所以每簇扇区数的值决定了簇的大小。但是因为是软盘,每簇扇区数固定为1。
- 保留扇区数:所谓保留扇区数,就是包含DBR在内的分区内,FAT表之前的扇区数。因为是软盘,DBR扇区后面紧跟着FAT表,所以保留扇区数为1。
- FAT表的数目:一般都是有两个FAT表,第二个表通常用作备份。如果第一个FAT表损坏了,就拿第二个表去覆盖第一个表。软盘FAT12有两个FAT表。
- 根目录表最大项数:这个值决定了根目录表的大小。
- 分区扇区总数:16位表示的分区的扇区总数,如果分区扇区总数大于65535个扇区,则这个值为零,并且扇区总数的32位数值保存在0x020处。因为是软盘,这个值等于80*18*2等于2880扇区(0x0B40)
- 介质类型:目前只能用到两个值:1、3.5英寸软盘,值为0xF0;2、硬盘,值为0xF8。其他类型的软盘很少见,我们也不想兼容。将来也不再出现。
- 每个FAT表的扇区数:即单个FAT表占用的扇区数。对于软盘,每个FAT占用9扇区。
- 每磁道扇区数:每个磁道占用的扇区数。对于软盘是18个扇区。
- 磁头数:磁头的总数。对于软盘为2。
- 隐藏扇区数:分区前的扇区数。软盘容量是在太小了,没必要给软盘分区,软盘只有一个分区,软盘的第一个扇区就是这个分区的第一个扇区。所以这个值为0。
- 分区扇区总数(32位):分区的扇区总数,如果第0x013处的值为0,这这里的值为分区的扇区总数。
- 驱动器号:用于调用BIOS中断INT 0x13读取功能的时候指定的驱动器号。事实上,我们指定软盘为0x00,硬盘为0x80。
- 当前磁头号:为0。这个值没有实际意义。
以下是扩展引导标志块。
- BPB扩展引导标识:值为0x29,如果有这个值说明扩展引导标识块存在。
- 分区序列号:一个32位随机数,指定分区的序列号。
- 卷标:用户指定的分区的名字。
- 文件系统类型:8字节的字符串,用来指定分区文件系统的类型。不过我们不能依靠这个值来判断文件系统的真实类型。它仅仅是一个存在于这里的字符串。对于软盘,值为“FAT12 ”(为了凑够8字节,空余部分用空格补全)
- 引导指令:引导指令部分,开头的JMP指令就是指向的这里。指令的内容通常是从文件系统找到引导文件,然后载入到内存引导指令的位置(0x0000:0x7C00处)运行。
- 可引导标识0xAA55: 值必须为0xAA55,否则这个DBR扇区不可引导。
FAT表的结构非常简单,其实就是一个“簇链表”。第0、1个表项用于其他用途,而剩下的表项则用于表示簇链。
第0个表项的值是用1补全的介质类型,对于软盘是0xFF0(FAT12是12位的,高位用1补齐。)
第1个表项的值是末簇。
剩下的表项是文件的簇链,文件在数据区按照簇链指定的方式排序。簇号为0表示这是个空闲簇,簇号1保留不使用。
对于FAT12,簇号2到簇号0xFEF都是有效的簇号,簇号0xFF0到0xFF7是坏簇,簇号0xFF8到簇号0xFFF是末簇。
所以,簇号为2的簇,它的实际位置是在数据区的最开始的部分。注意FAT12根目录区的位置是在数据区的前面,FAT表并没有描述根目录区的位置信息。
通常情况下,FAT12的软盘有两个FAT表,它们内容相同,第二个表通常是第一个表的备份。所以当FAT表出现问题的时候,可以把第二个FAT表复制到第一个FAT表处。
如何通过簇号来找到对应扇区的位置呢?公式如下。
FAT表位置=隐藏扇区数+保留扇区数
根目录区位置=FAT表位置+FAT占用扇区数×FAT数量
根目录区占用扇区数=根目录区最大项数×32÷每扇区字节数(注:这里的32是每个根目录项占用的字节数)
数据区位置=根目录区位置+根目录区占用扇区数
簇位置=数据区位置+(簇号-2)×每簇扇区数
如何从FAT12的软盘中读取文件呢?先确定首簇号,然后根据首簇号算出文件第一个簇对应磁盘的位置,读取该簇的内容到内存,然后从FAT表找到下一个簇号,继续读取,直到最后读取的簇号是末簇号为止。这样描述显得不太容易理解,我来举个例子。
假定FAT表的内容如下:
0xFF0 0xFFF 0x003 0x005
0x006 0x004 0x00A 0x008
0x00B 0x007 0x009 0x00C
0x00D 0x00E 0x00F 0xFFF
假定文件的首簇号为2,隐藏扇区数为0,保留扇区数为1,FAT表占用扇区数为9,FAT表数量为2,根目录区最大项数为224,每簇扇区数为1,那么我们按照如下的流程来读取。
首先无视第0、1个表项(0xFF0,0xFFF这里),文件首簇号是2,则我们应该读取第2个簇。
要计算第2个簇的位置,我们应该按照如下的计算流程来计算。
首先计算FAT表的位置,按照上述公式,我们得出如下计算结果:
FAT表的位置为0+1=1(扇区号从0开始计)。
根目录区位置为1+9*2=19
根目录区占用扇区数=224*32/512=14
数据区位置=19+14=33
文件首簇号为2,我们应该先读取第2个簇,那么簇的位置按照如下来计算:
簇位置=33+(2-2)*1=33
那么第33个扇区就是文件第一个簇的位置,因为每簇扇区数为1,所以我们应该读取1个扇区到内存中。然后我们准备根据簇链来读取下一个簇。
从上面给出的FAT表可以看出,第2个FAT表项(FAT表项从0开始计)值为0x003,那么我们应该继续读取第3个簇。
簇位置=33+(3-2)*1=34
那么第34个扇区就是文件第二个簇的位置,我们从这里继续读取1个扇区到内存中。然后我们继续根据簇链读取下一个簇。
从上面给出的FAT表可以看出,第3个FAT表项值为0x005,那么我们应该继续读取第5个簇。
簇位置=33+(5-2)*1=36
我们继续从第36个扇区读取一个簇到内存中,然后从FAT表第5个表项可以得出,下一个簇的簇号为4,我们应该继续读取第4个簇到文件。
簇位置=33+(4-2)*1=35
我们从第35扇区读取一个簇到内存中。继续从FAT表找出下一个簇的簇号为6,继续读取第6个簇到内存中,然后再看FAT表第6个表项值为0x00A,于是我们又接着读取第10个簇,以此类推。
按照这样的读取方式读取下来,我们按照一下顺序读取了这些簇:
2,3,5,4,6,10,9,7,8,11,12,13,14,15。共14个簇。因为FAT表第15个表项值为0xFFF,即末簇,因此我们停止了读取。这样描述,大家应该能明白了吧?
那么,我对FAT表的介绍就到此结束了。
对于FAT12,根目录区是在固定的几个扇区里存储的。根目录区由一定个数的根目录项组成,每个根目录项占用32个字节,因此每个扇区只能保存16个根目录项。
根目录区保存了根目录的文件、文件夹列表,我可以列出一个表格来显示根目录项的结构。
偏移 | 大小 | 描述 | 0x00 | 11 | 8.3格式的文件名和文件扩展名。 | 0x0B | 1 | 文件属性 | 0x0C | 1 | 保留不用 | 0x0D | 1 | 文件创建时间毫秒数的10倍 | 0x0E | 2 | 文件创建时间 | 0x10 | 2 | 文件创建日期 | 0x12 | 2 | 最后访问日期 | 0x14 | 2 | 文件首簇号高16位 | 0x16 | 2 | 最后写操作的时间 | 0x18 | 2 | 最后写操作的日期 | 0x1A | 2 | 首簇号的低16位 | 0x1C | 4 | 文件大小 |
详细描述:
- 文件名和文件扩展名:这里的文件名和文件扩展名均为短文件名,不包括表示文件扩展名的句点“.”。文件名不足8字节的用空格补全,超过8字节的就用长文件名方法解决(长文件名方法我会在后面介绍到)。扩展名为3个字符。文件名必须为全大写。文件名第一个字节如果是0xE5,表示这个文件已经被删除。
- 文件属性:这个字节的值用于表示文件的属性。这个字节的结构如下:
位0:只读位,值为1表示只读,否则表示不只读(可写)
位1:隐藏位,值为1表示为隐藏文件,否则不是隐藏文件。
位2:系统文件,值为1表示这是系统文件,否则为用户产生的文件。
位3:卷标,值为1表示这个目录项的“文件名”部分用于表示卷标。
位4:子目录,值为1表示这个目录项是一个文件夹。
位5:存档,值为1表示这个文件被编辑过但是没有被备份过。
位6:保留不用,值为0
位7:保留不用,值为0
如果第0,1,2,3位值都是1(低4位值都是1)则这个目录项为“长文件名”目录项,这个目录项用于表示长文件名。后面我会介绍长文件名的存储方式。 - 文件创建时间毫秒数的10倍:这个字节的存在是因为“文件创建时间”中只能表示文件秒数除以2的值。这个字节用于表示精确的文件创建时间。这个字节的合法的值为0-199,即0-1990毫秒(约为2秒)。
假设一个文件创建于下午1点30分50秒500毫秒,则这个字节的值为50。
假设一个文件创建于下午1点30分51秒500毫秒,则这个字节的值为150。 - 文件创建时间:这个双字节用于表示文件创建的时间,它的保存格式遵循“时间格式”,我会在后面介绍时间格式。
- 文件创建日期:这个双字节用于表示文件创建的日期,它的保存格式遵循“日期格式”,我会在后面介绍日期格式。
- 最后访问日期:最后一次读取文件、写入文件的日期,它的保存格式遵循“日期格式”,我会在后面介绍日期格式。
- 首簇号的高16位:文件首簇号的高16位,对于FAT32而言很重要。而对于FAT12、FAT16而言,这个值必须为0。
- 最后写操作的时间:文件最后的写操作的时间。它的保存格式遵循“时间格式”,我会在后面介绍时间格式。
- 最后写操作的日期:文件最后的写操作的日期。它的保存格式遵循“日期格式”,我会在后面介绍日期格式。
- 首簇号的低16位:文件首簇号的低16位。不必多解释了吧?
- 文件大小:这个双字记录了文件的实际大小(以字节为单位)。因此FAT文件系统不能保存超过4GB的文件。当然,如果忽略这个双字,则FAT仍然有可能保存超过4GB的文件。注意对于文件目录(文件夹),它的值为0。
- 时间格式:根目录项的时间格式是按照如下表示的:
位0-4:秒数的二分之一,值为0-29,能够表示的秒数范围为0-58之间的偶数。
位5-10:分钟,值为0-59。
位11-15:小时,值为0-23。 - 日期格式:根目录项的日期格式是按照如下表示的:
位0-4:日期,值为1-31
位5-8:月份,值为1-12
位9-15:年份,值为0-127,从1980年开始,到2107年 - 短文件名可以使用的字符:$ % ' - _ @ ~ ` ! ( ) { } ^ # &
- 长文件名的存储格式:
偏移 | 大小 | 描述 | 0x00 | 1 | 长文件名目录项的顺序 | 0x01 | 10 | 长文件名的第一个部分 | 0x0B | 1 | 属性,值必须为0x0F | 0x0C | 1 | 类型,必须为0 | 0x0D | 1 | 校验和 | 0x0E | 12 | 长文件名的第二个部分 | 0x1A | 2 | 文件首簇号低16位,必须为0 | 0x1C | 4 | 长文件名的第三个部分 |
详细介绍:
- 长文件名目录项的顺序:
这个值表示这个长文件名目录项在对应同一个文件的长文件名多个目录项中的顺序。如果这个字节第6位的值为1,则这是这个文件的最后一个长文件名目录项。 - 长文件名的第一个部分:
这里的10个字节组成了长文件名的第一个部分,共5个UNICODE字符。如果到了长文件名的结尾,剩余字符都是0xFFFF。 - 属性:
这个字节值必须为0x0F。 - 类型:
这个字节必须为0。 - 校验和:
这个字节的值用于确定哪些长文件名目录项用于表示哪一个文件。它的算法我会在后面给出。 - 长文件名的第二个部分:
这里的12个字节组成了长文件名的第二个部分,共6个UNICODE字符。如果到了长文件名的结尾,剩余字符都是0xFFFF。 - 文件首簇号低16位:
这个值必须为0。 - 长文件名的第三个部分:
这里的4个字节组成了长文件名的第三个部分,共有2个UNICODE字符。如果到了长文件名的结尾,剩余字符都是0xFFFF。
- 校验和的算法:注:长文件名禁止使用的字符:+ , = [ ]
长文件名使用UNICODE编码。
校验和计算方式:- //------------------------------------------------------------------------------
- // ChkSum()
- // Returns an unsigned byte checksum computed on an unsigned byte
- // array. The array must be 11 bytes long and is assumed to contain
- // a name stored in the format of a MS-DOS directory entry.
- // Passed: pFcbName Pointer to an unsigned byte array assumed to be
- // 11 bytes long.
- // Returns:Sum An 8-bit unsigned checksum of the array pointed
- // to by pFcbName.
- //------------------------------------------------------------------------------
- unsigned char ChkSum (unsigned char *pFcbName)
- {
- short FcbNameLen;
- unsigned char Sum;
- Sum=0;
- for (FcbNameLen=11; FcbNameLen!=0; FcbNameLen--) {
- // NOTE: The operation is an unsigned char rotate right
- Sum = ((Sum & 1) ? 0x80 : 0) + (Sum >> 1) + *pFcbName++;
- }
- return (Sum);
- }
复制代码 每个长文件名都对应一个短文件名。
如果你要创建一个文件並且你得到了长文件名,那么你必须创建对应的短文件名。
短文件名的生成方法:- 把长文件名转换成全大写。
- 把全大写的长文件名转换成OEM内码頁编码,非法字符换成下划线'_'。
- 去掉所有长文件名前后的空格。
- 去掉所有长文件名前的句点'.'。
- 把长文件名前面的不是句点的8个字符作为基本的短文件名。
- 从长文件名中找到最靠后的句点,如果找到句点则把句点后三个字符作为短文件名后缀。
- 如果长文件名中没有非法字符,长文件名长度在8.3以內,生成的短文件名不与其它短文件名重复。
- 那么这个短文件名就已经可以使用了。
- 否则把文件名尾改成~n,注意~n可以从~1到~999999,选一个数字使其不与其它短文件名重复。
数据区没什么可以说的地方。其实数据区应该说明的是“文件夹”,也就是文件目录。文件目录的存储格式和根目录区一样。只是,第一个文件目录项必须是“. ”,首簇号指向自己。第二个目录项必须是“.. ”,首簇号指向其父目录。如果首簇号为0,则指向根目录。
文件在数据区是按簇存储的,也就是说,就算一个文件大小并没有达到一个簇的大小,那么这个文件仍然要占用1个簇的空间。如果这个文件的文件大小为0,则这个文件的首簇号也是0,意味着这个文件并没有占用任何一个簇。 |
|