MIT 6.S081 lec16总结 —— ext3的log机制

这节课难度比较高
Lec16 File system performance and fast crash recovery

xv6 File system Log

  • 我们会有一些用户程序调用write/create系统调用来修改文件系统。在内核中存在block cache,最初write请求会被发到block cache。block cache就是磁盘中block在内存中的拷贝,所以最初对于文件block或者inode的更新走到了block cache。
  • 在write系统调用的最后,这些更新都被拷贝到了log中,之后我们会更新header block的计数来表明当前的transaction已经结束了。
  • 在拷贝完成之后,文件系统会将修改过的block数量,通过一个磁盘写操作写入到log的header block,这次写入被称为commit point。在commit point之前,如果发生了crash,在重启时,整个transaction的所有写磁盘操作最后都不会应用。在commit point之后,即使立即发生了crash,重启时恢复软件会发现在log header中记录的修改过的block数量不为0,接下来就会将log header中记录的所有block,从log区域写入到文件系统区域。

ext3和xv6主要的区别在于ext3可以同时跟踪多个在不同执行阶段的transaction

ext3 File system Log

  • ext3的数据结构与XV6是类似的。在内存中,存在block cache,这是一种write-back cache(注,区别于write-through cache,指的是cache稍后才会同步到真正的后端)。block cache中缓存了一些block,其中的一些是干净的数据,因为它们与磁盘上的数据是一致的;其他一些是脏数据,因为从磁盘读出来之后被修改过;有一些被固定在cache中,基于前面介绍的write-ahead rule和freeing rule,不被允许写回到磁盘中。
  • ext3还维护了一些transaction信息。它可以维护多个在不同阶段的transaction的信息。
    • 一个序列号
    • 一系列该transaction修改的block编号。这些block编号指向的是在cache中的block,因为任何修改最初都是在cache中完成。
    • 以及一系列的handle,handle对应了系统调用,并且这些系统调用是transaction的一部分,会读写cache中的block
  • 接下来我们详细看一下ext3的log中有什么。
    在log的最开始,是super block。这是log的super block,而不是文件系统的super block。
    log的super block包含了log中第一个有效的transaction的起始位置和序列号。起始位置就是磁盘上log分区的block编号,序列号就是前面提到的每个transaction都有的序列号。
    • log是磁盘上一段固定大小的连续的block。log中,除了super block以外的block存储了transaction。每个transaction在log中包含了:
      • 一个descriptor block,其中包含了log数据对应的实际block编号,这与XV6中的header block很像。
      • 之后是针对每一个block编号的更新数据
      • 最后当一个transaction完成并commit了,会有一个commit block
    • 这里有一些细节对于后面的内容很重要。在crash之后的恢复过程会扫描log,为了将descriptor block和commit block与data block区分开,descriptor block和commit block会以一个32bit的魔法数字作为起始。这个魔法数字不太可能出现在数据中,并且可以帮助恢复软件区分不同的block。

ext3如何提升性能

  • 首先,它提供了异步的(asynchronous)系统调用,也就是说系统调用在写入到磁盘之前就返回了,系统调用只会更新缓存在内存中的block,并不用等待写磁盘操作。不过它可能会等待读磁盘。
  • 第二,它提供了批量执行(batching)的能力,可以将多个系统调用打包成一个transaction。
  • 最后,它提供了并发(concurrency)。

首先是异步的系统调用。**这表示系统调用修改完位于缓存中的block之后就返回,并不会触发写磁盘。**所以这里明显的优势就是系统调用能够快速的返回。同时它也使得I/O可以并行的运行,也就是说应用程序可以调用一些文件系统的系统调用,但是应用程序可以很快从系统调用中返回并继续运算,与此同时文件系统在后台会并行的完成之前的系统调用所要求的写磁盘操作。这被称为I/O concurrency,如果没有异步系统调用,很难获得I/O concurrency,或者说很难同时进行磁盘操作和应用程序运算,因为同步系统调用中,应用程序总是要等待磁盘操作结束才能从系统调用中返回。

另一个异步系统调用带来的好处是,它使得大量的批量执行变得容易。

**异步系统调用的缺点是系统调用的返回并不能表示系统调用应该完成的工作实际完成了。**举个例子,如果你创建了一个文件并写了一些数据然后关闭文件并在console向用户输出done,最后你把电脑的电给断了。尽管所有的系统调用都完成了,程序也输出了done,但是在你重启之后,你的数据并不一定存在。这意味着,在异步系统调用的世界里,如果应用程序关心可能发生的crash,那么应用程序代码应该更加的小心。这在XV6并不是什么大事,因为如果XV6中的write返回了,那么数据就在磁盘上,crash之后也还在。而ext3中,如果write返回了,你完全不能确定crash之后数据还在不在。所以一些应用程序的代码应该仔细编写,例如对于数据库,对于文本编辑器,我如果写了一个文件,我不想在我写文件过程断电然后再重启之后看到的是垃圾文件或者不完整的文件,我想看到的要么是旧的文件,要么是新的文件。

**==所以文件系统对于这类应用程序也提供了一些工具以确保在crash之后可以有预期的结果。这里的工具是一个系统调用,叫做fsync,所有的UNIX都有这个系统调用。==这个系统调用接收一个文件描述符作为参数,它会告诉文件系统去完成所有的与该文件相关的写磁盘操作,在所有的数据都确认写入到磁盘之后,fsync才会返回。所以如果你查看数据库,文本编辑器或者一些非常关心文件数据的应用程序的源代码,你将会看到精心放置的对于fsync的调用。fsync可以帮助解决异步系统调用的问题。**对于大部分程序,例如编译器,如果crash了编译器的输出丢失了其实没什么,所以许多程序并不会调用fsync,并且乐于获得异步系统调用带来的高性能。

在任何时候,ext3只会有一个open transaction。ext3中的一个transaction可以包含多个不同的系统调用。所以ext3是这么工作的:它首先会宣告要开始一个新的transaction,接下来的几秒所有的系统调用都是这个大的transaction的一部分。我认为默认情况下,ext3每5秒钟都会创建一个新的transaction,所以每个transaction都会包含5秒钟内的系统调用,这些系统调用都打包在一个transaction中。在5秒钟结束的时候,ext3会commit这个包含了可能有数百个更新的大transaction。

为什么这是个好的方案呢?

  • **首先它在多个系统调用之间分摊了transaction带来的固有的损耗。**固有的损耗包括写transaction的descriptor block和commit block;在一个机械硬盘中需要查找log的位置并等待磁碟旋转,这些都是成本很高的操作,现在只需要对一批系统调用执行一次,而不用对每个系统调用执行一次这些操作,所以batching可以降低这些损耗带来的影响。
  • **另外,它可以更容易触发write absorption(写吸收)。**经常会有这样的情况,你有一堆系统调用最终在反复更新相同的一组磁盘block。举个例子,如果我创建了一些文件,我需要分配一些inode,inode或许都很小只有64个字节,一个block包含了很多个inode,所以同时创建一堆文件只会影响几个block的数据。类似的,如果我向一个文件写一堆数据,我需要申请大量的data block,我需要修改表示block空闲状态的bitmap block中的很多个bit位,如果我分配到的是相邻的data block,它们对应的bit会在同一个bitmap block中,所以我可能只是修改一个block的很多个bit位。所以一堆系统调用可能会反复更新一组相同的磁盘block。通过batching,多次更新同一组block会先快速的在内存的block cache中完成,之后在transaction结束时,一次性的写入磁盘的log中。这被称为write absorption,相比一个类似于XV6的同步文件系统,它可以极大的减少写磁盘的总时间。
  • **最后就是disk scheduling。**假设我们要向磁盘写1000个block,不论是在机械硬盘还是SSD(机械硬盘效果会更好),一次性的向磁盘的连续位置写入1000个block,要比分1000次每次写一个不同位置的磁盘block快得多。我们写log就是向磁盘的连续位置写block。**通过向磁盘提交大批量的写操作,可以更加的高效。这里我们不仅通过向log中连续位置写入大量block来获得更高的效率,甚至当我们向文件系统分区写入包含在一个大的transaction中的多个更新时,如果我们能将大量的写请求同时发送到驱动,即使它们位于磁盘的不同位置,我们也使得磁盘可以调度这些写请求,并以特定的顺序执行这些写请求,这也很有效。**在一个机械硬盘上,如果一次发送大量需要更新block的写请求,驱动可以对这些写请求根据轨道号排序。甚至在一个固态硬盘中,通过一次发送给硬盘大量的更新操作也可以稍微提升性能。所以,只有发送给驱动大量的写操作,才有可能获得disk scheduling。这是batching带来的另一个好处。

ext3使用的最后一个技术就是concurrency,相比XV6这里包含了两种concurrency。

  • **首先ext3允许多个系统调用同时执行,所以我们可以有并行执行的多个不同的系统调用。**在ext3决定关闭并commit当前的transaction之前,系统调用不必等待其他的系统调用完成,它可以直接修改作为transaction一部分的block。许多个系统调用都可以并行的执行,并向当前transaction增加block,这在一个多核计算机上尤其重要,因为我们不会想要其他的CPU核在等待锁。在XV6中,如果当前的transaction还没有完成,新的系统调用不能继续执行。而在ext3中,大多数时候多个系统调用都可以更改当前正在进行的transaction。
  • **另一种ext3提供的并发是,可以有多个不同状态的transaction同时存在。**所以尽管只有一个open transaction可以接收系统调用,但是其他之前的transaction可以并行的写磁盘。这里可以并行存在的不同transaction状态包括了:
    • 首先是一个open transaction
    • 若干个正在commit到log的transaction,我们并不需要等待这些transaction结束。当之前的transaction还没有commit并还在写log的过程中,新的系统调用仍然可以在当前的open transaction中进行。
    • 若干个正在从cache中向文件系统block写数据的transaction
    • 若干个正在被释放的transaction,这个并不占用太多的工作

通常来说会有位于不同阶段的多个transaction,新的系统调用不必等待旧的transaction提交到log或者写入到文件系统。对比之下,XV6中新的系统调用就需要等待前一个transaction完全完成。

concurrency之所以能帮助提升性能,是因为它可以帮助我们并行的运行系统调用,我们可以得到多核的并行能力。如果我们可以在运行应用程序和系统调用的同时,来写磁盘,我们可以得到I/O concurrency,也就是同时运行CPU和磁盘I/O。这些都能帮助我们更有效,更精细的使用硬件资源。

Linux中的文件系统调用,并介绍抽象上每个系统调用的结构。

在Linux的文件系统中,我们需要每个系统调用都声明一系列写操作的开始和结束。实际上在任何transaction系统中,都需要明确的表示开始和结束,这样之间的所有内容都是原子的。所以系统调用中会调用start函数。ext3需要知道当前正在进行的系统调用个数,所以每个系统调用在调用了start函数之后,==会得到一个handle,它某种程度上唯一识别了当前系统调用。==当前系统调用的所有写操作都是通过这个handle来识别跟踪的(注,handle是ext3 transaction中的一部分数据,详见16.3)。

之后系统调用需要读写block,它可以通过get获取block在buffer中的缓存,同时告诉handle这个block需要被读或者被写。如果你需要更改多个block,类似的操作可能会执行多次。之后是修改位于缓存中的block。

当这个系统调用结束时,它会调用stop函数,并将handle作为参数传入。

除非transaction中所有已经开始的系统调用都完成了,transaction是不能commit的。因为可能有多个transaction,所以文件系统需要有种方式能够记住系统调用属于哪个transaction,这样当系统调用结束时,文件系统就知道这是哪个transaction正在等待的系统调用,所以handle需要作为参数传递给stop函数。

因为每个transaction都有一堆block与之关联,修改这些block就是transaction的一部分内容,所以我们将handle作为参数传递给get函数是为了告诉logging系统,这个block是handle对应的transaction的一部分。

stop函数并不会导致transaction的commit,它只是告诉logging系统,当前的transaction少了一个正在进行的系统调用。transaction只能在所有已经开始了的系统调用都执行了stop之后才能commit。所以transaction需要记住所有已经开始了的handle,这样才能在系统调用结束的时候做好记录。

commit transaction步骤

  • 每隔5秒,文件系统都会commit当前的open transaction(一段时间只有一个,每个一段时间会更新),下面是commit transaction涉及到的步骤:

    1. **首先需要阻止新的系统调用 (新的系统调用被放在了新的open transaction) 。**当我们正在commit一个transaction时,我们不会想要有新增的系统调用,我们只会想要包含已经开始了的系统调用,所以我们需要阻止新的系统调用。这实际上会损害性能,因为在这段时间内系统调用需要等待并且不能执行。
    2. **第二,需要等待包含在transaction中的已经开始了的系统调用们结束。**所以我们需要等待transaction中未完成的系统调用完成,这样transaction能够反映所有的写操作。
    3. 一旦transaction中的所有系统调用都完成了,也就是完成了更新cache中的数据,那么就可以**开始一个新的transaction,并且让在第一步中等待的系统调用继续执行。**所以现在需要为后续的系统调用开始一个新的transaction。
    4. 还记得ext3中的log包含了descriptor,data和commit block吗?现在我们知道了transaction中包含的所有的系统调用所修改的block,因为系统调用在调用get函数时都将handle作为参数传入,表明了block对应哪个transaction。接下来我们可以更新descriptor block,其中包含了所有在transaction中被修改了的block编号。
    5. 我们还需要将被修改了的block,从缓存中写入到磁盘的log中。之前有同学问过,新的transaction可能会修改相同的block,所以在这个阶段,我们写入到磁盘log中的是transaction结束时,对于相关block cache的拷贝。所以这一阶段是将实际的block写入到log中。
    6. 接下来,我们需要等待前两步中的写log结束。
    7. 之后我们可以写入commit block。
    8. 接下来我们需要等待写commit block结束。结束之后,从技术上来说,当前transaction已经到达了commit point,也就是说transaction中的写操作可以保证在面对crash并重启时还是可见的。如果crash发生在写commit block之前,那么transaction中的写操作在crash并重启时会丢失。
    9. 接下来我们可以将transaction包含的block写入到文件系统中的实际位置。
    10. 在第9步中的所有写操作完成之后,我们才能重用transaction对应的那部分log空间。

总结

  • 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的一个细节点是,它使用了批量执行和并发来获得可观的性能提升,不过同时也带来了可观的复杂性的提升。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值