具体实施方式
图1和下面讨论试图提供可应用本发明的适合的计算环境的简明描述。虽然并非必要,但本发明以计算机可执行指令的一般上下文范围中描述,如由个人计算机执行的程序模块。通常,程序模块包括例行程序、程序、对象、组件、数据结构等,它们执行特定的任务或实现特定的抽象数据类型。
此外,本领域熟练人员知道,本发明可以应用其他计算机***配置来实现,包括手持式装置、多处理器***、基于微处理器或可编程消费者电子设备、网络PC、小型计算机、大型主机等。本发明也能在分布式计算环境中实现,其中任务由通过通信网落链接的远程处理设备执行。在分布式计算机环境中,程序模块可以放置在本地或远程的存储器储存设备中,
参考图1,实现本发明的一个示例性***包括以传统的个人计算机20或类似形式的通用计算设备,它包括处理单元21、***存储器22和耦合包括***存储器的各种***部件到处理单元21的***总线23。***总线23可以是若干类型总线结构的任一种,包括存储器总线或存储器控制器、***总线和使用各种总线结构的任一种的局部总线。***存储器包括只读存储器(ROM)24和随机存取存储器(RAM)25。在ROM 24中存储基本输入/输出***26(BIOS),它包括基本的例行程序,以帮助如在起动过程中在个人计算机20中各单元之间传递信息。个人计算机20还可以包括硬盘驱动器27,用于向硬盘(未示出)读和写;磁盘驱动器28,用于可向可移动磁盘29的读和写;和光盘驱动器30用于向如CD-ROM或其他光媒体之类的可移动光盘31的读和写。硬盘驱动器27、磁盘驱动器28和光盘驱动器30分别通过硬盘驱动器接口32,磁盘驱动器接口33和光驱动器接口34连接到***总线23。这些驱动器和与其相关的计算机可读媒体提供计算机可读指令、数据结构、程序模块和用于个人计算机20的其他数据的非易失性存储。虽然这里描述的示例性环境使用了硬盘、可移动磁盘29和可移动光盘31,本专业中熟悉人员应理解,能储存由计算机可访问的数据的其他类型计算机可读媒体在示例性操作环境中也能使用,如盒式磁带、闪存卡、数字视频盘、Bernoulli盒式盘带,随机存储器(RAM),只读存储器(ROM)等。
包括操作***35(最好是微软公司的Windows2000,以前的WindowsNT)在内的许多程序模块能存入硬盘、磁盘29、光盘31、ROM 24或RAM 25。计算机20包括与操作***35相关或包括在其中的文件***36,如Windows NT文件***(NTFS)、一个或多个应用程序37、其他程序模块38和程序数据39。用户能通过如键盘40和指示设备42之类的输入设备将命令和信息输入到个人计算机20中。其他输入设备(未示出)可以包括:麦克风、游戏杆、游戏板、卫星碟(satellite disk)、扫描器等。这些及其他输入设备通常通过耦合到***总线的串口接口46连接到处理单元21,但也能通过如并行端口、游戏端口或通用串口总线(USB)之类的其他接口连接。监视器47或其他类型的显示设备也能通过如视频适配器48这种接口连接到***总线23。除了监视器47外,个人计算机通常包括其他***输出设备(未示出),如扬声器和打印机。
个人计算机20可使用与如远程计算机49之类的一个或多个远程计算机的逻辑连接在网络环境中运行。远程计算机49可以是另一个人计算机、服务器、路由器、网络PC、对等设备或其他常用的网络节点,而且虽然在图1中只示出存储器存储设备50,但通常包括许多或所有上述与个人计算机20相关的单元。在图1画出的逻辑连接包括局域网(LAN)51和广域网(WAN)52。这种网络环境在办公室、企业范围计算机网络、企业网和因特网中是常见的。
当在LAN网络环境中使用时,个人计算机20通过网络接口或适配器53连到局域网51。当在WAN网络环境使用时,个人计算机20通常包括调制解调器54或其他用于在如因特网之类的广域网52上建立通信的装置。调制解调器54(可以是内制或外制式的)通过串口接口46连接到***总线23。在网络环境中,相对于个人计算机20所描述的程序模块或其一部分可以储存在远程存储器存储设备中。应该明白,示出的网络连接是示例性的,可以使用在计算机之间建立通信链路的其他方法。
虽然本发明是相对于Windows2000操作***和微软的Windows NT文件***(NTFS)描述,本专业熟悉人员明白,也能使用其他操作***和/或文件***,并从本发明中得益。
事务文件***的通用结构
通常,这里使用的术语“事务”、“事务的”等被认为是具有某些共同属性的操作,在本发明中是应用于多重文件***的操作。事务属性常称之为“ACID”属性,代表着原子性、一致性、隔离性和持续性。如在下面所理解,本发明实现了与文件***相关的这些属性,为应用程序和一般计算提供许多益处。
如在图2中通常所示,从应用程序60等所发出的给如微软Windows NT文件***(NTFS)36(图1)这种事务启用文件***62(如这里相对于本发明叙述)的文件***请求58,经过分派机制66达到NTFS部件64。对于传统的文件***已经知道,为了产生例如可以导致由I/O管理器发送到文件***的I/O请求包(IRP)的这些请求,应用程序60可以作出应用程序接口(API)调用。按本发明并如下所述,文件***请求58的某些与事务相关,而其他则不是。
在没有事务情况,文件***请求58被分派并直接由NTFS部件64处理,实际上用本发明以前的方法。类似地,由事务发出的或指向如下述被打开事务修改过的文件或目录的请求58也继续正常分派到NTFS部件64,或从NTFS部件分派。但是,这种事务请求导致在其他正常处理期间的策略点处调出(调回)到如在文件***62内部实现的TxF部件70。
如图2所示并如下所述,TxF部件70包括到外部事务服务72和记录服务74的接口,并与NTFS部件64一起工作处理事务请求。外部事务服务72可以包括微软公司的分布式事务协调程序(MS DTC,或简称为MTC或DTC),其中一个客户(如应用程序60)调用启用一个事务,随后的调用提交或中断该操作。DTC有文档已很好描述,在这里只作简单说明而不作详细描述,且说明范围只限于与TxF 70相关的部分。
通常如图3所示,在MS DTC中,经过COM/DLE如应用程序60这样的应用程序借助调用事务协调程序76(图3)的方法(即BeginTransaction方法)启用事务。事务协调程序76可以是网络中的事务服务器或其本地的代理。此调用建立了代表事务的事务对象/范围78。然后应用程序60调用一个或多个资源管理器做事务工作。在本发明中,TxF部件70作为了用于事务文件***操作的资源管理器。在图3所示及下面描述中,对文件***62的API调用(如CreateFileEx 80和其他文件***操作)产生调出到TxF部件70。
应用程序对文件***62的第一次调用是对文件、目录或具有与其相关的事务范围78的应用程序的当前的线程/过程的识别。如果事务范围是相关的,文件***62调出到TxF 70。当TxF 70首次代表事务执行工作时,它通过调用事务协调程序76列入该事务,然后通知事务协调程序76:该TxF 70是在此事务中用到的资源管理器。注意,其他资源管理器84(如数据库部件的资源管理器)也能类似地列入此事务,因而数据库的操作和文件***的操作能在同一事务中一起提交(或中断)。
为了判断什么时候TxF 70需要列入事务,如图3所示使用与ITransaction对象78一起进入的事务标识符(ID),TxF部件70的事务管理器82层将ID与事务ID的事务表86中保持的已知列入的事务进行校验。如果已经列入,事务ID和事务参照被记录在I/O请求包(IRP)中,而IRP继续。IRP在NTFS中的使用有文档已很好描述,为简单起见在后面不再描述。但是,如果该事务未被列入表86中,TxF通知事务协调程序76:TxF 70是需要与此事务关联的资源管理器,并将该事务标识符存入获得的事务表86中。
更具体说来,当事务是未列入表86的新事务时,需要用事务协调程序76列入。为此,TxF管理器82采用代理88使用OLE事务或其他协议与事务协调程序76通信。适合本发明使用的替代协议等包括(X/OPEN的)XA,TIP(事务因特网协议)和/或在操作***中的内在事务控制。Create FileEX 80请求将ITransaction对象78(如通过DTC ItransactionTransmitter方法)安排到统一的字节集合。如果需要列入,这些字节发送到代理器88,它转而调用DTC ItransactionReveiver方法取回列入需要的ITransaction对象78。代理88保持DTC对象的ITransactionResourceAsync和ITransactionEnlistmentAsync。ITransactionResourceAsync应用TxF回调例行程序,使得事务协调程序76调用驱动二阶段提交,并具有列入调用。ItransactionEnlistmentAsyc通过IResourceManage::enlist()返回,并包含TxF 70调用确认二阶段提交控制的方法。代理88作为在ItransactionResourceAsync和ItransactionEnlistmentAsync及基于文件***控制(FSCTL)远程程序调用(RPC)的方法之间的中介,而RPC用于在TxF部件82和代88之间的通信。
注意,具有以与DTC协调程序过程同样的过程运行的TxF调协程序代理是可行的,且将事务管理器移到核心程序中以消除过程切换额外开销也是可行的。DTC代理的占位程序(Stub)也能移到核心程序中使得在建立用户模式代理中不需要TxF工作,同时也消除了从TxF代理到事务管理器的切换。TxF代理能以与DTC协调程序同样的过程运行,后者需要由TxF代理工作,但是其具有与以前解决方案相同数目的过程切换。
列入以后,当事务进行时,事务协调程序76保持对包括列入在事务中的TxF 70的每个资源管理器(和可能其他资源管理器84,如其他的TxF或数据库资源管理器)的跟踪。注意,这就使得其他信息(如数据库信息)作为还提交文件***信息的事务的一部分被提交,而且使得多重事务启用文件***的文件(如在远程机器上)作为同一事务的一部分被提交。
通常,应用程序60借助调用(通过COM)事务协调程序76的提交事务方法来完成该事务以提交该事务。事务协调程序76随后通过二阶段提交协议使每个列入的资源管理器得以提交。二阶段提交协议保证所有资源提交该事务,或均中断该事务。在第一阶段,事务协调程序76询问包括TxF部件70的每个资源管理器,是否准备提交。如果这些资源管理器肯定地响应,则在第二阶段,事务协调程序76向它们广播一个提交消息。如果任何一个资源服务器否定地响应,或未能响应此准备请求,和/或事务任何部分失败,则事务协调程序76通知资源管理器,事务中断。而且,如果应用程序不能完成,应用程序60调用中断事务方法。如果应用程序失败,事务协调程序76代表应用程序中断该事务。如下所述,包括TxF 70的各种资源管理器就撤消任何部分动作。
因此,TxF部件70就作为在标准事务服务(如DTC)的范围中的资源管理器,因而真正的用户定义的事务支撑扩展到文件***。注意,如下所述,NTFS允许TxF将瞬态的每个文件和每个流的事务状态链接到正常的NTFS结构。
按照本发明的一个方面,应用程序60可选择包括在一个事务中的文件***操作。这可对每个文件实现,使得每个文件标记为经事务处理的,且这种的操作按事务方式完成,或者对每个线程/过程实现,其中线程/过程标记为经事务处理的,而由该线程/过程所作的操作按事务方式完成。
为了在一个事务中包括在一个文件,定义一个事务处理模式标志(即:位),它能与CreateFileEx应用程序编程接口(API)调用(下面描述)、CreateFile WIN32 API变化的一起使用。当设置标志时,本发明的***自动将此文件包括在事务的范围中。为此,如在图3中表示,当通过I/O请求包(IRP),一个建立请求80进入文件***(NTFS)62时,现有的事务范围78可以通过将一指针转到该范围78而附加到该请求上,从而该文件能作为现有事务范围78的一部分被建立/打开。或者,如果在CreateFileEx API调用中指向Itransaction指针的指针是空,就如在MicrosoftTransaction Server(MTS)/Component Object Model(com)模型中那样,该范围被自动地挑选出到该线程之外。响应成功地建立/打开请求80而返回的文件句柄90包括到该事务范围78的指针。然后,用那个句柄90作出的调用通过此指针识别为具有与其关联的事务范围,由此识别出相关的事务,而且使用该句柄的文件***操作代表该事务被执行,直到该事务结束。
CreateFileEx API是现有的CreateFile Win32 API的适当扩展集,并加上″dwAdditionalFlags″DWORD参数以取得标志″FILE_FLAG_TRANSACTED″来设置该事务的模式。还定义的是指向事务范围的对象(LPUNKNOWNpunkTrasction)的参数,如上所述,如果参数为空,该对象就从当前的MTS/COM范围中挑出。
为了标记一个线程/过程为经事务处理的,提供SetTransactedFiles API,它有效地处理一组CreateFile/CreateFileEx调用,好象它们对经事务处理模式标志设定。如果特定的CreateFileEx指定非空ITransaction对象指针,该对象用作事务范围78,否则该MTS事务对象被挑出该线程。
使用SetTransactedFiles API将线程/过程标记为经事务处理的,从而通过该线程/过程的任何文件***访问都是经事务处理的。能设置三个不同的标志,即一个在设置时导致从当前线程的任何文件***访问都成为经事务处理的标志;一个在设置时导致从当前过程中每个线程的任何文件***访问都成为经事务处理的标志;以及一个在设置时导致从当前过程衍生的子过程对这些标志的第二和第三个标志进行设定。因此,有可能以衍生过程继承这种模式的方式标记该线程/过程,这是一种非常有效的机制,因它允许现有的应用程序利用经事务处理的NTFS。此外,它允许应用程序作出不具有事务处理模式位的文件***操作,如删除文件和复制文件。也能将此特征用于允许经事务处理命令行批处理脚本。下面描述SetTransactedFiles API:
SetTransactedFiles(
[in]DWORD dwMode,//零或来自列举的TxFILEMODE更多值。此值
//包含对标志的新设置,它们将按照dwMask
//参数所指示进行设置。
[in]DWORD dwMask, //零或来自列举的TxFILEMODE的更多值。只
//有出现在此掩码中的那些标志值会受
//SetTransactedFiles调用的影响。
<dp n="d10"/>
[out]DWORD*pdwPrevMode,//可选。如果提供,则通过这里,将以
//前的模式返回给调用者。
);
合法的标志值如下:
Enum TxFILEMODE
{
TxFILEMODE THISTHREAD=0x00000001,//对当前的线程
TxFILEMODE ALLTHREADS=0x00000002,//对该过程中所有线程
TxFILEMODE CHILDPROCESSES=0x00000004,//在设置此模式时
//使从当前过程衍
//生的所有子过程
//自动将_ALLTHREADS
//和_CHILDPROCESSES
//设置。
TxFILEMODE ALL=0xFFFFFFFF
};
如图4所示,对除建立/打开以外的文件操作,应用程序60为文件***提供句柄90,如通过API调用92请求文件读操作,从而通过事务范围指针,文件***能定位事务范围。注意,如上所述TxF 70必须列入该事务。由于在文件句柄90中指出事务范围,文件***就知道此操作包括在事务之中,并知道特别相关事务的标识符。将文件包括在事务范围中意味着包括读、写、文件建立和删除的对该文件的操作将经事务处理。任意数量的文件***请求可组合在一个事务内并原子地和持续地提交或中断。此外,在任何时刻可以进行任意数量的事务并互相隔离。
事务访问—读和写隔离
如上所述,为事务处理的访问可以打开或建立文件。目前,为了提供直接的、安全的和可预测的性能,***将在任意给定时间中在***中更新(写)程序事务数目限定到1,即如果多个事务试图同时打开文件进行读/写(RW)访问,在文件打开时刻就返回一个错误。因此,这些限制配置在文件级(相对于流级)。此限制伴随该文件直至以后提交或中断。
但是,另外可行的是用更精细的精度实现***,如文件可以由多个写入程序打开,但是没有一个能改写文件中由另一个程序所写的(脏(dirty))页,即一旦某页被写过,该页就被锁定。而且,“最后-写入程序-成功”类型的访问在这里也能实现。注意,在一个给定的***中这些类型的文件“写”访问不是互相排斥的,因为下面情况是可行的,一个API打开一个文件进行写访问而锁定整个文件,另一个API打开一个文件(不是同时锁定的文件)进行写访问而锁定每页(或其他文件段),和/或又一个API是最后-写入程序-成功写访问。然而,这里为了简单起见,本发明是以整个文件在给定时刻只能由事务打开一次(即其他只能顺次(serialize)打开)以读/写访问来叙述。文件的非事务更新程序也可以与用于写入的事务打开顺序化。注意,这并没有阻止属于同一事务的多个线程同时打开文件以写入。对于作只读访问打开文件的读程序的数目未加以严格的限制。
继续考虑本发明,由事务为读访问打开的文件与由另外的事务对此文件作出的同时改变隔离,而不管写程序是在读程序之前还是在其后打开该文件。而且,此隔离一直持续到只读事务访问的结束而不管改变该文件的事务是否提交该事务。例如,如图5所示,考虑事务处理读程序X它打开页面的文件V0用于只读访问,在图5中在时间轴起点由X/R0表示。注意,文件中每页的大写字符“0”表示在打开时间的原始数据。如果写入程序Y在以后时间在另一个事务中打开文件V1用于读/写访问(Y/RW),并随后作出改变(Y/Writes),事务处理的读程序X将继续看到在文件V0中的原始数据,而不是写入程序Y在V1中的改变。注意,当发生改变时,非事务将看到文件发生的改变。
如下所述,为了实现事务的隔离,(至少)在读程序X使文件打开的时间内,为读程序保留文件的V。“版本”。即使当事务的写程序Y提交时这仍然保持正确。注意,如下将详述,写程序Y对文件本身作出改变,而被读程序X看到的版本是在写入改变之前作出的原始数据的逐页复制;但相反情况也是可行的,即对读程序x保持原始数据完整而对写程序Y保持改变的页面的版本。而且注意,这是使用的术语“版本”,“已成版本(versioned)”,“正作成版本(versioning)”等是指时间瞬间的点(并且不应与源代码控制***中永久性的版本混淆)。此外注意,事务处理的读程序能与非事务处理的写程序依次排序以便于实现。另外,非事务处理的写程序可仅为了隔离的目的包括在“***拥有”的事务中。因此提供可预测的事务处理的读语义,因而事务处理的读程序在给定的时间点可以依赖文件的“静止”图像。
回到图5,一旦事务处理的写程序提交文件V1,事务处理的写程序Z可以打开文件V2(从V1没有改变)用于读一写访问(Z/RW),如图9所示。写程序Z能看到写程序Y的提交的改变,并能作出进一步的改变(Z写入)。但注意,此时读程序X仍然看到在文件首次被X打开时X所看到的原始文件页,而不是任何Y所提交的改变。仅当读程序X关闭文件并随后重新打开时,读程序X才可能看到写程序Y的改变。如图5所示,读程序X也能看到写程序Z所提交的改变,只要读程序X关闭文件V2并在Z提交以后再打开它。换言之,如果读程序X关闭文件并在Z提交以前重新打开它,读程序X将看到版本V1,但是如果读程序关闭文件并在Z提交后重新打开它,读程序将看到文件版本V2。注意,如下所述,在文件打开时刻保持并打开比最新提交的版本更老的版本也是可行的。
应该注意,这些语义是不能使用任何现有的文件共享模式表达的。这里描述的事务隔离语义隔离了各事务互相的影响,这与互相隔离句柄的文件共享模式成对比。现有的文件共享模式不变,并可用于附加的顺序化。例如,在为由规定“拒绝写”文件共享模式的同一事务的两个不同线程为事务处理更新而打开文件的情况中,第二次打开将以破坏共享为由而拒绝。这就允许分布式应用程序分配事务的工作负荷到多个线程、过程或机器,而同时保护由该事务作出的改变不受其他事务或非事务处理的工作程序的破坏。此外,这些语义保证可预测的已成版本的读,其中每个读程序能依赖文件的内容在保持打开时仍然稳定。
在下面列出的兼容性矩阵中,“是”意味着相对于附加的事务处理的限制是兼容的:
|
事务读程序 |
事务读程序/写程序 |
非事务读程序 |
非事务读程序/写程序 |
事务读程序 |
是 |
是 |
是 |
否/是(可选) |
事务读程序/写程序 |
是 |
否 |
是 |
否 |
非事务读程序 |
是 |
是 |
是 |
是 |
非事务读程序/写程序 |
是 |
否 |
是 |
是 |
因此,更新事务查看包括其改变的文件的最新版本,而经事务处理的读得到文件的提交的版本。一个另选方法(上面一般描述的方法)是在打开时提供文件最近提交的版本,而同时它是为事务处理读打开,当作出更多改变并提交时,不允许版本改变。其好处在于读程序在打开期间看到事务处理一致的数据样式。
在第二个另选方法中,由读程序看到的版本可以是第一次文件***访问时的版本或某个更早时间(即在TxF日志中更早点)的版本。,这可提供在此读程序开始时最新提交的版本。此开始时间可以是该事务首次访问该***中任何NTFS对象的时间,或者这时间可以使用在一个集成的情况中其他的API(如使用日志序列号,或LSN)来定义。这种特性的优点在于事务在多个文件中得到时间点的瞬态图(point-in-timesnapshot),当存在多个文件依赖性和链接时(如HTML或XML文件)是有用的。注意在此另选方法中,在同一事务中文件的多次打开可得到在该事务中第一次打开时所选的版本。但是可以认识到,需要由***保存的版本历史的数量在第二个另选方法中增加了。
术语“版本窗口(version window)”描述时间周期,在其中以前提交的版本的组被保持以支持选择的版本方案。对上述的第一另选方法,版本窗口随每个文件而变化,且是至今活动的文件的最早打开时间与当前时间之间的时间。对第二种方案,该窗含义为在***中最早事务的开始LSN到当前时间的时间。可以支持这些方案的一种或两种方案,而且由TxF 70所做的维持版本的工作基本上相同。为简单起见,本发明在这里主要对于第一方案进行讨论,其中由读程序查看的版本是在该事务中第一次打开时刻的最新提交的文件版本。因此,在此第一方案中,因为流版本在打开时刻确定,应用程序如果需要最新提交的数据,就必须关闭并重新打开句柄。这类似于在万维网服务器中特别有关的情况(其中网站能事务处理地在线更新),因此读程序为了看到新提交的状态需要关闭并重新打开句柄。
在一种实现中,写入一个文件是到实际文件,因为假定改变最终是由写程序提交的。如下所述,如果未能提交,通过在日志中得到的撤消信息,取消任何改变。因此,为提供版本隔离,每个针对一页的写首先导致为事务处理的读程序保存老的页。但注意,反过来做也是可行的,即保留原始文件完整直到改变被提交,从而写程序(而非读程序)将具有建立的新页面。
在使用微软Windows2000(或NT)操作***的较佳实现中,从高速缓存管理器和虚拟存储管理器或VMM的观点呈现各自内部存储流,而不是在盘上为老版本建立各自文件。高速缓存管理器、VMM及它们与非事务处理的NTFS的关系在下列参考文献中作详细描述:Helen Custer的“Inside WindowsNT”,Microsoft Press(1993);Helen Custer的”Inside the Windows NTFileSystem”,Microsoft Press(1994)和David A.Solomon的“Inside WindowsNT,Second Edition”Microsoft Press(1998),通过引入作为参考。
关于为事务处理读程序保持版本,从虚拟存储管理器和高速缓存管理器的角度来看,读文件的较旧版本如同读不同文件进行管理。这允许应用程序简单地将较旧版本映射到它们的地址空间,并使采用存储描述符表(如重定向程序)访问数据的客户能透明地操作。注意,这之所以可能是因为在Windows2000操作***中,VMM和高速缓存管理器参与文件***的输入/输出(I/O)。文件***(为非高速缓存访问打开的文件除外)使用高速缓存管理器将数据映射到***存储器,而高速缓存管理器转而使用VMM开始I/O。脏写页面的写通常以延迟模式在后台线程中发生。作为此结构的结果,直接映射到应用程序地址空间的文件与高速缓存管理器共享页面,这样,不管使用什么***服务得到它,都能提供一致的数据样式。注意,由此,网络重定向程序(下面描述)对数据进行本地高速缓存,并在服务器处得到与其他客户一致的数据。
为实现隔离,保持从仍然能读到的最早提交的版本开始到经更新的最新版本的多个版本。每个版本具有与追踪有关最新版本变化的版本相关的数据结构。如果读出没有改变的页,该页从文件中读出(它可以在高速缓存中或写入盘中),而如果被改变的页读出,它从已改变的页数据中读出(它也可以在高速缓存中)。注意,某些版本可以没有任何读它们的事务,但可以保持它们的存储器内部结构,因为它们在版本窗中,且将来可以得到打开请求。那些从未打开的版本不占据任何存储器来储存数据页面。最新版本对应于基文件流,且可以更新。
如图6所示,每个版本用TxF“版本流控制块”(TxFVSCB)描述。对一个文件的版本流控制块以时间顺序链接到一个表中,且除最新版本外的其他版本被提交/中断,且是只读的。最新版本可以提交或不提交。
每个TxFVSCB(如94)包括一个版本日志序列号96(版本LSN),它在记录到TxF日志中时,储存事务的提交LSN。在一个实现中,对(最新的)未提交的版本,此LSN是TxF定义的“MAX_LSN”,以便于寻找小于当前时间点的最高LSN。希望读早与此版本的提交的数据的读程序能借助使用在改变表中的项(如981)访问它,该表是存储器内部表98,它记录了由TxFVSCB指向的版本改变的页号。如TxFVSCB 94之类的每个TxFSCB还包括对应于此版本的段对象指针(SOP)结构100,它由高速缓存管理器和虚拟存储管理器使用,并表示存储器内部流。还提供状态标志102,其中之一表示该版本是否被提交。注意,只有最新的版本可以是未提交的。还包括VersionLength104数据字段,以及Change Table Pointer field(改变表指针字段)106,它包括指向记录由版本改变的页号的改变表98的指针。
如图6所示,在改变表中(如981),可以储存与页号相关的盘地址,以便于盘上找到该页的以前版本,只要该页在此版本中至少被写一次。注意,如图6所示,主要为了节省存储器,页的范围能存在一个项中,该范围处页面连续存在盘上。图7示出文件的多个版本的改变表940-943。可使用如树这种有效的搜索结构组织此改变表。
如果在事务中文件被打开用于只读访问,挑选出适当的提交的版本,版本号由“readLSN”识别。如上所述,readLSN或者是当前的LSN,或者是较早的LSN,取决于使用什么类型的版本。选择的版本是readLSN以前的最近提交的版本。如果版本不存在,如此版本太老,则打开失败。如果该文件没有任何与其相关的TxFSCB,用空的改变表建立新的TxFVSCB,并标记为未提交。使用默认的存储器内部流,使得现有的高速缓存的数据能用于读。对写访问,如果最近的版本是未提交,它作为已提交被使用,否则如果没有标记为未提交,建立新的VSCB,并标记为未提交。
当写入一文件时为便于隔离,每当一页数据(如在用户级缓冲器108)被事务改变时,该页基本上在原位(即在高速缓存110中)编辑(见图8)。然后在适当时间,高速缓存110被高速缓存管理器和/或VMM 112写到盘上(或其他合适的非易失性存储媒体)114。通常如上所述,数据能通过将文件映射到存储器或使用写API来改变。当使用写API时,通常使用高速缓存管理器112将改变复制到存储器驻留页116。注意,使用高速缓存管理器112将文件映射到***存储器。当使用存储器映射时,由应用应用程序直接对与高速缓存管理器112映射页相同的***存储器页(如页116)作出改变。改变经“脏”位记录下来,它指出在存储器映射I/O的情况,改变驻留在过程专用页表项(PTE)118中。通常,当存储管理器对来自过程的工作集的页面进行修整时,这些位就传播到共享的(页面帧号)PFN结构120。它们也能由应用程序60使用***API直接传播,以刷新映射段。注意,脏页也能周期地写出。
为了保证存储器映射的改变包括在事务中,在提交时刻***将刷新每个应用映射段的虚拟地址范围。从映射它们的应用程序的范围中起动此刷新。事务处理的语义可以这样定义:只有直接由应用程序刷新的页才能包括在该事务中(如刷新是事务性地作出,而不是对用户段中的字节的单独修改)。另外,这可以通过附着(KeAttachProcess)到具有映射段并做此刷新的过程的***线程来实现。段的列表保持在相应的事务表项中。注意,由文件API作出的改变在提交时也需要刷新到盘中。这是因为在页面写蚀刻,不可能在从以前事务留下的脏页面写和在当前的事务中由存储器映射作出的改变之间进行区分。
因此TxF同时支持由事务作出的只读和读/写的文件打开。当事务以只读访问方式打开文件,而该文件目前未被任何其他事务打开时,该文件上的语义与以非事务方式打开的相同。如果事务打开一文件用于读/写,则TxF需要的一种该文件的结构,每个流一个,以及对流版本的一种结构,以存储其每个事务的范围,如图9所示。用于此打开的数据结构如图9所示,其中“文件对象”是由用户的文件句柄映射的对象,“FCB”是NTFS文件控制块,“SCB”是用于打开的特定流的NTFS流控制块,“NP SCB”是主要用于保持对文件映射的段对象指针的非页面流控制块,而“CCB”是每个文件对象范围结构。注意在TxFFO中的标志指出该文件何时由事务打开用于读。
在图9中,TxFCB结构是用于由TxF保持的每个文件的改变的撤消数据的一个锚标,而且还包括对该事务的参照。TxFSCB是用于流版本的锚标,TxFVSCB是用于流的特定版本的撤消数据的锚标。TxFO结构描述对流的版本的特定事务访问,且它捕捉指向该版本的有关共享TxF结构的指针。
如图10所示,如果第二事务t3在以前的只读事务做完以前打开文件用于读/写,则该文件的老版本基本上移位(到图10中的右侧),为表示新版本的结构留出空间。因此,图10表示由修改文件当前版本的事务t3作出的读/写打开,由访问该文件最近提交的版本的事务t2作出的只读打开,以及由访问更早提交的版本的事务t1作出的另一个只读打开。注意,为简单起见,每个FileObject指向同一个SCB,而NTFS不知道文件的版本。而且,每个FileObject在唯一的非分页SCB中拥有其自己的段对象指针组。注意,通常并不使用用于只读事务的段对象指针,除非用户实际上映射该流。从对未加修改的页面的当前流以及从对已修改页的记录文件维护高速缓存访问。对每个文件对象的TxFO有效地捕捉该事务访问文件的哪个版本。
通常,因为TxF事务具有无关于NTFS句柄的生命周期,因此,TxF结构具有无关于NTFS句柄的生命周期。当两者都出现时,如图9-10所示,它们链接在一起,其中使用意义明确的接口在两边建立单向链结。例如,当发生对一个文件的事务处理访问时,校验到TxFCB的FCB链接。如果是空,它使用TxF例行程序建立。但是如果TxF已经存在,使用File-Id由TxF从TxF文件表中对其进行查找,否则分配一个新的。类似地,当FCB被重新分配且TxFCB链接是非空,则调用TxF例行程序用于单向(NTFS到TxF)链接的删除。
当没有事务处理的读程序使文件打开或能在将来打开文件的这个版本时,对文件重新分配TxF结构。即使NTFS目录由于目录本身的删除(在递归删除方式中发生)可以去掉,但只要在TxFSCB结构中存在名字空间隔离信息,就能维护目录。TxF结构的生命周期通过参照计数来管理。
记录服务
按本发明的另一方面并如下所述,对于永久性状态的记录与恢复,TxF 70使用记录服务74(图2),它允许多级记录而不是只依赖于普通的NTFS记录,以支持长期运行的事务。下面将明白,这提供了许多好处。例如,典型的NTFS记录大小在约4兆字节,对于目前的短期元数据记录,这个大小很适合,但是典型的用户定义事务将很快超过这样的记录。而且,相对于记录的TxF事务操作数目,很可能有大量记录的NTFS操作。此外,NTFS元数据提交操作锁定目录,而占用长时间的TxF事务将对文件***的性能有不利的影响(在假定的单级记录方案中)。
传统的NTFS记录已有文档很好描述,因此在这里除简单概要以外不作详述,叙述的范围是结合本发明的事务处理文件***对其的使用。在NFTS作出改变以前,通过写入对操作的撤消和/或重做记录到NTFS中,NTFS提供文件***操作的中断/失败恢复功能。NTFS记录是每卷的文件,用于记录影响该NTFS卷的操作,包括改变NTFS数据结构的操作,如建立文件命令、改名等。注意,记录的是元数据,而不是用户文件数据,如被写入的字节。该日志作为文件保持,并被访问以便从***失败中恢复,即如果***崩溃,将使用已知的技术,可以撤消或重做部分完成的操作。NTFS不提供持久性,即NTFS不强制其日志提交。
按本发明的一个方面,在多级恢复机制中,TxF事务和恢复管理分层在NTFS的顶部。如上所述,TxF将NTFS操作作为建立用户级事务的低级部件处理。为了恢复,TxF保持更高级日志,并且在检测到TxF强迫其自己的TxF日志在“数据”之前,将记录的NTFS操作处理作为关于更高级日志的“数据”进行处理。在此情况下的“数据”是NTFS日志,一个可恢复的存储本身。
如图11所示,为了完成多级记录,通过以利用已经可用的NTFS 64的可恢复性的方式协调每个日志的LSN(这里称为TxFLSN和NTFSLSN)强制高级TxF日志124在低级NTFS日志126之前完成。如下所述,对于不由NTFS事务管理的数据(即流字节本身),TxF 70实质上完全地管理可恢复性。
为保证较高级TxF日志124强制在其“数据”(即在NTFS日志126中的记录)之前(而没有无效地强制TxF日志在每次NTFS操作之前),提供TxF回拨,使得每当NTFS 64将要强制在其日志126中的数据时,NTFS 64就调用它。在此调用中,NTFS 64指出需要刷新的最高NTFS LSN。同时,TxF 70保持TxF使用的最近的NTFS事务的映射128,以便将NTFS提交LSN映射到对应的TxF LSN。注意,设计了名字空间修改操作,使得TxFN知道TFS提交LSN。NTFS日志并不持久,因为它相对不频繁地刷新到盘中。因此,在日志高速缓存中存在合理数量的TxF记录,它们在单个I/O操作中一起刷新到盘中。
响应此回拨,TxF 70强制TxF日志124直到对应于在NTFS日志中被强制的最高NTFS Commit-LSN的TxF记录。但是应该注意,刷新TxF日志124到最高记录仅是更加优化,因此保证较高级日志首先刷新的其他方法(如当NTFS要刷新其日志时刷新所有新的TxF记录)也满足需要。在恢复期间,在TxF开始其恢复以前NTFS完成其的恢复。
虽然这保证TxF日志124在NTFS日志126之前刷新,但某些靠近TxF日志结束的日志记录可能已完成未被NTFS提交的NTFS操作,这种记录与已被提交的记录混合。重要的是将其对应的NTFS操作已被提交的TxF日志记录与未被提交的那些加以区别,因为这决定了在恢复期间是否应用TxF日志记录。
也将明白,这是重要的,因为在重做期间重复一个操作,或撤消从未发生过的操作是不正确的。作为一个例子,考虑下面在TxF日志中可能记录的情况:
Create YCommitRename X→Y(superseding rename){actually two log records:delete Y(rename to a link in a system dir)--undo of this is:rename system linkback to Yrename X→Y}SYSTEM CRASH |
在上述情况中,不可能知道是否正确地反转(撤消)了改名操作。每次简单地做此反转操作是不正确的。因为如果在NTFS中改名实际上从未发生过,Y将改名为X,取代它。因此,在试图打开***链接时可能失败,因为该链接由于未发生NTFS操作而不存在。文件X将被丢失,而Y改名为X,但是,如果TxF 70能够查明改名是否发生,就能精确判定是否要应用撤消操作。
为在请求一个操作前判定操作是否实际发生,即是否被NTFS 64提交,TxF将相应记录写到其日志124。然后TxF接收TxF LSN,后者为NTFS 66提供对给定文件的请求的操作。虽然在提交后让NTFS 66将TxF LSN放到其对应的NTFS日志记录(或多个记录)中是可行的,但这是低效的。取代的是当NTFS提交操作时,作为提交的一部分,NTFS将TxF LSN写到在NTFS卷上保持用于该文件的记录中。在NTFS中,对卷上每个文件(及目录)已经以称为主文件表的结构保持了记录。因此,如在图11所示,TxF LSN写到在对此文件(如文件3)的记录中的一个字段(如1323)中。注意,也可使用另外的数据结构,只要每文件的记录已经在每个NTFS卷上可以得到。
随后,***崩溃之后,在恢复期间,在TxF使NTFS完全实现其恢复之后,TxF首先检查以确定在***崩溃前在TxF日志中记录的操作是否在盘上进行(通过NtfsTxFGetTxFLSN(file-id,*TxFLsn)调用来调用NTFS)。如果对文件的NTFS操作提交并在***崩溃前保存到盘中,TxF日志124中记录的TxF LSN就小于或等于在文件记录字段中的TxF LSN,因为NTFS恢复保证文件记录将被恢复。如果在文件记录的TxF LSN小于TxF日志记录的LSN(或不是在该文件的文件记录中),则可以知道NTFS操作未被提交,而且对应的TxF日志记录不能用于撤消。
但注意,为保证正确的恢复,如果一个对象在恢复窗期间被删除,TxF将推迟该文件记录的删除(因而保留文件标识符file-id),直到所删除日志记录在日志中被遗忘之后为止。这是借助建立到该文件的***链接完成的。此外,如果建立一个新文件,在NTFS确定将用于建立的文件标识符之前不写入TxF日志记录。这就实际上将文件标识符记录入TxF日志。注意,对非事务处理的建立也如此,NTFS将当前的TxF LSN写入文件记录,然后处理这种情况,其中在恢复窗期间重新使用文件标识符(包括序号),并在建立之前使TxF跳过日志记录。
因此,如果NtfsTxFGetTxFLSN调用发现,在恢复时刻文件标识符不存在,则或者在事务提交之后且在***崩溃之前文件被非事务处理地删除,或者在建立操作之后立即发生***崩溃。注意,在第一种情况,没有涉及TxF且在恢复窗期间文件记录被删除。在第二种情况,TxF建立的日志记录送到TxF日志盘中,但NTFS对它的提交未持续。只有当处理一个建立日志记录时第二种情才能检测到。
因为撤消记录用于中断未完成的事务,如由NtfsTxFGetTxFLSN看到的文件标识符不存在的记录可以简单地忽略。
应该注意,在中断、崩溃恢复和向前恢复期间,由日志驱动的重做和撤消动作在NTFS过滤一驱动程序模型中的过滤驱动程序堆栈的顶部起动,允许任何中间的过滤驱动程序看到这些动作。对应于重做和撤消动作的IRP被专门标记,使得过滤驱动程序能选择忽略它们。这些IRP将包括通常的事务状态而文件对象一般将指向事务对象。但是,因为事务处于特定状态,TxF将知道它们需要专门处理。例如,TxF不试图将这些动作包括在一个事务中,或将它们作为非事务处理。
除了记录名字空间操作以外,TxF部件70与记录服务74协同工作以记录在其他操作中的页改变。如上所述,在中断情况为维持版本也为了支持撤消操作,在通过API对存储器中的页面实际作出改变以前,相应的撤消记录写到(非强制性的)TxF日志126。如图12所示,然后写整个页面(通常写到存储器内和如下所述称为TOPS流134的盘上的页流),它允许已成版本的读程序在单个I/O操作中读出该页面。在日志写以后,对该文件的改变表98用日志序号(TxF LSN和在TOPS流134中的偏移量)标记,此改变随后应用到该页。
对由页I/O改变的页,如从已被用户映射段修改的页和/或由较早对正在写入的API的调用修改的页所得到的页,完成页面的写。此分页的写能在后台的线程中,或可以是在提交时刻刷新部分。在任何情况,TxF 70首先检查改变表98(图6),以查看该撤消是否在TxF日志126中已被抓住。若是,***强制TxF日志126直到表98中标记的TxF LSN,在大多数情况它将返回而没有I/O。如果改变表98未被标记,得到该页的撤消版本并写到TOPS流134和TxF日志126。多页I/O是常见的,因为后台线程试图以文件偏移量的次序将页面组合在一起。在这些情况,多个撤消被写入单个、大的I/O。在这些情况的撤消也将在单个、大的I/O中读出。
在准备的记录被强制到TxF日志126以后,撤消映象在TxF日志126和TOPS流134中的盘上,而修改的文件页在文件中它们的位置处。因此,提交是将提交记录写入日志126的简单操作。中断的实现是通过以逆序执行撤消记录,并将它们应用到基文件,随后刷新文件,随后强制写中断记录。如果中断记录存在于日志126之中;在恢复时刻就忽略这些撤消记录。注意,通过在不频繁的操作(中断)期间刷新文件,大的(页面大小)补偿日志记录(CLR)不需要作为重做记录写入,这显著地保存了空间。
获得一个撤消映象与得到以前提交的页版本是一样的,即文件的撤消映象首先在文件的以前版本中搜索。如果映象留在存储器中,撤消映象从存储器中取出。否则,映象由非高速缓存的I/O操作从盘中读出,因为脏位被非公开地处理且并不需要知道,就无法确定当前留在存储器的映象是否是脏的。
如上所述,每当一页面由使文件打开以写入的事务改变时,该页面就在原位(即高速缓存器中)编辑。随后,高速缓存在不同时刻被写入盘中(图8)。但是在页面数据被改变时,老的页面数据需要保存,所以如果事务中断或***失败,老的页面数据能够恢复。为此,老的页面被复制到TOPS流134,而改变记录在TxF日志126中。如图12所示,日志记录(如X2)包括到此页面的偏移量,而日志126不需要保持数据,而只需对应其的记录。因此,为使页面恢复,TxF使用随时间顺序记录改变的改变日志。注意,对于正在做版本,为有效起见使用在改变表98中到TOPS流134的偏移,而不是访问TxF日志126。但是在***失败的情况,存储器内结构的版本流控制块在恢复时刻不存在。而且单独在存储器内的任何文件版本是不可恢复的。因此为了恢复,可以将日志中的记录用于在失败期间中断事务,并用于持久地完成在***失败前提交的事务。日志项(或日志记录)的顺序特性保存了改变的次序。
在本发明中,由于其性能和其他原因,页面写的日志记录分离成两部分。与主日志内联(inline)的部分保存其相对于其他日志的次序,而另一部分包括(相对更大量的)字节,它们提供操作的细节,即改变的页面数据。因此,按本发明的一个方面,如在图12所示,每当页面由事务改变,老的页面数据复制到(连续的)TOPS流134,并在TxF日志126记录改变。如上所述,在调整表以将事务处理的读程序映射到复制页面以后,该页面随后可以改变。如图12所示,日志记录(如X2)包括到在复制页的流中的此页的偏移,因而主日志不需要保持数据,只保持具有对应其的偏移的记录。
但是,出于性能原因,这些日志被不同地刷新到盘中。因此,页面和日志126在给定时刻可能都不能持久,例如***可以在日志126被刷新到盘和/或页面被刷新到盘上之前失败。保证页面数据不丢失的简单方法是在两者之间强加排序,即在刷新日志记录到盘上之前总是先将页面刷新到盘上。因此当恢复过程中使用日志时,如果日志记录存在,对应于该记录的正确的页版本也已知继续保持,但是发现此次序依赖关系很大地降低了***的性能,因为日志刷新操作根据许多无关的因素在不同的日志上更加有效操作。例如为了改善性能,页面通常以多组形式被刷新,如使用惰性写算法一次16页,而日志在满时,或在后台处理的不同时刻被刷新。
按本发明的另外方面,提供一个***和方法,它们使页面和日志能以相对彼此任意的次序刷新到永久存储器中,而且以这种的方式,确保在失败的情况能恢复正确的页面。这是通过将信息加到日志126和页面数据中来实现,将两段信息以一致状态(例如及时)有效互相链接。更具体地说,保持一个循环计数136(如以字节,虽然可选择使用一个字或更大容量),表示页面的当前状态,如每当指向TOPS流134的指针翻滚回到起点时循环计数就加1,而且该循环计数与日志记录同步。
如图12所示按本发明的一个方面,同步是通过将循环计数值保持在与复制到TOPS流134页面相关的日志记录来实现的。这在图12中用标号为138的方框表示,它提供某些记录的数据字段的扩展表示。还示出。在每段中的最后部分(如字节)复制到日志记录以便在那里保存。注意,一个页面包括8个512字节的段,每个段如这里所描述,但可以理解,其他页和/或段的大小是可能的。而且在流数据中每段的最后部分用循环计数代替,如在图12中用标号为140的方框表示,用在每段的最后部分中代替的循环计数提供页数据的扩展表示。如在图12所示,如果页和日志均被刷新,在每段的未端的循环计数值将匹配在记录中的循环计数值,即两者具有匹配的特征。
如果只有页面数据(外部部分)被写入盘,***将找不到内联(日志)记录,因此找不到该页面,没有什么可恢复。该状态就认为是一致的。
但如果记录出现在日志中,在***崩溃前,页面可以被或可以不被刷新。图13通常表示当在退回过程中记录到达时,页面及其日志是否均被刷新到盘中。首先在步骤1300,记录被访问,以通过其储存到流134的偏移量找到页面。然后,在步骤1302读出页面并从中取出每段的最后部分,在步骤1304将其与存在日志记录中的循环计数比较。如果只有内联(日志)记录被写入盘,***崩溃以后存在外部部分(页面数据)的每个段中的唯一的记号(每个循环计数)将不匹配存在于内联记录数据的循环计数。在此情况中,如步骤1306所示,***得出结论,因为老的页面未写入盘,而新页面也未写(只有在两个日志被刷新时它才被刷新)。因此该页面已知处于以前的老状态。
相反,如果在步骤1304中,日志中的循环计数匹配对应页的每段的最后部分中的循环计数,日志和页面就已知均被成功地刷新。因此,就知道被复制的页面保持,而存在日志记录中每段的最后部分在步骤1308恢复到复制的页。
此时,被复制的页面可由读程序访问,并提供适当的版本。任何对当前页面作出的记录改变可用于(步骤1310)使新的读程序和/或写程序看到。在该情况,知道老的数据被正确地捕获,并必须作为中断的一部分恢复到文件页。注意,尽管中断,现有的事务处理的读程序将继续从TOPS流134读到老的数据。
应该注意,使用在每段的结尾的唯一的记号还进一步检测***(部分)写,其中某些页面被复制,但不是所有页面。注意,盘硬件保证段将写满,但不保证一页数据(如8段)将作为一个单元被写。在这种情况,循环计数是“n”和(推测的)“n-1”值的某种混合,该记号将不匹配记录的记号信息。就如同整页没有保存来处理这种情况。
注意,当循环计数本身重算,有可能使其匹配构成现有页面上的记号的计数(如它已经在存储器内相当长时间),因此使部分的写入不能被检测。例如,如果使用重算的循环计数,且如果它匹配存储在页面上的现有循环计数,则不管是所有页面还是某些页面被复制,记号是一样的。可以理解,在此情况的记号校核将指出,整个页面数据的保持,虽然事实不是。
此问题能以许多方法解决。一个解决方法是在每次循环退回事件之后读页面一次,以验证是否存在不匹配。如果匹配,可以调节二个循环计数之一以避免匹配。为保证每次循环退回(即每次循环计数回到0)只发生一次上述情况,可使用单独的验证位映象141保持每页的“验证”状态,即每位是退回之后的一个状态,且当页面首次检查循环计数匹配时作切换。注意,使用自由空间位图跟踪一页是否为空闲或使用,且为有效起见,上述解决方案将附加的位图加到跟踪验证状态。
另选的解决办法(对上述读和比较操作)再次跟踪验证状态,但是当“验证”状态是在页面使用时设置,如上所述循环计数被写到页面中,且强制写入。如果写入成功,则写入不是部分进行。对大的TOPS流,由于较少的输入/输出I/O操作,此另选方法调节得很好,因为循环计数匹配页面的情况可能相对很少出现。
又一种另选的方法是结合检查页面驻留的首次两种方法的组合,即如果页面驻留在高速存储器中,因为不需要实际上的读盘而实现第一种(读)另选方法,否则执行第二种(写)另选方法。
延迟的重做另选方法
上述恢复机制将文件的脏页在提交时刻写入盘,防止在多个事务上成批页写入。为达到在多个事务上成批页写入,可提供一个另选的“延迟重做”方案,它在恢复的方面上做了相反工作。此方案将重做记录写入日志,并当没有读程序仍在读它时,将老的提交处理施加给基文件。为了支持老的提交的版本的读,不在原位做出改变,而是当现有的页的原位版本不再需要时,只能原位应用至该文件。
延迟重做方案共享由原位更新方案使用的许多原则,例如,它以与版本控制块和多个存储器内流十分类似的方法支持做版本。但是,改变表保持重做页面的LSN,而不是撤消的LSN。通常如图14所示,最早的盘上的版本总是基文件,而较新的版本在其上建立增加的改变。当读程序离开时,较早的版本合并在基文件中。为了利用此方案主要优点,多个版本能同时合并到基文件中,因此获得I/O效率。同时合并多个版本的另一优点是在大的读操作中日志能被有效地读出。
但是,日志可以用页面充填,它们返回存储器用于(可能许多)活动的文件,实质将顺序的日志调整到既作为恢复日志也是随机页面的文件中,这可能成为***中的瓶颈。
类似于原位更新方案,最新的版本是可更新的。存在一个版本控制块(TxFVSCB)与每个版本相关,且每个TxFVSCB指向改变表,这是一个记录由该版本改变的页面号的存储器内的表。盘地址可以与每个页号一起存入,以便找出在盘上的页面,只要它至少写入一次(重做映象)。缺少盘地址意味着该页从未写入盘中。为了节省存储器,页范围可存入一个项中,在这个范围内页面连续地存在盘上。
版本LSN是提交该版本的事务的提交记录的LSN。对当前可更新的版本没有这种LSN。SOP指针是指向对应此版本的段对象指针结构的指针。使用该指针,能找到该存储器内页面。类似地提供版本长度。
版本控制块以时间顺序链接到表中。最早的版本是基流,而改变表不包含此版本的任何项。
在打开时刻,如在上述另选方案,给予版本之一一个文件句柄。最新版本的存储器内流部分由日志返回(不是由基文件全部返回)。这样,对流的改变写入日志。如果在版本窗的任何版本中该页未改变,则从基文件完成读,否则它们从日志中完成。
在读的时候,查阅对应于该版本的改变表,以判定在该版本中页面是否被修改。若是,I/O对着日志中适当的位置以取入该页面消除故障。若不是,则查阅该页下一先前版本;此过程一直持续到找到该页的最新提交的复制。如果多个版本包括该页的复制,用VMM调用检查它们的存储器驻留内容。如果找到存储器驻留页,对其复制,否则使用最新版本的LSN从日志中将其读出。注意,如果该页是在驻留内容被检查和作出复制的时间之间从***存储器裁剪出,也没有关系,因为产生递归的故障并且页面在其后被复制。为了得到***地址以复制这些页面,使用高速缓存管理器映射它们到***地址空间。
在图14中示出四个版本V0-V3(但其他数目也可以),其中用“X”标记的页表示在版本中的改变。改变表1420-1423示出已写入页面的LSN。在最近(可更新的)版本中的某些页还未被写入。在此情况,考虑一个例子,其中FileObjectB访问页面50(50)。对文件版本V1的改变表1421表示此页在该版本中未被改变。因此,通过对该页检查文件版本V0的驻留内容并若是驻留的(不出故障)将其复制,借此处理故障。如果文件版本V0没有页是驻留的,则将它从盘读出(在此情况,从基文件读出)。
作为另一个例子,如果FileObjectB访问页面200(200),且该页在存储器内,访问很简单完成。但是如果不是,产生页面故障,并通过从LSN 2500处的日志读出它以达到读的目的。
作为另一个例子,考虑FileObjectc访问页面100(100)。因为该页面在版本V2没有改变,检查版本V1,且通过从存储器映象(若是驻留的)或通过在LSN 2000读出日志以满足读取。
对于文件写,在页调出的时刻,页面以重做记录的形式写到日志中,重写记录也描述了流偏移和流名字。在此时,LSN被标记在对该版本的改变表的页缝中。页面写由一个***线程在后台中发生,并通常以顺序页面次序写入。在提交时刻,在那个版本中的脏页面被写入日志,随后是一个提交记录。如果在事务期间页面被写出多次,则完成了多个日志写。这些写进到日志的末端,且改变表项被改变成指向新的位置。如果新的写事务在提交之后开始而没有对读事务有任何干涉,主存储器流被新事务再使用。否则,就由读程序对其申请,而写程序事务建立新的流来工作。注意,在延迟重做方案中,改变的页面能写到TOPS的流(就象原地更新方案)以得到有关的好处。
在***崩溃后,恢复是相对直接的,因为提交事务的重做信息在日志中,并能简单地加到主数据流。注意,版本控制块是存储器内结构,因而在恢复时刻不存在。
当老的读程序结束它们的事务时,老版本不再需要保存。在此时,版本从最老的开始以每次一个版本的方式合并到主流中。因为版本被合并,就从版本的链接表把它们去除。通过将那个版本中的改变页面(在改变表中查找的页号)复制到基流并强制到盘中,以每次一页方式发生合并。此复制操作读出不是当前驻留的页面的日志。若可能,执行大量的I/O操作以从日志捕获页面范围。如在图14中,如果版本V0不再需要支持做版本,版本V1就合并到版本V0中。此合并能发生而不必锁定版本V1,因为当合并进行时每个页面的副本存在于版本V1和版本V0中,而对V1的改变表在整个过程中不变。
当合并完成以后,若版本V1不在版本窗中,V1的版本控制块简单地从版本表中除去。通常,合并将延迟直到从读程序释放多个版本。在本例中,V0、V1和V2在它们离开版本窗时能一起合并到基文件中。对多版本的合并,改变表首先以这种方式合并:当在多个表中同样的项被修改时,从最高版本号来的LSN先挑选出来。这实质是在各事务之间将写分批,这是此方案的一个优点。在一个版本被合并以后,其日志记录适当地从活动的日志中去除。
通常,合并应尽可能早做。每当写程序离开时,版本窗前移。那时,某些版本标记为适合于合并。当标记了多个版本,一个工作项放在***线程上以进行合并。
原地更新方案和延迟重做方案都执行差不多相同数量的I/O。原地更新方案可以同步地读撤消命令(因为有时可以在存储器中找到它,例如如果并发的读程序最近在读它们)。原地更新方案将页面写出到基文件,并还将撤消顺序地写到日志中。相反,延迟重做方案需要将重做写入大的随机I/O,且需要随机地读日志以便合并一版本。此外,延迟读方案需要将文件页写到盘上,虽然它使各版本上的写最小。在存储器中寻找这些日志页面的机会因此是非常低,给出了合并可能延迟多长时间。
在何时何地完成I/O方面有性质上的差别。在延迟重做方案中,最新的存储器流由日志返回,而不是基文件。这很可能是最经常使用的流,因为它处理更新工作,比较地加重日志的负担。对于已成版本的读程序,两种方案使用日志作为分页设备。
延迟重做方案与提交处理较少同步工作,因为大量的事务工作在后台完成,但是对每次写API或存储器更新的写程序没有显出更快,因为这些在高速缓存中完成。相反,在提交时刻的刷新是在提交的响应性方面的差别显示增加处。对较大的更新事务,后台***线程看来调度异步的写,它有时减少响应性的差别。类似地,原地更新方案也能通过在后台中完成对文件API的撤消工作而减轻在提交时的负担,但对在用户映射段中作出的改变是不可行的。
原地更新方案比延迟重做方案简单,因为原地更新方案不需要处理调度异步合并操作的问题。而且,原地更新方案不需要处理在前台和后台活动之间的速度不匹配问题,这有时可能阻碍日志空间并产生资源获取问题。
最后,用延迟重做方案不必改变正常运行记录算法,就可能存档及向前退回,因为在日志中重做记录是可得到的。但是,因为没有撤消记录,需要执行对日志中的某些前向扫描,以便在对该事务应用任何重做之前找出事务的提交状态。
在网上的文件***事务
通常如图15所示,通过内部核心到核心的“重定向程序”协议(如SMB协议)来访问远程文件。此协议反映了在如文件服务器之类的远程机器148上的客户机146上执行的文件***操作。当然,其他协议和机制(如Wev DAV、NFS等)能得到等效的结果。所以,如非事务文件***访问那样,远程文件被识别,并且IRP指向在客户机器146上的重定向程序文件***驱动程序150。已经知道,此驱动程序150与客户机的高速缓存对接以读和写数据。如从针对远程机器的文件***154的应用程序152来的文件***请求(如访问在远程盘156上的文件G:/Fname)之类的请求被重定向程序驱动程序150截取,并发送到远程机器148,在远程机器148,代理程序158(数据自适应鉴定监视器(daemon))线程将它们翻译成在驱动程序堆顶层的文件***操作。
对远程事务文件***操作,为打开文件,客户重定向程序能,例如,使用COM/OLE将DTC事务对象160c编排到统一的字节流中,具有给服务器148的打开请求。可以看到,其他机制能达到等效的功能和/或结果,且虽然这里描述COM/OLE操作,本发明的这方面不限于COM/OLE。在COM/OLE实例中,事务对象160c附着于请求打开的客户线程。注意,服务器机器148不关心事务在哪里开始的,只要它能将DTC事务对象160s的副本保持在其核心空间中。类似地,服务器148不在乎哪个线程或过程代表该事务工作。而是,在服务器148处的代理程序158将统一的字节流转回到在核心中可得到的可用对象。此时,服务器把请求当作本地事务160s,并用在服务器上的副本DTC代理162s支持它,主要是告诉DTC为了后续的事务工作与服务器148联系(而在这里TxF部件164作为资源管理器)。注意,因为服务器拥有此事务对象160s,因此,这是合适的。因为事务-ID处在分布式名字空间中,事务能在任何地方开始,但是基于事务-id的正确文件同步发生在服务器148上。
服务器实际上将文件***请求当作就像是在本地情况的请求,而本地的TxF部件164处理事务的文件***请求。但是,服务器148必须记住对应的文件对象是针对被客户146打开的文件,且该客户具有高速缓存页面。因此,在提交时刻,服务器148通知(通过重定向程序协议)客户146刷新其高速缓存到服务器,并刷新任何能在客户处打开的映射段(映射段的客户跟踪)。数据正常地以某些惰性方式(lazy fashion)到达服务器148,即每当它分页出客户的高速缓存/存储器时到达。当数据到达时,它改写在服务器上的高速缓存的副本。注意,这类似于以前的文件***模型,其中多重打开的句柄或映射段互相改写。
对基于重定向的文件建立操作,上述概念也能用于在网上编排ITransaction,在所述概念中用户模式中的CreateFileEx将ITransaction对象编排(如通过DTC ItransactionTransmitter方法)到统一的字节集。因为在ITransactionTransmitter调用中不需要与事务管理器进行通信,其花费相对较少,因此对每次建立都能完成。但是,接收调用(如上述)确实需要与事务协调程序(或它的代理)通信,在基于重定向程序的情况,该程序是在远程机器148上。然而,因为对于在整个网络中每个事务,ITransactionReceiver只做一次(在服务器148上),所以与事务协调程序162s的开销并不明显。
在此方式中,透明地支持事务的远程文件访问,即,使用远程文件访问,并通过在多个机器上建立应用程序代理,应用程序实际上能直接访问网络上任何处的文件。因此,同一事务能在同时涉及一个或多个本地过程及远程过程。
对于单个客户具有一个为远程访问打开的文件的情况,通常可以优化重定向程序协议。在这种情况,通过保持该文件的本地盘高速缓存而避免了大量的网络数据传输。仅当需要时(即文件关闭时),才刷新改变。但是一旦另一客户同时打开同一文件时,这样的安排是无效的。机会主义的锁定(oplock,主要是指示所有权的令牌)可完成此事,从而对上面的“关闭时更新”方案的改变最小。尤其是,在提交时刻,通常询问客户是否将改变刷新到服务器上去。在中断时刻,客户请求将客户句柄标记成“毁灭的(doomed)”,使得一旦该句柄关闭,改变将简单地丢弃。注意,重定向程序协议可以增强,使得服务器在某些环境让客户映射段无效,如同在本地情况。
名字空间隔离
将一个事务的变化与其他事务隔离是事务的关键特性。在事务文件***中,隔离不仅施加到存在文件中的数据(如上述),还施加到文件名和文件组织下的目录名的层次,按本发明的另外方面,提供了在文件/目录名层次内实现名字空间隔离的技术。该技术不需要在事务处理期间锁定名字或目录,并能与试图用在事务中的文件上的非事务操作工作。
作为例子,考虑由事务建立而未被提交的文件。注意,可以建立目录而不单是文件,但为简单起见,本发明主要关于文件讨论。然而应该理解,关于下面叙述的名字空间操作,通常能等效地处理文件与目录。事务建立的文件(或目录)应能用于没有限制地建立事务,但应不能为其他事务(如试图打开它或列出其父目录的事务)可视。仅当建立的事务提交时,该文件才能被其他事务看到,若它中断了,则该文件对任何人都不可见。非事务(如请求父目录列举)将看到这种文件,或者,使这种文件在提交前对非事务不可见也是可能的。
类似地,若文件(或目录)被尚未提交的事务删除,被删文件需要继续对其他事务可访问,就像在提交时刻以前此删除从未发生。但是,删除事务将看到删除的效果,并能够在其空间中用相同的名字建立不同的文件。提交以后,被删除的文件被去除。非事务将看到删除的效果,即看不到被删的文件,但非事务不能建立带有与由非提交事务所删除的文件有同样名字的新文件,以避免由于删除文件/目录的事务中断或删除撤消而产生冲突。而且还可以将非事务当作好象是不同的事务进行处理,因而继续看到被事务删除的文件,但这并不是很好。
此外,如果文件(或目录)被事务改名,对其他事务继续可用具有原始目录中的原始名字的该文件,新名字对其他事务不可见。做改名的事务将看到改名的效果,且可以使用老名字建立不同的文件。注意改名本质上是建立新链接和删除老的链接的组合。
为完成名字空间的隔离以处理上述情况,本发明保存名字空间的状态以在事务处理期间为其他事务所用。为此,如图16-18所示,建立称为隔离目录1701-1704的各个目录,并将其链接到由执行名字空间操作的事务改变的对应NTFS目录。尤其是,每个隔离目录(如1701)包括与父目录(如目录D3)的TxFSCB结构有关的搜索结构(如二进制搜索树)。此外,隔离目录搜索结构,并且有关的处理例行程序包括支持增加一个项及用名字快速查找一个项的通用接口,且还支持目录列举算法。
这些隔离目录包括受使名字空间改变的事务影响的各个名字,并且只是主存储器结构。结构中的每个项还包括与该名字相关的事务ID(Tid),和可见性配置,它有两个标志,对事务Tid可见,或对其他事务可见。这些可见性标志中的一个或两个都可以分别设置。隔离目录结构还包括短名字/长名字标志,其中如果一个配对可用,该结构包括指向对应于该配对名字的结构的指针。还提供一个标志指出名字由Tid保持,而其他事务不能请求它;一个Fid(用于对删除的和改名的名字重定向建立());和其他信息,即NTFS复制信息如时间标记和用于目录列举的类似标记。为了空间使用效率,结构可以分成名字,指向该信息的指针,指向其他名字的指针,和其他信息。这导致单组其他信息被两个名字共享。
作为隔离目录如何使用的例子,如图16所示,如果文件F3被事务T1删除,文件F3的名字和各种信息在名字从NTFS目录D3去除(几乎)同时被加到隔离目录1701。注意,为了删除NTFS中的文件,打开文件被标记为删除,文件***关闭该文件而维持打开句柄的计数,并当没有句柄保持打开时执行删除操作。此外注意,隔离目录1701可能由于此事务T1的或另外事务(如T2)更早的操作已经存在,或者在需要支持此删除操作时建立。下面参考图19的流程图来进一步描述删除操作。
使用隔离目录1701来处理由不同事务(例如T2)对该文件F3的后续访问,这样,事务T2将继续看见文件F3。然而,如果删除文件F3的同一事务T1(或非事务)查找文件F3,它将不能找到该文件。为了处理这些情况,如上所述,为此文件维持该文件的名字、其可见性配置、删除文件的事务ID、重定向文件的ID、$TxF文件标识符(例如,单调增加序号)和复制的信息(数据标记、大小、属性)。
图19提供了处理对打开文件删除请求的通用逻辑表示。注意图19和类似的流程图针对提供如何使用隔离目录的理解进行了简化,并且不应认为是基础代码的精确表示,并没有包括特别情况、差错处理等等。在任何情况,在步骤1900开始,在事务处理及非事务处理请求实体之间作出区分,因为事务处理的用户将导致不同于非事务处理的用户的删除操作的操作。如果非事务请求删除一个文件(由其句柄识别),删除以另外的正常方式执行,即在步骤1902指定的文件从盘中被删除。当最后的句柄关闭时删除开始。
若事务(如Tid1)在步骤1900请求删除,则执行步骤1904,它主要是改名文件。例如,如图16所示,具有任意名(如“0”)的链接加到隐含的目录168($TxF),它链接到在主文件表130(图11)中的文件记录。同时,来自删除的文件F3的链接从父目录D3中去除。
然后在步骤1906,删除信息被记录到删除记录中,即文件名F3,对原始父目录和新的链接信息的参考,如果在删除该文件的事务提交以前***崩溃,该事务将中断,而日志通过简单地如上述改名,即通过恢复以前的链接($TxF目录没有了,因为它是存储器内结构)正确地恢复该文件。
按照本发明,该文件信息随后加到与正常目录D3链接的隔离目录树1701。隔离目录树1701可能与正常目录相关地已经存在,但若没有,就建立它。执行步骤1910适当地调节可见性配置标志以指出该事务Tid1已请求删除此文件,所以此文件对其他事务仍可见,但对Tid1不可见。在步骤1912,任意命名的链接被加到随后将从盘删除(在事务提交以后)的文件表中。
当事务终止时,对应于该事务的名字项从隔离目录中去除,而当隔离目录中不存在任何项时,将将隔离目录删除。注意,若***崩溃,存储器内结构的隔离目录就丢失。但是,因为***崩溃中断了未提交的事务,隔离目录不再需要隔离,而日志文件的打开合适地复位了文件的状态。
文件的建立有些类似于删除,当文件由一个事务(如Tid2)在一个目录中建立时,名字实际上加到链接(父)NTFS目录的隔离目录中。对其他事务,由于为打开该文件所作的可见性标志设置,或在事务提交之前列出其父NTFS目录时,该文件名字被滤掉。对Tid2和非事务,建立的文件在其被提交前是可见的。
命名的项在其加入以后可以被事务修改。例如,若一个文件被删除且另一文件使用同样名字建立,建立将修改项的状态,这样其他事务将继续看到在删除前存在的文件,但此事务将看到它刚建立的文件。注意,在NTFS或隔离目录上没有保持将事务级锁定。这使得***还保持基文件***的同时性。
如图18所示,若事务Tid2建立文件F6(在正常的父目录内请求建立),则F6在目录D4中建立,且因而一个项加到父目录D4相关的隔离目录1702。如需要,建立隔离目录1702。适当地调节标志以反映Tid2建立的状态(即对Tid2可见但对其他事务不可见)以及对Tid2保存的名字。注意,事务Tid2在提交以前也能删除新建立的文件F6,在这种情况它将不能被Tid2及其他事务看到。处理这种建立-然后-删除操作的一个方法是从目录D4去除该项和从隔离目录1702去除该项。另外方法是将该项留在隔离目录1702中,而它的配置标志(disposition flag)设置成对创建该文件的Tid2及其他事务均不可见,这样防止在Tid2提交或中断前该文件名被其他事务使用。
回到典型的情况,其中F6被事务Tid2建立但未被删除,当(且如果)事务Tid2提交或中断,隔离的项从隔离目录1702去除,从而使得被建立的文件F6在提交的情况对所有事务均可见。如果事务T2中断,该文件从正常NTFS目录D4删除。每个隔离的项保留直到与其相关的事务结束,且在提交或或中断时被去除。为便于去除,每个事务保留一个TxFSCB指针表,其中该事务至少具有一个这种项。事务还在每个TxFSCB上适当地增加和减少参照计数,使得TxFSCB被使用它们的事务所掌握。
图20提供处理对建立一个文件的请求的通用逻辑的表示,其中请求是New_File_Create(请求的类型是若文件带有与已经存在文件相同的文件名,则不允许建立)。在步骤2000开始,执行测试以确定该文件名(如图17的F6)是否已出现在正常的父目录中,如父目录D4。若是,文件不能建立,且在步骤2000转移到步骤2002,返回一个错误,若在父目录D4中未找到文件F6,有可能该文件名已经被事务使用。为测试这点,步骤2000转移到步骤2004,其中搜索与D4有关的隔离目录1702以寻找此文件名。若此文件F6(或隔离目录)的项不存在,步骤2004转移到步骤2006,在那里作出判断:是否事务正请求建立,或者是非事务请求。若非事务在请求,步骤2006转移到步骤2018,其中文件在正常目录D4中建立。否则,事务(如Tid2)请求建立,执行步骤2010,从而将一个项加到隔离目录1702(若对父目录中不存在什么,则在建立隔离目录1702之后)。然后步骤2014表示合适的标志设置,对此项获得其他信息等。步骤2014随后延续到步骤2018,其中文件F6在正常目录D4中实际建立。注意,在NTFS中,在建立时定位文件,在主文件表中对此文件建立文件记录,且建立记录加到日志中。
在步骤2004,如果在隔离目录1702找到此名字,则不允许建立,除非该指定的文件被正请求建立的同一Tid(如Tid2)删除。以此方式,一个事务可以建立它所删除的文件,但在建立和/或删除该文件的事务提交或中断之前,其他事务或非事务均不能使用该文件名。若找到,执行步骤2012以测试标志状态,判断同一事务是否正请求建立。若是,步骤2012转移到步骤2014以改变对此项的标志状态,实质上现在表示“由Tid2建立”(对Tid2可见,对其他不可见)而非“由Tid2删除”(对Tid2不可见,对其他可能可见)。若另一事务或非事务请求建立,步骤2012转移到步骤2016返回一个错误,指出一个事务保留此文件名。
图18表示事务文件改名操作,它本质上是建立链接请求和删除链接请求的组合。因此,若事务T1将文件“\D2\D3\F2”改名成“\D2\D3\D4\F7”,则链接F2从目录D3被删除,并在目录D4中建立链接F7。但是,因为在改名中涉及到事务,在合适的隔离目录1703和1704中反映这些操作。注意,文件可以在同一父目录内改名,或使文件改名而在不同的目录中具有相同文件名。
按照本发明,对文件的事务处理改名,提供涉及改名的每个父目录的隔离目录,例如一个指出事务的删除操作,一个指出事务的建立操作。注意,在同一父目录中的改名只需要一个隔离目录,一个项用于老文件的删除,一个用于新文件的建立。如上述从图19(删除)和20(建立)能理解,其他事务仍看到该文件,好象文件仍未改名,而在事务提交之前看不到改名后的文件。如果事务中断,其他事务将看不到任何表示曾经发生过改名的迹象,除了可能看到,使用中的文件名在该事务的活动周期暂时被保留。
最后,图21-22表示例如在试图打开文件或获得其文件信息(如作为列举的一部分)时,一个事务是否将看到指定的文件,这取决于文件的状态。步骤2100表示对文件是否在正常目录中的测试。若是,需要搜索隔离目录(如果存在),寻找该文件的项,以确定该文件对请求者是否可见(步骤2102)。如不在正常目录中,文件有可能被正在进行的事务从正常目录中删除,这在下面图22中处理。
若该文件在正常目录中(步骤2100),而该文件的项不在隔离目录中,步骤2102,则它是普通可访问的文件,即它还未由没有提交的事务建立。若是这样,文件***将好象在事务以前其已创建操作(由步骤2104表示),即可返回文件句柄(如在文件打开请求的情况),或文件信息可以从主文件表中的信息返回(如在列举请求的情况)。
若该文件的一个项在隔离目录树中,它必须由进行的事务建立,而步骤2102转移到2106,在那执行测试,判断建立文件的事务是否为正请求访问或请求信息的同一事务。若是,步骤2106转移到2108,测试可见性配置标志(是否对此Tid可见)。若可见,则返回文件句柄(或文件信息)给请求的事务(步骤2110)。注意,在这里的实现中,应该不是文件在正常目录且项在隔离目录中的情况,(因为是由事务建立),但标志指出,该文件应对建立它的事务不可见。因此,在这里的实施中,步骤2108的测试基本上是不必要的,除非用于检测正常和/或隔离目录的破坏等。
若文件的一个项在正常目录(步骤2100)及隔离目录树(步骤2102)中,但步骤2106判定,同样的事务未作出请求,则在本实现中,文件在步骤2114中可以是或可以不是可见的。若不可见,除非其他事务请求的部分是使用文件名,否则步骤2116可认为该文件未找到,返回指出该文件被其他事务使用的错误信息。例如,若找不到指定文件,试图建立新文件的这种打开文件请求类型将失败,因为名字被使用。若在步骤2114为其他事务可见(文件删除后被建立),使用重定向Fid从$TxF目录打开被删除的文件(步骤2118)。
图22处理文件不在正常目录中的情况。若一个尚未提交或中断的事务已删除一个文件,对该文件的项将在隔离目录中,而那个事务不能看见该文件,但其他事务能看见。步骤2200测试,对该文件的项是否不在该隔离目录中(由图21的步骤2100不在正常目录中),若不在,在步骤2202该文件找不到并相应地处理。
若相反,在步骤2200中该名字出现在隔离目录中,则事务已删除它。步骤2204测试是否删除该文件的同一事务在请求访问该文件(或请求其信息)。如果是,在步骤2212文件对删除其的事务是不可见的,并且这样存在没有找到的状态(步骤2206)。注意如果由于某些原因,该文件对事务可见,则将存在错误。
如果在步骤2204与删除该文件不同的事务请求对该文件的访问(或其信息)。如果如在随后的步骤2212所测试的文件对其他事务可见的话,步骤2214返回该文件的句柄,或文件信息(从保存的文件ID,或如下所述的Fid,包括复制的信息)。
另一种可能性是一个进行的事务建立并随后删除一个文件,因而该文件不在正常的目录中。如上所述,文件名或者处理成为其他事务可用,或为进行的事务保留,直到该事务提交或中断。对于前者,可以通过在建立文件的事务删除它时,简单地将该文件的项从正常目录及隔离目录中去除未实现;注意,若这种文件项从隔离目录中去除,则将不达到步骤2212。对于后者,可以通过在删除时将文件从正常目录中去除而将该文件的项留在隔离目录,并设置标志指出对任何事务都不可见来实现。可以理解,这是可能的,因为可见性配置标志是独立设置的(即它们不是互相排斥的)。但是,如果文件留在隔离目录中并标记为对其他事务(和对建立它的事务)不可见,则在步骤2216存在找不到文件的状态,但文件名为进行的事务保留。
在此方式中,本发明方便了整理搜索,如使用NTFS调整规则和NTFS例行程序以调整次序寻找下一个名字。本发明在空间方面是有效的,并允许并发的读/写访问。
注意,出于它看到或看不到的目的,非事务仅看到在正常目录中的内容。但是对于使用现有的文件名的目的,非事务不能使用为事务保留的文件名。为此,当非事务试图建立具有在正常目录中不存在的名字的文件时,如上所述检查隔离目录。
考虑到上面例子和描述,下面例子示出事务如何使用和修改隔离目录中的项。首先,考虑事务Tid10,它在目录X中建立名为YisAVeryLongName的新文件,即建立X\YisAVeryLongName。隔离目录节加入下面两个项:
Name:YisAVeryLongName;
Tid:10;
(
对Tid可见:TRUE,
对其他可见:FALSE);
LongName:TRUE;
pairedNamePtr:Ptr到短名项
Reserved:TRUE;
Fid:INVALID_ID;
其他复制信息。
Name:YisAVery
LongName:FALSE;
pairedNamePtr:Ptr到长名项
这保证了X的后续的目录列举如果是由Tid10以外的事务做的,将不返回这些名字,而非事务将看到这两个名字。此外,如果另一事务Tid20试图建立或打开两个名字的任一个,该事务将得到从上述隔离结构检测的“File-already-exist-but-sharing-violation”错误。
如果非事务处理线程打开这些名字中任一个,如果为了写,删除或任何类型的修改而打开,它得到共享破坏的信息,这种非事务能只读地打开它。这是由于上述分别强加的TxF的文件锁定语义。
考虑第二个例子,从父目录X事务处理删除现有的文件YisAVeryLongName。在此例中在目录X中还有此名字的短名链接(名字对情况,与链接删除情况相反)。而且,该事务具有标识符Tid10,而隔离目录具有下列两个加入项目:
Name:YisAVeryLongName;
Tid:10;
(
对Tid可见:FALSE,
对其他可见:TRUE);
LongName:TRUE;
pairedNamePtr:Ptr到短名项
Reserved:TRUE;
Fid:文件Id;
其他复制信息。
Name:YisAVery
LongName:FALSE;
pairedNamePtr:Ptr到长名项
这两个链接也从目录X的索引SCB删除,但现在可以假设TxF保证文件未被物理上去除,因为在删除前TxF将***拥有的链接加到文件上。因此,两个名字均不能为Tid10以外的任何事务用于建立新文件或新链接。这是因为Tid10能决定中断和重新使用这些名字。而且,这些名字在目录列举或建立中对Tid10不可见,使Tid可用这两个名字的任一个建立新链接/文件,这些名字对其他事务可见,这意味着这些事务能使用该文件ID(Fid)打开它们。非事务处理的用户不能看到这些文件,他们也不能使用这些名字来建立新的文件。
在第三例子中,认为第一例子已经发生,即文件已经建立。然而,因为该名字对事务Tid10可见,Tid10能自由地打开该文件并删除它。若Tid10打开该文件用于写,并随后删除它,删除后的隔离项如下:
Name:YisAVeryLongName;
Tid:10;
(
对Tid可见:FALSE,
对其他可见:FALSE);
LongName:TRUE;
pairedNamePtr:Ptr到短名项
Reserved:TRUE;
Fid:INVALID_ID;
没有复制信息。
Name:YisAVery
LongName:FALSE;
pairedNamePtr:Ptr到长名项
这些项对该事务保留名字,但使它对任何事务都不可见,注意,执行保留以允许退回到工作。
浮动存储器映射段
本发明的另外方面是定位于解决一个问题,其中应用程序对一个或多个为写访问而打开的文件上执行存储器映射,而不觉察包括该应用程序的事务已中断(或提交)。例如,当分布式事务在网络的另一个网络节点中断时会发生这样情况。在那时,应用程序工作情况会不佳或有破坏性。
当应用程序对写访问而打开的文件上执行存储器映射,并不觉察其有关的事务已中断(或提交),和/或工作情况会不佳或有破坏性,另一写程序能打开仍然是存储器映射的文件用于写访问。因此与文件数据能发生冲突,因为存在多个同时的写程序。更具体地,当由应用程序执行时,存储器映射指使用段对象(共享存储器块)将文件映射到过程地址空间。若应用程序修改一页,存储管理器能在正常页面操作期间将改变写回到盘上的文件中,或者,应用程序能直接引起一次刷新。虽然在事务的环境中不希望这样,但允许应用程序执行存储器映射,因此可能通过另外的事务应用程序引起对为写访问而打开的文件的写操作。
知道事务何时提交或退出,并且例如清除受该事务影响的数据结构的文件***可以询问存储管理器以确定事务的应用过程(或多个过程)是否是存储器映射,即是否已经建立一个段句柄。如果存在任何这种应用程序,不知道该应用程序操作状态的文件***不能直接关闭该应用程序或保证它不会继续写到映射段。
图23示出一个方法,其中文件***62防止应用程序180(不再是事务的一部分)写到为另一应用程序182打开用于写访问的映射文件。为此,文件***调节段控制块(SCB)188,各个应用程序180,182的文件对象184、186指向不同的段对象指针190、192。无效事务应用程序1(180)的段对象指针190是空的,而有效事务应用程序2(182)的段对象指针192具有指向用于该应用程序182的存储器196的指针。这使得存储器段194浮动。
无效事务应用程序180能持续对浮动的存储器段读或写,但它不再对应于该文件。同时,一旦在高速缓存/存储管理器114通过代表有效应用程序182的文件***62发现页面存在错误,适当的虚拟存储器页面198(及由应用程序182使用的存储器196)用从事务处理的正确文件(如保存在TOPS流版本中的正确页面)来的数据,或从盘上文件来的数据进行填充。类似的,当存储管理器114指令时,文件***62将由有效应用程序改变的页面写到盘112中。
但是,对于映射到无效应用程序180的段中的页面,任何从达到对应于存储器段194的文件***62的存储管理器114来的写请求被文件***62接受,但实际上不写入盘,因此,映射的存储器是浮动的段;可以允许其写入存储器,但其改变决不刷新到盘中。由存储管理器114对从盘112来的页面请求失败导致返回零。因此,段194的此版本不再由盘上的文件返回。以此方式,有效事务的应用程序的数据文件与由无效应用程序对映射文件的数据改变相隔离。
另外还可能将存储器的映射段改变成对无效应用程序不能访问或只读。从而由无效应用程序的写导致访问破坏。如果读是允许的,每当由有效应用程序作出的改变在段194中错误,无效应用程序能看到这些改变。
注意,任意上述解决方法能引起无效的应用程序180崩溃,而有效应用程序182的数据被适当地隔离。为避免破坏无效应用程序180,作出的改变被写到盘上另一文件,但是,目前支持这种后事务处理的版本被认为对这种应用程序是不必要的开销增加。
TxF日志记录格式
//log record types that are known to the recovery manager.
typedef enum{
TxfLogRecTypeRedo,
TxfLogRecTypeUndo,
TxfLogRecTypePrepare,
TxfLogRecTypeAbort,
TxfLogRecTypeCommit,
}TXF_LOGREC_TYPE;
typedef enum{
TxfLogRecActionCreateFile,
TxfLogRecActionDeleteFile,
TxfLogRecActionWriteFile,
TxfLogRecActionOverwriteFile,
TxfLogRecActionFcbInfoUpdateFile,
TxfLogRecActionTemporaryBitChangeFile,
TxfLogRecActionUpdateDupInfo,
TxfLogRecActionTruncateFile,
TxfLogRecActionRestoreFileSizes,
TxfLogRecActionCancelRecord,
TxfLogRecActionTestPrint
<dp n="d41"/>
} TXF_LOGREC_ACTION;
typedef struct {
TXF_LOGREC_TYPE Type;
TXF_LOGREC_ACTION Action;
TXF_TRANS_ID TransId;
} TXF_LOGREC,*PTXF_LOGREC;
/*
typedef struct {
TXF_LOGREC_HDR header;
char data[1];
} TXF_LOGREC,*PTXF_LOGREC;
*/
//
// Delete File log record.
//
//
// The Long name and the short name are laid out
// immediately after the record.
//
typedef struct _TXF_DELETE_FILE_UNDO_LOGREC {
TXF_LOGREC Header;
//
// See below for flag values
//
USHORT Flags;
//
// ShortNameLength is 0 if there′s no short name.
<dp n="d42"/>
// The short name begins right after the
// FileName. FileName ends.
// It′s at PWCHAR FileName.FileName +
// FileName.FileNameLength.
// ShortNameLength is in unicode chars.
//
USHORT ShortNameLength;
//
// MungedFileNumber to which the rename happened.
//
ULONG MungedFileNumber;
//
// The Txf subdirectory to which the rename happened.
//
ULONG SubDirNumber;
//
// The long/combined name with valid dup info,parent
// directory,length
// etc.
//
FILE_NAME FileName;
//
// Don′t add any fields after this.
//
<dp n="d43"/>
} *PTXF_DELETE_FILE_UNDO_LOGREC,
TXF_DELETE_FILE_UNDO_LOGREC;
//
// TRUE if the file is a directory.
//
#define TXF_DELETE_FILE_UNDO_FLAGS_DIRECTORY 0x01
//
// TRUE if this delete operation had stored the Fid flags.
//
#define TXF_DELETE_FILE_UNDO_FLAGS_FID_STORED 0x02
//
// IgnoreCase flag for the CCB that opened the name for
// delete.
//
#define TXF_DELETE_FILE_UNDO_FLAGS_IGNORE_CASE 0x04
//
// Create-File undo log record.
//
// The Long name and the short name are laid out
// immediately after the record.
//
typedef struct_TXF_CREATE_FILE_UNDO_LOGREC {
TXF_LOGREC Header;
FILE_REFERENCE ParentFid;
<dp n="d44"/>
//
// LongNameLength is in unicode characters.
//
USHORT LongNameLength;
//
// LongNameOffset=sizeof(struct
// _TXF_CREATE_FILE_UNDO_LOGREC)
//
//
// See below for flag values
//
USHORT Flags;
//
// ShortNameLength is 0 if there′s no short name.
// Length is in unicode chars.
//
USHORT ShortNameLength;
//
// ShortNameOffset is sizeof(struct
// _TXF_CREATE_FILE_UNDO_LOGREC) +
// LongNameLength*sizeof(WCHAR)
//
USHORT Reserved1;
ULONG Reserved2;
<dp n="d45"/>
} *PTXF_CREATE_FILE_UNDO_LOGREC,
TXF_CREATE_FILE_UNDO_LOGREC;
//
// TRUE if the file is a directory.
//
#define TXF_CREATE_FILE_UNDO_FLAGS_DIRECTORY 0x01
//
// IgnoreCase flag for the CCB that created the name.
//
#define TXF_CREATE_FILE_UNDO_FLAGS_IGNORE_CASE 0x02
//
// Overwrite-File undo log record.
//
typedef struct _TXF_OVERWRITE_FILE_UNDO_LOGREC {
TXF_LOGREC Header;
//
// File reference of the file that was overwritten
//
FILE_REFERENCE Fid;
//
// File reference of the TxF file that was created in
// the TxF directory.
<dp n="d46"/>
//
FILE_REFERENCE TxfFileFid;
//
// MungedFileNumber of the TxF file that was created in
// the TxF directory.
//
ULONG MungedFileNumber;
//
// The Txf subdirectory in which the TxF file was
// created.
//
ULONG SubDirNumber;
USHORT Flags;
USHORT Reserved1;
ULONG Reserved2;
} *PTXF_OVERWRITE_FILE_UNDO_LOGREC,
TXF_OVERWRITE_FILE_UNDO_LOGREC;
//
// FcbInfoUpdate undo log record.It is undone
// unconditionally without checking the TxfLsn in the
// standard-info.
//
typedef struct TXF FCB_INFO_UPDATE_UNDO_LOGREC {
<dp n="d47"/>
TXF_LOGREC Header;
//
// File reference of the file that was overwritten
//
FILE_REFERENCE Fid;
//
// Fcb Info to be restored on undo.
//
DUPLICATED_INFORMATION FcbInfo;
} *PTXF_FCB_INFO_UPDATE_UNDO_LOGREC,
TXF_FCB_INFO_UPDATE_UNDO_LOGREC;
//
// FcbInfoUpdate undo log record.It is undone
// unconditionally without checking the TxfLsn in the
// standard-info.
//
typedef struct _TXF_TEMPORARY_BIT_CHANGE_UNDO_LOGREC {
TXF_LOGREC Header;
//
// File reference of the file that was overwritten
//
<dp n="d48"/>
FILE_REFERENCE Fid;
ULONG PreviousBitValue;
//
// Attribute name length is 0 if this is the default
// data stream.
// Length is in unicode chars.
// Attribute name follows the log record,if present.
//
USHORT AttrNameLength;
WCHAR AttrName[1];
} *PTXF_TEMPORARY_BIT_CHANGE_UNDO_LOGREC,
TXF_TEMPORARY_BIT_CHANGE_UNDO_LOGREC;
//
// UpdateDupInfo undo log record.
//
// The Long name is laid out immediately after the record.
//
typedef struct _TXF_UPDATE_DUPINFO_UNDO_LOGREC {
TXF_LOGREC Header;
//
// Fid of the parent directory.
//
FILE_REFERENCE ParentFid;
<dp n="d49"/>
//
// LongNameLength is in unicode characters.
//
USHORT LongNameLength;
//
// See below for flags.
//
USHORT Flags;
//
// Duplicated information.
//
DUPLICATED_INFORMATION DupInfo;
WCHAR LongName[1];
} *PTXF_UPDATE_DUPINFO_UNDO_LOGREC,
TXF_UPDATE_DUPINFO_UNDO_LOGREC;
#define TXF_UPDATE_DUPINFO_UNDO_FLAGS_DIRECTORY 0x0001
//
// Truncate undo log record.
//
// The attribute name is laid out immediately after the
// record.
//
typedef struct _TXF_TRUNCATION_UNDO_LOGREC {
<dp n="d50"/>
TXF_LOGREC Header;
//
// Fid of the file.
//
FILE_REFERENCE Fid;
LONGLONG ValidDataLength;
LONGLONG FileSize;
//
// Attribute name length is 0 if this is the default
// data stream.
// Length is in unicode chars.
// Attribute name fc lows the log record,if present.
//
USHORT AttrNameLength;
WCHAR AttrName[1];
} *PTXF_TRUNCATION_UNDO_LOGREC,TXF_TRUNCATION_UNDO_LOGREC;
//
// Restore file sizes undo log record.
//
// The attribute name is laid out immediately after the
// record.
//
typedef struct _TXF_RESTORE_FILE_SIZES_UNDO_LOGREC {
TXF_LOGREC Header;
<dp n="d51"/>
//
// Fid of the file.
//
FILE_REFERENCE Fid;
LONGLONG ValidDataLength;
LONGLONG FileSize;
//
// Attribute name length is 0 if this is the default
// data stream.
// Length is in unicode chars.
// Attribute name follows the log record,if present.
//
USHORT AttrNameLength;
WCHAR AttrName[1];
} *PTXF_RESTORE_FILE_SIZES_UNDO_LOGREC,
TXF_RESTORE_FILE_SIZES_UNDO_LOGREC;
//
// Define the format of the Change Table entries,and some
// related contents.
//
#define TOPS_SECTOR_SIZE (512)
#define TOPS_PAGE_SIZE (4096)
#define TOPS_PAGE_SHIFT (12)
<dp n="d52"/>
#define TOPS_SECTORS_PER_PAGE (TOPS_PAGE_SIZE/
TOPS_SECTOR_SIZE)
#define TOPS_MAXIMUM_FLUSH_SIZE (0x10000)
typedef struct_CHANGE_ENTRY[
//
// These two fields describe the virtual address of the
// displaced range of the stream.
//
ULONGLONG VirtualPageNumber;
ULONG NumberPages;
//
// This is the starting page number in the Tops stream
// to where the old pages were written.
//
ULONG TopsPageNumber;
//
// This is the Lsn of the log record describing this
// change.
//
CLFS_LSN Lsn;
//
// SequenceNumber being written into all bytes of the
// undo pages covered
// by this change.
//
<dp n="d53"/>
UCHAR SequenceNumber;
//
// May as well reserve bytes here for alignment,since
// the size will always round to quad word anyway.
//
UCHAR Reserved[7];
//
// Finally,these are the displaced bytes of data,
// allowing torn write detection in the Tops stream.
// Enough are allocated here for one page,yet
// additional bytes will be allocated if NumberPages is
// greater than one.
//
UCHAR DisplacedBytes[TOPS_SECTORS_PER_PAGE];
} CHANGE_ENTRY,*PCHANGE_ENTRY;
//
// Create-File undo log record.
//
// The Long name and the short name are laid out
// immediately after the record.
//
typedef struct _TXF_WRITE_FILE_UNDO_LOGREC {
TXF_LOGREC Header;
<dp n="d54"/>
//
// File Reference for file undo data was captured from.
//
FILE_REFERENCE FileReference;
//
// Describe where the undo data was written and store
// the displaced bytes which were replaced by a
// sequence number.
//
CHANGE_ENTRY ChangeEntry;
}TXF_WRITE_FILE_UNDO_LOGREC,*PTXF_WRITE_FILE_UNDO_LOGREC;
如上面详细描述可见,提供了一个事务处理文件***及方法,使得应用程序能容易地对一个或多个文件执行多重事务操作。多重文件***操作在文件***中以事务处理方式互相结合,使得操作要么一起提交,要么任何部分的活动都被撤消。此外一个事务的操作和数据改变与另外事务的操作和数据改变互相隔离。因此,例如本发明能以快速,有效和安全的方式将网站作为由文件***部件处理的单个事务进行更新。同时,在事务提交前进行中的改变互相隔离。
然而本发明易受各种修改和改变的结构的影响,某些这里说明的实施例在图中示出,并在上面详细描述。但是应该理解,这并不是要将本发明限制在特定的形式或所揭示的形式,而相反,本发明覆盖所有修改、另外结构和落在本发明的精神和范围内的等效事物。