大文件
在这个作业中,你将增加 xv6 文件的最大大小。目前 xv6 文件的大小限制为 268 个块,或 268*BSIZE 字节(BSIZE 在 xv6 中为 1024)。这个限制源于 xv6 的 inode 包含 12 个"直接"块编号和一个"单间接"块编号,后者引用一个包含最多 256 个块编号的块,总共 12+256=268 个块。
bigfile 命令创建它所能创建的最长文件,并报告该大小:
$ bigfile .. wrote 268 blocks bigfile: file is too small $测试失败是因为 bigfile 期望能够创建一个 65803 个块的文件,但未修改的 xv6 将文件限制在 268 个块。 你将修改 xv6 文件系统代码,以支持每个 inode 中的"双间接"块,其中包含 256 个单间接块的地址,每个单间接块可以包含最多 256 个数据块的地址。结果将是文件能够由最多 65803 个块组成,或 256*256+256+11 个块(11 而不是 12,因为我们将为双间接块牺牲一个直接块编号)。
预备知识 mkfs 程序创建 xv6 文件系统磁盘映像,并确定文件系统拥有的总块数;这个大小由 FSSIZE 在 kernel/param.h 中控制。你会看到这个实验仓库中的 FSSIZE 设置为 200,000 块。你应该在 mkfs/mkfs 的 make 输出中看到以下输出:这一行描述了 mkfs/mkfs 构建的文件系统:它有 71 个元数据块(用于描述文件系统的块)和 199,929 个数据块,总计 200,000 块。 注意 make qemu 会构建一个新的 fs.img ,并将旧文件保存在 fs.img.bk 中。如果你想在现有的 fs.img 上运行 xv6 而不是构建一个新的,请运行 make qemu-fs 。
要查看的内容 磁盘上的 inode 格式由 struct dinode 在 fs.h 中定义。你特别感兴趣的是 NDIRECT 、 NINDIRECT 、 MAXFILE 以及 struct dinode 的 addrs[] 元素。查看 xv6 文本中的图 10.3,了解标准 xv6 inode 的示意图。 查找文件数据在磁盘上的代码位于 bmap() 中的 fs.c 。查看它并确保你理解它在做什么。 bmap() 在读取和写入文件时都会被调用。在写入时, bmap() 会根据需要分配新的数据块来保存文件内容,如果需要的话还会分配一个间接块来保存数据块地址。
bmap() 处理两种类型的块编号。 bn 参数是一个"逻辑块编号"——文件内的块编号,相对于文件开始的位置。 ip->addrs[] 中的块编号以及 bread() 的参数是磁盘块编号。你可以将 bmap() 视为将文件的逻辑块编号映射到磁盘块编号。
你的任务 修改 bmap() ,使其除了直接块和单间接块外,还能实现双间接块。你需要只有 11 个直接块,而不是 12 个,以腾出空间给你的新双间接块;你不允许改变磁盘 inode 的大小。 ip->addrs[] 的前 11 个元素应该是直接块;第 12 个应该是单间接块(就像当前的);第 13 个应该是你的新双间接块。当你 bigfile 写入 65803 个块并且 usertests -q 运行成功时,你就完成了这个练习:
$ bigfile .................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................. wrote 65803 blocks done; ok $ usertests -q ... ALL TESTS PASSED $bigfile 至少需要运行一个半小时。
提示:
- 确保你理解 bmap() 。画出 ip->addrs[] 、间接块、双重间接块及其指向的单间接块和数据块之间的关系图。确保你理解为什么添加一个双重间接块会增加最大文件大小 256*256 块(实际上是 -1,因为你必须减少直接块的数量)。
- 思考一下你将如何使用逻辑块号来索引双重间接块及其指向的间接块。
- 如果你更改 NDIRECT 的定义,你可能需要更改 file.h 中 struct inode 中 addrs[] 的声明。确保 struct dinode 和 struct inode 在其 addrs[] 数组中有相同数量的元素。
- 如果你更改了 NDIRECT 的定义,请确保创建一个新的 fs.img ,因为 mkfs 使用 NDIRECT 来构建文件系统。
- 如果你的文件系统进入不良状态,可能是由于崩溃,请删除 fs.img (从 Unix 系统执行,而不是从 xv6 执行)。 make 将为你构建一个新的干净文件系统映像。
- 别忘了对你 bread() 的每个块进行 brelse() 。
- 你应该根据需要分配间接块和双重间接块,就像原始的 bmap() 一样。
- 确保 itrunc 释放文件的所有块,包括双重间接块。
- usertests 运行比之前的实验要慢,因为在这个实验中 FSSIZE 更大,大文件也更大。
我的实现步骤如下:
- fs.h:
- 将
NDIRECT从12改为11(留一个给双间接块) - 将
MAXFILE增加NINDIRECT*NINDIRECT(文件最大大小) - 将
addrs[]元素数量改为NDIRECT+2(因为前面减了一)
- 将
- file.h: 将
addrs[]的总大小也加一(与上面保持一致) - fs.c
- 修改
bmap,伪代码如下:
- 修改
bn = bn - NINDIRECT
IF bn < NINDIRECT * NINDIRECT THEN
// 分配一级间接块
IF inode.addrs[NDIRECT + 1] == 0 THEN
addr = allocate_block()
IF addr == 0 THEN RETURN 0
inode.addrs[NDIRECT + 1] = addr
ENDIF
buf1 = read_block(inode.addrs[NDIRECT + 1])
arr1 = buf1.data
i = bn / NINDIRECT
// 分配二级间接块
IF arr1[i] == 0 THEN
addr = allocate_block()
IF addr == 0 THEN
release(buf1)
RETURN 0
ENDIF
arr1[i] = addr
write_log(buf1)
ENDIF
addr2 = arr1[i]
release(buf1)
buf2 = read_block(addr2)
arr2 = buf2.data
j = bn MOD NINDIRECT
// 分配数据块
IF arr2[j] == 0 THEN
addr = allocate_block()
IF addr == 0 THEN
release(buf2)
RETURN 0
ENDIF
arr2[j] = addr
write_log(buf2)
ENDIF
result = arr2[j]
release(buf2)
RETURN result
ENDIF
符号链接
在这个练习中,你将为 xv6 添加符号链接。符号链接(或软链接)通过路径名引用一个链接的文件或目录;当打开符号链接时,内核会查找链接的目标名称。符号链接类似于硬链接,但硬链接仅限于指向同一磁盘上的文件,不能引用目录,并且与特定的目标 i 节点绑定,而不是像符号链接那样引用目标名称当前所在的位置(如果存在的话)。实现这个系统调用是一个很好的练习,可以理解路径名查找的工作原理。
在这个实验中,你不需要处理指向目录的符号链接;唯一需要知道如何跟随符号链接的系统调用是 open() 。
你的任务 你将实现 symlink(char *target, char *path) 系统调用,该系统调用在路径 path 上创建一个新的符号链接,该链接指向名为 target 的文件。更多信息请参阅 symlink 的手册页。为了测试,将 symlinktest 添加到 Makefile 中并运行它。当测试产生以下输出(包括 usertests 成功)时,你的解决方案就完成了。
$ symlinktest Start: test symlinks test symlinks: ok Start: test concurrent symlinks test concurrent symlinks: ok $ usertests -q ... ALL TESTS PASSED $提示:
- 首先,为符号链接创建一个新的系统调用号,向 user/usys.pl、user/user.h 添加条目,并在 kernel/sysfile.c 中实现空的 sys_symlink。
- 在 kernel/stat.h 中添加一个新的文件类型( T_SYMLINK ),用于表示符号链接。
- 在 kernel/fcntl.h 中添加一个新的标志( O_NOFOLLOW ),可用于与 open 系统调用一起使用。请注意,传递给 open 的标志使用按位或运算符组合,因此您的新标志不应与任何现有标志重叠。这将允许您在将其添加到 Makefile 后编译 user/symlinktest.c。
- 实现 symlink(target, path) 系统调用,在路径上创建一个新的符号链接,该链接指向目标。请注意,目标不需要存在系统调用才能成功。您需要选择一个位置来存储符号链接的目标路径,例如,在 inode 的数据块中。 symlink 应返回一个整数,表示成功(0)或失败(-1),类似于 link 和 unlink 。
- 修改 open 系统调用以处理路径指向符号链接的情况。如果文件不存在, open 必须失败。当进程在 open 的标志中指定 O_NOFOLLOW 时, open 应该打开符号链接(而不是跟随符号链接)。
- 如果链接的文件也是一个符号链接,你必须递归地跟随它,直到达到一个非链接文件。如果链接形成了一个循环,你必须返回一个错误代码。你可以通过在链接深度达到某个阈值(例如,10)时返回错误代码来近似实现这一点。
- 其他系统调用(例如,link 和 unlink)不应跟随符号链接;这些系统调用直接作用于符号链接本身。
我的实现步骤如下:
- 添加系统调用(过程如之前添加一样)
- 添加
O_NOFOLLOW(代表open时直接打开symlink而不是follow它) - 增加
sys_symlink()- 获取
path和target参数到内核空间 - 开启事务
- 调用
create创建一个symlinkinode - 调用
writei将target写入inode - 结束事务
- 获取
- 修改
sys_open()- 先判断是否同时为
T_SYMLINK且!NO_FOLLOW才进入特殊逻辑,否则按照原先过程处理 - while循环中不断检查是否为
T_SYMLINK,如果是则调用readi读出target,并用namei获取新inode。整个过程注意锁和事务的处理 - 需要设置递归最大次数(如10次),避免循环引用
- 先判断是否同时为
结果
== Test running bigfile (takes minutes) ==
$ make qemu-gdb
running bigfile (takes minutes): OK
== Test running symlinktest ==
$ make qemu-gdb
(6.9s)
== Test symlinktest: symlinks ==
symlinktest: symlinks: OK
== Test symlinktest: concurrent symlinks ==
symlinktest: concurrent symlinks: OK
== Test usertests (takes minutes) ==
$ make qemu-gdb
usertests (takes minutes): OK
== Test time ==
time: OK
Score: 100/100