Xv6内核分析(十四)

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2020年12月20日18:21:28 评论 443

日志子系统分析

1. 日志子系统简介

Xv6中的log日志子系统主要用于系统以外崩溃挥着掉电后,再次运行时能够恢复运行。xv6中的磁盘中的文件系统布局如下:

Xv6内核分析(十四)

 

日志区域由一个日志头加上30个数据区组成,当有系统调用需要往磁盘中写入数据时,首先需要写到日志区的数据区部分,随后才能从日志数据区复制到真正需要写的磁盘区。

2. 用于日志子系统的一些数据结构

2.1 struct logheader

Xv6内核分析(十四)

这个数据结构用于表示日志头,其中的n表示日志数据区共有多少个数据块需要写回真正的数据区;block[]成员表示0~29号日志数据区的数据分别对应着真正的数据块的扇区号。

2.2. struct log

Xv6内核分析(十四)

这个数据结构用于整个日志子系统的操作和控制。

  • start表示log区在磁盘中的起始扇区号。
  • size表示整个log区的扇区个数。
  • lh表示log头部在内存中的表示。

3. initlog函数

Xv6内核分析(十四)

initlog在系统启动初始化过程中被调用,主要工作是尝试从磁盘上恢复执行之前被打断的写磁盘操作。

  • 首先对logheader的大小进行校验,目前xv6中的logheader不能超过一个扇区大小。
  • 接着读取磁盘上超级块的数据。
  • 然后将超级块中有关log的信息记录到log变量中。
  • 最后调用recover_from_log尝试恢复数据。

3.1. recover_from_log函数

Xv6内核分析(十四)

此函数用于恢复日志数据区数据到真正的数据区。

3.1.1 read_head函数

Xv6内核分析(十四)

  • 从磁盘中读取log头部信息到内存中。
  • 记录头部中的n成员。
  • 根据n的实际个数复制n个block[]成员数据到内存中的log头部中。
3.1.2 install_trans函数

Xv6内核分析(十四)

  • n表示需要恢复的数据块个数,所以用for循环依次恢复。
  • 首先读取log数据区中数据块的数据。
  • 然后读取真正的数据区中的数据。
  • 将log数据区中的数据复制到真正的数据区(这一步实际是复制buffer cache中的数据)。
  • 将真实数据区的buffer cache回写磁盘。
3.1.3 write_head函数

当log中的数据全部恢复完毕后,将log头部中的n置0,这是通过两步实现。首先将内存中的log头部中n置0,然后再将磁盘上log头部中的n置0。log.lh.n = 0;这是将内存中log头部中n置0:

Xv6内核分析(十四)

  • 读取磁盘上log头部。
  • 修改对应的buffer cache中的数据。
  • 将buffer cache数据回写磁盘。

4. 日志子系统的使用

Xv6中使用日志子系统的伪代码如下:

Xv6内核分析(十四)

4.1 begin_op函数

Xv6内核分析(十四)

  • 如果日志系统处于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 能够知道在一次会话中对同一块进行了多次读写,并且覆盖之前同一块的日志。

Xv6内核分析(十四)

  • 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

Xv6内核分析(十四)

  • 首先将log.outstanding减一。
  • 如果log.outstanding不是0,表示有其他文件系统调用在进行,并且可能由于log数据区空间不够而阻塞,所以执行wakeup尝试唤醒这些进程。
  • 如果log.outstanding是0,表示所有文件系统调用都执行过了end_op,当前系统没有文件系统调用在执行,这时就可以执行commit操作,将log中的log.committing设置为1。
4.3.1 commit函数

Xv6内核分析(十四)

  • 首先将数据写到磁盘log数据区。
  • 然后将最新的log头信息写到磁盘log头信息区。
  • 调用install_trans将log数据区中的数据写回真正的数据扇区。
  • 数据回写成功后将log.lh.n置0,表示所有log数据块回写完毕。
  • 将更新后的log头信息再次写回磁盘log头部。
4.3.1.1 write_log函数

Xv6内核分析(十四)

  • 读取log数据区buffer cache。
  • 读取真正数据区buffer cache(这时buffer cache中的数据一定是最新的,所以不会读取磁盘上真正数据区中的数据)。
  • 将真正数据区buffer cache中的数据复制到log数据区buffer cache中。
  • 将log数据区buffer cache写到磁盘上。
4.3.1.2 write_head函数

Xv6内核分析(十四)

  • 读取磁盘上最新的log头部信息。
  • 将内存中最新的n和block[]信息写到log头部的buffer cache中。
  • 将log头部的buffer cache写到磁盘上。

由此可见,commit()的工作就是buffer cache中的数据和头部信息写到磁盘log数据区和log头部。

4.4 剩余操作

回到end_op中的commit处理中:

Xv6内核分析(十四)

在commit完毕之后,磁盘写操作才算是真正的完成了,这时修改log.committing为0,表示commit完成。这时通过wakeup尝试唤醒可能由于commit操作而被阻塞的进程。

gewenbin
  • 本文由 发表于 2020年12月20日18:21:28
  • 转载请务必保留本文链接:http://www.databusworld.cn/9499.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: