XV6 Lab8:File System

Lab8:File System

本 lab 的任务是使 xv6 文件系统支持大文件和符号链接。

详细要求及提示见链接
https://pdos.csail.mit.edu/6.1810/2021/labs/fs.html

参考文章:
主要参考:xv6-lab9-fs
xv6 6.S081 Lab8: fs

阅读指路:
xv6book:Chapter 8 File system

Large files (moderate)

当前xv6文件被限制为最大268blocks,因为xv6 inode有12个直接映射的块号以及1个"singly-indirect"块号,内部再指向256个块,一共12+256=268块

以下是xv6book chapter8中对于inode数据结构的示意:

xv6book示意

目标:

增加xv6系统中文件的最大长度,创建一个大小为65803 blocks的文件(发现正好是11+256+256*256,即减少一个直接映射块,修改为二级映射即可)
Support a “doubly-indirect” block in each inode

方法:

kernel/fs.h中的 struct dinode 定义了磁盘上inode的数据结构。

kernel/fs.c 中的 bmap() 函数:在磁盘上找到文件数据,在读和写文件时都会调用,写入文件时,分配足够的块来装入文件内容(包括分配 indirect block)

bmap() 处理两种块号(block numbers),一种是 “逻辑块号”(和文件起始块的相对位置),另一种是 真实的磁盘块号(ip->addrs[],以及bread()的参数都是真实的磁盘块号)
bmap() 函数实现从文件的 逻辑块号 到 磁盘块号 的映射。

Hints:

  • 理解 bmap() 函数是关键√
  • 理解为什么加入"doubly-indirect" 块可以增加256*256(-1)个块的文件大小√
  • 想清楚如何对二级映射块做 index索引,找到逻辑块号√
  • 修改NDIRECT的大小,就需要修改file.h 中的struct inode的变量addrs[],保证它们有相同的元素个数√
  • 修改NDIRECT后,需要重新创建fs.img,因为mkfs需要用NDIRECT来构建文件系统√
  • 对于每个调用bread()的块,都需要调用brelse()(这一点结合Lab7更好理解)√
  • 对于一级和二级映射的块,和原始一样,采用需要多少给多少的策略√
  • itrunc() 函数需要释放所有的块(包括一级和二级映射的块)√

主要工作:

fs.h 修改:

#define NDIRECT 11	// 【DIRECT直接映射的块号,由12变为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];   // Data block addresses
  // NDIRECT+1 变为 【NDIRECT+2】
};

file.h 中同样需要修改addrs[],保证大小一致


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

fs.cbmap() 函数:

// 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;
  struct buf *bp;

  // direct blocks(0-10)
  if(bn < NDIRECT){
    if((addr = ip->addrs[bn]) == 0)	// 对应地址为0,则为未分配磁盘块
      ip->addrs[bn] = addr = balloc(ip->dev); // 分配
    return addr;
  }
  
/*
 *当NDIRECT <= bn < NINDIRECT
 *先从对应的磁盘块读取目录
 *检查一级目录是否存在(若不存在,分配磁盘块)
 *在一级目录中寻找对应块
 *存在返回,不存在分配并用log_wirte()写
 *调用了bread(),需要调用brelse()
 */
// log_write() replaces bwrite(); a typical use is:
//   bp = bread(...)
//   modify bp->data[]
//   log_write(bp)
//   brelse(bp)

  // singly-indirect blocks(11~11+255=266)
  bn -= NDIRECT;  //(0~255)
  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);	// 调用了bread就要记得brelse
    return addr;
  }

/* My Code: */
// (模仿以上结构)
  // doubly-indirect blocks(267~266+256*256=65802)
  bn -= NINDIRECT;  //(0~256*256-1)

  if(bn < NINDIRECT * NINDIRECT){

    uint index_1 = bn / NINDIRECT;	// 第几个一级映射表
    uint index_2 = bn % 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[index_1]) == 0){
      a[index_1] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);

    bp = bread(ip->dev, addr);	// 继续读取第二级映射目录
    a = (uint*)bp->data;
    if((addr = a[index_2]) == 0){
      a[index_2] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);

    return addr;
  }

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

后面紧接着的 itrunc() 函数:释放inode下的所有数据块

// Truncate inode (discard contents).
// Caller must hold ip->lock.
void
itrunc(struct inode *ip)
{
  int i, j, k;
  struct buf *bp, *bp_doub;
  uint *a, *b;

  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;
  }
  
/*My code start*/
  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]){
        bp_doub = bread(ip->dev, a[j]);
        b = (uint*)bp_doub->data;
        for (k = 0; k<NINDIRECT;k++){
          if(b[k])
            bfree(ip->dev, b[k]);
        }
        brelse(bp_doub);
        bfree(ip->dev, a[j]);
      }
    }
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT+1]);
    ip->addrs[NDIRECT+1] = 0;
  }
/*My code end*/
  ip->size = 0;
  iupdate(ip);
}

Symbolic links (moderate)

符号链接(软链接)是通过路径名链接的文件;当打开一个符号链接时,内核会根据该链接指向所引用的文件。符号链接类似于硬链接,但硬链接仅限于指向同一磁盘上的文件,而符号链接可以跨磁盘设备。实现这个系统调用,可以更好地理解路径名查找的工作原理。

目标:

实现系统调用 symlink() ,即软链接功能。

Hints:

  • 首先是创建一个新的系统调用的准备工作

  • kernel/stat.h 中添加一个新的文件类型 T_SYMLINK

  • kernel/fcntl.h 中添加一个新标志 O_NOFOLLOW,该标志可用于系统调用open(请注意,传递给open的标志使用位运算符OR,因此新标志不应与任何现有标志重叠)
    表示将symbolic link文件视为普通文件打开

  • 在symlink中【实现指向目标文件的软链接】需要选择一个地方储存你的目标路径,比如在 inode 的数据块里

  • 修改系统调用open,增加处理软链接的情况(如果该文件不存在,open返回一个fail;如果标志位O_NOFOLLOW为true,open应该打开这个软链接,而不是FOLLOW这个链接)

  • 如果标志位O_NOFOLLOW为false,系统调用open应该递归,找到不是软链接的文件将其打开,需要设置一个递归深度上限防止链接的循环

  • 其他系统调用不允许FOLLOW软链接

准备工作

增加系统调用步骤:
user/usys.pl:

entry("symlink");

user/user.h:

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

kernel/syscall.h:

/** Symbol link  */
#define SYS_symlink 22

kernel/syscall.c:

[SYS_symlink] sys_symlink

加入文件类型:
kernel/stat.h:

#define T_SYMLINK 4

加入标志位信息:
kernel/fcntl.h:

#define O_NOFOLLOW 0x800

主要工作:

kernel/sysfile.c:

symlink需要实现的主要功能:

  • 取回两个地址:目标地址和虚拟地址
  • 将目标地址储存在某个地方,你必须在open 时能够根据虚拟地址找到目标地址

那么我们直接在根据虚拟地址创建一个 struct inode 然后把目标地址储存在 inode 的磁盘块里
这里主要关注两个操作inode 的函数: create 和 writei


/* create a new symbolic link at path that refers to target */
uint64
sys_symlink(void){
  char target[MAXPATH];
  char path[MAXPATH];
// 读入target和path
  if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
    return -1;

  begin_op(); // called at the start of each FS system call.
  
  struct inode *ip; // 【create an inode】
  if((ip = create(path, T_SYMLINK, 0, 0)) == 0)	// 根据虚拟地址创建inode
  {
    end_op(); // called at the end of each FS system call.
    return -1; // failed
  }

  // [Write data to inode->data]
// Caller must hold ip->lock.
// If user_src==1, then src is a user virtual address;
// otherwise, src is a kernel address.
// Returns the number of bytes successfully written.
// If the return value is less than the requested n,
// there was an error of some kind.
// int
// writei(struct inode *ip, int user_src, uint64 src, uint off, uint n)
  if(writei(ip, 0, (uint64)target, 0, MAXPATH) < MAXPATH)	//将目标地址存在inode的磁盘块里
  {
    iunlockput(ip);
    end_op(); // called at the end of each FS system call.
    return -1;
  }
  /*
 *
 *【create默认上锁返回,记得解锁】
 *
 */
  iunlockput(ip);
  end_op(); // called at the end of each FS system call.
  return 0;

}

sys_open():
在具体操作文件前先通过软链接递归找到真正的地址
【读到的 path 转换为 ip 模仿 sys_open() 开始的操作即可】


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

/*mycode start*/
// 文件类型是SYMLINK,并且O_NOFOLLOW是false,应该递归寻找到不是软链接的文件(否则按照正常文件打开)
    if(!(omode & O_NOFOLLOW) && ip->type == T_SYMLINK) {
      char path[MAXPATH];
// [Read data from inode]
// Caller must hold ip->lock.
// If user_dst==1, then dst is a user virtual address;
// otherwise, dst is a kernel address.
// int
// readi(struct inode *ip, int user_dst, uint64 dst, uint off, uint n)
      for(int i = 0; i < 12; i++) {	 // 【设定12是最大递归次数】
      
        if(readi(ip, 0, (uint64)path, 0, MAXPATH) < MAXPATH){	// #define MAXPATH 128
            iunlockput(ip);
            end_op();
            return -1;
        }

        iunlockput(ip);
        if((ip = namei(path)) == 0){
          end_op();
          return -1;
        }
        ilock(ip);

        if(ip->type != T_SYMLINK)	// 找到不是软链接类型的文件inode,继续正常打开
          break;
      }
      if(ip->type == T_SYMLINK){	// 如果超出阈值,返回fail
        iunlockput(ip);
        end_op();
        return -1;
      }
    }
/*mycode end*/
//...
  iunlock(ip);
  end_op();

  return fd;
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值