15.5 end_op函数

接下来我们看看位于log.c中的end_op函数中会发生什么?

可以看到,即使是这么简单的一个文件系统也有一些微秒的复杂之处,代码的最开始就是一些复杂情况的处理(注,15.8有这部分的解释)。我直接跳到正常且简单情况的代码。在简单情况下,没有其他的文件系统操作正在处理中。这部分代码非常简单直观,首先调用了commit函数。让我们看一下commit函数的实现,

commit中有两个操作:

  • 首先是write_log。这基本上就是将所有存在于内存中的log header中的block编号对应的block,从block cache写入到磁盘上的log区域中(注,也就是将变化先从内存拷贝到log中)。

  • write_head会将内存中的log header写入到磁盘中。

我们看一下write_log的实现。

函数中依次遍历log中记录的block,并写入到log中。它首先读出log block,将cache中的block拷贝到log block,最后再将log block写回到磁盘中。这样可以确保需要写入的block都记录在log中。但是在这个位置,我们还没有commit,现在我们只是将block存放在了log中。如果我们在这个位置也就是在write_head之前crash了,那么最终的表现就像是transaction从来没有发生过。

接下来看一下write_head函数,我之前将write_head称为commit point。

函数也比较直观,首先读取log的header block。将n拷贝到block中,将所有的block编号拷贝到header的列表中。最后再将header block写回到磁盘。函数中的倒数第2行,bwrite是实际的commit point吗?如果crash发生在这个bwrite之前,会发生什么?

这时虽然我们写了log的header block,但是数据并没有落盘。所以crash并重启恢复时,并不会发生任何事情。那crash发生在bwrite之后会发生什么呢?

这时header会写入到磁盘中,当重启恢复相应的文件系统操作会被恢复。在恢复过程的某个时间点,恢复程序可以读到log header并发现比如说有5个log还没有install,恢复程序可以将这5个log拷贝到实际的位置。所以这里的bwrite就是实际的commit point。在commit point之前,transaction并没有发生,在commit point之后,只要恢复程序正确运行,transaction必然可以完成。

回到commit函数,在commit point之后,就会实际应用transaction。这里很直观,就是读取log block再查看header这个block属于文件系统中的哪个block,最后再将log block写入到文件系统相应的位置。让我们看一下install_trans函数,

这里先读取log block,再读取文件系统对应的block。将数据从log拷贝到文件系统,最后将文件系统block缓存落盘。这里实际上就是将block数据从log中拷贝到了实际的文件系统block中。当然,可能在这里代码的某个位置会出现问题,但是这应该也没问题,因为在恢复的时候,我们会从最开始重新执行过。

在commit函数中,install结束之后,会将log header中的n设置为0,再将log header写回到磁盘中。将n设置为0的效果就是清除log。

学生提问:install_trans函数在写block的时候,先写的缓存。可不可以优化一下直接写磁盘而不写缓存让代码运行的更快一些?

Frans教授:这里的接口是不太好。你可能会想问反正都要写入新数据,为什么要先读出目标block来。这里的代码肯定还有很多优化空间,但是为了看起来简单我们并没有这么做。

以上就是commit内容。

Last updated