本次 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 中的这几个宏:
#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 数组修正一下:
uint addrs[NDIRECT+1+1]; // Data block addresses
核心便是修正 bmap,仿照本来的代码,对 doubly-indirect block num 进行搜索即可。
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){
// Load indirect block, allocating if necessary.
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;
/**
现在的 bn / 256 的值用于在第一级索引中定位一个 block num,取出这个 block 作为二级索引。
bn % 256 的值用于在第二级索引中定位一个 block num,这个 block num 便是 data block num。
一切的 block 都是按需请求,没有的话就创立一个。
**/
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 多遍历一层就可以了。
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 函数来开释锁而且撤销一次引用。
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); // 开释在 create 中获取的锁
end_op();
return 0;
}
修正 kernel/sysfile.c#sys_open 体系调用,新增判别当时 path 指向的 inode 是否是软衔接,而且查看O_NOFOLLOW 标志位。Hints 中写到两点注意事项:
- 假如一个软衔接又指向一个软衔接,那么要递归找出最终的方针文件;
- 软衔接或许会成环,这个时分就回来错误,hints 中的处理策略是限制递归次数为 10 次。
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),也卡了不少时间。