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()
提交日志。
日志的数据结构不多,主要是一个 log
数据块,其中里面有几个变量:
logheader
用来建立日志区和数据区的盘块映射。n = 0 表示无映射,无须同步操作。outstanding
用来记录日志层被系统调用的次数。dev
记录日志层所处设备号。log_write()
,但是读盘块是不需要经过日志区的。