现在的位置: 首页 > 综合 > 正文

21世纪的文件系统:概述WindowsNT 5.0文件系统(NTFS)(一)

2014年02月13日 ⁄ 综合 ⁄ 共 7188字 ⁄ 字号 评论关闭

原文地址: http://www.microsoft.com/msj/1198/ntfs/ntfs.aspx

文章参考代码下载

正是因为NTFS系统存储文件属性的方式,单个文件的所有属性(包括它的数据属性)都被存储着。这一点在访问小型文件的时候提高了性能。在文件的目录条目中,NTFS系统同样存放着一个文件最常用的属性。
很多人的编程任务在利用了Windows NT 5.0文件系统(NTFS)新变革的优势之后变得简化了。让我们继续进行这些新特征的旋风之旅。记得,我们在讨论测试版的软件,那么每一样东西都可能发生变化。所以在基于这些信息写代码之前要查阅Microsoft的最新文档。
我们先来一个NTFS文件系统在硬盘布局上的总揽。然而这些信息对应用开发人员来说是程序禁地,但是这些高层次的解释会让你更容易的明白这些NTFS新特征。NTFS文件系统的核心是一个特殊的文件——主文件表(MFT)。在格式化NTFS卷的时候这个文件就被创建了。MFT包括一系列的1KB大小条目单位(数组);每个条目单位标识了硬盘卷中的一个单个文件。当你创建了一个文件,NTFS首先必须要在MFT数组中开辟一个空的条目单位,然后把这个新文件的信息填充到这个1KB空条目单位中。图1
展示了单文件(或者是目录)关联的标准属性清单。
Figure 1:
属性: 描述
标准信息
像只读、隐藏、系统文件等标志。还包括了创建、访问、修改的时间戳。一个文件的硬链接计数也在这里维护保存。
名字
Unicode格式的文件(目录)名。*如果一个文件还有一个短名或者有硬链接,那么它可以有多重名字的属性。
安全描述符
文件的安全数据结构支配着用户对文件的访问。
数据
这个是文件的内容。更具体些,这个属性标识文件未命名流中的数据。NTFs系统把一个文件的数据简单看做是该文件的另一个属性,这样就允许一个文件存在多重流。记住,和文件不同,目录没有相关的数据属性。
命名数据
这个可选的属性标识与文件相关的附加命名数据流。注意,单个文件或者一个目录可以有0到多个命名数据属性。
索引根、索引分配,位图
这三个属性用来实现大型目录的文件名索引。这些属性只和目录相关联,文件没有。
重分析
存储在这个属性中的数据标识了当访问这个文件或目录时执行的文件系统过滤器。

当文件被创建时,系统会为它的MFT文件条目单元创建属性集合,然后填充到1KB条目中。但有两个问题:很多属性都是变量长度,同时很多属性像名字、数据、命名数据的长度都会远远超过1KB。所以NTFS不能简单的把属性扔进MFT条目当中去,取而代之,NTFS要进行测试,如果一个属性长度值很小,那么把它扔进条目中,这称作常驻属性;如果属性长度值很大,系统会把属性值放在硬盘中的其他一个位置,(这个是非常驻属性)给条目单位中放一个指针指向属性值。
现在,每个人都会在硬盘里放很多很多的小文件。我们都有大量的.Lnk文件(快捷方式),可能还有很多的Desktop.ini文件,到处都是。由于NTFS在一个MFT条目单位中存放属性这种方式,它有可能为单个文件的所有属性,包括它的数据属性,来驻留。这个在访问小文件是大大提高了性能。进一步说,*NTFS还存储一个文件最常用属性,位于代表该文件的目录条目中。这意味着当系统做了FindFirstFile/FindNextFile操作来拿到一个文件的名字还有其基本属性,这些属性的数据是位于其目录条目中的,所以不需要其他硬盘访问了。
在Windows NT 4.0之前,NTFS的一个MFT条目大小是4KB,这个,当然是允许有略多数据的文件有它们自己的数据驻留。到了NT 4.0时,微软把大小削减到1Kb,因为已经意识到在很多NTFs系统上文件的数目及其大小导致了MFT条目浪费大量空间,如果把条目缩减到1KB,会更有效率的。现在让我们来复习一下软件开发人员可以利用的NTFS新功能。
流(Streams)
很少有人知道NTFS允许单个文件有多个数据流。这功能在NT 3.1时就产生了,但一直被淡化着。很不幸啊,因为流在很多情况下都是很有奇效的。举个例子,如果你正在开发一个位图编辑应用程序,当用户保存自己的数据时,你在其硬盘上创建一个BMP文件。并且你会保存一个图片的缩略版。缩略图通常存储在文件的末尾,在主图像文件之后。为了显示缩略图,必须要打开其文件,解析文件的头信息,寻至主图像后的字节数据,读取缩略图的数据,然后将其显示。其实我们可以把缩略图数据存在另外一个文件当中,不过这个主意不适合,这样很容易就让主图像文件和缩略图文件分离了。
NTFS命名流提供了两全其美的办法。当你的应用创建了文件,你可以把主图像数据写在默认流(未命名流)中,同时为缩略图数据创建另一个数据流(命名流),这两个流位于同一个文件中。只有一个文件,但它有两个数据流。为了搞懂到底是怎么运行的,我们做一个试验,在一台NT系统的机子上打开cmd(命令Shell),切换目录到一个NTFS分区下,然后输入:
C:\>ECHO "Hi Reader">XX.TXT:MyStream
当执行该命令时,系统会创建一个叫XX.TXT的文件。这个文件有两个流:一个0字节的未命名流和一个包含Hi Reader字符文本的命名流(MyStream)。如果至此你还没有猜到,你刚才做的是:在文件名称的后面放一个冒号紧跟着流的名字,以此访问该文件的命名流。正如文件名字一样,WIn32函数将流的名称作为保留大小写,同时搜索不区分大小写。
不幸的是,系统提供的这项功能至多把流视作二等公民,举个例子,执行下面命令:
C:\>DIR XX.TXT
Volume in drive C is wizard
Volume Serial Number is 40E5-92D4

Directory of C:\
03/18/98 08:36a 0 XX.TXT
                   1 File(s)
0 bytes
                   0 Dir(s)
3,399,192,576 bytes free

正如你看到的,DIR报告文件大小是0字节,这肯定不对。DIR命令仅仅把文件未命名流报告给用户;命名流的大小是不给用户看的。顺便说一句,资源管理器同样会报告一个0字节大小的文件。这使得一些古怪的派对游戏里你可以在朋友的硬盘上分配一个大数据流。所有硬盘空间耗尽了朋友也不会发现的,因为所有的工具会报告文件仅占据了0字节!在处理数据流时,记住,仅仅是这种工具不给予数据流以应得的对待;NTFS全面支持各种数据流(他甚至会计入你的存储指标)。现在,为了看流的内容,执行下面命令:
C:\>MORE <XX.TXT:MyStream
"Hi Reader"
这是使用流的另一个办法。假如说你正在写一个文字处理应用程序。当用户打开了一个现有文档,你可能会创建一个临时文件来保存住用户所有的操作。然后呢,当用户决定保存这些操作时,你把所有的更新信息写到临时文件中,原文件删掉,最后把临时文件给予其重命名移至原文件处。
这听起来相当的简单明了。但是你可能忘了点什么,最终文件应该具有和原文件一样的创建时间戳,你就不得不把这个修改过来。同样原文件的文件属性、安全信息都要考虑。这个文件保存的操作过程中很容易会错过正确更新某些属性。
如果你使用数据流,这些隐患都不用担心了。单个文件里的所有流都会共享这个文件的属性(像时间戳、安全信息等等),你应该修改你的程序,让用户的临时信息写入到文件里的命名数据流。然后,当用户保存了数据,重命名临时命名数据流到未命名数据流,NTFs系统会删掉旧有的未命名数据流,同时重命名操作是以一个全有或全无的方式。你一点也不用对文件属性做什么,它们肯定会准确的保留。
在我们离开数据流之前,我们再列举出一些事宜。首先,如果你从一个支持数据流的文件系统里拷贝一个文件到另一个不支持数据流的文件系统(像使用在软盘上的FAT系统),只有未命名流的数据被拷贝了;任何命名流里的数据没有得到拷贝。
第二,命名数据流可以和目录进行关联。目录从不会有未命名数据流来关联,但它们可以有命名数据流。可能你熟悉资源管理器下的DESKTOP.ini。如果资源管理器在某个目录里发现了这个文件,它知道要加载一个Shell命名空间扩展,并允许Shell命名空间扩展来解析目录的内容。系统把这技术用在了诸如My Document、Fonts、Internet Channels等等文件夹。既然DESKTOP.ini这个文件描述了资源管理器该怎么显示目录的内容,那微软要是把DESKTOP.ini的数据放进目录的命名数据流中岂不是更有意义?
微软不这么做的原因是向后兼容。数据流只在NTFS驱动器下实现;FAT文件系统或者CD-Rom驱动盘下是不存在数据流的,同样是这个原因,数据流可能并不适合你的应用程序。但如果你的程序需要NTFS,你当然要利用好这个新功能的优势。
FIgure 2 的代码展示了一个应用程序怎样与数据流配合工作。代码注释好了,那么这里就不再描述了,你编译这段代码后,编译器里按步调试,到达每一个TEST行,执行命令行的命令之后看显示的结果。
Figure 2:
FileStreams.cpp

	/***
	//Module name:FileStreams.cpp
	***/
	#define STRICT
	#include <windows.h>
	
	int WINAPI WinMain(HINSTANCE hinstExe,HINSTANCE hinstPrev,LPSTR pszCmdLine,int nCmdShow)
	{
		LPCTSTR pszFile = _TEXT("D:\\StreamTest.tst");
		LPCTSTR pszFirstStream = _TEXT("D:\\StreamTest.tst:FirstStream");
		LPCTSTR pszCopyStream = _TEXT("D:\\StreamTest.tst:CopyStream");
		LPCTSTR pszRenameStream = _TEXT("D:\\StreamTest.tst:RenameStream");
		LPCTSTR pszMoveStream = _TEXT("D:\\StreamTest.tst:MoveStream");
		
		char szDataToWrite[] = "THis is some data";
		char szDataToRead[100] = {0};
		HANDLE hfile;
		DWORD cb;
		//note:in a real application,you do not hace to open and close each stream's handle repeatedly as I've done below
		//create a file with no data in its unnamed stream and no named streams
		hfile = CreateFile(pszFile,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
		CloseHandle(hfile);
		//TEST:DIR (FILE should exist)
		
		//add a named stream to the file
		//note:step above does not have to execute first
		hfile = CreateFile(pszFirstStream,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
		CloseHandle(hfile);
		//TEST:more< c:\streamtest.txt (nothing should be displayed)
		//TEST:more < c:\streamtest.txt:FirstStream (nothing should be displayed)
		
		//put some data in the named stream
		hfile = CreateFIle(pszFirstStream,GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
		WriteFile(hfile,(PVOID)szDataToWrite,strlen(szDataToWrite),&cb,null);
		CloseHandle(hfile);
		//TEST:more < c:\streamtest.txt(nothing should be displayed)
		//TEST:more <c:\streamtest.txt:firststream(text should be displayed)
		
		//get the size of the named stream
		hfile = Createfile(pszFirstStream,0,0,null,OPEN_EXISTING,0,null);
		DWORD dwSize = GetFIleSize(hfile,null);
		CloseHandle(dfile);
		//TEST:dwsize should be the correct number of bytes
		
		//Read the contents of the named stream
		hfile = CreateFile(pszFirstStream,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_FLAG_SWQUENTIAL_SCAN,NULL);
		ReadFile(hfile,(PVOID)szDataToRead,sizeof(szReadToread),&cb,null);
		CloseHandle(hfile);
		//TEST:szDataToRead should contain "this is some data"
		
		//make a copy of the named stream to another named stream
		CopyFile(pszFirstStream,pszCopyStream,false);
		//TEST:more < c:\StreamTest.txt:CopyStream(text should be dispalyed)
		
		//note:CopyFile does not always behave as expected;see below
		//1st param		2nd param		Result
		//Unnamedstream	Unnamedstream	complete file copy with all streams
		//unnamedstream namedStream		unnamedStream copied to NamedStream
		//NamedStream		UnnamedStream	File deleted:NameStream copied to UnnamedStream
		//NamedStream		NamedStream		nameStream copied to NamedStream
		
		//delete all the data in a stream
		hfile = CreateFile(pszCopyStream,GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
		SetFilePointer(hfile,0,null,FILE_BEGIN);
		SetEndOfFile(hfile);
		CloseHandle(hfile);
		//TEST:more <c:\StreamTest.txt:CopyStream (nothing displayed)
		
		//delete the first named stream
		DeleteFile(pszFirstStream);
		//TEST: more <c:\streamtest.txt:FIrstStream(error should occur)
		//TEST:DIR(file should exist)
		
		delete the contents of the unnamed stream
		hfile = CreateFile(pszFile,GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
		SetFilePointer(hfile,0,null,FILE_BEGIN);
		SetEndOfFile(hfile);
		CloseHandle(hfile);
		//TEST:MORE <c:\StreamTest.txt (nothing should display)
		//TEST:DIR (file should exist)
		
		//delete the file and all of its streams
		DeleteFile(pszFile);
		//TEST:more <c:\Streamtest.txt(error should occur)
		//TEST:DIR (file should not exist)
		
		//unfortunately,the win32 function moveFile does not support the moving/renaming of streams.this function only works on complete files.there is no documented way to move/rename a stream.
		
		//the win32 Backup functions can be used to enumerate the streams within a file.but they are very hard to work with and their performance is poor because the function also reads the stream's data.
		
		return 0;
}

抱歉!评论已关闭.