XV6操作系统实验四(File System)满分通关指南:突破容量瓶颈与软链接实现
XV6操作系统实验终极挑战:文件系统扩容与软链接实现 本实验对XV6操作系统简陋的文件系统进行两大核心改进:1)通过修改inode结构,将文件最大容量从268KB提升至65MB,实现方案是将12个直接块改为11个直接块+1个单级间接块+1个双级间接块;2)新增软链接功能,支持创建T_SYMLINK类型文件并实现路径自动解析。实验过程中需特别注意编译器安全检查陷阱和边界条件处理,修改涉及fs.h、f
经历了前三个实验的洗礼,我们终于迎来了 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(...) 重新定义了一遍,编译器会认为原有的变量被“闲置”了,直接拒绝编译! 避坑指南: 仔细检查代码,去掉重复的类型声明,直接复用已有的 bp 和 a。
踩坑点 2:原地爆炸的 panic: bmap: out of range
做大文件实验时,很多同学修改完结构体后,一跑 bigfile 命令,系统立刻抛出这个报错并死机。 原因: 这是因为 bmap 函数里原有处理单级间接块的边界判断没改对,或者没有把双级间接逻辑放在 panic 的正上方。一旦请求的块号超出了旧版的限制,系统还没来得及跑你的新代码,就直接触发了自毁程序。
💽 二、 任务一:大文件扩容 (Bigfile)
目标: 修改系统的 inode 结构,将原本的 12 个直接块 + 1 个单级间接块,修改为:11 个直接块 + 1 个单级间接块 + 1 个双级间接块。这样单文件最大块数就能从 268 跃升至 65803 块。
第 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];
-
-
打开
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 步:注册全新的系统调用
这是一个纯体力活,需要在多个文件里开绿灯:
-
Makefile:UPROGS里加上$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!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)