MIT6.S081实验九学习记录

lab 9 Filesystem

这个大实验主要做两部分,一个是增加inode的可用“block”,第二个是增加一个符号链接的功能。
这两个实验核心代码不多,但是需要对整体思路有一个非常清晰的把握。

首先讲一下inode,下面的图是表示inode在文件系统中的位置,属于第四层,上面是目录,下面是logging,缓存,硬盘,
在这里插入图片描述
然后书里介绍,The inode layer provides individual files, each represented as an inode with a unique i-number and some blocks holding the file’s data. 意思就是说这一层提供了单个的文件,每一个都索引到了具体存放数据的blocks,此时应配合下图理解。
在这里插入图片描述
inode里面存了data block的地址(起始地址),映射到了数据实际存储地址,这样通过inode就可以索引到数据块。这也就是为什么后面可以用inode建立软链接的原因,只需要在inode里面放对应目录或者数据的地址即可。

1、Large files

这个实验就是给inode增加间接块的大小,本来的inode是下面这样的。
在这里插入图片描述
这里可以看到,从address1 - address12指向了12个数据块,然后通过一个indirect block指向了256个数据块,这样就可以实现一个inode “存储” 268个数据块。这个实验要求将inode改为能够存储256*256+256+11 blocks 的形式,解释起来比较麻烦,直接上个图。
在这里插入图片描述
现在大体思路就有了,接下来开始实现。实验指南说要求在bmap上修改,这个bmap是用来返回inode数据块对应磁盘块的。

在此之前,首先需要调整inode原有的一些参数,比如NDIRECT, NINDIRECT, MAXFILE,改成需要的大小。直接块修改为11,最大值改为11+256+256×256,然后把inode的地址块改为直接块+单间接块+双间接块

#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT* NINDIRECT)

struct dinode {
  short type;           // File type
  short major;          // Major device number (T_DEVICE only)
  short minor;          // Minor device number (T_DEVICE only)
  short nlink;          // Number of links to inode in file system
  uint size;            // Size of file (bytes)
  uint addrs[NDIRECT+2];   // 把这里修改掉
};
static uint
bmap(struct inode *ip, uint bn)
{
  uint addr, *a, *b;
  struct buf *bp;
  struct buf *double_bp;
  
  //当前处于直接块
  if(bn < NDIRECT){ //Is bn a direct block?
    if((addr = ip->addrs[bn]) == 0)
      ip->addrs[bn] = addr = balloc(ip->dev);
    return addr;
  }
  //向下减去直接块的大小
  bn -= NDIRECT;
  
  //当前处于单间接块,注意索引从0开始,所以NDIRECT指的是第一个单间接块
  if(bn < NINDIRECT){ 
    // Load indirect block, allocating if necessary.
    if((addr = ip->addrs[NDIRECT]) == 0) //inode is not exited
      ip->addrs[NDIRECT] = addr = balloc(ip->dev);
    bp = bread(ip->dev, addr); //read this buf from disk
    a = (uint*)bp->data; //first pointer of data
    if((addr = a[bn]) == 0){ //need to allocate
      a[bn] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);
    return addr;
   }
   
   bn -= NINDIRECT;
   //当前处于双间接块,注意索引从0开始,所以NDIRECT + 1指的是第一个双间接块
   if(bn < NINDIRECT * NINDIRECT){
      if((addr = ip->addrs[NDIRECT + 1]) == 0) //双间接块是否存在,没有就分配
        ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev);
      double_bp = bread(ip->dev, addr); //read this buf from disk
      a = (uint*)double_bp->data; //first pointer of data
      if((addr = a[bn/NINDIRECT]) == 0){ //need to allocate
        a[bn/NINDIRECT] = addr = balloc(ip->dev); //每隔256分配一个起始地址
        log_write(double_bp);
      }
      brelse(double_bp);
      //分配256*256块
      bn = bn % NINDIRECT;
      double_bp = bread(ip->dev, addr); //read this buf from disk
      b = (uint*)double_bp->data; //first pointer of data
      if((addr = b[bn]) == 0){ //need to allocate
        b[bn] = addr = balloc(ip->dev); //将上面256个起始地址块之间填满256
        log_write(double_bp);
      }
      brelse(double_bp);
      return addr;
  }
 
  //bn is out of range MAXFILE
  panic("bmap: out of range");
}

然后还需要修改itrunc 函数,既然增加映射添加了,那么删除映射也要添加。

void
itrunc(struct inode *ip)
{
  int i, j, k;
  struct buf *bp, *double_bp;
  uint *a, *b;

  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 < 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 < NINDIRECT; j++){
      if(a[j]){
        double_bp = bread(ip->dev, a[j]); //找到第j个双间接块
        b = (uint*)double_bp->data;
        for (k = 0; k < NINDIRECT; k++) { //循环删除第j个双间接块的数据
          if (b[k])
            bfree(ip->dev, b[k]);
        }
        brelse(double_bp);
        bfree(ip->dev, a[j]);
      }
    }
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT + 1]);
    ip->addrs[NDIRECT + 1] = 0;
  }
  
  ip->size = 0;
  iupdate(ip);
}

最后编译测试一下bigfile。

jimmy@ubuntu:~/xv6-test/xv6-labs-2020$ make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 1 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

init: starting sh
$ bigfile
..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
wrote 65803 blocks
bigfile done; ok

2、Symbolic links

对我来说,这个实验比上一个理解起来难度要大很多,看了教材和实验要求后,也是参考了很多博主的博客才大概理解了符号链接的含义。

总体来说,就是要求建立一系列的inode,里面存放了链接的文件或者目录,因此可以通过这些inode来快速访问需要的文件或目录,用T_SYMLINK来标志这个inode是符号链接的inode。但是一旦访问这个节点就转去符号链接的话,明显不合理,因为有可能还要对当前的文件或目录进行操作,因为还需要加上一个标志位O_NOFOLLOW,来表明是否要跟随当前的符号链接到更下一级的文件或目录。

## /kernel/stat.h
#define T_DIR     1   // Directory
#define T_FILE    2   // File
#define T_DEVICE  3   // Device
#define T_SYMLINK 4   //SYMLINK

## /kernel/fcntl.h
#define O_RDONLY  0x000
#define O_WRONLY  0x001
#define O_RDWR    0x002
#define O_CREATE  0x200
#define O_TRUNC   0x400
#define O_NOFOLLOW 0x800  //NOFOLLOW flag

然后需要将其封装为一个系统调用,这一步类似之前的实验二,在user态添加了入口后,需要在kernel态的sysfile.c 添加实现。

按照上面的理解,这个函数有两个参数,需要把target 放到path中,然后创建一个inode,将其flag设置为T_SYMLINK,然后把target写入这个inode即可。仿照link,ulink的一些写法,代码如下。

uint64
sys_symlink(void)
{
  char target[MAXPATH], path[MAXPATH];
  struct inode *ip;

  if(argstr(0, path, MAXPATH) < 0 || argstr(1, target, MAXPATH) < 0)
    return -1;
    
  begin_op();
  //this path already exits
  if((ip = namei(path)) != 0){
    end_op();
    return -1;
  }
  
  ip = create(path, T_SYMLINK, 0 , 0);
  if(ip == 0){
    end_op();
    return -1;
  }
  
  if((writei(ip, 0, (uint64)target, 0, MAXPATH)) < 0){
    end_op();
    return -1;
  }
  
  iunlockput(ip); 
  end_op();
  return 0;
}

然后需要修改sys_open,加上判断当前inode是否是T_SYMLINK而且可以跟随,如果满足条件就一直递归到下一级路径。但是如果递归次数超过一个阈值,就认为进入死循环,就直接返回-1即可。

uint64
sys_open(void)
{
  ......
  begin_op();

  if(omode & O_CREATE){
    ip = create(path, T_FILE, 0, 0);
    if(ip == 0){
      end_op();
      return -1;
    }
  } else {
    if((ip = namei(path)) == 0){
        end_op();
        return -1;
      }
      ilock(ip);
    if(ip->type == T_DIR && omode != O_RDONLY){
      iunlockput(ip);
      end_op();
      return -1;
    }
    
    int depth = 0;
    while(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){
    	//读取当前inode里的路径,并且放到path中(下一级路径)
      if(readi(ip, 0 , (uint64)path, 0, MAXPATH) < 0){ 
        iunlockput(ip);
        end_op();
        return -1;
      }
      iunlockput(ip);
      
      //将当前ip指向下一级的路径
      if((ip = namei(path)) == 0){ //point the next path
        end_op();
        return -1;
      }
      ilock(ip);
      
      //已经递归超过十次,直接退出
      if(++depth > 10){
        iunlockput(ip);
        end_op();
        return -1;
      }
    }
  }
  ......
}

然后编译一下,看看结果。

== Test running bigfile == 
$ make qemu-gdb
running bigfile: OK (154.2s) 
    (Old xv6.out.bigfile failure log removed)
== Test running symlinktest == 
$ make qemu-gdb
(0.4s) 
== Test   symlinktest: symlinks == 
  symlinktest: symlinks: FAIL 
    ...
         Start: test symlinks
         FAILURE: failed to stat b
         Start: test concurrent symlinks
         test concurrent symlinks: ok
         $ qemu-system-riscv64: terminating on signal 15 from pid 4249 (make)
    MISSING '^test symlinks: ok$'
== Test   symlinktest: concurrent symlinks == 
  symlinktest: concurrent symlinks: OK 
== Test usertests == 
$ make qemu-gdb
usertests: OK (236.1s) 
    (Old xv6.out.usertests failure log removed)
== Test time == 
time: OK 
Score: 80/100

发现这里有一个用例一直没通过。然后debug发现是写系统调用时,把symlink的返回值类型写错了,而且把参数列表填反了。。。一定要细心啊!

## /kernel/sysfile.c/sys_symlink
//第一个参数是target,第二个是path
if(argstr(0, path, MAXPATH) < 0 || argstr(1, target, MAXPATH) < 0)
  return -1;

这里第一个参数应该是target,第二个是path。修改后,再次编译发现全部通过。

jimmy@ubuntu:~/xv6-test/xv6-labs-2020$ ./grade-lab-fs 
make: 'kernel/kernel' is up to date.
== Test running bigfile == running bigfile: OK (143.5s) 
== Test running symlinktest == (0.7s) 
== Test   symlinktest: symlinks == 
  symlinktest: symlinks: OK 
== Test   symlinktest: concurrent symlinks == 
  symlinktest: concurrent symlinks: OK 
== Test usertests == usertests: OK (243.7s) 
== Test time == 
time: OK 
Score: 100/100

总结

这个实验重在理解文件系统的结构,然后改写了inode的数据块索引,编写了软链接的系统调用,总的来说难度还是有的,需要对整体内容做深刻把握。
[1]. https://pdos.csail.mit.edu/6.S081/2020/labs/fs.html
[2]. https://zhuanlan.zhihu.com/p/630222721

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值