mit6s081 lab9 file system - 实践

mit6s081 lab9 file system

一、Large Files

题目描述

目前 xv6 系统文件大小限制为 268 个块,因为 inode 中包含:

  • 12 个直接区块号
  • 1 个单间接区块号(singly indirect)

单间接块号最多容纳 256 个块(1024 / 4 = 256),因此最大块数为:

12 + 256 = 268

现在希望能够创建 65803 个块大小的文件。
为此,需要修改文件系统,使 inode 支持 双间接块(doubly indirect),即三级索引:

11 + 256 + 256*256 = 65803 块

任务:修改 bmap() 以支持双间接块。
ip->addrs[] 中:

  • 前 11 个元素是直接块;
  • 第 12 个是单间接块;
  • 第 13 个是双间接块。

完成后运行 bigfileusertests 验证功能。


实现步骤

1️⃣ 查看文件系统信息
make fs.img | grep "nmeta"

在这里插入图片描述

2️⃣ 定义 inode 索引结构
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define N2INDIRECT (BSIZE / sizeof(uint))*(BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT + N2INDIRECT)

修改 bmap() 实现双间接索引

static uint
bmap(struct inode *ip, uint bn)
{
  uint addr, *a, *a1;
  struct buf *bp;
  struct buf *bp1;
  struct buf *bp2;
  uint *a2;
  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) // ==0代表没有间接块,需要分配
      ip->addrs[NDIRECT] = addr = balloc(ip->dev); //先右后左
    bp = bread(ip->dev, addr); //读取间接块
    a = (uint*)bp->data; // 把bp里面的内容转换为uint*,然后可以用数组形式访问
    if((addr = a[bn]) == 0){ // ==0代表没有数据块,需要分配
      a[bn] = addr = balloc(ip->dev);
      log_write(bp); // 只是把bp写到log中,并没有写到磁盘
    }
    brelse(bp);
    return addr;
  }
  bn -= NINDIRECT;
  if(bn < N2INDIRECT){
    if((addr = ip->addrs[NDIRECT+NINDIRECT]) == 0)
      ip->addrs[NDIRECT+NINDIRECT] = addr = balloc(ip->dev);
    bp1 = bread(ip->dev, addr); //读取间接块
    a1 = (uint*)bp1->data; // 把bp1里面的内容转换为uint*,然后可以用数组形式访问
    if((addr = a1[bn/NINDIRECT]) == 0){
      a1[bn/NINDIRECT] = addr = balloc(ip->dev);
      log_write(bp1);
    }
    brelse(bp1);
    bp2 = bread(ip->dev, addr);
    a2 = (uint*)bp2->data;
    if((addr = a2[bn%NINDIRECT]) == 0){
      a2[bn%NINDIRECT] = addr = balloc(ip->dev);
      log_write(bp2);
    }
    brelse(bp2);
    return addr;
  }
  panic("bmap: out of range");
}

修改 itrunc() 实现清空双重间接块

这个函数相当于把一个文件内容清零

// Truncate inode (discard contents).
// Caller must hold ip->lock.
void
itrunc(struct inode *ip)
{
  int i, j;
  struct buf *bp;
  struct buf *bp1;
  struct buf *bp2;
  uint *a1;
  uint *a2;
  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]){
    bp1 = bread(ip->dev, ip->addrs[NDIRECT+1]);
    a1 = (uint*)bp1->data;
    for(j = 0; j < NINDIRECT; j++){
      bp2 = bread(ip->dev, a1[j]);
      a2 = (uint*)bp2->data;
      for(int k = 0; k < NINDIRECT; k++){
        if(a2[k])
          bfree(ip->dev, a2[k]);
          a2[k] = 0;
      }
      bfree(ip->dev, a1[j]);
      a1[j] = 0;
      brelse(bp2);
    }
    brelse(bp1);
    bfree(ip->dev, ip->addrs[NDIRECT+1]);
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT+NINDIRECT]);
    ip->addrs[NDIRECT+NINDIRECT] = 0;
  }
  ip->size = 0;
  iupdate(ip);
}

修改 inode 结构体定义

kernel/fs.h
// kernel/fs.h
// 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];   // Data block addresses
};
kernel/file.h
// kernel/file.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];
};

✅ 测试结果

运行:

make qemu

如果报错,删除旧的 fs.img 再重新生成。

运行 bigfileusertests 均通过,说明实现成功。

在这里插入图片描述


二、Symbolic Links(符号链接)

题目描述

向 xv6 添加 符号链接(symbolic link) 功能。

当打开符号链接时,内核根据路径名找到被引用的文件。

⚙️ 符号链接与硬链接不同:

  • 硬链接指向同一磁盘文件;
  • 符号链接文件内容是目标路径字符串,可以跨设备。

实现步骤

1️⃣ 定义符号链接类型与标志位

kernel/stat.h

#define T_SYMLINK 4   // Symlink

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

2️⃣ 实现系统调用 symlink(target, path)
uint64
sys_symlink(void)
{
  char path[MAXPATH];
  char target[MAXPATH];
  struct inode *ip;
  // 用户接口:symlink(target, path)
  if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
    return -1;
  begin_op();
  if((ip = create(path, T_SYMLINK, 0, 0)) == 0){ // 分配一个符号链接类型的inode
    end_op();
    return -1;
  }
  // 向符号链接的inode中写入目标路径(按实际长度)
  if(writei(ip, 0, (uint64)target, 0, strlen(target)) != strlen(target)){
    iunlockput(ip); // 写入不成功则释放inode
    end_op();
    return -1;
  }
  iunlockput(ip);// 使用完毕应该解锁并对inode引用数(nlink代表该inode被使用数)-1;
  end_op();
  return 0;
}

3️⃣ 修改 sys_open() 支持符号链接解析

在path处创建一个链接文件,文件内容是target这个字符串本身,而不是将target解析为绝对路径在存储。

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;
    }
  }
  // 符号链接处理:根据 O_NOFOLLOW 决定是否解析,解析时正确加锁/解锁并限制深度
  if(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){
    int max_depth = 10;
    while(ip->type == T_SYMLINK){
      char linkpath[MAXPATH];
      int len = ip->size;
      if(len >= MAXPATH) len = MAXPATH - 1;
      if(len < 0){
        iunlockput(ip);
        end_op();
        return -1;
      }
      if(len > 0){
        if(readi(ip, 0, (uint64)linkpath, 0, len) != len){
          iunlockput(ip);
          end_op();
          return -1;
        }
      }
      linkpath[len] = '\0';
      iunlockput(ip);
      ip = namei(linkpath);
      if(ip == 0){
        end_op();
        return -1; // 目标不存在
      }
      ilock(ip);
      if(--max_depth <= 0){
        iunlockput(ip);
        end_op();
        return -1; // 循环或过深
      }
    }
    // 解析完后若是目录且非只读,禁止
    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;
  }
  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){ // 如果打开文件并指定O_TRUNC,则清空文件内容
    itrunc(ip);
  }
  iunlock(ip);
  end_op();
  return fd;
}

✅ 测试结果

运行:

make symlinktest

结果如下图所示:

在这里插入图片描述


实验总结

实验核心修改点主要函数
Large filesinode 增加双重间接块索引bmap()itrunc()
Symbolic links实现符号链接系统调用及解析sys_symlink()sys_open()

要点回顾:

  • 实验一:通过增加双间接块扩展文件系统可支持文件大小;
make symlinktest

结果如下图所示:

[外链图片转存中…(img-DqbgBAMh-1760455284134)]


实验总结

实验核心修改点主要函数
Large filesinode 增加双重间接块索引bmap()itrunc()
Symbolic links实现符号链接系统调用及解析sys_symlink()sys_open()

要点回顾:

  • 实验一:通过增加双间接块扩展文件系统可支持文件大小;
  • 实验二:实现软链接,使文件系统支持路径引用。
posted @ 2025-11-10 08:49  ycfenxi  阅读(3)  评论(0)    收藏  举报