15.4 log_write函数

接下来让我们看一些代码来帮助我们理解这里是怎么工作的。前面我提过事务(transaction),也就是我们不应该在所有的写操作完成之前写入commit record。这意味着文件系统操作必须表明事务的开始和结束。在XV6中,以创建文件的sys_open为例(在sysfile.c文件中)每个文件系统操作,都有begin_op和end_op分别表示事物的开始和结束。

begin_op表明想要开始一个事务,在最后有end_op表示事务的结束。并且事务中的所有写block操作具备原子性,这意味着这些写block操作要么全写入,要么全不写入。XV6中的文件系统调用都有这样的结构,最开始是begin_op,之后是实现系统调用的代码,最后是end_op。在end_op中会实现commit操作。

在begin_op和end_op之间,磁盘上或者内存中的数据结构会更新。但是在end_op之前,并不会有实际的改变(注,也就是不会写入到实际的block中)。在end_op时,我们会将数据写入到log中,之后再写入commit record或者log header。这里有趣的是,当文件系统调用执行写磁盘时会发生什么?

让我们看一下fs.c中的ialloc,

在这个函数中,并没有直接调用bwrite,这里实际调用的是log_write函数。log_write是由文件系统的logging实现的方法。任何一个文件系统调用的begin_op和end_op之间的写操作总是会走到log_write。log_write函数位于log.c文件,

log_write还是很简单直观的,我们已经向block cache中的某个block写入了数据。比如写block 45,我们已经更新了block cache中的block 45。接下来我们需要在内存中记录,在稍后的commit中,要将block 45写入到磁盘的log中。

这里的代码先获取log header的锁,之后再更新log header。首先代码会查看block 45是否已经被log记录了。如果是的话,其实不用做任何事情,因为block 45已经会被写入了。这种忽略的行为称为log absorbtion。如果block 45不在需要写入到磁盘中的block列表中,接下来会对n加1,并将block 45记录在列表的最后。之后,这里会通过调用bpin函数将block 45固定在block cache中,我们稍后会介绍为什么要这么做(注,详见15.8)。

以上就是log_write的全部工作了。任何文件系统调用,如果需要更新block或者说更新block cache中的block,都会将block编号加在这个内存数据中(注,也就是log header在内存中的cache),除非编号已经存在。

学生提问:这是不是意味着,bwrite不能直接使用?

Frans教授:是的,可以这么认为,文件系统中的所有bwrite都需要被log_write替换。

Last updated