2020 MIT6.s081 Lab file system

友情链接:全部实验哟


Lec11: CrashRecovery学习笔记

  1. Block划分情况:

    在这里插入图片描述

    名称索引作用
    BootBlock0存放操作系统启动的代码
    SuperBlock1存放文件系统的元数据信息,例如:构成文件系统的Block数量
    Log Block[2, 31]其大小由SuperBlock进行定义,主要作用是CrashRecovery
    Inode Block[32,44]一个Inode是64字节,一个Inode可以唯一标识一个文件,因此,操作系统中所有文件的Inode信息都保存在这里。
    BitMap Block45标示Data Block是否空闲
    DataBlock[46, xxx)真正保存数据的区域
  2. Inode简介:

    1. 包含Type字段,表明它是文件还是目录;
    2. 包含nlink字段,表明当前有多少文件指向它(包含软连接);
    3. 数据文件所存放的Block,一般分为两种类型,Direct Block Number和Indirect Block Number,Direct类型直接指向数据真实存放的Block,而Indirect类型需要一次跳转,因为Indirect一般指向一个跳转Block,该跳转Block包含256个BlockNumber(1k/4),从跳转的这个Block里才能真正的找到数据存放的位置。

    在这里插入图片描述

  3. 目录和文件的查找路径。

  4. SpinLock和SleepLock的区别?

    1. SpinLock的限制比较多,其加锁时必须关中断;因此,如果一个系统只有一个CPU核,当我们持有SpinLock时,就永远不能从磁盘上收到数据(因为磁盘);持有SpinLock期间,不能调用Sleep函数;
    2. SleepLock可以在持有锁的过程中不关闭中断;因此,可以在I/O操作过程中持有Sleep Lock;
  5. File System Logging: Logging机制主要用来实现Crash Recovery,进而保证文件系统的一致性。

    1. 核心思想:将磁盘分为两部分,一部分为写Log,一部分写文件系统的数据。所有要更新文件系统的操作,都会先在Log中进行记录。(在下图中,Log主要存储在Log Block区域,Log Block的第一个区域为Log Header Block,用于存放此次WAL的一些元数据信息)

      在这里插入图片描述

    2. 所有写文件系统的操作将分为四步:
      1. log writes: 将所有更新文件系统的操作落盘至Log Block。(例如,用户调用echo "hi" > x,则该过程会涉及多个步骤:创建Inode、更新BitMap、写Data Block、更新Inode的size字段等操作,即所有这些更新文件系统的操作必须是原子的,要么全部执行,要么全部不执行,才能保证文件系统最后的状态是一致的。在该步骤中,我们会将这些操作记录下来,并落盘至Log Block中。)
      2. commit log : commit是将上一步中落盘的的操作数(假设为5)记录下来,并写入commit header block中。注意:在向磁盘的block或者sector写数据时,系统能保证要么数据全部写入成功,要么数据全部失败,因此,不会出现部分写入的情况。
      3. install log:该过程是将写入Log的操作真正的执行到文件系统中,且该操作是幂等的,即可以多次执行。
      4. clean log:将已经执行的操作删除,其实就是在Log中记录当前某个文件系统的待执行数为0

    3. 系统在四个步骤中的任何时间点Crash,都能保证文件系统的一致性。

    4. 本人之前一直有一个疑问,为什么在文件系统之前要先在Log中记录一遍,Log占用的不也是磁盘的一部分么?这不是一个死循环么,写Log的过程中也可能会产生系统的Crash,不也会导致Log的不一致么?仔细想想之后,觉得是这样的:(1)我们的目标是保证文件系统的一致性,Log只是我们为实现这个一致性的中转工具,通过Log,我们能保证一系列的操作要么在文件系统中全部执行,要么全部失败。(2)Log的一致性,其实主要体现在log write过程的落盘,如果在这个过程中系统Crash了,我们只能自认倒霉,即相关更新文件系统操作全部失效,就像从没调用过文件系统的接口一样,这是可以接受的。(3)Crash Recovery的过程在重启之后会自动执行,因此,通过Log这个中转工具,可以保证文件系统在正常使用之前肯定是被修复好的。

  6. 函数Log_write,该函数是在内存的Logging相关数据结构中记录对block cache的更改,方便之后将对block的更改记录到Log中,并进行commit、install、clean等一些列操作。

    1. 每个对文件系统的操作,都包含在begin_op()end_op()之间,这两个函数类似于数据库中的事务。只有在end_op调用之后,才会进行将数据写入Log,并进行commit、install、clean等操作。而在两个函数之间,只会进行磁盘数据或者内存数据对应的数据结构(在内存里)的更新,也就是block cache的更新。
    2. 因为写数据的过程中,我们在Log和文件系统上执行共两次,所以相应操作的效率会下降一半。

实验链接

https://pdos.csail.mit.edu/6.S081/2020/labs/fs.html

实验

Large Files

在这里插入图片描述

没改代码之前,XV6只支持最大写入大小为258(256 + 12)*1K的文件大小,本实现的目的是更改代码使得文件系统支持更大的文件大小。如上图所示,该实验要求文件系统支持最大(11 + 256 + 256乘以256)乘1K大小的文件。这里将Inode指向文件数据Block的索引分为三类。

  1. Direct Block:直接索引,可指向11个Block;
  2. Singly Indirect Block:只跳转一次的间接索引,可指向256个Block;
  3. Doubly Indirect Block: 跳转两次的间接索引,可指向256乘以256个Block。

具体实现如下:

  1. 文件kernel/fs.h

    xxxx
    
    #define NDIRECT 11 // 改为11个,因为要留出来一个给跳转两次的索引
    #define NINDIRECT (BSIZE / sizeof(uint))
    #define NDOUBLE ((NINDIRECT) * (NINDIRECT)) // 两次跳转索引可增加的Block引用数量
    #define MAXFILE (NDIRECT + (NINDIRECT) + (NDOUBLE))
    
    // 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)
      short nlink;          // Number of links to inode in file system
      uint size;            // Size of file (bytes)
      // 这里改为加2,一个给singly indirect索引,一个给doubly indirect索引  
      uint addrs[NDIRECT+2];   // Data block addresses
    };
    
    xxx
    
  2. 文件kernel/file.h

    xxxxx
    
    // in-memory copy of an inode
    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;
      // 这里改为加2,一个给singly indirect索引,一个给doubly indirect索引  
      uint addrs[NDIRECT+2];
    };
    
    xxxxx
    
  3. 文件kernel/fs.c,按照提示,主要修改bmap函数和itrunc函数。

    // Inode content
    //
    // The content (data) associated with each inode is stored
    // in blocks on the disk. The first NDIRECT block numbers
    // are listed in ip->addrs[].  The next NINDIRECT blocks are
    // listed in block ip->addrs[NDIRECT].
    
    // Return the disk block address of the nth block in inode ip.
    // If there is no such block, bmap allocates one.
    static uint
    bmap(struct inode *ip, uint bn)
    {
      uint addr, *a, *a1;
      struct buf *bp, *bp1;
    
      if(bn < NDIRECT){
        if((addr = ip->addrs[bn]) == 0)
          ip->addrs[bn] = addr = balloc(ip->dev);
        return addr;
      }
      bn -= NDIRECT;
    
      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 < NDOUBLE) {
        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 index = bn / NINDIRECT;
        int remain = bn % NINDIRECT;
        // 一级跳转  
        if ((addr = a[index]) == 0) {
          a[index] = addr = balloc(ip->dev);
          log_write(bp);
        }
        brelse(bp);
          
        // 二级跳转  
        bp1 = bread(ip->dev, addr);
        a1 = (uint*)bp1->data;
        if ((addr = a1[remain]) == 0) {
          a1[remain] = addr = balloc(ip->dev);
          log_write(bp1);
        }
    
        brelse(bp1);
        return addr;
      }
    
      panic("bmap: out of range");
    }
    
    // Truncate inode (discard contents).
    // Caller must hold ip->lock.
    void
    itrunc(struct inode *ip)
    {
      int i, j, k;
      struct buf *bp, *bp1;
      uint *a, *a1;
    
      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]) {
            bp1 = bread(ip->dev, a[j]);
            a1 = (uint*)bp1->data;
            // 二级跳转    
            for (k = 0; k < NINDIRECT; k++) {
              if (a1[k]) {
                bfree(ip->dev, a1[k]);
              }
            }
            brelse(bp1);
            bfree(ip->dev, a[j]);
          }
        }
    
        brelse(bp);
        bfree(ip->dev, ip->addrs[NDIRECT + 1]);
        ip->addrs[NDIRECT + 1] = 0;
      }
    
      ip->size = 0;
      iupdate(ip);
    }
    
  4. 执行结果1

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  5. 执行结果2

    在这里插入图片描述


Lab: Symbolic links

实验目标是添加关于软连接的系统调用,软连接与硬连接的区别如下:

软连接硬连接
也叫符号连接,是指该连接只保存原文件的位置信息。如果原文件被删除了,则该软连接就失效了。一个文件可以有多个名字,但是该文件只有一个inode编号,即文件名与inode编号是多对一的关系。如果一个文件有多个硬连接,则只有所有硬连接都删除了,该文件才被删除,否则,删除任意一个硬连接,该文件还是真实存在的。
可以创建不存在文件或者目录的软连接不能创建不存在文件的硬连接
可以在不同的文件系统中创建软连接只能在相同的文件系统中创建硬连接,即硬连接不能跨操作系统
可以创建文件或者目录的软连接只能创建文件的硬连接,不能创建目录的硬连接

除此之外,该实验还要求在open文件时,如果是软连接,且用户要求循环查找,则需要循环查找该连接的原文件,直到查找路径深度超过10。

代码如下:

  1. 文件Makefile,可执行文件生成symlinktest

    UPROGS=\
    	$U/_cat\
    	$U/_echo\
    	$U/_forktest\
    	$U/_grep\
    	$U/_init\
    	$U/_kill\
    	$U/_ln\
    	$U/_ls\
    	$U/_mkdir\
    	$U/_rm\
    	$U/_sh\
    	$U/_stressfs\
    	$U/_usertests\
    	$U/_grind\
    	$U/_wc\
    	$U/_zombie\
    	$U/_symlinktest\ // --add 
    
    
  2. 文件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 // ++++++
    
    
  3. 文件kernel/stat.h,增加类型软连接。

    #define T_DIR     1   // Directory
    #define T_FILE    2   // File
    #define T_DEVICE  3   // Device
    #define T_SYMLINK 4   // Symbol link ++++
    
    struct stat {
      int dev;     // File system's disk device
      uint ino;    // Inode number
      short type;  // Type of file
      short nlink; // Number of links to file
      uint64 size; // Size of file in bytes
    };
    
    
  4. 文件kernel/syscall.h,增加系统调用编号

    #define SYS_symlink 22

  5. 文件user/user.h,增加用户态的调用接口

    int symlink(const char*, const char*);
    
  6. 文件user/usys.pl,增加perl语言自动生成asm的entry

    entry("symlink");
    
  7. 文件kernel/syscall.c,更改如下:

    xxxx
        
    extern uint64 sys_symlink(void); //+++++++++
    
    static uint64 (*syscalls[])(void) = {
    [SYS_fork]    sys_fork,
    [SYS_exit]    sys_exit,
    [SYS_wait]    sys_wait,
    [SYS_pipe]    sys_pipe,
    [SYS_read]    sys_read,
    [SYS_kill]    sys_kill,
    [SYS_exec]    sys_exec,
    [SYS_fstat]   sys_fstat,
    [SYS_chdir]   sys_chdir,
    [SYS_dup]     sys_dup,
    [SYS_getpid]  sys_getpid,
    [SYS_sbrk]    sys_sbrk,
    [SYS_sleep]   sys_sleep,
    [SYS_uptime]  sys_uptime,
    [SYS_open]    sys_open,
    [SYS_write]   sys_write,
    [SYS_mknod]   sys_mknod,
    [SYS_unlink]  sys_unlink,
    [SYS_link]    sys_link,
    [SYS_mkdir]   sys_mkdir,
    [SYS_close]   sys_close,
    [SYS_symlink]   sys_symlink, //+++++++++
    };
    
    xxxx
    
  8. 文件kernel/sysfile.c中,增加函数sys_symlink,并修改函数sys_open

    xxxxxx
    
    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();
      // create 函数会对inode加锁
      ip = create(path, T_SYMLINK, 0, 0);
      if (ip == 0) {
        end_op();
        return -1;
      }
    
      // 将目标路径写入inode的data block
      if (writei(ip, 0, (uint64)target, 0, MAXPATH) != MAXPATH) {
        return -1;
      }
      // iput类似于对Inode的ref减一
      iunlockput(ip);
      end_op();
      return 0;
    }
    
    xxxxxx
    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();
    
      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;
        }
      }
    
      if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
        iunlockput(ip);
        end_op();
        return -1;
      }
    
      //   
      // ++++++++begin++++++++++++++++
      if (ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)) {
        int cnt = 0;
        while (ip->type == T_SYMLINK) {
          if (readi(ip, 0, (uint64)&path, 0, MAXPATH) == -1) {
            iunlockput(ip);
            end_op();
            return -1;
          }
          iunlockput(ip);
          if ((ip = namei(path)) == 0) {
            end_op();
            return -1;
          }
          cnt++;
          if (10 == cnt) {
            end_op();
            return -1; // 超过最大的深度
          }
          ilock(ip); //namei不会对inode加锁,所以这里需要手动加锁
        }
      }
      // -----------end-----------  
    
      if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
        if(f)
          fileclose(f);
        iunlockput(ip);
        end_op();
        return -1;
      }
    
      if(ip->type == T_DEVICE){
        f->type = FD_DEVICE;
        f->major = ip->major;
      } else {
        f->type = FD_INODE;
        f->off = 0;
      }
      f->ip = ip;
      f->readable = !(omode & O_WRONLY);
      f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
    
      if((omode & O_TRUNC) && ip->type == T_FILE){
        itrunc(ip);
      }
    
      iunlock(ip);
      end_op();
    
      return fd;
    }    
    
    
  9. 执行结果1

    在这里插入图片描述

  10. 执行结果2

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述


结果

$ make grade

在这里插入图片描述


提交结果

$ git commit -m "lab fs"
$ make handin

查看结果

登录网站https://6828.scripts.mit.edu/2020/handin.py/student,可以看到提交的结果。

在这里插入图片描述


参考链接

https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081

https://pdos.csail.mit.edu/6.S081/2020/schedule.html


Github

源码: https://github.com/aerfalwl/mit-xv6-labs-2020

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值