MIT6.1810 Lab9

790 阅读4分钟

Lab: file system

实验链接

实验准备

切换到fs分支

  $ git fetch
  $ git checkout fs
  $ make clean

Large files

需求

在这个任务中,你将增加一个xv6文件的最大大小。

目前,xv6文件被限制在268个块或268 * BSIZE字节(BSIZE在xv6中为1024)。这个限制来自于xv6 inode包含12个"直接"块号和一个"单间接"块号,它引用一个可以容纳256个更多块号的块,总计12+256=268个块。你将修改xv6文件系统代码,以支持每个inode中的一个"双间接"块,其中包含256个单间接块的地址,每个单间接块可以包含最多256个数据块的地址。结果将是一个文件最多由65803个块组成,即256*256+256+11个块(11个而不是12个,因为我们将牺牲一个直接块号来放置双间接块)。

你需要修改bmap()函数,以实现双间接块的功能,除了直接块和单间接块外。为了给新的双间接块腾出位置,你需要将ip->addrs[]的前11个元素作为直接块,第12个元素作为单间接块(与当前的设置相同),第13个元素作为新的双间接块。当bigfile写入65803个块,并且usertests -q成功运行时,你就完成了这个练习。

The solution

首先去fs.h中添加双间接块对应的直接块总数宏定义,并修改文件最大大小的宏定义:

#define NDIRECT 12
#define NINDIRECT (BSIZE / sizeof(uint))
#define DINDIRECT (NINDIRECT*NINDIRECT)//双间接块对应的直接块总数
#define MAXFILE (NDIRECT + NINDIRECT + DINDIRECT - 1)//要减去被占用的直接块

然后在kernel/fs.c中修改bmap定义,添加用双间接块获取指定物理块号的代码:

static uint
bmap(struct inode *ip, uint bn)
{
  uint addr, *a;
  struct buf *bp;

  if(bn < NDIRECT -1){//判断是否为前11个直接块
    if((addr = ip->addrs[bn]) == 0){
      addr = balloc(ip->dev);
      if(addr == 0)
        return 0;
      ip->addrs[bn] = addr;
    }
    return addr;
  }
  bn -= (NDIRECT - 1);//减去11

  if(bn < NINDIRECT){//判断是否可由“单间接”块找到
    // Load indirect block, allocating if necessary.
    if((addr = ip->addrs[NDIRECT - 1]) == 0){//判断"单间接"块是否已分配
      addr = balloc(ip->dev);
      if(addr == 0)
        return 0;
      ip->addrs[NDIRECT - 1] = addr;//修改下标
    }
    bp = bread(ip->dev, addr);//获取直接块
    a = (uint*)bp->data;
    if((addr = a[bn]) == 0){
      addr = balloc(ip->dev);
      if(addr){
        a[bn] = addr;
        log_write(bp);
      }
    }
    brelse(bp);
    return addr;
  }
  
  bn -= NINDIRECT;//减256
  
  if(bn < DINDIRECT){
    if((addr = ip->addrs[NDIRECT]) == 0){//判断“双间接”块是否已分配
      addr = balloc(ip->dev);
      if(addr == 0)
        return 0;
      ip->addrs[NDIRECT] = addr; 
    }
    
    uint an = bn / NINDIRECT;//判断是第几个单间接块
    bp = bread(ip->dev,addr);
    a = (uint*)bp->data;
    if((addr = a[an]) == 0){
      addr = balloc(ip->dev);
      if(addr){
        a[an] = addr;//将单间接块地址放入双间接块中
        log_write(bp);
      }
      //else return 0;
    }
    brelse(bp);
    
    an = bn % NINDIRECT;//获取直接块
    bp = bread(ip->dev,addr);
    a = (uint*)bp->data;
    if((addr = a[an]) == 0){
      addr = balloc(ip->dev);
      if(addr){
        a[an] = addr;//将直接块地址放入单间接块中
        log_write(bp);
      }
      //else return 0;
    }
    brelse(bp);
    
    return addr;
  }

  panic("bmap: out of range");
}

然后修改itrunc的定义以释放双间接块有关物理块,确保释放文件所有的块:

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

  for(i = 0; i < NDIRECT - 1; i++){//释放前11个直接块
    if(ip->addrs[i]){
      bfree(ip->dev, ip->addrs[i]);
      ip->addrs[i] = 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])
        bfree(ip->dev, a[j]);
    }
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT-1]);//释放单间接块
    ip->addrs[NDIRECT-1] = 0;
  }
  
  if(ip->addrs[NDIRECT]){//判断双间接块是否存在
    bp = bread(ip->dev, ip->addrs[NDIRECT]);//若存在则读取
    a = (uint*)bp->data;
    for(i = 0; i < NINDIRECT; i++){
      if(a[i]){//判断单间接块是否存在
        ap = bread(ip->dev,a[i]);
        b = (uint*)ap->data;
        for(j = 0; j < NINDIRECT; j++){
          if(b[j])//判断直接块是否存在
            bfree(ip->dev,b[j]);
        }
        brelse(ap);
        bfree(ip->dev, a[i]);//释放单间接块
      }
    }
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT]);//释放双间接块
    ip->addrs[NDIRECT] = 0;
  }
  
  ip->size = 0;
  iupdate(ip);
}

由于我们未修改NDIRECT的宏定义而是在逻辑上认为前11块为直接块,第12块为单间接块,第13块为双间接块,故我们要注意依赖NDIRECT的代码,确保它符合我们规定的逻辑,发现建立初始文件系统的mkfs/mkfs.c中的iappend函数中涉及NDIRECT,我们要对其进行修改:

void
iappend(uint inum, void *xp, int n)
{
  ...
    if(fbn < NDIRECT - 1){
      if(xint(din.addrs[fbn]) == 0){
        din.addrs[fbn] = xint(freeblock++);
      }
      x = xint(din.addrs[fbn]);
    } else {
      if(xint(din.addrs[NDIRECT - 1]) == 0){
        din.addrs[NDIRECT - 1] = xint(freeblock++);
      }
      rsect(xint(din.addrs[NDIRECT - 1]), (char*)indirect);
      if(indirect[fbn - NDIRECT + 1] == 0){
        indirect[fbn - NDIRECT + 1] = xint(freeblock++);
        wsect(xint(din.addrs[NDIRECT - 1]), (char*)indirect);
      }
      x = xint(indirect[fbn-NDIRECT + 1]);
    }
    ...
}

Symbolic links

需求

在这个练习中,你将向xv6添加符号链接。符号链接(或软链接)通过路径名引用链接的文件;当打开符号链接时,内核会跟随链接到被引用的文件。符号链接类似于硬链接,但是硬链接限制只能指向同一磁盘上的文件,而符号链接可以跨越磁盘设备。尽管xv6不支持多个设备,但实现这个系统调用是一个很好的练习,可以理解路径名查找的工作原理。

你将实现 symlink(char *target, char *path) 系统调用,该调用在 path 路径下创建一个新的符号链接,指向 target 所命名的文件。更详细的信息可以参考 symlink 的 man 页面。为了进行测试,需要将 symlinktest 添加到 Makefile 中并运行它。

The solution

预处理

  1. 在user/user.h中为该系统调用添加用户态的声明int symlink(char *target, char *path);
  2. 在user/usys.pl中为其添加一个脚本生成项entry("symlink")
  3. 在kernel/syscall.h中为其添加一个宏定义#define SYS_symlink 22
  4. 在kernel/syscall.c中的函数指针数组syscalls中添加一条[SYS_symlink] sys_symlink,

sys_symlink实现

为了让系统能区分出软链接inode,需要先在kernel/stat.h添加一个新的文件类型宏定义#define T_SYMLINK 4 // symlink

然后在kernel/sysfile.c中定义sys_symlink函数:

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

  if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)//获取路径
    return -1;

  begin_op();
  
  if((ip = namei(path)) != 0){//path下已经存在该符号链接
    end_op();
    return -1;
  }
  
  ip = create(path, T_SYMLINK, 0, 0);//在path下创建一个新的符号链接
  if(ip == 0){
    end_op();
    return -1;
  }

  if(writei(ip, 0, (uint64)target, 0, MAXPATH) < 0){//将target写入inode
    iunlockput(ip);
    end_op();
    return -1;
  }
  
  iunlockput(ip);//create会返回一个锁定的inode,故要记得解锁
  end_op();
  return 0;
}

为了让open系统调用确定是要跟随软链接还是单纯打开软链接,我们要在kernel/fcntl.h中添加一个新的标志#define O_NOFOLLOW 0x004

最后我们修改kernel/sysfile.c中sys_open的定义,当inode类型为T_SYMLINK,并且用户未设置O_NOFOLLOW标志时我们就可以跟踪该软链接,直至获取到文件。这里需要注意的是软链接可能会出现循环链接的情况,我们要限制循环查找次数

uint64
sys_open(void)
{
  ...
  if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
    iunlockput(ip);
    end_op();
    return -1;
  }

  int tag = 0;
  while(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){//可跟踪
    readi(ip, 0, (uint64)target, 0, MAXPATH);//读取target
    iunlockput(ip);
    
    if(++tag > 10){//循环查找
      end_op();
      return -1;
    }
    
    ip = namei(target);//链接到下一inode
    if(ip == 0){
      end_op();
      return -1;
    }
    ilock(ip);
  }

  if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
  ...
}