6.S081/6.828: 9 Lab file system

2022-12-02 00:30:06 浏览数 (3)

参考xv6源码分析--文件系统。

一、Large files

1 目的

目前xv6包含12个直接索引和1个二级索引,共索引12KB 256KB。增加xv6文件的索引范围,使得能够索引256*256KB 256KB 11KB,牺牲一个直接索引,将其转变为三级索引。

2 问题分析

mkfs程序会创建xv6文件系统磁盘镜像并决定文件系统有多少blocks,这个大小受kernel/param.h中的FSSIZE控制。

文件系统启动文件系统启动

访问索引的场景:

  1. 读写block时需要根据文件偏移量从索引块中获取磁盘block编号,bmap函数;
  2. 清空文件时需要遍历索引块并释放掉磁盘空间,itrunc函数。

3 代码实现

读写block时调用bmap获取磁盘块地址,

  1. 如果在0, 10,那么就读取直接索引;
  2. 如果在11, 266,那么就先读取single-indirect索引块,然后读取指定索引项;
  3. 如果在267, 65803,那么就读取两级索引块,再根据偏移量获取索引项。
代码语言:c复制
//返回该偏移量的block地址
static uint
bmap(struct inode *ip, uint bn)
{
  uint addr, *a;
  struct buf *bp;
  //从直接索引读取blockno
  if(bn < NDIRECT){
    if((addr = ip->addrs[bn]) == 0)
      ip->addrs[bn] = addr = balloc(ip->dev);
    return addr;
  }
  bn -= NDIRECT;
  //从二级索引中读取blockno
  if(bn < 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-=NINDIRECT;
  if(bn < NDBINDIRECT){
    // Load indirect block, allocating if necessary.
    //分配顶级目录
    if((addr = ip->addrs[NDIRECT 1]) == 0)
      ip->addrs[NDIRECT 1] = addr = balloc(ip->dev);
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    //分配二级目录
    int mediumindex=bn/NINDIRECT;
    if((addr = a[mediumindex]) == 0){
      a[mediumindex] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);
    //分配一级目录
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    int bottomindex=bn%NINDIRECT;
    if((addr = a[bottomindex]) == 0){
      a[bottomindex] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);
    // printf("三级索引,bn[%d],addr[%d]n",bn,addr);
    return addr;
  }

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

释放文件空间时也需要访问索引并释放掉所有block,依次释放直接索引指向的block,single-indirect指向的block以及double-indirect指向的block,并将索引块也释放掉,设置size=0。

代码语言:c复制
// Truncate inode (discard contents).
// Caller must hold ip->lock.
void
itrunc(struct inode *ip)
{
  int i, j;
  struct buf *bp;
  uint *a;
  //释放直接索引的block
  for(i = 0; i < NDIRECT; i  ){
    if(ip->addrs[i]){
      bfree(ip->dev, ip->addrs[i]);
      ip->addrs[i] = 0;
    }
  }
  //释放间接索引的block
  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;
  }
  //释放二级间接索引
  //释放间接索引的block
  if(ip->addrs[NDIRECT 1]){
    bp = bread(ip->dev, ip->addrs[NDIRECT 1]);
    a = (uint*)bp->data;
    struct buf* bbp;
    uint *aa;
    for(j = 0; j < NINDIRECT; j  ){
      if(0==a[j])
        continue;
      bbp=bread(ip->dev,a[j]);
      aa=(uint*)bbp->data;
      for(int i=0;i< NINDIRECT;i  ){
        if(aa[i])
          bfree(ip->dev,aa[i]);
      } 
      brelse(bbp);
    }

    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT 1]);
    ip->addrs[NDIRECT 1] = 0;
  }

  ip->size = 0;
  //更新磁盘上inode
  iupdate(ip);
}

二、Symbolic links

1 目的

本任务是实现symlink(char *target, char *path)系统调用,给xv6添加符号链接。符号链接是一个特殊文件,保存了被链接文件的path。硬链接是创建新的目录项指向已存在文件的inode,但是只能指向磁盘上文件,而符号链接更加灵活,能指向设备文件。

2 问题分析

符号链接文件和普通文件的读写、打开、创建不一样,如下:

  1. 符号链接是从文件内容读取path,然后根据dirlookup查找目标文件的inode,与目录、普通文件不一样,所以需要定义新的文件类型T_SYMLINK
  2. 添加symlink系统调用,它会调用create创建符号链接文件,这个和普通文件的创建有所不同;
  3. 添加一个flag O_NOFOLLOW,使得能够打开符号文件,如果被链接的文件也是一个符号链接文件,那就需要递归的查找直到遇到普通文件为止,如果链接关系是一个环,则需要error,可以根据递归深度来决定;
  4. 其他的系统调用打开符号链接文件就是操作符号链接文件本身,不需要查找被链接的文件。

3 代码实现

首先增加系统调用symlink;

代码语言:c复制
//syscall.h
#define SYS_symlink  22

//usys.pl
entry("symlink");

//syscall.c
[SYS_symlink]   sys_symlink,

//stat.h
#define T_SYMLINK 4   //symbolic link

然后实现sys_symlink(),如下:

  1. 读取target字符串和path地址;
  2. 查找path是否存在,如果不存在就创建该符号文件;
  3. 向该文件写入target。
代码语言:c复制
uint64
sys_symlink(void){

  char target[MAXPATH],path[MAXPATH];
  if(argstr(0,target,MAXPATH)<0)
    return -1;
  if(argstr(1,path,MAXPATH)<0)
    return -1;
  struct inode *ip;
  struct file *f;
  int fd;

  begin_op();
  //查找该path的文件inode是否存在
  if((ip = namei(path)) == 0){
    ip=create(target,T_SYMLINK,0,0);
    if(ip==0){
      end_op();
      return -1;
    }
  }else{
    ilock(ip);
  }
  if(writei(ip,0,target,ip->size,MAXPATH)!=MAXPATH){
    panic("panic: symlink");
  }
  iunlockput(ip);
  end_op();
  return 0;
}

打开符号链接文件时要区分,以普通文件形式打开来读写符号链接文件,还是以O_NOFOLLOW=0模式打开读取被链接的普通文件。如果是访问被链接的文件,那么就递归的查找inode,通过readi从inode中读取数据symbolic path,然后读取symbolicpath inode并判断是不是T_SYMLINK,如果是就继续递归。为了防止出现环,最多递归10次,超过就认为有环。

代码语言:c复制
if(ip->type==T_SYMLINK && (omode & O_NOFOLLOW)==0){
      int count=0;
      char symlinkpath[MAXPATH];
      while(1){
        if(count>10){
          iunlockput(ip);
          end_op();
          return -1;
        }
        if(readi(ip,0,(uint64)symlinkpath,ip->size-MAXPATH,MAXPATH)!=MAXPATH){
          panic("panic: symlink");
        }
        iunlockput(ip);
        if((ip=namei(symlinkpath))==0){
          end_op();
          return -1;
        }
        ilock(ip);
        if(ip->type!=T_SYMLINK){
          break;
        }
        count  ;
      }
    }

测试结果

测试结果测试结果

1 人点赞