本次 lab 感觉便是对 xv6 文件体系代码进行了解,咱们要扩大 xv6 支撑的最大文件巨细而且给 xv6 完结软链接。

Large files (moderate)

xv6 本来支撑的最大文件巨细只要 12 + 256 个 block,也便是 inode 结构体中 addr 数组的前 12 个元素指出的 12 个 block加上最后一个元素指出的一个 block 中指出了 256 个 block。

MIT 6.s081 Lab9: file system

如上图所示,最后一位 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 中写到两点注意事项:

  1. 假如一个软衔接又指向一个软衔接,那么要递归找出最终的方针文件;
  2. 软衔接或许会成环,这个时分就回来错误,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),也卡了不少时间。