Mit6.S081笔记Lab9: file system 文件系统

课程地址:https://pdos.csail.mit.edu/6.S081/2020/schedule.html
Lab 地址:https://pdos.csail.mit.edu/6.S081/2020/labs/fs.html
我的代码地址:https://github.com/Amroning/MIT6.S081/tree/fs
xv6手册:https://pdos.csail.mit.edu/6.S081/2020/xv6/book-riscv-rev1.pdf
相关翻译:https://xv6.dgs.zone/labs/requirements/lab9.html
参考博客:https://blog.miigon.net/posts/s081-lab9-file-system/

学习笔记记录,如有错误恳请各位大佬指正

Lab9: file system

​ 增加XV6系统文件的最大大小(当前只支持268kb以下的文件);实现软链接

完整题目要求请去顶部链接查看

Large files(moderate)

在本作业中,您将增加xv6文件的最大大小。目前,xv6文件限制为268个块或268*BSIZE字节(在xv6中BSIZE为1024)。此限制来自以下事实:一个xv6 inode包含12个“直接”块号和一个“间接”块号,“一级间接”块指一个最多可容纳256个块号的块,总共12+256=268个块。

您将更改xv6文件系统代码,以支持每个inode中可包含256个一级间接块地址的“二级间接”块,每个一级间接块最多可以包含256个数据块地址。结果将是一个文件将能够包含多达65803个块,或256*256+256+11个块(11而不是12,因为我们将为二级间接块牺牲一个直接块号)

​ 原本的XV6文件系统中,inode的结构如下:

// kernel/fs.h
#define NDIRECT 12
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT)

// 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)
  uint addrs[NDIRECT+1];   // Data block addresses
};

addrs字段用来索引记录数据的所在盘块号。每个文件所占用的前 12 个盘块的盘块号是直接记录在 inode 中的(每个盘块 1024 字节),所以对于任何文件的前 12 KB 数据,都可以通过访问 inode 直接得到盘块号。这一部分称为直接记录盘块(addr[0~11],12个直接块)

​ 对于大于 12 个盘块的文件,大于 12 个盘块的部分,会分配一个额外的一级索引表(addr[11],1024Byte),用于存储这部分数据的所在盘块号。一个索引项是4字节,一级索引表可以包含 BSIZE(1024) / 4 = 256 个盘块号,加上 inode 中的 12 个盘块号,一个文件最多可以使用 12+256 = 268 个盘块,也就是 268KB

​ 该实验需要修改inode的结构,修改成11个直接块,1个一级间接块,1个二级间接块。所以可以先修改inode的结构体,注意这是磁盘inode的结构体:

// kernel/fs.h
#define NDIRECT 11                                               // 直接块数量
#define NINDIRECT (BSIZE / sizeof(uint))                         // 一级间接块数量
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT * NINDIRECT)    // 二级间接块数量

// 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)
  uint addrs[NDIRECT+2];   // 0~10:直接索引    11:一级间接索引    12:二级间接索引
};

​ 然后还需要修改内存中inode的副本结构体:

// kernel/fs.h
// 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;
  uint addrs[NDIRECT + 2];
};

​ 直接块腾一个位置出来给二级间接块

​ 需要修改的第一个函数是bmap,这个函数是将inode中的第bn个逻辑块号和物理块号简历映射,其原理备注在源代码中(这里是解释原本的bmap):

// kernel/fs.c
static uint
bmap(struct inode *ip, uint bn)
{
  uint addr, *a;
  struct buf *bp;

  // 如果bn小于直接块数量,则按直接映射处理
  if(bn < NDIRECT){
    if((addr = ip->addrs[bn]) == 0)				// 如果对应的物理块号为0,表示还没分配,则分配一个新的物理块,建立映射
      ip->addrs[bn] = addr = balloc(ip->dev);
    return addr;
  }
    
  // 到这里已经是间接块了,减去直接块的数量得到间接块的逻辑块号
  bn -= NDIRECT;

  if(bn < NINDIRECT){
    // 如果间接块还没分配,分配一个
    if((addr = ip->addrs[NDIRECT]) == 0)			// 0~NDIRECT-1是直接块,NDIRECT是间接块
      ip->addrs[NDIRECT] = addr = balloc(ip->dev);	// 此时这个块可以映射256个块
      
    // 获取刚分配的缓存块,检查bn对应的块,如果为0则没有分配,建立映射
    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);			// 建立映射结束,释放掉缓存块(可以通过ip->dev和bn找到这个块了)
    return addr;
  }

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

​ 清楚了原理,加上刚才增加的二级映射,就很好修改了:

// kernel/fs.c
static uint
bmap(struct inode *ip, uint bn)
{
  uint addr, *a;
  struct buf *bp;

  // 如果bn小于直接块数量,则按直接映射处理
  if(bn < NDIRECT){
    if((addr = ip->addrs[bn]) == 0)				// 如果对应的物理块号为0,表示还没分配,则分配一个新的物理块,建立映射
      ip->addrs[bn] = addr = balloc(ip->dev);
    return addr;
  }
    
  // 到这里已经是一级间接块了,减去直接块的数量得到间接块的逻辑块号
  bn -= NDIRECT;

  if(bn < NINDIRECT){
    // 如果间接块还没分配,分配一个
    if((addr = ip->addrs[NDIRECT]) == 0)			// 0~NDIRECT-1是直接块,NDIRECT是间接块
      ip->addrs[NDIRECT] = addr = balloc(ip->dev);	// 此时这个块可以映射256个块
      
    // 获取刚分配的缓存块,检查bn对应的块,如果为0则没有分配,建立映射
    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);			// 建立映射结束,释放掉缓存块(可以通过ip->dev和bn找到这个块了)
    return addr;
  }

  // 到这里已经是二级间接块了,减去一级间接块的数量得到二级件结块的逻辑块号
  bn -= NINDIRECT;

  // 原理很上面的一样
  if (bn < NINDIRECT * NINDIRECT) {
      if ((addr = ip->addrs[NDIRECT + 1]) == 0)
          ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev);

      bp = bread(ip->dev, addr);
      a = (uint*)bp->data;
      if ((addr = a[bn / NINDIRECT]) == 0) {        // bn处在二级索引中间级的第 bn/NINDIRECT 个索引处
          a[bn / NINDIRECT] = addr = balloc(ip->dev);
          log_write(bp);
      }
      brelse(bp);

      // 最后一级索引
      bn %= NINDIRECT;
      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;
  }

​ 还有一个函数itrunc,释放该inode所映射的所有数据块。原理类似,直接循环释放所有数据块:

// kernel/fs.c
void
itrunc(struct inode *ip)
{
  int i, j;
  struct buf *bp;
  uint *a;

  // 释放直接块的映射
  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 (int i = 0;i < NINDIRECT;++i) {
          if (a[i]) {
              struct buf* bp2 = bread(ip->dev, a[i]);
              uint* a2 = (uint*)bp2->data;
              for (int j = 0;j < NINDIRECT;++j) {
                  if (a2[j])
                      bfree(ip->dev, a2[j]);
              }
              brelse(bp2);
              bfree(ip->dev, a[i]);
          }
      }
      brelse(bp);
      bfree(ip->dev, ip->addrs[NDIRECT + 1]);
      ip->addrs[NDIRECT + 1] = 0;
  }

  ip->size = 0;
  iupdate(ip);
}

此时可以验证实验是否通过

Symbolic links(moderate)

您将实现symlink(char *target, char *path)系统调用,该调用在引用由target命名的文件的路径处创建一个新的符号链接。有关更多信息,请参阅symlink手册页(注:执行man symlink)。要进行测试,请将symlinktest添加到*Makefile*并运行它。当测试产生以下输出(包括usertests运行成功)时,您就完成本作业了。

​ 该实验需要实现软链接机制,实现一个软链接系统调用函数symlink

​ 我习惯先把新的系统调用函数声明给做好,详细添加步骤参考Lab2笔记,涉及文件:syscall.hsyscall.cusys.pluser.h

​ 根据提示,添加新的文件类型T_SYMLINK,表示文件为软链接文件:

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

​ 添加新的文件标志位O_NOFOLLOW,该标志可用于open系统调用:

// 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 

​ 实现具体的sys_symlink,使用create创建一个加了锁的指向源文件的inode,再将链接目标文件地址通过writei写入inode,操作完之后使用iunlockput解锁创建的inode并使其引用计数-1,表示创建软链接的操作结束,释放锁。

create 在文件系统中创建一个新的文件,设置其类型是 T_SYMLINK,使用一个新的inode关联这个文件, inode 的初始状态为空,没有任何数据。create 还在目录中为这个新文件创建了一个目录项,使得路径名 path 可以找到这个 inode。使用 writei 函数将符号链接的目标路径(target)作为数据,写入到新创建的 inode 的数据块中,文件的内容就变成了目标路径 target

// kernel/sysfile.c
// 软链接
uint64
sys_symlink(void) {
    struct inode* ip;
    char target[MAXPATH], path[MAXPATH];
    if (argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
        return -1;

    begin_op();

    ip = create(path, T_SYMLINK, 0, 0);     // 创建一个新的inode,类型为T_SYMLINK,指向path文件
    if (ip == 0) {
        end_op();
        return -1;
    }

    if (writei(ip, 0, (uint64)target, 0, strlen(target)) < 0) {     // 将target路径写入inode
        end_op();
        return -1;
    }

    iunlockput(ip);
    end_op();

    return 0;
}

​ 修改sys_open函数,如果通过循环调用namei 获取对应的inode,如果指向的仍是软链接,就继续循环读取,直到找到真正指向的文件,或者超过了一定的链接深度:

// kernel/sysfile.c
uint64
sys_open(void)
{
  ......

  if(omode & O_CREATE){
    ip = create(path, T_FILE, 0, 0);
    if(ip == 0){
      end_op();
      return -1;
    }
  }
  else {
      int symlink_depth = 0;
      while (1) {
          if ((ip = namei(path)) == 0) {    // 解析路径,获取对应的inode
              end_op();
              return -1;
          }

          ilock(ip);
          if (ip->type == T_SYMLINK && (omode & O_NOFOLLOW) == 0) {     //如果当前指向的仍是软链接,则继续循环
              if (++symlink_depth > 10) {               // 链接深度超过10层就退出
                  iunlockput(ip);
                  end_op();
                  return -1;
              }
              if (readi(ip, 0, (uint64)path, 0, MAXPATH) < 0) {     // 读取链接的目标路径
                  iunlockput(ip);
                  end_op();
                  return -1;
              }
              iunlockput(ip);
          }
          else
              break;
      }

      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;
  }

  ......
}

​ 此时可以验证实验是否通过

posted @   Amroning  阅读(225)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示