元文件$LogFile分析 | 数据恢复迷
日志文件的结构比较复杂,其日志区域由一系列4KB大小的日志记录组成,记录头固定标志是“RCRD”,其重启页的头部固定标志是“RSTR”,位于重启区域的开始部分。
当文件被写到磁盘上时,系统要做两件事,一是写文件本身的数据,二是更新和文件系统有关的一些数据(如文件创建时间)。如果此操作完成,则可以确认文件被写到存储单元上,并且文件系统处于正常状态。如果此操作未完成(如电源故障、系统瘫痪等),则文件系统处于非正常状态。NTFS系统将其恢复到正常状态的途径是在该特殊文件里记录日志,这个日志文件会记录某个操作的成功与失败。在系统故障后第一次进入磁盘时,系统读取日志文件并使其恢复到最后一次操作开始前的状态。当系统写日志文件时,操作必须是自动且即时的,可以在很短的时间内把卷恢复到正常状态,恢复时间与磁盘大小无关,只与失败任务的复杂程度有关。
日志文件服务
日志文件由格式化程序和日志文件服务(LogFile Service,LFS)共同创建。日志文件服务是一组NTFS驱动程序内的核心态程序。NTFS是通过LFS例程来访问日志文件的。其结构如图4-435所示。
图4-435 LFS结构示意图
LFS将日志文件分为两个区域:重启动区域(Restart Area)和无限记录区域(Infinite Logging Area),它的结构如图4-436所示。
图4-436 日志文件结构
NTFS使用重启动区域来存储系统故障后从哪里进行恢复的信息。在系统失败后的恢复过程中,NTFS将从这个位置开始读取信息。由于重启动区域的重要性,在紧随其后的磁盘空间上,LFS保存了它的一个副本。
在LFS重启动区域之后是记录区域,用于存放NTFS的日志记录,包含了改变文件系统数据和卷目录结构的I/O操作的处理记录,由逻辑顺序号(Logical Sequence Numbers,LSN)来标识这些记录。LSN为64位,通过循环使用日志记录,LSN使日志文件看起来像是可以保存无限多的日志记录。
NTFS不会直接从日志文件中读/写记录,而是通过LFS来读/写记录。LFS提供了许多操作来处理日志文件,包括打开(Open)、写入(Write)、向前(Prev)、向后(Next)、更新(Update)等操作。在恢复过程中,NTFS通过向前读取日志记录,重做已在日志文件中记录的、系统失败时还没有及时刷新到磁盘上的所有事务;NTFS通过向后读取日志记录,撤销或是回退系统崩溃前没有完全记录在日志文件中的事务。当NTFS不再需要日志文件中较早的事务记录时,它就调用LFS来将日志文件的开始部分设置为一个具有更高LSN的记录,从而实现日志记录的“无限”使用。
下面是NTFS为实现卷的可恢复性而执行的操作步骤:
①NTFS首先调用LFS在日志文件中记录所有改变卷结构的事务;
②NTFS执行在高速缓存中的更改卷结构的操作;
③高速缓存管理器调用LFS将日志文件刷新到磁盘;
④完成上一步之后,卷更改(事务本身)最后被刷新到磁盘上。
以上步骤如图4-437所示。
图4-437 NTFS实现卷的可恢复性的操作步骤
严格执行这些操作步骤就保证了即使文件系统的最终修改是不成功的,也能通过日志文件恢复相应的事务。重新引导系统以后,当第一次使用卷时,文件系统的恢复工作就自动开始,这样就保证了无论何时发生意外,NTFS都可以通过日志文件记录中的操作信息来恢复文件系统的一致性。
日志记录类型
LFS允许用户在日志文件中写入任何类型的记录。更新记录(Update Record)和检查点记录(Checkpoint Record)是NTFS所支持的两种主要类型的日志记录,它们在系统的恢复过程中起了主要作用。
更新记录
更新记录所记录的是文件系统的更新信息,是NTFS写入日志文件中的最普通的记录类型。NTFS为以下事务写入更新记录:
- 创建文件。
- 删除文件。
- 扩展文件。
- 截断文件。
- 设置文件信息。
- 重新命名文件。
- 更改应用于文件的安全信息。
在更新记录中一般包含两种信息:
①重做信息。如果事务在高速缓存中的操作记录刷新到磁盘之前系统崩溃,如何重新执行这个对卷来说是已提交的事务子操作。
②撤销信息。当系统失败时,如何撤销这个对卷来说未提交的事务子操作。
创建一个新文件事务的日志文件记录结构如图4-438所示。
图4-438 日志文件中的更新记录
其中每个记录代表了该事务的一个子操作,NTFS根据每个更新记录中的重做项来决定如何重新执行该子操作,而根据撤销项来决定如何执行回退子操作。
当一个事务的最后一个子操作被记录后,NTFS就对高速缓存中的卷自身执行子操作。在完成高速缓存的更新以后,NTFS就向日志文件写入该事务的最终记录——被称为“提交一个事务”的子操作,完整地记录整个事务,从而完成了该事务的提交。这时,即使操作系统立即发生崩溃,NTFS也能保证卷上该事务的完整性。
当发生系统失败需要进行恢复时,NTFS根据读取的日志文件信息,重做每一个已经提交的事务。由于NTFS并不清楚已经提交的事务是否从高速缓存中得到及时更新,所以如果在事务最终提交以前发生系统崩溃,NTFS就再一次执行已经提交的事务,从而保持磁盘的一致性。
在文件系统恢复过程中完成了重做操作之后,NTFS根据系统崩溃时未被提交的事务日志文件中的撤销信息来回退已经记录的每一个子操作。在图4-438中,NTFS首先撤销了T1c子操作,然后是Tlb,以此类推,直到事务中的第一个子操作。
对于重做和撤销信息可以采用物理或是逻辑的表达方式。物理表达是根据磁盘上特定的字节范围来指定卷的更新,这些字节可以被更改、移动等;而逻辑表达则是根据操作来表达更新信息,例如“删除文件1”等。当在软件的最底层维护文件系统结构时,NTFS根据物理表达写入更新记录。事务处理或其他应用程序级软件则可能得益于用逻辑表达来写入更新记录,因为逻辑表达的更新比物理表达的更新更加简洁。
NTFS设计小组对更新记录中的重做和撤销信息结构进行了仔细而慎重的设计,使其保持信息的完备性,以防止NTFS试图重做一个已经做过的事务,或相反地,试图撤销一个根本没有进行或是已经被撤销的事务。
类似地,NTFS也可能试图重做在磁盘上只是部分完成的几个更新记录组成的事务。更新记录的格式必须保证执行冗余重做或是撤销操作是“幂等”(Idempotent)的,也就是说,具有中立的作用。例如,设置一个已经设置的位不会起作用(可以冗余重做),但是切换一个已经切换的位,就会起作用(不可以冗余重做)。
检查点记录
除了更新记录之外,NTFS还周期性地向日志文件中写入检查点记录。NTFS在写入检查点记录以后,还在重启动区域存储记录的LSN。在发生系统失败后的恢复过程中,NTFS通过存储在检查点记录中的信息来定位日志文件中的恢复点,参见图4-439。
图4-439 日志文件中的检查点记录
随着日志记录的增长,虽然NTFS可以不断检查并释放日志文件的空间,但是日志文件也还是有可能被填满的,LFS通过跟踪以下数值来做出判断:
- 可用的磁盘空间;
- 在日志文件中写入一个新的日志记录和撤销该写入所必需的空间大小;
- 回退所有未提交事务所必需的空间大小。
如果最后两项所需空间的总和超过了日志文件的可用空间,LFS将返回一个“日志文件已满”的错误,并且引起一个NTFS异常。NTFS异常处理程序将回退到当前事务,并将其放置在一个队列中,以便在调整空间以后重新启动它。
为了释放日志文件中的空间,NTFS必须暂时防止进一步的事务:NTFS首先停止文件的创建和删除,然后请求获得对所有系统文件的独占访问和对所有用户文件的共享访问。这样逐渐地,活动事务或者成功完成,或者因日志文件已满而引起异常。对于异常,NTFS将回退到当前事务,并将其放置在队列中,以便重新启动后再用。
一旦NTFS开始释放日志文件的空间,它将调用高速缓存管理器,将所有未写入的数据,包括未写入的日志文件数据,都刷新到磁盘上。在NTFS完成每个事务的安全刷新后,它就清空失去作用的日志文件,把当前位置重新设置为日志文件的开始部分,然后NTFS重新启动已排队的事务。
可恢复性实现
NTFS通过LFS来实现可恢复功能。NTFS的可恢复性支持确保了系统发生意外时磁盘卷结果的完整性和一致性,即使是很大的磁盘,也能在几秒之内恢复过来。需要注意的是,这种恢复只是针对文件系统的数据,而并不能保证用户数据完全被恢复。
NTFS在内存中维护两张表:
①事务表。事务表跟踪已经启动但尚未提交的事务。在恢复过程中,必须从磁盘删除这些活动事务的子操作。
②脏页表。脏页表记录了在高速缓存中还未写入磁盘的包含改变NTFS卷结构操作的页面。在恢复过程中,这些改动必须刷新到磁盘上。
NTFS每隔5s向日志文件写入一个检查点记录,在此之前,NTFS调用LFS在日志文件中存储事务表和脏页表的一个当前副本。这样,NTFS写入的检查点记录就包含了已复制表的日志记录的LSN,当系统失败后开始恢复时,NTFS调用LFS来定位日志文件记录,这些日志记录包含了最近的检查点记录以及最近的事务表和脏页表的副本,然后,NTFS将这些表复制到内存。
在最近的检查点记录之后,日志文件通常包含更多的更新记录。这些更新记录显示了在最后的检查点记录写入后卷的更改。为此,NTFS必须更新事务表和脏页表,通过更新这些表和日志文件中的内容来更新卷本身。
要实现NTFS卷的恢复,NTFS要对日志文件进行三次扫描。
分析扫描(Analysis Pass)
NTFS从日志文件中最近的一个检查点操作的起点开始分析扫描。检查点操作起点之后的每一个更新记录都代表对事务表或脏页表的修改,如“事务提交”记录代表的事务必须从事务表中删除;“页面更新”记录则表示因为对一个文件系统数据结构做了修改,相应的脏页表也必须更新,如图4-440所示。
图4-440 分析扫描的过程
这两个表被复制到内存中以后,NTFS将搜索这两个表。事务表包含了未提交(不完整)事务的LSN,脏页表则包含了高速缓存中还未刷新到磁盘的记录的LSN。NTFS根据其中的信息来确定最早的更新记录(该记录记录了在磁盘上尚未进行的操作)的LSN,由此决定重做扫描的起点。当然,如果最近一个检查点记录更早,NTFS将从那里开始启动重做扫描,由此进入事务恢复的第二阶段。
重做扫描(Redoing Pass)
在重做扫描过程中,NTFS将从分析扫描得到的最早记录的LSN开始,在日志文件中向前扫描。NTFS将查找“页面更新”记录,这个记录包含了在系统失败前就已经写入的卷更新,但是这些卷的更改可能还未刷新到磁盘,NTFS将在高速缓存中重做这些更新。当NTFS到达日志文件的末端时,它已经利用必要的卷更改更新了高速缓存,高速缓存管理器的延迟写线程能够开始在后台向磁盘写入高速缓存的内容,这个过程如图4-441所示。
图4-441 重做扫描的过程
撤销扫描(Undoing Pass)
在NTFS完成重做扫描后,它将开始撤销扫描。NTFS可以在这一扫描中回退系统失败时任何未提交的事务,如图4-442所示。
图4-442 撤销扫描的过程
图4-442中有两个事务:事务1在断电时已经提交,事务2未提交。假设事务2创建一个文件,有3个子操作,它们之间通过指针连接。事务表为每个未提交的事务更新记录列出最后记录的LSN。
每个更新记录包含两种信息,一个是如何重做一个子操作,另一个是如何撤销子操作。NTFS在定位LSN1093后,执行撤销操作直到LSN1089,完成事务2的回退。当然,在日志文件中也要记录撤销操作,因为撤销时也可能发生系统崩溃。
恢复完成后,NTFS将高速缓存写入磁盘从而保证卷是最新的。最后,NTFS写入一个“空”到LFS重启动区,指明卷是一致的。这时,即使系统再次崩溃,也不必再恢复了。
由于NTFS使用的是“延迟提交”的算法,这意味着每次“事务提交”记录写入时,日志文件不能立即刷新到磁盘,而是被批处理写入的。同时,多个事务可能是平行操作的,它们的事务提交记录可能一部分被写入磁盘,而另一部分没有写入。这样就只能保证NTFS恢复到某一先前存在的一致状态,而不能保证NTFS恢复到刚巧系统崩溃时的状态。
NTFS还能够利用日志记录实现文件系统错误的恢复,因为NTFS日志记录了每个更改卷结构的事务,包括正常文件I/O过程中发生的文件系统错误,所以日志文件可大大简化文件系统的错误处理代码。当然,一个程序收到的大多数I/O错误不是文件系统错误,调用者必须适当地依次响应错误。
一个典型的恢复过程如图4-443所示。
图4-443 一个典型的恢复过程
日志文件的文件记录
$LogFile的文件记录号是02H,一般由3个属性构成,如图4-444所示。
图4-444 $LogFile的文件记录
10H属性
10H属性定义了$LogFile的创建时间、最后修改时间、该文件记录修改时间、文件最后访问时间及标志等信息,具体参数见其模板,如图4-445所示。
图4-445 $LogFile文件10H属性的参数模板
30H属性
30H属性定义了$LogFile的父目录的文件参考号为根目录;定义了系统分配给整个磁盘的日志文件的大小(本例中为04000000H字节)、实际使用的大小(本例中为04000000H字节);并再次定义了文件的标志为06H,表示其为隐藏、系统文件,定义了文件名的长度为8个字,命名空间为3,也即Win32 & DOS;在属性的最后定义了该文件的文件名为Unicode字符串“$LogFile”。具体参数如图4-446所示。
图4-446 $LogFile文件30H属性的参数模板
80H属性
80H属性定义了日志文件的MFT的起始VCN(本例中为0)、结束VCN(本例中为3FFFH)、系统分配给该日志文件的大小(本例中为04000000H字节)、日志文件实际的大小(本例中为04000000H字节)、初始的属性大小(本例中为04000000H字节)、数据流的起始LCN(本例中为11H)、所占的簇数(本例中为4000H)等信息。具体参数如图4-447所示。
图4-447 $LogFile文件80H属性的参数模板
在80H属性之后跟着4个字节的“FF FF FF FF”,这是属性的结束标志,到此为止该文件记录中的属性就结束了。
日志文件的数据流
系统用于记录日志操作的文件就是$Logfile文件的数据流部分。从其文件记录的80属性中可以看到$Logfile文件的数据流开始于11H簇,也就是十进制的17号簇,换算为扇区号为136扇区,该扇区就是其重启动记录,如图4-448所示。
图4-448 $Logfile的重启动区域
重启动区域共有两份副本,每个大小为4KB,其结构见表4-78。
表4-78 重启动区域结构描述
字节偏移 | 字段长度(字节) | 描述 |
0x00 | 4 | 固定字符“RSTR” |
0x1E | 12 | 固定值 |
0x30 | 4 | 记录序列号a |
0x58 | 4 | 记录序列号B |
0x6C | 1 | 卷干净标志 |
0x90 | 8 | Unicode字符形式的“NTFS” |
无限记录区域的前4个字符一定是“RCRD”,其第一个扇区如图4-449所示。
图4-449 $Logfile的无限记录区域