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,值为0x10=(00010000) 根据短文件名目录项参数的意思,这个文件是一个子目录。其余参数读者可以根据上面提到的计算方法得出。

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

 int i, j = , chksum=;
for (i = ; i > ; i--)
chksum = ((chksum & ) ? 0x80 : ) + (chksum >> ) + 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

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

 // 短文件名目录项数据结构
typedef struct ShortDirItem
{
char strFilename[];
char strExtension[];
char attribute;
char reserved;
char millisecond;
unsigned short createTime;
unsigned short createDate;
unsigned short accessDate;
unsigned short highWordCluster;
unsigned short updateTime;
unsigned short updateDate;
unsigned short lowWordCluster;
unsigned int filesize;
}ShortDirItem;
     // 定位到FAT1表
SetFilePointer(hDisc, *, , FILE_BEGIN);
DWORD dwNumber2Read = ;
// 实际读取的字节数
DWORD dwRealNumber;
// 分配缓冲区
char* buffer = new char[];
// 读取一个扇区的数据
BOOL bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
// 把4第项改为 0x0FFFFFFF
buffer[] = buffer[] = buffer[] = 0xFF;
buffer[] = 0x0F;
// 写回FAT1
bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
// 定位到FAT2表
SetFilePointer(hDisc, (+)*, , FILE_BEGIN);
// 把4第项改为 0x0FFFFFFF
bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
// 定位到根目录
SetFilePointer(hDisc, (+*)*, , FILE_BEGIN);
// 读取根目录扇区
bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
// 准备数据
ShortDirItem* item = new ShortDirItem();
strcpy(item->strFilename, "root");
strcpy(item->strExtension, " ");
item->attribute = 0x10;
item->millisecond = 0x00;
item->createTime = 0x9249;
item->createDate = 0x4508;
item->accessDate = 0x4508;
item->highWordCluster = 0x0000;
item->updateTime = 0x9249;
item->updateDate = 0x4508;
item->lowWordCluster = 0x0004;
item->filesize = 0x00; // 修改根目录数据
char* pData = (char*)item;
for (int i = ; i < ; ++i)
{
buffer[i] = *(pData++);
}
// 写回根目录
bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL);
// 扫尾工作,释放缓冲区,关闭句柄
delete[] buffer;
delete item;
CloseHandle(hDisc);

但是笔者发现在win7 (准确的说是win7、vista、win8,XP下获取管理员权限即可执行)下调用WriteFile函数无法将数据写入U盘,可能是由于系统保护措施的关系,由于时间关系,笔者也没去深究。

-----------------------------------------------------------2014/8/9-----------------------------------------------------------------

后来笔者专门去查找了相关资料,总的来说原因确实是因为系统保护措施的关系导致WriteFile函数操作的失败,具体的解释如下:

首先是msdn上的解释 http://msdn.microsoft.com/en-us/library/windows/hardware/ff551353(v=vs.85).aspx 大概意思是说在win7和vista上加入了一些新的特性,为了能够更好得保护系统,如果应用程序没有独占的权限就直接对装有文件系统的存储设备进行写入操作的话,这个操作是会被拒绝的。笔者上面的程序通过GetLastError()函数得到的ErroeCode=5,意思也确实是拒绝访问。那么到底要如何写入呢,msdn上给出了以下几种情况:

Write operations on a DASD volume handle will succeed if the file system is not mounted, or if:

  • The sectors being written to are the boot sectors.

  • The sectors being written to reside outside file system space.

  • The file system has been locked implicitly by requesting exclusive write access.

  • The file system has been locked explicitly by sending down a lock/dismount request.

  • The write request has been flagged by a kernel-mode driver that indicates that this check should be bypassed. The flag is called SL_FORCE_DIRECT_WRITE and it is in the IrpSp->flags field. This flag is checked by both the file system and storage drivers.

这里比较方便的做法可以采用第4种,即显示地发送一个锁定驱动的请求,然后再尝试写入。具体做法参考这个帖子22L吧,笔者打算去尝试一下,成功的话再来更新结果。 http://bbs.csdn.net/topics/390731448?page=1

-------------------------------------------------------------------------------------------------------------------------------------

好了,看了下篇幅这篇文章也差不多可以结束了。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

3、Blocking Direct Write Operations to Volumes and Disks  http://msdn.microsoft.com/en-us/library/windows/hardware/ff551353(v=vs.85).aspx

4、vc 直接写物理磁盘,writefile 失败 错误返回5 拒绝访问  http://bbs.csdn.net/topics/390731448?page=1

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

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

    FAT 32 文件系统学习 1.本文的目标 本文将通过实际读取一个FAT32格式的U盘来简单了解和学习FAT32文件系统的格式.虽然目前windwos操作系统的主流文件系统格式是NTFS,但是FAT3 ...

  2. FAT32文件系统学习(上)

    2011-06-02 22:30:48 目的:需要编写SD读图片的底层驱动程序.所以要了解一个SD卡常用文件系统基本概念.累计学习用时2.5小时. 一,FAT32的保留区 1,引导扇区 :引导扇区是F ...

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

    1.题外话 在继续本文学习FAT32文件系统之前,先来插入一点别的话题.我们都知道U盘有一个属性是容量,就拿笔者的U盘为例,笔者手上的U盘是金士顿的DataTraveler G3 4GB的一个U盘.电 ...

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

    FAT(File Allocation Table ) 这个网页的目的是帮助你理解怎么样在微软FAT32文件系统下取得数据,处理的硬盘的大小通常在500M到几百G之间.FAT是一个相对简单和纯净的文件 ...

  5. 数据挖掘(data mining),机器学习(machine learning),和人工智能(AI)的区别是什么? 数据科学(data science)和商业分析(business analytics)之间有什么关系?

    本来我以为不需要解释这个问题的,到底数据挖掘(data mining),机器学习(machine learning),和人工智能(AI)有什么区别,但是前几天因为有个学弟问我,我想了想发现我竟然也回答 ...

  6. 机器学习&深度学习经典资料汇总,data.gov.uk大量公开数据

    <Brief History of Machine Learning> 介绍:这是一篇介绍机器学习历史的文章,介绍很全面,从感知机.神经网络.决策树.SVM.Adaboost到随机森林.D ...

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

    来自:互联网 磁盘上的数据按照其不同的特点和作用大致可分为5部分:MBR区.DBR区.FAT区.DIR区和DATA区.我们来分别介绍一下: (1)MBR区(主引导扇区) MBR(Main Boot R ...

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

    一.jQuery数据缓存基本原理 jQuery数据缓存就两个全局Data对象,data_user以及data_priv; 这两个对象分别用于缓存用户自定义数据和内部数据: 以data_user为例,所 ...

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

    最近在看<深入理解Java虚拟机>,书中给了几个例子,比较好的说明了几种OOM(OutOfMemory)产生的过程,大部分的程序员在写程序时不会太关注Java运行时数据区域的结构: 感觉有 ...

随机推荐

  1. drawable自定义字体颜色

    一个很基础简单的问题,但是以前没用过,都是代码控制效果的,最近新的项目发现设置了color属性没效果,后来查了会资料才发现得单独设置,记录一下,虽然是小问题 上面的xml控制背景的变化,一开始我设置在 ...

  2. OkHttpUtils简单的网络去解析使用

    先添加依赖: implementation 'com.google.code.gson:gson:2.2.4' implementation 'com.zhy:okhttputils:2.0.0' 网 ...

  3. Vue项目用于Ios和Android端开发

    起因 前公司商城App项目使用的是H5开发,有微信公众号.Ios和Android三个版本,H5版本是自己写的一套框架,已经用了有些年头了,承载不下不断涌现出的新需求.而Ios和Android端通过we ...

  4. 如何在数据表当中找出被删掉的数据行ID

    这个问题是一年前我刚步入IT行业的一个面试题,当时抓破头皮都想不到的问题,但现在回想过去自身不禁感到可笑,不多扯直接写解决方案.如何在数据表当中找出被删掉的数据行ID,意思是:在一堆的数据当中,让你找 ...

  5. HttpWebRequest 请求带OAuth2 授权的webapi

    OAuth 2.0注意事项: 1. 获取access_token时,请使用POST private static string GetAuthorization(string username, st ...

  6. Javascript模版引擎mustache.js简介

    背景 最近使用ELK的sentinl进行告警配置,sentinl的邮件通知支持mustache,借此机会学习了mustache相关知识,记录在此. mustache的思想 mustache的核心是标签 ...

  7. element-ui的table动态生成表头和数据,且表中数据可编辑

    1.实现表头的动态渲染 2.表头label和prop字段都要定义 3.去判断显示那个数据表 4.实现双击的时候在可编辑 // 双击修改 弹出input tableDbEdit(row, column, ...

  8. [转载]Windows 2003 R2 SP2 VOL 企业版(简体中文)

    Windows 2003 R2 SP2 VOL 企业版(简体中文) 要是这个的话,分享个电驴的下载连接吧(可以复制后用快车和迅雷直接下)32位版CD1:SHA1值:d0dd2782e9387328eb ...

  9. [20181219]script使用小技巧.txt

    [20181219]script使用小技巧.txt --//前几天在使用strace时遇到问题,它的输出使用标准错误句柄.--//我在想平时使用sqlplus如果输出字段很多,屏幕看起来一片混乱.-- ...

  10. AspNet mvc的一个bug

    [HttpPost] public ActionResult updateLoan(TuWenMilitaryRank entity) 使用mvc绑定表单 每次绑定的对象都为null,查看Reques ...