FAT32文件系统学习(3) —— 数据区(DATA区)

FAT32文件系统学习(3) —— 数据区(DATA区)

今天继续学习FAT32文件系统的数据区部分(Data区)。其实这一篇应该是最有意思的,我们可以通过在U盘内放入一些文件,然后在程序中读取出来;反过来也可以用程序在U盘内写入一下数据,然后在windows下可以看到写入的文件。这些笔者都会在这篇文章中演示。同时,在写这篇文章的时候笔者也发现了许多意想不到的规律。

1、本文目录

1、读取根目录

2、短文件名目录项

3、长文件名目录项

4、U盘写入文件夹

5、参考文献

2、读取根目录

两张FAT之后的所有扇区都是数据区部分。我们再通过图1来回顾一下整个FAT32文件系统的分布规则。

图 1 FAT32文件系统分布图

通常情况下根目录都是位于数据区头部的,前面也提到过,有文献上说是因为一旦U盘格式化完毕之后,根目录就创建好了。本着探究的精神,笔者尝试格式化了一下U盘,发现其实并没有创建根目录。不过一旦有文件操作,马上就会创建根目录,因为这时整个数据区都是空的,所以自然是写入数据区的头部。到头来其实道理是一样的,也就是说根目录一般情况下都是在数据区的头部(第2簇)。

  • 数据区偏移计算

经过前两篇关于BPB和FAT部分学习之后,我们就可以计算出数据区头部的偏移

数据区偏移 = (保留扇区数 +  FAT表扇区数 * FAT表个数(通常为2) + (起始簇号-2) * 每簇扇区数) * 每扇区字节数

笔者首先格式化了U盘,通过偏移读取出了数据区的头部,发现都是0x00。

  • 题外话

这里又要插一些题外话了,笔者试着改了一下U盘的卷标,把它改名为“FAT”。然后还记得BPB当中有一个参数叫做卷标吗?笔者看了下发现卷标这个参数还是“NO NAME”并没有改变。这时笔者把数据区的头部读取了出来,如图2所示:

图2 卷标

原来被写在了这里。最后经笔者的测试,卷标最长长度11个字节,偏移从0x00~0x0A,而偏移0x0B处的值0x08值的意思就是卷标(关于此处值的意思相面还会详细描述)。因为这个U盘其实还没有写入过任何数据,所以卷标才会显示在数据区的开头,但是如果换种情况呢,文件系统又是如何找到卷标的呢?笔者查阅了相关资料后发现,卷标一般都是写在根目录的下的,如果发现根目录项的其中一项其0x0B偏移处的值为0x08那么读取该项的前11个字节即为卷标。

3、短文件名目录项

  • 短文件名目录项参数

好,回到正题。先来讲一下理论的东西:目录区是由一个个目录项构成,类似于FAT表。其中每一个目录项占用32个字节,可以是代表长文件名目录项、文件目录项、子目录项等。对于短文件名格式的目录项,其参数的含义如表1所示(不会画这种表,从别处引用了一个)[1]

表1 FAT32短文件名目录项参数表

  • 参数解释

用一个实际的例子来解释一下这些参数的意思,首先创建一个短文件名文件,如“file1.txt”,读取根目录,如图3所示:

图3 file1.txt 短文件名目录项

先不管其他数据,我们来看一下红色矩形框部分的数据,它就是一个短目录项。我们来一个个对比表1的参数进行说明:

字节偏移 参数含义
0x00~0x07 文件名
对应字符串“FILE1”

0x08~0x0A 后缀名
对应字符串“TXT”

0x0B 属性字节
0x20 = 00100000(2进制) 表示归档

0x0C 系统保留  无
0x0D 创建时间的10毫秒位  88,即0x88 * 10ms = 1360ms(10进制)
0x0E~0x0F  文件创建时间 
0x785C = (0111100001011100)(2进制)

即为 15:02:57(注释1)

0x10~0x11 文件创建日期
0x4508 = (0100010100001000)(2进制)

即为 2014/8/8(注释2)

0x12~0x13  文件最后访问日期 
0x4508 = (0100010100001000)(2 进制)

即为 2014/8/8 算法参考创建日期

0x14~0x15  文件起始簇号高16位
0x0000,可以用来计算出文件实际内容的偏移值,

这个放到后面单独计算。

0x16~0x17  文件最近修改时间 
0x7869 = (0111100001101001)(2进制)

即为 15:03:18 算法参考创建时间

0x18~0x19  文件最近修改日期 

0x4508 = (0100010100001000)(2进制)

即为 2014/8/8 算法参考创建日期

0x1A~0x1B  文件起始簇号低16位 0x0005 
0x1C~0x0F   文件长度  0x0000000C = 12 

表2 file1.txt 参数解释

注释1:01111 000010 11100

1)这里高5位代表小时,由于2^5 = 32,足够表示24小时,这边01111(2进制) = 15(10进制);

2)次6位代表分钟,同理2^6 = 64,足够表示60分钟,这边000010(2进制) = 2;

3)低5位表示秒的1/2, 计算结果需要加上毫秒位上的进位,这边11100(2进制) = 28(10进制),所以秒数 = 28*2 = 56,再加上毫秒上的进位1所以结果为57。

注释2:0100010 1000 01000

1)这里高7位代表从1980年开始的年数,笔者计算了下可以到2108年,总之还有90多年可以使用,这边0100010(2进制) = 34,所以年份 = 1980+34 = 2014;

2)次4位代表月份,2^4=16,可以表示12个月份,这边 1000(2进制) = 8(10进制);

3)低5位代表日期,2^5 = 32,可以表示28~31天,这边 01000(2进制) = 8(10进制)。

这样除了文件起始簇号字段,其他字段的意思和计算方法都弄清楚了。下面来看一下文件起始簇号,首先根据高低各16位,计算出完整的文件起始簇号 = 0x00000005 ,文件起始地址偏移的计算

文件起始地址 = (保留扇区数 + FAT表扇区数 * FAT表个数(2) + (文件起始簇号-2)*每簇扇区数)*每扇区字节数

本例中计算结果为0x4010,然后到这个地址读取内容并切入到磁盘文件中(详细操作参考第一篇文章),如图4所示,windows下打开内容如图5所示:

图4 图5 文件内容

这个时候再去看一下FAT表的5号簇,计算方式再上一篇当中,结果如图6所示:

图 6 FAT表5号表项

红色矩形框的位置就是5号簇的位置,可以看到值0x0FFFFFFF,意思就是文件在这一簇结束了。 (具体不同数值的含义详见上一篇)。如果这里文件大小超过1簇,那么这个值应该是下一簇的簇号,继续读取下一簇的内容即可。虽然我们知道了文件占用的空间是1簇,但是怎么知道文件具体的大小呢?再回过头来看上面的短文件目录项,最后一个属性是文件长度,上面已经计算得到为12,“Hello World!”的长度正好是12 :)。

至此短文件目录项应该已经分析的差不多了。

4、长文件名目录项

  • 长文件名目录项参数

下面是长文件名目录项,笔者思考了好久该怎么把它讲清楚,毕竟理解是一回事,讲清楚就是另一回事了。

在讲长文件目录项之前先来说一下FAT32的一个很重要的特性,支持长文件名。长文件名也是记录在目录项当中的,区别与短目录项的是,前者可能会占据好几个目录项。为了兼容低版本的OS或程序能正确读取长文件名文件,系统自动为所有长文件名文件创建了一个对应的短文件名,使对应数据既可以用长文件名寻址,也可以用短文件名寻址。不支持长文件名的OS或程序会忽略它认为不合法的长文件名字短,而支持长文件名的OS或程序则会以长文件名为显式项来记录和编辑,并隐藏起短文件名[2]

当创建一个长文件名文件时,系统会自动加上对应的短文件名,其原则如下:

(1)、取长文件名的前6个字符加上"~1"形成短文件名,扩展名不变。

(2)、如果已存在这个文件名,则符号"~"后的数字递增,直到5。

那么系统是如何判断当前目录项是短文件名目录项呢还是长文件名目录项,这里关键是看目录项的第12个字节的值,如果为0x0F时则系统认为是长目录项。而如果是旧版本的系统看到第12个字节是0x0F则认为是异常而忽略掉。这里可以回过头去看一下短文件名目录项,第12个字节是文件属性字节,0x0F即为全1是无效的,所以系统认为是异常。系统将长文件名以13个字符为单位进行切割,每一组占据一个目录项。所以可能一个文件需要多个目录项,这时长文件名的各个目录项按倒序排列在目录表中,以防与其他文件名混淆。

这样讲可能还是很抽象,先来看一下长文件名目录项的参数,如表3所示[1]

表3 长文件名目录项参数表

  • 参数解释

然后还是以一个实际的例子来说明,在根目录区读入一个长文件名目录项,如图7所示:

图7 长文件名目录项

图中选定部分即为多个长文件名目录项。我们来慢慢分析。系统在读入一个目录项的时候首先查看它的第12个字节,发现是0x0F,所以认为这是一个长文件名目录项。我们来看长文件名目录项的参数,如表4所示:

 偏移  字段含义  值
0x00 属性字节位 0x42 = (01000010)(2进制)(注释1)
0x01~0x0A  10个字节的Unicode码  即字符串”ename” (注释2)
0x0B 长文件名目录项 
0x0F前面已经讲过

0x0C  系统保留  无 
0x0D  校验值  这个等整个文件名读取完再讲 
0x0E~0x19  12字节Unicode  即字符串"Test" 
0x1A~0x1B  文件起始簇号  常置0 
0x1C~0x1F  4字节Unicode 
0xFFFFFFFF

如果文件名已经结束的话则全部为0xFF

表4 长文件名目录项参数解释

注释1:01000010

第7位为1,说明是文件最后一个目录项目,

低5位为顺序 0010(2进制) = 2(10进制),说明这是第2个长目录项,且是最后一个目录项。即为这个长文件名占用了两个目录项。

注释2:Unicode 百度百科Unicode 点我详细解释

这边有3个Unicode区,加起来正好是26个字节13个Unicode码,所以这就是为什么上面讲的以13个字符为单位切割。因为这是第2个目录项,所以后面应该还有第1个目录项,继续分析下一个目录项其余参数同上,看一下3个Unicode分别是“LongL” “engthF” “il”而0x00的属性字节是01,说明这是第一个。至此这个长文件名读取完毕了。按照倒序(这里也解释了前面说的倒序的意思)的顺序拼接起来的话就是”LongLengthFilename”——这就是这个文件的文件名。

下面再来看一下下一个目录项,长文件名目录项后面还会跟一个短文件名目录项,这个目录项记录了除文件名以外的这个文件的信息,而文件名部分则用上面提到的短文件名目录项替换。所以读取方法和短文件名目录项是一样的,这里只看一下文件属性字节,偏移为0x0B,值为10=(00010000) 根据参数的意思,这个文件是一个子目录。其余参数读者可以根据上面提到的计算方法得出。

最后再来补上刚才的校验码计算方法:

1 int i, j = 0, chksum=0;
2 for (i = 11; i > 0; i--)
3         chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];

其中shortname即长文件名目录项对应的短文件名,所以这个校验码需要等到读完短文件名目录项之后才可以计算。这一段程序是笔者从网上摘来的,还没有时间验证一下。

5、U盘写入文件夹

这样关于数据区的部分差不多就讲完了。

最后在做一点有趣的事情,尝试向磁盘的扇区中写入一些数据,然后看是否会生成这个文件。为了方便起见,这里直接在根目录创建一个文件夹好了,

文件夹的名字叫做root,

创建时间日期2014/8/8 18:18:18

访问日期 2014/8/8

最近修改时间日期 2014/8/8 18:18:18

起始簇低16位 04 00

起始簇高16位 00 00

文件长度 0

同时修改2个FAT表第4项为0x0FFFFFFF

这样应该就可以了,好了,开始编码:

 1 // 段文件名目录项数据结构
 2 typedef struct ShortDirItem
 3 {
 4     char strFilename[8];
 5     char strExtension[3];
 6     char attribute;
 7     char reserve;
 8     char millisecond;
 9     unsigned short createTime;
10     unsigned short createDate;
11     unsigned short accessDate;
12     unsigned short highWordCluster;
13     unsigned short updateTime;
14     unsigned short updateDate;
15     unsigned short lowWordCluster;
16     unsigned int filesize;
17 }ShortDirItem;
 1     // 定位到FAT1表
 2     SetFilePointer(hDisc, 1016*512, 0, FILE_BEGIN);
 3     DWORD dwNumber2Read = 512;
 4     // 实际读取的字节数
 5     DWORD dwRealNumber;
 6     // 分配缓冲区
 7     char* buffer = new char[512];
 8     // 读取一个扇区的数据
 9     BOOL bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
10     // 把4第项改为 0x0FFFFFFF
11     buffer[12] = buffer[13] = buffer[14] = 0xFF;
12     buffer[15] = 0x0F;
13     // 写回FAT1
14     bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
15     // 定位到FAT2表
16     SetFilePointer(hDisc, (1016+7684)*512, 0, FILE_BEGIN);
17     // 把4第项改为 0x0FFFFFFF
18     bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
19     // 定位到根目录
20     SetFilePointer(hDisc, (1016+7684*2)*512, 0, FILE_BEGIN);
21     // 读取根目录扇区
22     bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
23     // 准备数据
24     ShortDirItem* item = new ShortDirItem();
25     strcpy(item->strFilename, "root");
26     strcpy(item->strExtension, "  ");
27     item->attribute = 0x10;
28     item->millisecond = 0x00;
29     item->createTime = 0x9249;
30     item->createDate = 0x4508;
31     item->accessDate = 0x4508;
32     item->highWordCluster = 0x0000;
33     item->updateTime = 0x9249;
34     item->updateDate = 0x4508;
35     item->lowWordCluster = 0x0004;
36     item->filesize = 0x00;
37
38     // 修改根目录数据
39     char* pData = (char*)item;
40     for (int i = 32; i < 64; ++i)
41     {
42         buffer[i] = *(pData++);
43     }
44     // 写回根目录
45     bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
46     // 扫尾工作,释放缓冲区,关闭句柄
47     delete[] buffer;
48     delete item;
49     CloseHandle(hDisc);

但是笔者发现在win7下调用WriteFile函数无法将数据写入U盘,可能是由于系统保护措施的关系,由于时间关系,笔者也没去深究。好了,看了下篇幅这篇文章也差不多可以结束了。FAT32文件系统其实差不多也都学习完了,为了巩固学习内容,笔者打算接下去根据前面所学的知识,并去了解一下windows快速格式化FAT32的机制,尝试自己格式化U盘,还可以根据FAT32的原理尝试删除数据的恢复等,总之还是有很多事情可以做的。

最后的最后,如果文章当中有任何错误或者遗漏指出,欢迎指出,谢谢。

6、参考文献

1、FAT32系统中长文件名的存储 http://blog.csdn.net/yanpingsz/article/details/5597893

2、FAT32文件系统的存储组织结构(一)  http://blog.chinaunix.net/uid-26913704-id-3213948.html

FAT32文件系统学习(3) —— 数据区(DATA区),布布扣,bubuko.com

时间: 2024-10-20 19:22:04

FAT32文件系统学习(3) —— 数据区(DATA区)的相关文章

FAT32文件系统学习(1) —— BPB的理解

FAT 32 文件系统学习 1.本文的目标 本文将通过实际读取一个FAT32格式的U盘来简单了解和学习FAT32文件系统的格式.虽然目前windwos操作系统的主流文件系统格式是NTFS,但是FAT32由于其兼容性原因,还是有一定的学习价值.为了能做出一个窗体程序提供直观的感觉,本文的代码采用c#编写,对应的c++代码也会附上. 2.本文目录 1.本文的目标 2.什么是FAT32 3.引导区 2.什么是FAT32 FAT32是Windwos系统硬盘格式分区的一种.这种格式采用32位的文件分配表,

FAT32文件系统学习(2) —— FAT表

1.题外话 在继续本文学习FAT32文件系统之前,先来插入一点别的话题.我们都知道U盘有一个属性是容量,就拿笔者的U盘为例,笔者手上的U盘是金士顿的DataTraveler G3 4GB的一个U盘.电脑上显示的容量如图1所示为3.75GB.那么这个3.75GB是怎么计算出来的呢? 图 1 系统显示U盘属性 我们先来回顾一下上一篇BPB参数当中的Sectors(扇区总数)这个参数,这一参数代表了这个U盘在出厂时的总扇区数,笔者手上这个是7884672个,可以从图2中看到.其中每个扇区为512 B,

入门级:理解FAT32文件系统(转载翻译)

FAT(File Allocation Table ) 这个网页的目的是帮助你理解怎么样在微软FAT32文件系统下取得数据,处理的硬盘的大小通常在500M到几百G之间.FAT是一个相对简单和纯净的文件系统.大多数文件系统都支持FAT,包括Linux和MacOS.所以也是需要访问硬盘的底层固件项目的常用文件系统.FAT16和FAT12是适用于小硬盘的小文件系统.这个网页将只重点介绍FAT32,和简单地比较一下他们的不同之处. 但是,这个网页的内容故意掩盖了很多细节和省略了很多这个文件系统出色的地方

MBR区、DBR区、FAT区、DIR区和DATA区的区别

来自:互联网 磁盘上的数据按照其不同的特点和作用大致可分为5部分:MBR区.DBR区.FAT区.DIR区和DATA区.我们来分别介绍一下: (1)MBR区(主引导扇区) MBR(Main Boot Record),按其字面上的理解即为主引导记录区,位于整个硬盘的0磁道0柱面1扇区.不过,在总共512字节的主引导扇区中,MBR只占用了其中的446个字节(偏移0--偏移1BDH),另外的64个字节(偏移1BEH--偏移1FDH)交给了DPT(Disk Partition Table硬盘分区表)(见下

jQuery源代码学习之六——jQuery数据缓存Data

一.jQuery数据缓存基本原理 jQuery数据缓存就两个全局Data对象,data_user以及data_priv; 这两个对象分别用于缓存用户自定义数据和内部数据: 以data_user为例,所有用户自定义数据都被保存在这个对象的cache属性下,cache在此姑且称之为自定义数据缓存: 自定义数据缓存和DOM元素/javascript对象通过id建立关联,id的查找通过DOM元素/javascript元素下挂载的expando属性获得 话不多说,直接上代码.相关思路在代码注释中都有讲解

从几个sample来学习JAVA堆、方法区、JAVA栈和本地方法栈

最近在看<深入理解Java虚拟机>,书中给了几个例子,比较好的说明了几种OOM(OutOfMemory)产生的过程,大部分的程序员在写程序时不会太关注Java运行时数据区域的结构: 感觉有必要通过几个实在的例子来加深对这几个区域的了解 1)Java堆 所有对象的实例分配都在Java堆上分配内存,堆大小由-Xmx和-Xms来调节,sample如下所示: [java] view plaincopyprint? public class HeapOOM { static class OOMObjec

大数据人才缺口多大?data大数据工程师好就业吗?这是大家学习大数据最关心的问题

小编来告诉你,大数据工程师年薪50万以上,技术人员缺口150万,高端技术人才未来会遭到企业疯抢.大数据方向由于人才稀缺度较高,薪资普遍更高,待遇涨幅也会超过其他岗位.接下分析大数据人才缺口和data工程师就业的问题. 高薪的背后是人才的紧缺,根据主流数据媒体调查,全国目前的大数据人才仅46万,未来3-5年内大数据的人才缺口将高达150万.有机构对一线城市2018年国内科技领域热门职位薪酬范围及跳槽涨幅进行了预测: 大数据方向由于人才稀缺度较高,相同工作年限的情况下,大数据工程师的薪资普遍更高,待

【转载】FAT32文件系统详解

硬盘是用来存储数据的,为了使用和管理方便,这些数据以文件的形式存储在硬盘上.任何操作系统都有自己的文件管理系统,不同的文件系统又有各自不同的逻辑组织方式.例如:常见的文件系统有FAT,NTFS,EXT,UFS,HFS+等等.作者后面的文章会一一讲到,下面就来学习一下基于Windows的FAT32文件系统. FAT32文件系统由DBR及其保留扇区,FAT1,FAT2 和 DATA 四个部分组成,其机构如下图: 这些结构是在分区被格式化时创建出来的,含义解释如下: DBR及其保留扇区:DBR的含义是

[Linux] linux文件系统学习

linux系统支持很多种文件系统. 1. 如何确认当前系统挂载了哪些文件系统? 使用mount命令可以查看当前系统上已经挂载了哪些文件系统, [email protected]:~$ mount/dev/sda1 on / type ext4 (rw,errors=remount-ro) proc on /proc type proc (rw,noexec,nosuid,nodev) sysfs on /sys type sysfs (rw,noexec,nosuid,nodev) none o