经历了前三个实验的洗礼,我们终于迎来了 XV6 操作系统大作业的最终 Boss 关卡:文件系统 (File System)

原版的 XV6 文件系统极其简陋:一个文件最大只能存 268KB,且不支持我们平时常用的“快捷方式”(软链接)。本次实验的核心目的,就是通过修改底层代码,让系统支持高达 65MB 的大文件 (Bigfile),并实现软链接 (Symlink) 功能。

这篇压轴博客将带你跨越所有编译陷阱,用最精准的刀法直接“爆破”满分!

🛠️ 一、 踩坑预警(血泪教训)

在动手修改文件系统底层代码时,极其容易触发 XV6 的系统级崩溃 (Panic)。以下是两个必踩的坑:

踩坑点 1:极其严格的编译器安全检查 (unused variable)

XV6 默认开启了 -Werror 编译选项,会把所有的警告(Warning)当作错误(Error)处理。 在修改 bmap 函数时,原作者已经在函数顶部声明了 struct buf *bp;uint *a;。如果你在粘贴双级指针代码时,手滑写成了 struct buf *bp = bread(...) 重新定义了一遍,编译器会认为原有的变量被“闲置”了,直接拒绝编译! 避坑指南: 仔细检查代码,去掉重复的类型声明,直接复用已有的 bpa

踩坑点 2:原地爆炸的 panic: bmap: out of range

做大文件实验时,很多同学修改完结构体后,一跑 bigfile 命令,系统立刻抛出这个报错并死机。 原因: 这是因为 bmap 函数里原有处理单级间接块的边界判断没改对,或者没有把双级间接逻辑放在 panic 的正上方。一旦请求的块号超出了旧版的限制,系统还没来得及跑你的新代码,就直接触发了自毁程序。

💽 二、 任务一:大文件扩容 (Bigfile)

目标: 修改系统的 inode 结构,将原本的 12 个直接块 + 1 个单级间接块,修改为:11 个直接块 + 1 个单级间接块 + 1 个双级间接块。这样单文件最大块数就能从 268 跃升至 65803 块。

第 1 步:修改核心结构体(腾出空间)

我们要牺牲一个直接块的空间,留给双重间接块。

  1. 打开 kernel/fs.h

    • #define NDIRECT 12 改为 #define NDIRECT 11

    • 增加或修改 MAXFILE#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT * NINDIRECT)

    • struct dinode 中,将 uint addrs[NDIRECT+1]; 改为 uint addrs[NDIRECT+2];

  2. 打开 kernel/file.h

    • struct inode 中,同样将 uint addrs[NDIRECT+1]; 改为 uint addrs[NDIRECT+2];

第 2 步:修改磁盘映射算法 (bmap)

打开 kernel/fs.c,搜索 bmap 函数。在处理完单级间接块(也就是 bn -= NINDIRECT; 之后),把双重映射逻辑插入到 panic正上方

  // ... 前面是处理单级间接块的代码 ...
  bn -= NINDIRECT;

  // 👇 处理双重间接块逻辑
  if(bn < NINDIRECT * NINDIRECT){
    // 1. 加载第一层间接块
    if((addr = ip->addrs[NDIRECT+1]) == 0){
      ip->addrs[NDIRECT+1] = addr = balloc(ip->dev);
      if(addr == 0) return 0;
    }
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    
    // 2. 加载第二层间接块
    if((addr = a[bn/NINDIRECT]) == 0){
      a[bn/NINDIRECT] = addr = balloc(ip->dev);
      if(addr == 0){ brelse(bp); return 0; }
      log_write(bp);
    }
    brelse(bp);
    
    // 3. 定位到最终的物理块
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    if((addr = a[bn%NINDIRECT]) == 0){
      a[bn%NINDIRECT] = addr = balloc(ip->dev);
      if(addr == 0){ brelse(bp); return 0; }
      log_write(bp);
    }
    brelse(bp);
    return addr;
  }

  // 只有当大过双重间接块的极限时,才允许抛出错误!
  panic("bmap: out of range");

第 3 步:修改磁盘释放算法 (itrunc)

既然能存,就得能删。依然在 kernel/fs.c 里搜 itrunc。在释放单级间接块的代码下方,追加释放双级间接块的代码:

  // ... 单级间接块释放完毕 ...
  // 👇 双级间接块释放逻辑
  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]){
        struct buf *bp2 = bread(ip->dev, a[j]);
        uint *a2 = (uint*)bp2->data;
        for(int k = 0; k < NINDIRECT; k++){
          if(a2[k]) bfree(ip->dev, a2[k]);
        }
        brelse(bp2);
        bfree(ip->dev, a[j]);
      }
    }
    brelse(bp);
    bfree(ip->dev, ip->addrs[NDIRECT+1]);
    ip->addrs[NDIRECT+1] = 0;
  }

🔗 三、 任务二:实现软链接 (Symlink)

目标: 创建一种新的文件类型 T_SYMLINK,里面不存数据,只存目标文件的路径。当 open 打开它时,系统能自动“顺藤摸瓜”找到真实文件。

第 1 步:注册全新的系统调用

这是一个纯体力活,需要在多个文件里开绿灯:

  • MakefileUPROGS 里加上 $U/_symlinktest\

  • kernel/stat.h:加上 #define T_SYMLINK 4

  • kernel/fcntl.h:加上 #define O_NOFOLLOW 0x800

  • user/usys.pl:加上 entry("symlink");

  • user/user.h:加上 int symlink(const char*, const char*);

  • kernel/syscall.h:加上 #define SYS_symlink 22

  • kernel/syscall.c:声明 extern uint64 sys_symlink(void); 并在数组加入 [SYS_symlink] sys_symlink,

第 2 步:实现软链接核心逻辑

打开 kernel/sysfile.c,在文件最底部添加创建软链接的函数:

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();
  // 创建一个 T_SYMLINK 类型的文件
  if((ip = create(path, T_SYMLINK, 0, 0)) == 0){
    end_op();
    return -1;
  }
  // 把目标路径(target)作为数据写入这个文件
  if(writei(ip, 0, (uint64)target, 0, strlen(target)) != strlen(target)){
    iunlockput(ip);
    end_op();
    return -1;
  }
  iunlockput(ip);
  end_op();
  return 0;
}

第 3 步:让 open 函数学会顺藤摸瓜

kernel/sysfile.c 搜索 sys_open 函数,在 if(ip->type == T_DEVICE && ...)正上方,插入解析套娃的逻辑:

  int depth = 0;
  // 如果遇到软链接,且没有带 O_NOFOLLOW 标志位,就开始解析
  while(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){
    if(depth++ >= 10){ // 防止恶意软链接造成无限循环套娃
      iunlockput(ip);
      end_op();
      return -1;
    }
    // 读出里面存的真实路径
    if(readi(ip, 0, (uint64)path, 0, MAXPATH) < 0){
      iunlockput(ip);
      end_op();
      return -1;
    }
    iunlockput(ip);
    // 顺藤摸瓜,找到下一个 inode
    if((ip = namei(path)) == 0){
      end_op();
      return -1;
    }
    ilock(ip);
  }

🎉 四、 终极验收与满分技巧

全部修改完毕后,直接在宿主机终端输入一键跑分指令:

./grade-lab-fs

等待大约 2-3 分钟(因为测试系统正在虚拟磁盘里疯狂塞满 6 万多个块),你将会看到极其舒爽的满屏绿字:

  • bigfile: OK

  • symlinktest: OK

  • usertests: OK

💯 强迫症满分小贴士: 如果你跑出来的分数是 99/100,并报错 FAIL: Cannot read time.txt,不要慌!这只是评分脚本想要读取你的耗时而已。直接在终端执行一行命令:

echo "5" > time.txt

再次运行打分脚本,你就会获得完美无瑕的 Score: 100/100

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐