# 15.4 log\_write函数

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

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MS_F6nYy0utF738c_Y7%2F-MS_NMysPF-mY9gV-y6e%2Fimage.png?alt=media\&token=c2a29d95-0e52-4b00-a2a4-94ffa2fa6a65)

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，

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MS_F6nYy0utF738c_Y7%2F-MS_PHSEoZWSt888RY7q%2Fimage.png?alt=media\&token=2e361c6b-ab7d-47f3-bd08-d6d4d210e8c0)

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

![](https://1977542228-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHZoT2b_bcLghjAOPsJ%2F-MS_F6nYy0utF738c_Y7%2F-MS_QF-YFBCY37u8H-q0%2Fimage.png?alt=media\&token=ca6fee73-7fcb-42e7-99be-00b5d9bbbf4e)

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替换。
