本次 lab 感觉就是对 xv6 文件系统代码进行熟悉,我们要扩充 xv6 支持的最大文件大小并且给 xv6 实现软链接。
Large files (moderate)
xv6 原本支持的最大文件大小只有 12 + 256 个 block,也就是 inode 结构体中 addr 数组的前 12 个元素指出的 12 个 block加上最后一个元素指出的一个 block 中指出了 256 个 block。
如上图所示,最后一位 singly-indirect block num 指出了一个 block,里面又存储了 256 个 direct block num。一个 block 是 1024B,一个 block num 为 4B,所以正好存储 256 个 direct block num。
我们要做的就是将文件容量扩充为 11 + 256 + 256*256。改为将 addr 数组前 11 位作为 direct block num,第 12 位作为 singly-indirect block num,将第 13 位作为 doubly-indirect block num。doubly-indirect block num 指向一个 block,这个 block 里面的每个条目都是一个 singly-indirect block num,也就是说还需要再定位一次,才能取到真正的 block num。
代码实现
修改 kernel/fs.h 中的这几个宏:
1 2 3 4
| #define NDIRECT 11 #define SINGLY_NINDIRECT (BSIZE / sizeof(uint)) #define DOUBLY_NINDIRECT SINGLY_NINDIRECT * SINGLY_NINDIRECT #define MAXFILE (NDIRECT + SINGLY_NINDIRECT + DOUBLY_NINDIRECT)
|
并且记得将 struct inode 和 struct dinode 结构体中的 addrs 数组修改一下:
1
| uint addrs[NDIRECT+1+1];
|
核心就是修改 bmap,仿照原来的代码,对 doubly-indirect block num 进行搜索即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| static uint bmap(struct inode *ip, uint bn) { uint addr, *a; struct buf *bp;
if(bn < NDIRECT){ if((addr = ip->addrs[bn]) == 0) ip->addrs[bn] = addr = balloc(ip->dev); return addr; } bn -= NDIRECT;
if(bn < SINGLY_NINDIRECT){ if((addr = ip->addrs[NDIRECT]) == 0) ip->addrs[NDIRECT] = addr = balloc(ip->dev); bp = bread(ip->dev, addr); a = (uint*)bp->data; if((addr = a[bn]) == 0){ a[bn] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); return addr; } bn -= SINGLY_NINDIRECT;
if(bn < DOUBLY_NINDIRECT){ if((addr = ip->addrs[NDIRECT+1]) == 0) ip->addrs[NDIRECT+1] = addr = balloc(ip->dev); bp = bread(ip->dev, addr); a = (uint*)bp->data; uint idx = bn / (BSIZE / sizeof(uint)); if((addr = a[idx]) == 0) { a[idx] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); bp = bread(ip->dev, addr); a = (uint*)bp->data; idx = bn % (BSIZE / sizeof(uint)); if((addr = a[idx]) == 0) { a[idx] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); return addr; }
panic("bmap: out of range"); }
|
最后修改 itrunc 来释放一个文件的所有 block,跟 bmap 是差不多的,doubly-indirect blocks 多遍历一层就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| void itrunc(struct inode *ip) { int i, j; struct buf *bp; uint *a;
for(i = 0; i < NDIRECT; i++){ if(ip->addrs[i]){ bfree(ip->dev, ip->addrs[i]); ip->addrs[i] = 0; } }
if(ip->addrs[NDIRECT]){ bp = bread(ip->dev, ip->addrs[NDIRECT]); a = (uint*)bp->data; for(j = 0; j < SINGLY_NINDIRECT; j++){ if(a[j]) bfree(ip->dev, a[j]); } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT]); ip->addrs[NDIRECT] = 0; }
if(ip->addrs[NDIRECT+1]){ bp = bread(ip->dev, ip->addrs[NDIRECT+1]); a = (uint*)bp->data; for (j = 0; j < SINGLY_NINDIRECT; j++) { if(a[j]) { int k; struct buf *b = bread(ip->dev, a[j]); uint *data = (uint*)b->data; for (k = 0; k < SINGLY_NINDIRECT; k++) { if(data[k]) bfree(ip->dev, data[k]); } brelse(b); bfree(ip->dev, a[j]); } } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT+1]); ip->addrs[NDIRECT+1] = 0; }
ip->size = 0; iupdate(ip); }
|
Symbolic links (moderate)
给 xv6 实现软连接(符号连接)。符号连接通过路径名连接到目标文件,也就是说在使用 open 系统调用的时候,如果打开的是一个符号连接,那么 file system 就会找到这个软连接指向的目标文件,再去打开目标文件(除非指定了 O_NOFOLLOW 标识,那么 fs 就会直接打开软连接,而不会去追踪到目标文件)。
代码实现
前面添加新的系统调用和这里就跳过了。
在 kernel/stat.h 中添加 T_SYMLINK 来标识一个 inode 类型是软连接,在 kernle/fcntl.h 中添加一个新的标识符 O_NOFOLLOW,以让 open 系统调用判断是要打开一个软连接还是追踪软连接的目标文件。
首先实现 kernel/sysfile.c#sys_symlink
这是一个系统调用函数,对应的用户空间的声明是 int symlink(char*, char*);所以我们需要先将两个参数拿到。
接着开启一个事务,在事务中完成 inode 的创建和写入。调用 create 函数创建 inode,要注意 create 返回的时候已经持有了 inode 的锁,不需要再次获取锁了,并且在事务结束时要调用 iunlockput 函数来释放锁并且取消一次引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| uint64 sys_symlink(void) { char path[MAXPATH], target[MAXPATH];
if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0) { return -1; }
begin_op();
struct inode *ip; if((ip = create(path, T_SYMLINK, 0, 0)) == 0) { end_op(); return -1; }
int n = strlen(target);
if(writei(ip, 0, (uint64)target, 0, n) < 0) { end_op(); return -1; }
iunlockput(ip); end_op();
return 0; }
|
修改 kernel/sysfile.c#sys_open 系统调用,新增判断当前 path 指向的 inode 是否是软连接,并且检查O_NOFOLLOW 标志位。Hints 中写到两点注意事项:
- 如果一个软连接又指向一个软连接,那么要递归找出最终的目标文件;
- 软连接可能会成环,这个时候就返回错误,hints 中的解决策略是限制递归次数为 10 次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| if(omode & O_CREATE){ ip = create(path, T_FILE, 0, 0); if(ip == 0){ end_op(); return -1; } } else { int symlinkdepth = 0; while (1) { symlinkdepth++; if (symlinkdepth > 10) { end_op(); return -1; } if((ip = namei(path)) == 0){ end_op(); return -1; } ilock(ip); if (ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)) { if(readi(ip, 0, (uint64)path, 0, MAXPATH) < 0) { iunlockput(ip); end_op(); return -1; } iunlockput(ip); } else { break; } } if(ip->type == T_DIR && omode != O_RDONLY){ iunlockput(ip); end_op(); return -1; } }
|
运行结果
不知道是代码写的太臭还是我这个台式捡垃圾捡的 CPU 太慢的原因,直接跑 make grade 是直接超时了,一会用笔记本跑一下。直接在 qemu 中跑 bigfile、symlinktest、usertests 都是没问题的。
总结
这次 lab 比上一个简单得多,在 symlink 部分需要好好读一下接口。我一开始没看清 create 中就已经获取了 inode 锁,并且没有释放,将 unlock(dp) 看成了 unlock(ip),也卡了不少时间。