这个作业主要是让你熟悉xv6的文件系统中log部分。该作业共分为两个部分,第一个部分是根据代码回答一些问题,第二个部分是对现有的代码做一点点优化。
xv6 log
xv6 中 log 主要是为了解决崩溃恢复的问题。因为在文件系统中,很多操作涉及到磁盘上很多块的读写,在这些读写中间如果发生系统崩溃,那么磁盘上的文件系统就会陷入一种不一致的中间状态。比如。而log的设计就可以让一连串的读写成为一个原子操作,要么这一连串读写都不成功,要么都成功,而不会出现一部分成功的中间状态。
xv6 文件接口例子
这部分用几个例子来说明xv6 文件系统接口的函数调用过程,这几个例子是依次进行的。弄懂了这几个例子,就能对 xv6的文件系统的接口执行时的函数调用有更深的了解。
1. xv6 创建文件,命令为 echo > a
, 如下:
$ echo > a
write 34 ialloc (from create sysfile.c; mark it non-free)
write 34 iupdate (from create; initialize nlink &c)
write 59 writei (from dirlink fs.c, from create)
call graph:
sys_open sysfile.c
create sysfile.c
ialloc fs.c
iupdate fs.c
dirlink fs.c
writei fs.c
首先是open系统调用,带了一个新建文件的参数,所以在 sys_open
中会去调用 create
函数,因为是新建文件,所以 dirlookup
在该文件夹中没找到名为 a
的文件,返回0。转而去执行 ialloc
, ialloc
会分配一个新的 inode
,修改它的 type
, 并用 log_write
写回,这就是上面的第一次 write
, 所以 34 就是新分配的 inode
所在的块号。 ialloc
返回后,create
修改它的 nlink
等属性,再用 iupdate
进行更新,这就是上面的第二次 write
。最后create
调用 dirlink
将新的 inode
挂在目标文件夹下面。在 dirlink
中,使用 writei
修改文件夹内容,writei
修改完后会用 log_write
写回磁盘,这就是第三次 write
,所以现在可以回答以下问题了。
Q: block 34
和 block 59
里存的是什么?
A: block 34
存的有新分配的文件 a
的 inode
, block 59
存的是文件 a
的上级文件夹的目录,里面有文件 a
的信息。
Q: 为什么有两次对 block 34
的 writei
?
A: 第一次是 create
调用 ialloc
分配 inode
时修改 type
进行保存所产生的。第二次是 create
随后修改 inode
的 nlink
等属性再保存产生的。不过这两次修改在 cache buf
和 磁盘的 log
都只占一份(对于同一个 block
在内存中只能有一个 cache buf
),所以也没啥影响。
Q: 如果并发地调用 ialloc
会怎么样?他们会得到同一个 inode
吗?
A: ialloc
主要调用 bread
来 读取 inode
, 而bread
主要是调用 bget
, 其在返回时会获取 buf
的sleeplock
, 即整个 block
都被锁住。其他人必须等 brelse
释放了这个 block
才可能使用这个 inode
。所以是不可能得到同一个 inode
的。
2. xv6 写入文件,命令为 echo x > a
, 如下:
$ echo x > a
write 58 balloc (from bmap, from writei)
write 508 bzero
write 508 writei (from filewrite file.c)
write 34 iupdate (from writei)
write 508 writei
write 34 iupdate
call graph:
sys_write sysfile.c
filewrite file.c
writei fs.c
bmap
balloc
bzero
iupdate
由于上面已经创建好了文件 a
, 所以 open
不会产生 write
。后面调用 write
系统调用才会产生 write
。sys_write
只是 filewrite
的一层包装。在 filewrite
中,会调用 writei
真正进行写入。writei
会对每个 block
先调用 bmap
进行映射,由于文件目前还是空的,所以 bmap
会调用 balloc
分配一个 block
, balloc
分配时会修改分配的块的 bit map
标志位,并用 log_write
保存。这对应着上面的第一次 write
。所以block 58
为文件 a
第一个block
的 bit map
标志所在的 block
。随后 balloc
调用 bzero
将新分配的块的内容全部置为0, bzero
对块写入后用 log_write
保存,这对应着上面的第二次 write
。所以 block 508
为 文件 a
的第一个块的块号。bmap
执行完返回后,writei
将数据写入到分配的块中,用 log_write
保存。这对应着上面的第三次 write
。接着它更新 文件 a
的 inode
的 size
, 并用 iupdate
保存,这对应着上面的第四次 write
,所以block 34
存的有文件 a
的 inode
。
到这里到这个文件的写入其实就已经结束了,后面的两次 write
其实是因为 echo
进行了两次 write
系统调用,第二次用来输出 newline
,也就是对应着最后的两次 write
。
Q: block 58
和 block 508
里存的是什么?
A: block 34
存的有新分配的文件 a
的 inode
, block 59
存的是文件 a
的上级文件夹的目录,里面有文件 a
的信息。block 508
为 文件 a
的第一个块的块号。
Q: 为什么有两次 writei
和 iupdate
?
A: 因为 echo
进行了两次 write
系统调用,第二次用来输出 newline
,也就是对应着最后的两次 write
。
3. xv6 删除文件,命令为 rm a
, 如下:
$ rm a
write 59 writei (from sys_unlink; directory content)
write 34 iupdate (from sys_unlink; link count of file)
write 58 bfree (from itrunc, from iput)
write 34 iupdate (from itrunc; zeroed length)
write 34 iupdate (from iput; marked free)
call graph:
sys_unlink
writei
iupdate
iunlockput
iput
itrunc
bfree
iupdate
iupdate
rm
命令调用的就是 sys_unlink
, 它会先调用 writei
去修改父文件夹的内容,在writei
中会将修改后的块用log_write
保存,这就是第一次 write
, 所以block 59
存的是文件 a
的上级文件夹的目录,这与上面分析得到的结论保持一致。然后紧接着 sys_unlink
会修改 inode
的 nlink
, 并用 iupdate 保存,这对应着上面的第二次 write
。所以block 34
存的是文件 a
的 inode
, 这也与上面分析得到的结论保持一致。最后就是 iunlockput(ip);
, 先解锁 inode
, 再调用 iput
释放inode
, iput
中调用 itrunc
再调用 bfree
,
bfree
会修改文件 a
的块在bit map
中的标志位,并用 log_write
保存,这对应着上面的第三次 write
。所以 block 58
为文件 a
第一个block
的 bit map
标志所在的 block
。bfree
执行完后,itrunc
修改 inode
的 size
为 0,并用 iupdate
保存。这对应着上面的第四次 write
。itrunc
执行完后,iput
会将 inode
的 type
置为 0, 并用 iupdate
保存,这对应着上面的第五次 write
Q: block 34
, block 58
和 block 59
里存的是什么?
A: block 34
存的有新分配的文件 a
的 inode
, block 59
存的是文件 a
的上级文件夹的目录,里面有文件 a
的信息,block 58
为文件 a
第一个block
的 bit map
标志所在的 block
。