xv6 6.S081 Lab8: fs

xv6 6.S081 Lab8: fs

    • 写在前面
    • 实验介绍
    • 开始!
      • Large File
      • Symbolic links

fs代码在这里。我的妈呀,终于要写完了,xv6的file system考察难度并不大,这里强烈推荐我工Ext2 Based File System,这里可以给一下参考代码与参考结果,后面找机会写写博客。

写在前面

xv6中的file system结构与Ext 2的结构类似,如下图所示:
xv6 6.S081 Lab8: fs_第1张图片

在完成本实验前,推荐阅读xv6 book Chapter 7.9,搞清楚inode的索引结构。

与往常一样,在我的博客OS实验xv6 6.S081 开坑中给出了一些有用的参考资料,大家也可以一并参考。

实验介绍

给出本次实验的实验指导书

本次实验主要需要完成两个部分:

  • 为inode添加一个二级索引,使之能够支持分配更大的文件;
  • 学会如何创建symbol link软链接

开始!

Large File

xv6 6.S081 Lab8: fs_第2张图片

首先要理解inode的结构是什么样的,这里的addrs数组即ext2 file system中的索引指针。根据官方指导书的说法,xv6中的inode有12个直接索引,1个一级索引。另外xv6的数据块大小为1024B,索引指针大小为4B,因此一个数据块可以装256个索引指针,因此,目前一个inode最大支持12 + 256 = 268个数据块。需要注意的是,在xv6中有两种类型的inode,其中一个是存在磁盘中的inode,另一种是缓存在内存中的inode,在对inode结构进行修改的时候需要修改这两处。inode的结构如下:

// On-memory inode structure
struct inode {
  uint dev;           // Device number
  uint inum;          // Inode number
  int ref;            // Reference count
  struct sleeplock lock; // protects everything below here
  int valid;          // inode has been read from disk?

  short type;         // copy of disk inode
  short major;
  short minor;
  short nlink;
  uint size;
  uint addrs[NDIRECT+1];
};

// On-disk inode structure
struct dinode {
  short type;           // File type 
  short major;          // Major device number (T_DEVICE only)
  short minor;          // Minor device number (T_DEVICE only)
  /** 
   * The nlink field counts the number of directory
   * entries that refer to this inode,
   * in order to recognize when the on-disk 
   * inode and its data blocks should be freed.   
   * */
  short nlink;          // Number of links to inode in file system
  /**
   * The size field records the number of bytes of content in the file.
   */
  uint size;            // Size of file (bytes)
  /**
   * The addrs array records the block numbers of 
   * the disk blocks holding the file’s content.
   */
  uint addrs[NDIRECT+1];   // Data block addresses
};

下图给出了一个基本的inode结构示意图:
xv6 6.S081 Lab8: fs_第3张图片
本次任务即为xv6中的inode添加一个Doubel Indirect索引块,这样每一个inode就能够支持11 + 256 + 256 * 256 = 65803个数据块的文件了。实现并不困难,我们首先修改宏定义如下:

/** direct blocks */
#define NDIRECT 11
/** indirect blocks */
#define SINGLEDIRECT (BSIZE / sizeof(uint))
#define NINDIRECT (SINGLEDIRECT + SINGLEDIRECT * BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT)

其中,NDIRECT代表直接索引指向块的数量;SINGLEDIRECT表示一级索引能够指向的块的数量;NINDIRECT索引代表一级和二级索引一共可指向的块的数量。

其次,根据指导书的Hint:
mkfs initializes the file system to have fewer than 2000 free data blocks, too few to show off the changes you’ll make. Modify kernel/param.h to change FSSIZE from 2000 to 200,000:

#define FSSIZE       200000  // size of file system in blocks

Rebuild mkfs so that is produces a bigger disk: $ rm mkfs/mkfs fs.img; make mkfs/mkfs

我们修改kernel/param.h中的FSSIZE宏定义为200000

好了,接下来开始正式实现二级索引列表,代码实现并不困难,只要搞清楚原来的bmap是如何分配块的就行,在此不做赘述。代码如下:

static uint
bmap(struct inode *ip, uint bn)
{
  /**
   * bmap() deals with two kinds of block numbers. 
   * The bn argument is a "logical block number" -- 
   * a block number within the file, relative to the start of the file. 
   * The block numbers in ip->addrs[], and the argument to bread(), 
   * are disk block numbers. 
   * You can view bmap() as 
   * 
   * 
   * mapping a file's logical block numbers into disk block numbers.
   */
  // printf("-----------------------------------------------\n");
  // printf("bn1: %d\n", bn);
  // NINDIRECT 256
  // printf("NINDIRECT: %d\n", NINDIRECT); //256
  uint addr, *a, *a2;
  struct buf *bp, *bp2;
  /** bn是相对file(inode)的虚拟编号  */
  /** 直接返回块ip->addrs[bn]  */
  if(bn < NDIRECT){
    if((addr = ip->addrs[bn]) == 0)
      ip->addrs[bn] = addr = balloc(ip->dev);
    return addr;
  }
  /** 否则减去NDIRENT  */
  bn -= NDIRECT;
  //11 + 256 + 256 * 256
  if(bn < NINDIRECT){
    if(bn < SINGLEDIRECT){
      // Load indirect block, allocating if necessary.
      if((addr = ip->addrs[NDIRECT]) == 0)
        ip->addrs[NDIRECT] = addr = balloc(ip->dev);
      /** Return a locked buf with the contents of the indicated block.  */
      bp = bread(ip->dev, addr);
      /**
       * 为何 #define NINDIRECT (BSIZE / sizeof(uint)) ?
       * 因为一个buf有BSIZE个data,一个block地址为64位,故刚好存储256个block指针,NINDIRECT = 256
       */
      /** 一个文件描述符 */
      a = (uint*)bp->data; 
      if((addr = a[bn]) == 0){
        a[bn] = addr = balloc(ip->dev);
        log_write(bp);
      }
      brelse(bp);
    }
    else
    {
      /** 
       * 实现double-indirect
       * 结构:
       * addr -> 256个single-indirect -> 256 * 256个block
       * 256 + 11 = 267 0 ~ 266为前面的,267开始后面为后面的
       *  */
      bn -= SINGLEDIRECT;
      /** 宏在做乘除运算时记得加括号5555555555555555555  */
      /** single-indirect索引*/
      int single_indirect_index = bn / SINGLEDIRECT;
      /** single-indirect内部相对索引  */   
      int relative_offset_bn = bn % SINGLEDIRECT;
      /** 下标12是double-indirect  */
      int pos = NDIRECT + 1;
      if((addr = ip->addrs[pos]) == 0)
        ip->addrs[pos] = addr = balloc(ip->dev);

      bp = bread(ip->dev, addr);
      a = (uint*)bp->data;
      if((addr = a[single_indirect_index]) == 0){
        printf("bn: %d, addr: %p\n",bn, addr);
        a[single_indirect_index] = addr = balloc(ip->dev);
        log_write(bp);
      }
      brelse(bp);

      bp2 = bread(ip->dev, addr);
      a2 = (uint*)bp2->data; 
      if((addr = a2[relative_offset_bn]) == 0){
        a2[relative_offset_bn] = addr = balloc(ip->dev);
        log_write(bp2);
      }
      brelse(bp2);
    }
    printf("-----------------------------------------------\n");
    return addr;
  }

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

下面,我们完成itrunc以释放inode下的所有数据块,同样,做法参考原本的itrunc即可,代码如下:

static void
itrunc(struct inode *ip)
{
  int i, j;
  struct buf *bp, *bp2;
  uint *a, *a2;
  /** Free掉所有Direct */
  for(i = 0; i < NDIRECT; i++){
    if(ip->addrs[i]){
      bfree(ip->dev, ip->addrs[i]);
      ip->addrs[i] = 0;
    }
  }
  /** Free掉single-direct  */
  if(ip->addrs[NDIRECT]){
    bp = bread(ip->dev, ip->addrs[NDIRECT]);
    a = (uint*)bp->data;
    /**
     * 修改NINDIRECT为SINGLEDIRECT
     * for(j = 0; j < NINDIRECT; j++){
     *   if(a[j])
     *     bfree(ip->dev, a[j]);
     * } 
     */
    for (j = 0; j < SINGLEDIRECT; j++)
    {
      /* code */
      if(a[j])
        bfree(ip->dev, a[j]);
    }
    
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT]);
    ip->addrs[NDIRECT] = 0;
  }
  /** Free掉double-direct  */
  if(ip->addrs[NDIRECT + 1]){
    printf("free double\n");
    int pos = NDIRECT + 1;
    bp = bread(ip->dev, ip->addrs[pos]);
    a = (uint*)bp->data;
    int number_of_single_direct = BSIZE / sizeof(uint);
    for (i = 0; i < number_of_single_direct; i++)
    {
      if(a[i]){
        bp2 = bread(ip->dev, a[i]);
        a2 = (uint *)bp2->data;
        for (j = 0; j < SINGLEDIRECT; j++)
        {
          if(a2[j])
            bfree(ip->dev, a2[j]);
        }
        brelse(bp2);
        bfree(ip->dev, a[i]);
        a[i] = 0;
      }
    }
    brelse(bp);
    bfree(ip->dev, ip->addrs[pos]);
    ip->addrs[pos] = 0;
  }
  ip->size = 0;
  iupdate(ip);
}

至此,我们完成了Large File部分,测试结果如下:
xv6 6.S081 Lab8: fs_第4张图片

Symbolic links

xv6 6.S081 Lab8: fs_第5张图片
在完成该实验前,首先需要明白Symbolic Link。可以简单的将它理解为一个存有目标路径的文件,在Windows中可以将其理解为快捷方式。symbolic link文件有两种打开方式,其一是FOLLOW方式,即打开symbolic link文件内容中的路径;其次是NOFOLLOW,即将symbolic link文件视为普通文件,直接打开它。

首先分析Hints:

  • First, create a new system call number for symlink, add an entry to user/usys.pl, user/user.h, and implement an empty sys_symlink.
  • Add a new file type (T_SYMLINK) to kernel/stat.h to represent a symbolic link.
  • Add a new flag to kernel/fcntl.h, (O_NOFOLLOW), that can be used with the open system call. Note that flags passed to open are combined using a bitwise OR operator, so your new flag should not overlap with any existing flags. This will let you compile user/symlinktest.c once you add it to the Makefile.
  • Implement the symlink(target, path) system call to create a new symbolic link at path that refers to target. Note that target does not need to exist for the system call to succeed. You will need to choose somewhere to store the target path of a symbolic link, for example, in the inode’s data blocks.
  • Modify the open system call to handle the case where the path refers to a symbolic link. If the file does not exist, open must fail. When a process specifies O_NOFOLLOW in the flags to open, open should open the symlink (and not follow the symbolic link).
  • If the linked file is also a symbolic link, you must recursively follow it until a non-link file is reached. If the links form a cycle, you must return an error code. You may approximate this by returning an error code if the depth of links reaches some threshold (e.g., 10).
  • Other system calls (e.g., link and unlink) must not follow symbolic links; these system calls operate on the symbolic link itself.
  • You do not have to handle symbolic links to directories for this lab.

逐条解读:

  1. 首先和syscall一样创建sys_symlink系统调用,相关代码如下:

user/usys.pl

entry("symlink");

user/user.h

/** Symbol link */
int symlink(char *target, char *path);

kernel/syscall.h

/** Symbol link  */
#define SYS_symlink 23

kernel/syscall.c

[SYS_symlink] sys_symlink

knerl/sysfile.c

uint64
sys_symlink(void){
  
  return 0;
}

  1. kernel/stat.h中添加T_SIMLINK来表示symbolic link文件
  2. kernel/fcntl.h中添加O_NOFOLLOW标志,表示将symbolic link文件视为普通文件打开
  3. 实现symlink(target,path)系统调用来创建一个symbolic link
  4. 修改open函数,使之能够处理T_SIMLINK类型的文件;另外还需要判断标志O_NOFOLLOW已决定是否FOLLOW
  5. 如果在标志不为O_NOFOLLOW的情况下,symbolic link链接的文件还是symbolic link文件,则应该递归寻找直到找到不是symbolic link的文件,将其打开。
  6. 其它系统调用不能FOLLOWsymbolic link 文件
  7. 在本次实验中不用处理指向目录的symbolic link文件

好了,我们可以开始实现了。首先,补充sys_symlink(),使之能够创建一个symbolic link,如何创建文件可以参考sys_open函数中if(omode & O_CREATE)中的内容。需要注意的是,创建后获得该文件的inode,我们应该释放掉该inode下的全部数据块,调用iunlockput即可,否则,会出现inodes不足的错误。 代码及相关注释如下:

uint64
sys_symlink(void){
  /** 建立软链接文件  */
  struct inode *ip;
  int target_len, path_len;
  char target[MAXPATH], path[MAXPATH];
  if((target_len = argstr(0, target, MAXPATH)) < 0 || 
     (path_len = argstr(1, path, MAXPATH)) < 0 )
    return -1;
  begin_op(ROOTDEV);
  /**
   * Create returns a locked inode, but namei does not
   */
  ip = create(path, T_SYMLINK, 0, 0);
  if(ip == 0){
    /** 这里记得要加  end_op(ROOTDEV),否则会锁死 */
    end_op(ROOTDEV);
    return -1;
  }
  if(target_len > MAXPATH)
    target_len = MAXPATH;
  memset(ip->target, 0, MAXPATH);
  memmove(ip->target, target, target_len);
  /** 
   * iunlock(ip)会报错:没有足够的数据块; 
   * 分配的这个inode可能已经占有了其他数据块,
   * 而我们只需要利用inode中的target字段保存target,其他的信息我们一概不用,
   * 因此,我们直接调用iunlockput(ip)将ip的所有dircet、indirect字段全部清理掉
   * 这样就解决了数据块不够的问题
   */
  iunlockput(ip);
  end_op(ROOTDEV);
  return 0;
}

接下来,我们修改sys_open函数,使之能够支持打开symbolic link文件。通过观察sys_open的内容,我们很容易知道可以通过namei函数获取相应路径对应的inode,于是我们便可通过递归的方式实现symbolic link。代码如下:

uint64
sys_open(void)
{
  char path[MAXPATH];
  int fd, omode;
  struct file *f;
  struct inode *ip;
  int n;
  if((n = argstr(0, path, MAXPATH)) < 0 || argint(1, &omode) < 0)
    return -1;

  begin_op(ROOTDEV);
  
  if(omode & O_CREATE){
    ip = create(path, T_FILE, 0, 0);
    printf("### create %s\n",path);
    if(ip == 0){
      end_op(ROOTDEV);
      return -1;
    }
  } else {
    if((ip = namei(path)) == 0){
      end_op(ROOTDEV);
      return -1;
    }
    /** Open symlink  */
    if(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){
      int counter = 0;
      /** 递归查找  */
      while ((ip = namei(ip->target)) && ip->type == T_SYMLINK)
      {
        counter++;
        if(counter >= 10){
          printf("open(): too many symlink\n");
          end_op(ROOTDEV);
          return -1;
        }
      }
      if(ip == 0){
        end_op(ROOTDEV);
        return -1;
      }
    }
    ilock(ip);
	......
}

最后,make grade测试,成功!
xv6 6.S081 Lab8: fs_第6张图片

你可能感兴趣的:(操作系统,OS-xv6,文件系统,操作系统,xv6,S6.081,C)