16.9 总结

最后我希望同学们记住的有关logging和ext3的是:

  • log是为了保证多个步骤的写磁盘操作具备原子性。在发生crash时,要么这些写操作都发生,要么都不发生。这是logging的主要作用。

  • logging的正确性由write ahead rule来保证。你们将会在故障恢复相关的业务中经常看到write ahead rule或者write ahead log(WAL)。write ahead rule的意思是,你必须在做任何实际修改之前,将所有的更新commit到log中。在稍后的恢复过程中完全依赖write ahead rule。对于文件系统来说,logging的意义在于简单的快速恢复。log中可能包含了数百个block,你可以在一秒中之内重新执行这数百个block,不管你的文件系统有多大,之后又能正常使用了。

  • 最后有关ext3的一个细节点是,它使用了批量执行和并发来获得可观的性能提升,不过同时也带来了可观的复杂性的提升。

以上就是今天的内容,接下来是问答环节:

学生提问:你刚刚说有一个文件系统线程会做这里所有的工作,那么只能有一个这样的线程,否则的话就会有不同步的问题了,对吗?

Robert教授:或许真的只有一个线程,我其实不知道有多少个线程,但是1是个不错的数字,因为logging的正确性取决于旧的transaction要在新的transaction之前提交。但是逻辑上来说又没有必要只有一个线程,你可以想象不同的transaction使用不同的线程来提交(注,只要锁加的合适多个线程应该也是没问题的)。

学生提问:当你在讨论crash的时候,你有一个图是T8正在使用之前释放的T5的空间,如果T8在crash的时候还没有commit,并且T5的commit block正好在T8的descriptor block所指定的位置,这样会不会不正确的表明T8已经被commit了(注,这时T8有一个假的commit block)?

Robert教授:让我尝试画出这里的场景。首先我们有一个古老的transaction T5,因为log的循环特性,在顺序上T8位于T5之前。因为T5已经被释放了,T8正在蚕食T5的log空间。假设T8没有完成commit,但是如果完成commit的话,T8的commit block会写到T5的commit block位置。

T8并没有能写入commit block,T8前面所有的block都写入了,但是最后跟的是T5的commit block。这里的答案是,descriptor block和commit block都有transaction的序列号,所以T8的descriptor block里面的序列号是8,但是T5的commit block里面的序列号是5,所以两者不能匹配。

学生提问:我们可以在transaction T8开始的时候就知道它的大小吗?

Robert教授:这是个复杂的问题。当T8作为活跃的transaction开始时,系统调用会写入数据,这时文件系统并不知道T8有多大。当文件系统开始commit T8时,是知道T8有多大的,因为文件系统只会在T8中所有的系统调用都结束之后才commit它,而在那个时间点,文件系统知道所有的写操作,所以就知道T8究竟有多大。除此之外,descriptor block里面包含了所有block的实际编号,所以当写入transaction的第一个block,也就是descriptor block时,logging系统知道T8会包含多少个block。

学生提问:为什么不在descriptor block里面记录commit信息。虽然这样可能不太好,因为要回到之前的一个位置去更新之前的一个block。

Robert教授:所以这里的提议是,与其要一个专门的commit block,可以让descriptor block来实现commit block的功能。XV6与这个提议非常像,我认为可以这么做,至少在ext3中这么做了不会牺牲性能。你需要像XV6一样来组织这里的结构,也就是需要在descriptor block包含某个数据表明这是一个已经提交过的transaction。

这样做的话,可以节省一个commit block的空间,但是不能节省整个时间。Linux文件系统的后续版本实现了你的提议,ext4做了以下工作来更有效的写commit block。ext4会同时写入所有的data block和commit block,它并不是等待所有的data block写完了之后才写的commit block。但是这里有个问题,磁盘可以无序的执行写操作,所以磁盘可能会先写commit block之后再写data block。如果中间有了crash,那么我们有了commit block,但是却没有全部的data block。ext4通过在commit block中增加校验和来避免这种问题。所以commit block写入之后发生了crash,如果data block没有全写入那么校验和不能得出正确的结果,恢复软件可以据此判断出错了。ext4可以通过这种方式在机械硬盘上写入一批block而避免磁碟旋转,进而提升磁盘性能。

学生提问:log中的data block是怎么写入到文件系统中的?

Robert教授:这个问题有多个答案。对于data block,ext3有三种模式,但是我只记得两个,journaled data和ordered data(注,第三种是writeback)。当你在配置ext3文件系统时,你需要告诉Linux你想要哪种模式。如果你想要的是journaled data,文件内容就是写入到log中,如果你向一个文件写数据,这会导致inode更新,log中会包含文件数据和更新了的inode,也就是说任何更新了的block都会记录在log中。这种方法非常慢,因为数据需要先写在log中,再写到文件系统中。所以journaled data很直观,但是很慢。

ordered data是最流行的模式,它不会将文件数据写入到log中,只会将metadata block,例如inode,目录block,写入到log中,文件的内容会直接写到文件系统的实际位置中。所以这种模式要快得多,因为你不用将文件内容写两次。但是它也会导致更多的复杂性,因为你不能随时写入文件内容。假设你执行一个写操作导致一个新的block被分配给一个文件,并将包含了新分配block编号的inode写入到log中并commit,在实际写入文件内容至刚刚分配的data block之前发生crash。在稍后的恢复流程中,你将会看到包含了新分配的block编号的inode,但是对应data block里面的内容却属于之前使用了这个data block的旧的文件。如果你运行的是一个类似Athena的多用户系统,那么可能就是一个用户拥有一个文件,其中的内容又属于另一个用户已经删除的文件,如果我们不是非常小心的处理写入数据和inode的顺序就会有这样的问题。

ext3的ordered data通过先写入文件内容到磁盘中,再commit修改了的inode来解决这里的问题。如果你是个应用程序,你写了一个文件并导致一个新的文件系统data block被分配出来,文件系统会将新的文件内容写到新分配的data block中,之后才会commit transaction,进而导致inode更新并包含新分配的data block编号。如果在写文件数据和更新inode之间发生了crash,你也看不到其他人的旧的数据,因为这时就算有了更新了的data block,但是也没关系,因为现在不仅inode没有更新,连bitmap block也没更新,相应的data block还处于空闲状态,并且可以分配给其他的程序,你并不会因此丢失block。这里的效果就是我们写了一个data block但是最终并没有被任何文件所使用。

Last updated