日志子系统分析
1. 日志子系统简介
Xv6中的log日志子系统主要用于系统以外崩溃挥着掉电后,再次运行时能够恢复运行。xv6中的磁盘中的文件系统布局如下:
日志区域由一个日志头加上30个数据区组成,当有系统调用需要往磁盘中写入数据时,首先需要写到日志区的数据区部分,随后才能从日志数据区复制到真正需要写的磁盘区。
2. 用于日志子系统的一些数据结构
2.1 struct logheader
这个数据结构用于表示日志头,其中的n表示日志数据区共有多少个数据块需要写回真正的数据区;block[]成员表示0~29号日志数据区的数据分别对应着真正的数据块的扇区号。
2.2. struct log
这个数据结构用于整个日志子系统的操作和控制。
- start表示log区在磁盘中的起始扇区号。
- size表示整个log区的扇区个数。
- lh表示log头部在内存中的表示。
3. initlog函数
initlog在系统启动初始化过程中被调用,主要工作是尝试从磁盘上恢复执行之前被打断的写磁盘操作。
- 首先对logheader的大小进行校验,目前xv6中的logheader不能超过一个扇区大小。
- 接着读取磁盘上超级块的数据。
- 然后将超级块中有关log的信息记录到log变量中。
- 最后调用recover_from_log尝试恢复数据。
3.1. recover_from_log函数
此函数用于恢复日志数据区数据到真正的数据区。
3.1.1 read_head函数
- 从磁盘中读取log头部信息到内存中。
- 记录头部中的n成员。
- 根据n的实际个数复制n个block[]成员数据到内存中的log头部中。
3.1.2 install_trans函数
- 读取磁盘上log头部。
- 修改对应的buffer cache中的数据。
- 将buffer cache数据回写磁盘。
4. 日志子系统的使用
Xv6中使用日志子系统的伪代码如下:
4.1 begin_op函数
- 如果日志系统处于commit阶段,则睡眠。
- MAXOPBLOCKS表示每次操作最多可以用的日志数据块个数,默认是10。(log.outstanding+1)*MAXOPBLOCKS表示如果算上本次操作,最多需要的log数据块个数,log.lh.n表示已经使用的log数据块个数。如果这两个数值相加大于log数据区总块数,表示日志数据区可能不够用,睡眠等待。
- 如果一切顺利,则将log.outstanding加1,表示文件系统调用个数加一。
可以看出,begin_op的主要工作就是将log.outstanding加1。
4.2 log_write函数
log_write像是 bwrite 的一个代理;它把块中新的内容记录到日志中,并且把块的扇区号记录在内存中。log_write 仍将修改后的块留在内存中的缓冲区中,因此相继的本会话中对这一块的读操作都会返回已修改的内容。log_write 能够知道在一次会话中对同一块进行了多次读写,并且覆盖之前同一块的日志。
- log_write首先检查在已有的log数据块中是否有和要写的buffer cache相同的数据块。如果有,则使用已有的log数据块位置。
- 如果没有,则将要写的buffer cahe的扇区号记录到新的log头部中对应的block[]区域。同时增加log.lh.n计数。
- 将buffer cache中的标志位加上B_DIRTY,表示数据已经被修改。
可以看出,log_write的工作其实就是修改并将要写的buffer信息记录在内存中的log头部中。
4.3 end_op
- 首先将log.outstanding减一。
- 如果log.outstanding不是0,表示有其他文件系统调用在进行,并且可能由于log数据区空间不够而阻塞,所以执行wakeup尝试唤醒这些进程。
- 如果log.outstanding是0,表示所有文件系统调用都执行过了end_op,当前系统没有文件系统调用在执行,这时就可以执行commit操作,将log中的log.committing设置为1。
4.3.1 commit函数
- 读取log数据区buffer cache。
- 读取真正数据区buffer cache(这时buffer cache中的数据一定是最新的,所以不会读取磁盘上真正数据区中的数据)。
- 将真正数据区buffer cache中的数据复制到log数据区buffer cache中。
- 将log数据区buffer cache写到磁盘上。
4.3.1.2 write_head函数
- 读取磁盘上最新的log头部信息。
- 将内存中最新的n和block[]信息写到log头部的buffer cache中。
- 将log头部的buffer cache写到磁盘上。
由此可见,commit()的工作就是buffer cache中的数据和头部信息写到磁盘log数据区和log头部。
4.4 剩余操作
回到end_op中的commit处理中:
在commit完毕之后,磁盘写操作才算是真正的完成了,这时修改log.committing为0,表示commit完成。这时通过wakeup尝试唤醒可能由于commit操作而被阻塞的进程。
评论