Mit6.S081:lab fs
实验:Lab: fs
实验开始之前需要将git分支切换到fs分支不然有些文件你是没有的
1 | $ git fetch |
Large files (moderate)
简介
在xv6中支持的最大文件大小是268KB,是因为在inode中保存磁盘块的地址指针只有13个(12直接指针,1个间接指针)。本次实验中我们需要扩大这个限制,主要的实现思路是使用二级间接指针,其具体思路请参考inode章节讲解。
任务:本次实验主要是将inode的地址字段修改为11直接指针+1间接指针+1间接指针,并修改fs.c中bmap函数与
itruc
函数,,实现二级指针的检测与删除,通过bigfile与usertests -q测试便可以完成本次实验。如果有疑问还请阅读实验文档。
本次实验的测试程序为bigfile
,它会在一个文件中创建65803个块大小的数据,如果不修改源码就会出现下述的问题。并且通过bigfile测试大概会花费2分半左右的时间完成,期间会打印.
告诉你程序在运行,如果没有持续的打印,那么说明你的程序出现死锁了。
1 | bigfile |
提示:
- 首先要熟悉bmap的内容,建议画一下二级间接指针的指向图,大致如下所示,并且你要保证间接指针指向的块能有256*256个。
- 理清逻辑块号(logical block number)与addr保存的指针的关系。ps:逻辑块号就是一个文件内的数据块的序列,通过逻辑块号配合
bmap
就能够找到对应的磁盘块号了。 - 需要修改
NDIRECT
宏定义,与dinode(fs.h)和inode(file.h)这两个数据结构的定义,主要要保持一致 - 在我们修改NIDRECT宏或是在文件系统出现故障后,我们需要删除fs.img并且重建系统,建议使用make clean与make qemu。
- 在使用bread函数后,记得使用brelse释放磁盘块。
- 在
itruc
函数中添加删除二重指针指向的数据块的代码,在原有代码的基础上添加即可,和bmap一样。
实现:
修改:相关宏与inode结构体
我的实现是将inode的地址字段(13个无符号整型)进行修改:前11的地址指针作为直接指针,第12个作为单级间接指针,第13个作为二级间接指针。
因此需要修改NDIRECT、MAXFILE宏,并且添加一个NDINDIRECT(N Double-indirect)的宏指定了二级间接指针保存的逻辑块的数量。
1 | ----file.h |
添加:bmap二级间接指针地址块设置
首先了解一下bmap
的作用:在使用writei
与readi
时,通过使用逻辑块号配合bmap
,找到对应的磁盘块号。地址字段的作用就是保存磁盘块号,那么11个直接指针逻辑块号就是b~0~到b~10~,分别对应一个磁盘块号,那么一级指针逻辑块号的就是b~11~到b~267~,二级间接指针就是b~268~到b~65802~。
需要注意的是逻辑块号是将这三种指针的范围拼接了起来在逻辑上是连续的,在不同的指针中是隔离的,因此我们需要从直接->一级->二级,这样来查找。因此在操作bn参数时就需要考虑这个问题,对于二级间接指针而言逻辑块号是从0开始的,也就是需要b~n~ -= NINDIRECT。接着寻找磁盘块过程如下:
- b~n~肯定是在二级间接指针(0-256*256-1)范围内的,在此需要进行判断。
- 通过
addr[12]
读取第13个地址整数,是二级间接指针。如果该值不存在,我们就需要使用balloc
创建一个磁盘块(indirect block),用于保存256个一级间接指针,并将其地址保存到add[12]
中并返回。 - 通过
bn/NINDIRECT
(范围是在0-255之间),作为indirect block的索引,找到保存直接间接指针块(direct block)的地址,和步骤2一样这个块不存在的话就创建一个保存地址并返回。 - 最后使用
bn%NINDIRECT
(也可以是bn/NINDIRECT%NINDIRECT
),作为direct block的索引,找到最终数据块的地址,不存在的话就创建保存后返回。
1 | ----fs.c |
添加:itrunc二级间接指针释放
itrunc函数就是依葫芦画瓢就可以了,使用两层循环清理完间接块(indirect block,1个)、直接块(direct block,256个) 、数据块(256*256个)。
1 | void |
bigfile实验相对来说还是比较简单的,照着原有的代码添加就可以了,就是在一级间接指针遍历的基础上,再添加一层循环就是二级间接指针了。
Symbolic links (moderate)
简介
以下内容为机翻,如有疑惑请观看实验文档。
在本实验中,我们将向xv6添加符号链接。符号链接(或软链接)指的是通过路径名链接的文件;当一个符号链接被打开时,内核沿着这个链接指向被引用的文件。符号链接类似于硬链接,但硬链接仅限于指向同一磁盘上的文件,而符号链接可以跨磁盘设备。
任务:实现
symlink(char *target, char *path)
系统调用,它将在path上创建一个新的符号链接,该链接指向以target命名的文件。有关更多信息,请参阅手册页符号链接。要进行测试,请将symlinktest添加到Makefile并运行它。当测试产生以下输出(包括用户测试成功)时,您的解决方案就完成了。
1 | symlinktest |
如果不了解软连接与硬链接之间的关系,以及实现的可以参考“区别:软硬链接”,如果知道其原理可以直接看实现部分。
提示:
- 为symlink创建一个新的系统调用号,在
user/usys.pl
、user/user.h
中添加一个条目,并在kernel/sysfile.c
中实现一个空的sys_symlink
。 - 在
kernel/stat.h
中添加一个新的文件类型(T_SYMLINK)来表示符号链接。 - 在
kernel/fcntl.h
中添加一个新的标志(O_NOFOLLOW),如果使用该标识则说明open系统调用打开的是symlink的inode。请注意,传递给open的标志是使用按位或(or,|
)操作符组合的,因此新标志不应与任何现有标志重叠。这允许我们在user/symlinktest.c
添加到Makefile
后编译它。 - 实现symlink(target, path)系统调用,在path上创建一个指向target的新符号链接。注意,系统调用成功并不需要目标存在。您需要选择一个位置来存储符号链接的目标路径,例如,在索引节点的数据块中
symlink
应该返回一个表示成功(0)或失败(-1)的整数,类似于link和unlink。- 修改打开系统调用以处理路径引用符号链接的情况。如果文件不存在,打开必须失败。当进程在标志中定
O_NOFOLLOW
要打开时,open应该打开符号链接(而不是跟随符号链接)。 - 如果链接的文件也是一个符号链接,则必须递归地跟踪它,直到到达非链接文件。如果链接形成一个循环,则必须返回一个错误代码。如果链接深度达到某个阈值(例如,10),您可以通过返回错误代码来近似地实现这一点。
- 其他系统调用(例如,link和unlink)不能跟随符号链接;这些系统调用对符号链接本身进行操作。
- 在本实验中,我们不需要处理指向目录的符号链接。
区别:软硬链接
硬链接:link与unlink
如下图所示,对于根目录inode(inum=1),指向块46保存的是目录条目,其中一个条目保存了x文件的名称(先忽略y文件名称)。此时使用ls
命令便会打印根目录下的文件名称,包括x文件。对于x文件有一个inode(inum=17),这个inode指向块70,是一个数据块,保存的是x文件的数据。根据x文件名称,找到x文件的inode,通过inode我们便可以写入数据到x了。
那么此时,使用ln
命令(xv6中默认是硬链接),我们将y做一个硬链接指向x文件。如下图所示,我们只是在根目录的目录条目块添加一个y文件名称与x文件的inum(17),并且这个y文件名是指向x文件的inode,这便实现硬链接。我们下次就可以通过y文件名称,去访问修改x文件了。
上述便是硬链接的实现方式,硬链接主要是在父目录下添加目录条目实现的,所有可以用多个文件名指向通过文件,只有当硬链接的数量为0(ip->nlink=0),我们才能够删除文件。
软链接
在linux中使用ln
命令加上-s
标识,就是用于创建软链接。如图所示,我们创建一个a文件的软链接文件b,这两个文件都是在根目录下,在根目录inode指向的目录条目块中保存了这两个文件名。
那么我们如何通过b文件访问到a文件的呢?步骤如下:
- 输入b路径调用open系统调用,打开y文件的inode;
- y文件的inode指向的数据块保存的是目标文件的路径,在这里就是x文件的路径名(/a);
- 通过x路径名打开x文件的inode
- 通过x文件的inode就可以访问到其数据块,访问a文件的数据了。
以上便是软链接的实现思路了,与硬链接最大的不同是软链接路径有自己的inode,而硬链接共享同一个inode。
不同点:软硬链接
软连接 | 硬链接 | |
---|---|---|
实现 | 特殊文件,保存对目标文件路径引用 | 目录条目,直接指向inode |
磁盘 | 可跨磁盘使用 | 不可跨磁盘使用 |
删除 | 不会影响文件本身 | 当硬链接数量为0时删除文件 |
大小 | 一个inode+一个磁盘块的大小 | 目录条目大小 |
示例:ln命令
接着我们在linux下熟悉以下链接命令(ln
),以便我们能够规范一些细节。
case1:ln命令
在linux下ln命令用于制作链接,默认情况下是创建硬链接,加上-s标识是创建软链接。如下所示,目标文件为a,使用ln
将b文件名链接到a文件,使用ln -s
将c文件作为软链接指向a。
当使用ls -li
,可以查看文件的inode信息,文件inode信息的第一个整数如1179775,这个就是inode-number,此时可以发现软链接的文件的inode中权限标识为lrwxrwxrwx
,其中l
说明这个文件就是一个软链接文件,并且文件名为c->a
。
1 | mkdir test && cd test |
case2:数据访问
向a文件写入数据hello,使用cat
命令可以发现,b和c两种链接都可以访问到a的数据。但是在删除a后,软链接c就不能访问到a文件的数据了,而硬链接b却能够访问到a文件的数据。
1 | echo hello > a |
case3:创建空目标文件与软链接文件
在linux中,我们在没有目标文件的情况下是可以创建软链接的(硬链接是不可以的),并且可以通过软链接路径直接向空目标文件写入数据。
如下所示,我们创建1、2文件,可以发现我们可以通过使用2文件路径直接写入数据到1文件中,此时可以说明在创建软链接的时候可能已经创造了1文件(目标文件)的inode了。
1 | ln -s 1 2 |
case4:链接文件已存在
如下所示,如果我们将已经存在的文件(有数据),作为软连接的时候会出现报错。
1 | ln -s 2 1 |
实现
按照提示,添加相关定义
如下面代码所示,在指定文件中添加软链接系统调用的相关定义,此时我们可以启动xv6,并运行symlinktest
程序测试symlink系统调用是否合理。
1 | ----usys.pl |
sys_symlink:软链接系统调用
在sysfile.c中创建 sys_symlink
函数,这个函数我们可以复制sys_link
函数并进行相应的调整,在dirlink
处我们修改为sym_dirlink(struct inode*,char*,char*)
,这个函数就是用于创建软链接,实际上就是将目标文件的路径名进行保存。
1 | ----sysfile.c |
sym_dirlink:创建软链接
模仿以下dirlink
的设计方式,新建一个sym_dirlink
函数,主要的执行流程如下:
- 通过
dirlookup
查找软链接文件名是否存储在父目录的目录条目中,如果软连接inode存在就需要进行相应修改,并且这个文件是已经存储了数据,那么就说明这个不能作为软链接文件(case4)。如果没有存储数据,我们就需要将文件修改为软链接文件,修改文件类型,并通过writei
记录目标文件的路径名到磁盘块中。 - 如果软连接inode不存在,那么就需要创建一个inode,指定类型为T_SYMLINK,并通过
writei
记录目标文件的路径名到磁盘块中。 - 最后,在软链接文件的父目录inode中,记录下链接文件的名称,就是一个目录条目。
1 | ----fs.c |
修改:更改open添加软链接
在实验提示中要求我们实现O_NOFOLLOW
模式位,这个模式位的主要作用就是找到软链接文件的inode。
- 在调用open函数时设置了
O_NOFOLLOW
位,我们打开的不是软链接文件这时候就会返回一个错误。 - 在调用open函数时没有设置
O_NOFOLLOW
位,如果我们输入路径的文件类型是T_SYMLINK
的话,我们就需要找到目标文件,如下所示,我们调用一个find_refinode(struct* inode)
函数,这个函数就是通过递归找到最终的目标文件。 - 在调用open函数时没有设置
O_NOFOLLOW
位,如果我们输入路径的文件类型不是T_SYMLINK
,那么就和正常情况一样,后续的代码也不需要修改。
1 |
|
find_refinode:递归查找目标路径的inode并返回
find_refinode
函数中设计有几个细节值得注意:
- 两个软链接文件相互连接,那么会导致无限递归,此时需要设置一个递归阈值
threshold
(设置上限为10),当我们递归寻找十次后就会返回错误。 - 可以通过
readi
函数,读取软链接文件保存的目标文件路径,使用namei
找到指定的目标文件的inode。 - 注意加锁和释放锁之间的搭配,避免死锁。
- 当目标文件的inode的类型不是
T_SYMLINK
的时候,函数就可以结束递归了。
1 |
|
添加函数声明到defs.h
在fs.c中添加了两个函数,需要在defs.h头文件中添加声明,才能在sysfile.c文件中调用这两个函数。
1 | ----defs.h |
结果
本次实验难度也是比较适中的,bigfile相对来说是比较简单的。symlink是比较有难度的,因为symlink中会频繁的使用inode锁,导致死锁的问题也会出现的比较频繁,而且实现较为准确的symlink还是要大刀阔斧的修改xv6的源码。后续有在自己的理解上实现了symlink的一个bug,这个bug不耽误我们理解软链接,但是是比较符合linux下的软链接的标准的,如果有兴趣的可以接着阅读。
实验之外:通过软连接路径直接修改文件
在完成实验后,使用以下命,会出现打开b文件错误的问题,也就是在未创建a文件时候使用echo
命令添加数据到b->a。
1 | $ ln -s a b |
这里我们看向open函数中,已知b->a那么,我们通过b是可以找到a的inode,但是在未创建a文件是,find_refinode
函数是无法找到a文件的inode的。因此在这里我们有两种解决办法:
- 在
sys_symlink
系统调用结束前,创建连接目标文件的inode。将文件做一个新的标识T_HIDDEN,设定为隐藏,也就是说明这个文件不使用的时候是查找不到的(ls
命令无法发现)。 - 在
find_refinode
函数中,当namei
,找到不到对于inode的时候创建一个目标文件的inode。这种情况需要修改inode结构,需要使用一个标识,标识文件是被首次创建的,因此在删除目标文件时候,会导致软连接无法找到目标文件。
两个文件是有一个先后关系,1是在创建软连接时候创建,2是在使用软连接文件时候创建。
新增:ln软连接命令
1 |
|
实现:在创建软连接时候创建目标文件inode
1.添加新的文件类型设定为隐藏,目的是使用ls时通过这个类型实现不打印这个inode
1 | ----stat.h |
2.在建立完软链接后,使用create
函数创建目标文件的inode。
1 | uint64 |
3.在create函数中,添加T_HIDDEN
的相关定义,避免并发调用时的问题。
1 | static struct inode* |
4.修改open函数,通过namei
提前查找路径的inode,这种方式有避免反复调用create造成错误,如果存在的并且为T_HIDDEN
的话我们就将其修改为T_FILE
类型。
1 |
|
缺陷
这种情况下是有所缺陷的,那就是会使用多余inode,这些inode只有在使用后才能被删除,导致inode table容量不够,这时我们也通过不了usertests -q
的iref
测试点(大量创建inode,测试inode table容量)了。
结果:
完成上述修改后就可以达到预设的情况了。
1 | ln -s a b |