XV6

XV6 文件系统虽然设计得比较简单,但还是考虑了日志功能。如果写操作过程中断电崩溃,将很大可能损坏文件系统,例如,在断电后目录有一个指向空闲 i 节点的项将可能导致严重的问题。XV6 使用了日志式文件系统来确保写操作不会导致文件系统的破坏,进程的写操作像一种 “原子” 操作,使读写操作要么完全完成,要么未完成。

所有的读写操作首先都会写入磁盘中存放日志的区域,只有当真正的读写操作完成后才会使日志撤销。这样,就算任何过程中断电或者其他原因导致系统崩溃,文件系统的组织结构都不会损坏,结果是要么操作完全完成,要么都未完成。尽管这样使得每个操作进行了两次,降低了读写效率。

XV6 在硬盘中的日志有一个初始块和数据块,初始块包括一个数组,数组的值为对应数据块的内容应该写入文件系统中的哪一块,初始块还有当前有效数据块的计数。在内存中同样要有一样的结构来存储数据。

通过这种方式,bwrite() 可以使用 log_write() 替代,当修改了内存中的块缓冲区后,log_wirte() 同时在 block[] 数组中记录这个块需要写到磁盘中的哪一块,但是没有立即写入,当调用 commit() 的时候,调用 write_log() 写入日志区域中,并调用 write_head() 更新初始块,然后调用 install_trans() 真正地更新文件系统,此时,发生崩溃都会导致日志有非零的计数,以便重启后再次进行写操作,最后将计数变量置零使日志失效并更新日志初始快。

通过 log_write() 写入磁盘时,数据并不会立即写入磁盘,只有当调用 commit() 来提交日志时, 磁盘操作才会正式开始磁盘操作。

XV6 日志读写支持并发操作,当要写操作时,调用 begin_op(),结束时调用 end_op()begin_op() 检查日志是否正在提交,如果正在提交则睡眠当前进程,如果不在提交则增加操作次数,end_op() 减少操作次数,当没有任何进程正在操作 log 时,调用 commit() 提交日志。

1. 实现

日志的数据结构不多,主要是一个 log 数据块,其中里面有几个变量:

  1. logheader 用来建立日志区和数据区的盘块映射。n = 0 表示无映射,无须同步操作。
  2. outstanding 用来记录日志层被系统调用的次数。
  3. dev 记录日志层所处设备号。
  4. 日志层是连续的,所以只需要记录起始地址和大小即可。
  5. 盘块的写操作必须经过 log_write(),但是读盘块是不需要经过日志区的。