目录
将U盘上的两个分区格式化,分别格式化为FAT32格式和NTFS格式,然后在两个分区分别创建大文件、小文件和文件夹,来观察两种文件系统的存储原理。
三、实验过程或算法(源程序)
1.验证FAT32文件系统文件存储原理
内容:利用Winhex软件,打开FAT32文件系统,通过DBR里提供的信息,手工计算FAT1的大小和位置,计算FDT表的位置,列举根目录下的文件和文件夹,利用FDT中某个文件的信息,列举该文件的簇号链,并根据簇号链,转入到该文件的data区。
首先观察DBR扇区:
得到以下可用信息:
DBR分区 | |||
字节偏移(十六进制) | 字段长度(字节) | 字段名和定义 | 值 |
0x0B | 2 | 每扇区字节数 | 200h,即每个扇区512字节 |
0D | 1 | 每簇扇区数 | 8:1Cluster = 8Sectors |
0E | 1 | DOS保留扇区数 | 26h |
10 | 1 | FAT表个数 | 2 |
24 | 4 | 每FAT扇区数 | 26FDh |
32 | 2 | DBR备份扇区号 | 6 |
52 | 8 | 文件系统类型 | FAT32 |
由此可以计算出FAT表的位置为26h,即相对于当前扇区便宜26h个扇区为FAT1的位置。
每个FAT表的大小为26FDh个扇区,用2个FAT表,所以数据区的起始扇区号为26h + 26FDh*2 = 4E20h,相对于当前文件系统偏移4E20h个扇区就是FDT表的位置。
根目录FDT表:
除了根目录的FDT表的前两项:
“.”表示当前目录(下文中的子)
“..”表示上级目录(下文中的父)
一个子目录的起始簇,前两个目录项为“.”目录项和“..”目录项,子目录通过这两个目录项及它在父目录中的目录项建立起父子目录的联系。
“.”目录项位于子目录起始簇的第一个目录项位置,它用以表明该簇是一个子目录的起始簇。另外,该目录项实际上是对目录自身的描述,它记录了该子目录时间信息、起始簇号等。需要注意的是,它所记录的起始簇号也就是该子目录目前所处的位置。
“..”目录项位于子目录起始簇的第二个目录项位置,用于描述该子目录的父目录的相关信息。
短文件目录项示例:
(红框框出的下面的32字节的目录项就是短文件目录项)
长文件名的实现有赖于目录项偏移为0xB的属性字节,当此字节的属性为:只读、隐藏、系统、卷标,即其值为0FH时,DOS和WIN32会认为其不合法而忽略其存在。这正是长文件名存在的依据。
因为0xB的属性字节值不为0FH,所以该目录项为短文件目录项,对应我们创建的小文件FatSam.txt。
长文件目录项:
长文件目录项的0x00:1 个字节,长文件名目录项的序列号,一个文件的第一个目录项序列号为 1,然后依次递增。如果是该文件的最后一个长文件名目录项,则将该目录项的序号与 0x40 进行“或(OR)运算”的结果写入该位置。如果该长文件名目录项对应的文件或子目录被删除,则将该字节设置成删除标志0xE5。
可以看到我们创建的长文件占5个文件目录项。
解析FAT表,从文件系统起始位置偏移26h个扇区到达此FAT32文件系统的FAT1表。
我们创建的文件(文件夹)的起始簇号如下表所示,此数据从文件目录项偏移0x1A处读取两个字节给出:
文件名 | 起始簇号 |
FatSam.txt | 5 |
fatBigSizeWithLongNameMyNameIsPineapple.txt | 6 |
ring0(文件夹) | 05BFH |
因此我们跳转到FAT1表处:
可以看到第5个簇号上的标志是0FFFFFFFH,即结束扇区号。此文件非常小只占用了一个扇区。
第5个簇号上的标志是7,也就是下一个簇号,这是一个大文件,占不止一个簇,因为簇号链表较长,最后一个簇号上的数据是结束簇号标志。
然后我们找文件夹所在簇号,即簇号05BFH。方法是将光标放在FAT1表的第0个字节处,偏移05bfh个双字。
操作如下图所示:
然后找到该簇号处,(果不其然)这里是一个结束标志,因为ring0是一个文件夹,ring0里面我只建立了四个文件夹,占一个簇就好。
下面完成根据簇号链,转入到文件的data区。
根据簇号链,先去小文本文件的数据区,即转到簇号是5的位置。
簇号是五即相对于第一个簇偏移18h个扇区,因为第一个簇的簇号是2,共相差三个簇号,每个簇由8个扇区组成,所以相对于FDT偏移3*8 = 24个扇区也就是18H个扇区。
跳转:
查看小文本:
查看长文本文件,簇号为6,即相对于小文本文件(簇号5)再偏移1个簇,即8个字节:
查看大文件:
查看文件夹。本磁盘分区的文件树如下:
I:\
├── System Volume Information
│ ├── IndexerVolumeGuid
│ └── WPSettings.dat
├── FatSam.txt
├── fatBigSizeWithLongNameMyNameIsPineapple.txt
└── ring0
├── ring1
│ ├── ring2
│ │ ├── ring3
│ │ ├── ring31
│ │ └── ring32
│ ├── ring21
│ ├── ring22
│ └── ring23
├── ring11
├── ring12
├── ring13
├── ring14
├── test.txt
├── test - 副本.txt
└── 新建文件夹
跳转到ring0文件夹,簇号为05BFH,相对于数据区开始,跳转(05bfh - 2)>> 3 ,左移三位即×8,用草纸计算得跳转2DE8H个扇区:
因为ring0只有4个文件夹,所以ring0作为文件只占用了一个扇区,所以也只分配了一个簇。
前两个文件目录项分别为当前文件夹和上一级文件夹,可以看到当前文件夹的簇号为05BFH,上一级文件夹的簇号为0,上一级为根目录。
文件目录项第0个字节为E5H的是已经删除的项。可以得出该文件夹下有四个文件夹,分别为ring1,ring11,ring12,ring13。
可以看到ring1文件夹的簇号为05C0H,即相对于当前文件夹偏移一个簇,我们在当前簇跳转8个扇区来到ring1文件夹:
可以看到ring1文件夹的数据区存储情况,作为文件夹他的数据区存储的是文件目录项,除了.和..之外,共有四个文件目录项,分别是ring2,ring21,ring22和ring23。
至此FAT32中文件夹的存储原理解析完毕。
2.NTFS文件系统:
NTFS的第一个扇区为引导扇区,即DBR扇区。
DBR扇区包括跳转指令,OEM代号,BPB参数,引导程序和结束标志。
BPB描述了每个扇区的字节数,每簇扇区数,$MFT的起始簇号,文件记录的大小描述等信息。
NTFS的DBR引导程序占426字节(5DH~1FDH),负责完成系统文件NTLDR的装入
通过解析第0号扇区得到以下信息:
NTFS的引导扇区 | |||
字节偏移(h) | 字段长度(字节) | 字段名和定义 | 值 |
0x00 | 3 | 跳转指令 | 跳转指令 |
0x03 | 8 | 文件系统ID | NTFS |
0x0B | 2 | 每扇区字节数 | 200h |
0x0D | 1 | 每簇扇区数 | 8 |
0x28 | 8 | 扇区总数 | 9c3fffh |
0x30 | 8 | $MFT的起始簇号 | 40000h |
0x38 | 8 | $MFTMirr的起始簇号 | 2 |
0x48 | 8 | 卷序列号 | 唯一的序列号 |
跳转到MFT,因为$MFT的起始簇号为40000h,所以跳转(40000>>3)个扇区,即跳转200000h个扇区:
它由文件记录构成,每个文件记录占用2个扇区。在$MFT中前16个文件记录总是元文件的纪录。系统通过$MFT来确定文件在磁盘上的位置以及文件的所有属性。
NTFS文件系统分配给主文件表的区域大约占据了磁盘空间的12.5%,剩余的磁盘空间用来存放其他元文件和用户的文件。
红色箭头所指的是更新序列号。
从$MFT中得到以下信息:
$MFT文件记录头 | |||
字节偏移(h) | 字段长度(字节) | 字段名和定义 | 值 |
0x00 | 4 | MFT标志 | FILE |
0x08 | 8 | 日志文件序列号 | 0EB6H |
0x12 | 2 | 硬连接数 | 1 |
0x14 | 2 | 第一个属性的偏移地址 | 38h |
0x16 | 2 | 标志 | 01H:文件正在使用 |
0x18 | 4 | 文件记录的实际长度 | 1A0H |
0x1C | 4 | 文件记录分配长度 | 400H |
0x20 | 8 | 基本文件记录中的 | 0 |
0x28 | 2 | 下一属性ID | 0004H |
MFT中共有四个属性,分别用蓝色和黄色、绿色以及灰色做了标记。
90H属性分析:90H属性是索引根属性,该属性是实现NTFS的B+树索引的根节点,它总是常驻属性。
读取MFT表的第五个记录(根目录)找到目录索引所在簇号:
根目录是MFT中的第5个记录(记录从0开始的),根目录的MFT记录项所在的簇数是 200000h+5*2h,直接从当前扇区($MFT)偏移AH个字节即可。
以下是根目录的记录:
在根目录的文件记录中有7个属性,对于小目录来说,有索引根(90H)属性就够了;而对于大目录来说,还需要有索引分配(A0H)属性。
解析其90H属性,可以得到什么....参考PPT:根目录是一个大目录,在它的文件记录中有一个90H属性,但里面没有实质的索引项。
解析其A0属性,得到Data run:31 01 52 E2 09, 偏移簇号为9E252H, 大小为1。
跳转到该簇,查看根目录下的索引项(9E252H>>>3)= 4F1290H:
会看到根目录下的索引项,其中可看到$AttrDef,$BadClus,$Bitmap,$Boot等系统文件的索引项。找到smallFileInNTFS.txt等自己创建的文件的索引项:
找到全部自己创建的四个文件如下:
查看小文本文件:
根据索引项可以获得其$MFT参考号,根据其$MFT参考号,找到文件记录。
计算小文本文件的MFT表项在$MFT中的簇号:29H>>>1 = 52H,所以相对于起始偏移200000h+52h个扇区查看小文件:
可以看到小文本文件的80属性为常驻属性,里面有小文件的全部内容:
在windows打开小文件:
文本内容一致,验证完毕。
继续找到大文本文件:
计算大文本文件的MFT表项在$MFT中的簇号:2AH>>>1 = 54H,所以相对于起始偏移200000h+54h个扇区查看大文本文件:
由于我设置的大文本文件的名字相对较长,所以其30H属性值长度较长,可以看到此大文本文件的80H属性为非常驻属性:
跳转到80H处的数据流:22 11 02 4F 07,计算。
偏移簇号为074FH, 大小为02 11H个簇,也就是(0211>> 3<<1) = 844HKB = 2116KB。
系统显示的文件大小为2114KB,验证正确。
跳转到该簇,查看数据区,跳转到第(074FH>>>3)= 3A78H个扇区:
可以看到我们写在这里面的数据,UTF-8兼容ascii,所以右边的字符编码要用UTF-8,由此我们解析完了大文本文件。
然后解析一下文件夹,学一下NTFS文件夹存储原理:
跳转到MFT的第28H项:
分析该文件夹的90H属性:
90H类型属性即$INDEX_ROOT,是索引根属性,该属性是实现NTFS的B+树索引的根节点,它总是常驻属性。该属性没有最大最小长度限制。属性结构如下:
标准属性头,索引根,索引头,索引项,索引项...
该文件夹的文件记录只有90H属性,没有A0H属性。
具体分析其90H属性:
对其90属性进行解析,会发现有四个索引项内容,其中有3个不同的$MFT文件记录号,所以可得,该文件夹下共有三个文件。
我们查看该文件下的文本文件,他有两个索引项,索引项3和4。
该文件的文件记录在$MFT的第32H偏移处,相对于分区起始偏移200000H + (32H>>1) = 200064H处。
查看littlesize文件夹下的一个.txt文本文件的文件记录:
可以看到其80H属性处存储了文本文件的全部内容。
在windows下打开这个文件:
与在winhex下的文件记录中找到的一致,验证完毕!
3. 编程实现上述过程。
编程实现NTFS文件系统的文件查找过程。
编程的思路是先通过0号扇区找到$MFT的首个记录所在簇号,然后计算出根目录所在簇号,进而根据输入的路径便利要查找的文件的具体地址。并打印该文件有关信息,打印其MFT目录项等与文件有关的信息。
以下是核心源代码:
1.用户输入具体的名字,程序来进行遍历查找,因为所用平台为Clion,所以为了在控制台打印中文字符,我们定义:system("chcp 65001");
打印出文件的一些基本信息。
然后打开ntfs磁盘,获取设备句柄,与上次实验使用的函数相同,上次打开的是设备,这次打开的是一个磁盘分区,有所区别。红色箭头所指的部分要根据我们查看的ntfs文件系统所在的卷标进行变换。因为ntfs卷表示L,所以设置为L盘,程序与设备进行IO,如果失败打印出error方便调试。
定义缓冲区,查看MFT目录项,并计算得到MFT的起始簇号,进而才能获取根目录的MFT目录项所在扇区号。
读取MFT表项,用ReadFile()函数来实现,每次读取1024个字节,即两个扇区。
得到根目录所在簇号,并将指针设置到根目录,读取根目录,多读一些字节,
在根目录中,我们根据用户也就是输入到的合法的路径来遍历,根据解析的文件名来查找文件,定位到该文件索引项所在的位置,然后计算出文件在MFT中的相对簇号。
然后我们计算出该文件的文件记录的扇区号:
DWORD lastnumber=(synumber*2+mftlocal*8);
然后根据其扇区号设置指针。扇区号*512就是字节编号。
然后就是打印文件信息,因为我们已经计算出文件的MFT记录所咋的扇区号,所以可以把2扇区的MFT记录打印出来,这里增加了一些小的判断。
接下来就是打印该文件的有关信息,这里要注意的是,要将每个字节按10进制打印出来,为了和winhex相似,我们每行只存放16个字节,之后插入一个换行符,也方便我们人为对比程序的结果和观察到的结果的一致性。
至此,程序完毕,便运行边调试,直至打印出正确的结果。这里大概就是差8位或者差一个扇区,需要检查计算公式“+1”问题。
四、实验结果及分析和(或)源程序调试过程
1.实验结果分析
所有的结果已经写在了第三部分,在此重述一些实验结果。
1.1手动分析FAT32文件系统
文件树如下:
I:\
├── System Volume Information
│ ├── IndexerVolumeGuid
│ └── WPSettings.dat
├── FatSam.txt
├── fatBigSizeWithLongNameMyNameIsPineapple.txt
└── ring0
├── ring1
│ ├── ring2
│ │ ├── ring3
│ │ ├── ring31
│ │ └── ring32
│ ├── ring21
│ ├── ring22
│ └── ring23
├── ring11
├── ring12
├── ring13
├── ring14
1.1.1 FAT32小文本文件
1.1.2 FAT32大文本文件
1.1.3 FAT32文件夹
1.2 手动分析NTFS文件系统
L:\
├── BigSizeFileInNTFS_So_I_am_a_big_file.txt
├── bushinidewenti
│ ├── test - 副本.txt
│ └── test.txt
├── littlesize
│ ├── ring1
│ │ ├── ring2
│ │ ├── ring21
│ │ └── ring22
│ ├── ring11
│ ├── smallFileInNTFS.txt
│ └── 文件夹中的文本文件.txt
├── smallFileInNTFS.txt
└── System Volume Information
1.2.1小文本文件:
NTFS文件系统的小文本文件,可以全部存储在文件记录的80H属性处。
1.2.2大文本文件
大文本文件存储在data run指向的簇里面。
1.2.3文件夹
1.3编程实现上述过程
编写C语言程序实现了NTFS文件系统中文件的查找过程,支持所有文件,并判断了文件大小,输出是大文件还是小文件。NTFS文件系统存储的所有的内容都是文件形式。
在第三部分已经对核心源代码做了解释说明,实验平台为Win10,IDE为Clion。
文件树如下,根目录为“L:\”:
L:\
├── BigSizeFileInNTFS_So_I_am_a_big_file.txt
├── bushinidewenti
│ ├── test - 副本.txt
│ └── test.txt
├── littlesize
│ ├── ring1
│ │ ├── ring2
│ │ ├── ring21
│ │ └── ring22
│ ├── ring11
│ ├── smallFileInNTFS.txt
│ └── 文件夹中的文本文件.txt
├── smallFileInNTFS.txt
└── System Volume Information
下面是查找大文本文件:
所以打印根目录下的小文本文件有关信息:
可以看到小文本文件的目录项相对目录号以及文件目录项与我们观察到的数据都一致。因为根据上文我们“小文本文件的MFT表项在$MFT中的记录号为:29H”,29H即十进制的32+9 = 41,与程序的控制台打印一致。
为了方便观看,我们这里只截取控制台打印的文件目录项两个扇区的一部分:
可以看到小文本文件的文件记录项与以下我们自己在winhex中找到的一致:
除此之外我们的程序也可以打印文件夹有关信息,输入L:/littlesize:
打印大文本文件L:/BigSizeFileInNTFS_So_I_am_a_big_file.txt:
也打印长文件的Data run。
经校对程序运行结果均正确。
2.实验错误记录
错误记录:
- 在winhex软件中,不能很好的在十六进制和十进制之间切换,常常输入十进制,导致浪费了很长时间。(因为winhex跳转扇区默认是十六进制)。
- 编号从0开始还是从1开始还是从2开始需要搞清楚。比如FAT32的簇号就是从2开始的。
- 编写的程序需要以管理员身份运行IDE。
除ppt之外参考:
根据NTFS结构在磁盘中寻找特定目录下文件 - 知乎 (zhihu.com)