news 2026/7/12 23:52:03

xv6 lab9 fs

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
xv6 lab9 fs

0.前置

1.文件系统如何使用磁盘?

扇区(512Byte)是磁盘读写的最小单位。一个磁盘块(Block)等于俩个连续扇区。站在os角度,磁盘可看作是一个巨大的block数组。磁盘结构分布如下:

+----------------+----------------+-------------+-------------+----------------+-------------+
| 引导扇区(block0)| 超级块(block1) | log区 | inode区 | bitmap块 | 数据区 |
+----------------+----------------+-------------+-------------+----------------+-------------+
1.引导扇区存储的代码负责加载内核并启动系统。

2.超级块存储着磁盘的使用情况。

3.日志区负责文件操作原子性和一致性,日志里存的是所有被修改的完整磁盘块副本,不是操作指令;系统挂了也可以通过磁盘上的日志区可以恢复磁盘状态。

4.inode区用来系统中有多少文件和目录,文件的元信息。本节重点。

5.bitmap块标记磁盘哪些块被使用。

6.数据区存储文件的数据或者目录文件的内容(目录项(dirent)数组)。

2.xv6文件系统如何工作?

文件系统整体调用流向:用户系统调用 →sysfile.c系统调用实现 →路径名解析层目录操作层inode 管理层块分配层→ 日志层 → 缓冲层 → 磁盘驱动

读文件流程:namei("/test.txt") → namex 逐层解析路径 → dirlookup 查目录 → iget 拿inode引用 → ilock 加锁、加载磁盘数据 → readi 读文件内容 → bmap 寻址磁盘块 → bread 读块缓存 → iunlockput 解锁、释放引用

创建文件流程:nameiparent 拿到父目录inode → ilock 加锁父目录 → ialloc 分配新inode → ilock 加锁新inode → dirlink 在父目录添加目录项 → iunlockput 释放父目录 → iput 释放新inode引用

  • icache 是专门给文件系统做的 “结构化缓存”,把磁盘上原始的 dinode 包装成带锁、带引用计数、可直接操作的内存 inode,既加速访问,又解决了多进程同步和生命周期管理的问题。
  • dinode磁盘上持久存储的 inode 真身,掉电不丢;inode内存中运行时的缓存副本,多了内核并发控制、缓存管理所需的额外字段,关机即消失。

1.Large files

原文件系统大小通过inode结构体里的 uint addrs[NDIRECT+1]; 实现,存储的是磁盘逻辑块号,分为11个直接块和一个一级间接块映射。可表示12+(1kb/4byte)= 268个块。

要求:扩大 xv6 文件系统的单个文件最大容量:通过添加 二级间接块(doubly-indirect block)来实现,把当前的 268 个块的限制扩展到 65803 个块。

修改inode结构体,fs.h和file.h里的inode,一个是磁盘内的,一个是缓存上的

#define NDIRECT 11 //11个直接块地址 #define NINDIRECT (BSIZE / sizeof(uint)) //每个间接块可以存储的块地址数量,等于1024字节的块大小除以每个块地址的大小(4字节),即256个块地址 #define NDINDRECT (NINDIRECT * NINDIRECT) //每个双重间接块可以存储的块地址数量,256 * 256 = 65536个块地址 #define MAXFILE (NDIRECT + NINDIRECT + NDINDRECT) //最大文件大小(块数)=直接块数+间接块数+双重间接块数 // On-disk inode structure struct dinode {//磁盘上的inode结构体,存储了文件的元数据 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 改为11个直接块+1个间接块+1个双重间接块 };
// in-memory copy of an inode struct inode { uint dev; // Device number 设备号 uint inum; // Inode number inode编号 int ref; // Reference count 引用计数 struct sleeplock lock; // protects everything below here 睡眠锁,保护inode内容 int valid; // inode has been read from disk? 有效标志,表示inode是否已经从磁盘读取 short type; // copy of disk inode 文件类型 short major; // 主设备号(仅适用于设备类型) short minor;//次设备号(仅适用于设备类型) short nlink;//硬链接数 uint size; //文件大小(字节) uint addrs[NDIRECT+2]; //数据块地址数组,存储了文件内容所在的磁盘块地址,包括直接块和间接块 };

修改 fs.c 文件系统中映射 inode 的bmap()函数和释放itrunc()函数,让它们支持二级间接块

static uint//返回inode ip中第n个块的磁盘块地址。如果没有这样的块,bmap会分配一个。核心寻址函数,用于将文件的逻辑块号映射到物理磁盘块号。 bmap(struct inode *ip, uint bn) { uint addr, *a; //addr用于存储磁盘块地址,a用于存储间接块中的块地址数组 struct buf *bp; //bp用于存储读取的间接块的缓冲区指针 if(bn < NDIRECT){//如果块号小于NDIRECT,直接从ip->addrs[]中获取对应的磁盘块地址 if((addr = ip->addrs[bn]) == 0)//如果对应的磁盘块地址为0,表示该块还没有分配,则调用balloc()分配一个新的磁盘块,并将其地址存储在ip->addrs[]中 ip->addrs[bn] = addr = balloc(ip->dev);//分配一个新的磁盘块 return addr; } bn -= NDIRECT; //重置块号,减去直接块的数量,计算间接块中的索引 if(bn < NINDIRECT){//如果块号小于NINDIRECT,说明该块是间接块,需要从ip->addrs[NDIRECT]中获取间接块的地址 // Load indirect block, allocating if necessary. if((addr = ip->addrs[NDIRECT]) == 0)//如果间接块的地址为0,表示还没有分配间接块,则调用balloc()分配一个新的间接块,并将其地址存储在ip->addrs[NDIRECT]中 ip->addrs[NDIRECT] = addr = balloc(ip->dev); bp = bread(ip->dev, addr);//读取间接块所在的磁盘块到缓冲区 a = (uint*)bp->data; if((addr = a[bn]) == 0){//如果间接块中对应的磁盘块地址为0,表示该块还没有分配,则调用balloc()分配一个新的磁盘块,并将其地址存储在间接块中 a[bn] = addr = balloc(ip->dev); log_write(bp); } brelse(bp);//释放缓冲区 return addr; } bn -= NINDIRECT; //重置块号,减去间接块的数量,计算双重间接块中的索引 //二级间接块 if(bn < NDINDRECT){ if ((addr = ip->addrs[NDIRECT + 1]) == 0) ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev); //二级间接块为分配,分配新块 bp = bread(ip->dev, addr);//读取缓冲区 a = (uint*)bp->data; //获取二级间接块中的块地址数组 uint index = bn / NINDIRECT; //计算二级间接块中的索引 if ((addr = a[index]) == 0) { //一级间接块未分配,分配新块 a[index] = addr = balloc(ip->dev); log_write(bp); //写入日志 } brelse(bp); //释放缓冲区 bp = bread(ip->dev, addr); //读取一级间接块 a = (uint*)bp->data; //获取一级间接块中的块地址数组 index = bn % NINDIRECT; //计算一级间接块中的索引 if ((addr = a[index]) == 0) { //数据块未分配,分配新块 a[index] = addr = balloc(ip->dev); log_write(bp); //写入日志 } brelse(bp); //释放缓冲区 return addr; //返回分配的磁盘块地址 } panic("bmap: out of range"); } // Truncate inode (discard contents). // Caller must hold ip->lock. void itrunc(struct inode *ip)//清空inode内容,释放所有与该inode相关联的磁盘块。调用者必须持有ip->lock。 { 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; } ip->size = 0; iupdate(ip); if(ip->addrs[NDIRECT+1]){ // 如果二级间接块存在 bp = bread(ip->dev, ip->addrs[NDIRECT+1]); a = (uint*)bp->data; // 遍历所有一级间接块 for(i = 0; i < NINDIRECT; i++){ if(a[i]){ struct buf *bp2 = bread(ip->dev, a[i]); uint *a2 = (uint*)bp2->data; for(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); }

bmap 按逻辑块号范围逐层寻址:0~NDIRECT-1 直接从 inode 直接块条目取物理块号;超出部分进入一级间接块索引;再超出则进入二级间接块,先通过高位索引定位一级间接块,再通过低位索引定位最终数据块,每层缺块时自动分配。itrunc 遵循从底向上的释放顺序:先释放所有直接数据块,再依次释放一级间接块及其指向的数据块、二级间接块下所有一级子块的数据与索引,最后释放二级间接块本身,完成后更新 inode 并写回磁盘。

2.Symbolic links

符号链接(软链接)的核心设计:

  1. 本质是一个特殊类型的 inode:新增文件类型T_SYMLINK,它本身也是一个独立的 inode,有自己的 inode 号、元数据。
  2. 文件内容存目标路径字符串:软链接的数据块里不存普通文件内容,只存它指向的目标文件的路径字符串。
  3. 两套核心操作
    • sys_symlink:创建软链接 → 新建一个T_SYMLINK类型的 inode,把目标路径写进它的数据区。
    • sys_open改造:打开文件时自动识别软链接 → 读出目标路径 → 递归解析路径 → 最终打开真实文件。
  • 硬链接:目录项直接指向目标 inode,和原文件完全等价,目标文件删除后链接依然有效。
  • 软链接:存路径字符串,目标文件删除后链接就失效(悬空链接);好处是可以跨文件系统、可以指向目录。

实现sys_symlink

int sys_symlink(char *target, char *path) { //创建符号链接的系统调用实现 char kpath[MAXPATH], ktarget[MAXPATH]; //内核缓冲区,用于存储用户传入的路径和目标路径 memset(kpath, 0, MAXPATH); //将kpath缓冲区清零,确保没有残留数据 memset(ktarget, 0, MAXPATH);//将ktarget缓冲区清零,确保没有残留数据 struct inode *ip;//指向新创建的符号链接的inode结构体指针 int n, r; if((n = argstr(0, ktarget, MAXPATH)) < 0)//从用户空间获取符号链接的目标路径,并存储在ktarget中,如果失败则返回-1 return -1; if ((n = argstr(1, kpath, MAXPATH)) < 0)//从用户空间获取符号链接的路径,并存储在kpath中,如果失败则返回-1 return -1; int ret = 0; begin_op();//开启日志事务包裹,确保文件系统操作的原子性 // 这个软链接已经存在了 if((ip = namei(kpath)) != 0){//检查符号链接路径是否已经存在,如果存在则返回-1 // symlink already exists ret = -1; goto final; } // 为这个软链接allocate一个新的inode ip = create(kpath, T_SYMLINK, 0, 0);//调用create函数创建一个新的符号链接inode,如果失败则返回-1 if(ip == 0){ ret = -1; goto final; } // 把target path写入这个软链接inode的数据[0, MAXPATH]位置内 if ((r = writei(ip, 0, (uint64)ktarget, 0, MAXPATH)) < 0)//将符号链接的目标路径写入新创建的符号链接inode的数据块中,如果写入失败则返回-1 ret = -1; iunlockput(ip); final: end_op(); return ret;//结束日志事务包裹,返回操作结果 }

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){ //如果打开模式包含O_CREATE标志,表示需要创建新文件 ip = create(path, T_FILE, 0, 0); if(ip == 0){ end_op(); return -1; } } else {//如果不包含O_CREATE标志,表示需要打开已存在的文件 if((ip = namei(path)) == 0){//调用namei函数根据路径查找对应的inode end_op(); return -1; } ilock(ip);//锁定inode,防止其他进程同时访问 // 新增部分,处理符号链接 int depth = 0; // 解析深度上限 char target[MAXPATH]; // 尝试解析符号链接(最多解析10层) while(depth < 10 && ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)) {// 如果inode是符号链接类型且打开模式不包含O_NOFOLLOW标志,则继续解析符号链接 // 读取链接目标路径 if(readi(ip, 0, (uint64)target, 0, MAXPATH) <= 0) {//读取符号链接的目标路径,如果读取失败则报错 iunlockput(ip); end_op(); return -1; } iunlockput(ip); if((ip = namei(target)) == 0) { // 查找目标文件 end_op(); return -1; } ilock(ip); depth++; } if(depth >= 10) { iunlockput(ip); end_op(); return -1; } if(ip->type == T_DIR && omode != O_RDONLY){//如果inode是目录类型且打开模式不是只读,则不允许写入目录 iunlockput(ip);//解锁并释放inode end_op(); return -1; } }

声明补充

kernel/fcntl.h ... #define O_TRUNC 0x400 #define O_NOFOLLOW 0x800 // 不跟随符号链接 kernel/stat.h ... #define T_DEVICE 3 // Device #define T_SYMLINK 4 // 符号链接标识 kernel/syscall.h ... #define SYS_close 21 #define SYS_symlink 22 kernel/syscall.c ... extern uint64 sys_uptime(void); extern uint64 sys_symlink(void); static uint64 (*syscalls[])(void) = { ... [SYS_close] sys_close, [SYS_symlink] sys_symlink, }; user/user.h // system calls ... int uptime(void); int symlink(const char*, const char*); user/usys.pl ... entry("uptime"); entry("symlink"); /Makefile UPROGS=\ ... $U/_zombie\ $U/_symlinktest\
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/12 23:51:21

Fuck Stop code: VIDEO_DXGKRNL_FATAL_ERROR (0x113)

设备信息提取与蓝屏故障解决方案 一、本机硬件&系统信息 硬件配置 机型&#xff1a;耀世16 Pro Series GM6IX0B处理器&#xff1a;Intel i7-14650HX&#xff08;14代酷睿移动端高性能处理器&#xff09;内存&#xff1a;32GB&#xff08;31.7GB可用&#xff09;特性&#x…

作者头像 李华
网站建设 2026/7/12 23:47:51

零基础入门AI绘画训练:kohya_ss图形化工具完全指南

零基础入门AI绘画训练&#xff1a;kohya_ss图形化工具完全指南 【免费下载链接】kohya_ss 项目地址: https://gitcode.com/GitHub_Trending/ko/kohya_ss 想要用AI创作属于自己的艺术作品&#xff0c;但被复杂的命令行训练吓退了&#xff1f;今天我要为你介绍一个让AI绘…

作者头像 李华
网站建设 2026/7/12 23:40:55

创客江湖的三柄利剑:Arduino、ESP32、STM32 终极选型心法

Arduino&#xff1a;定义的是一种“简易开发方式”和一个“创客生态”&#xff0c;硬件经典&#xff0c;软件极简。ESP32&#xff1a;是一块“自带 WiFi 和蓝牙的强大芯片”&#xff0c;以极低成本实现物联网连接。STM32&#xff1a;是一个“专业嵌入式工程的全能工具箱”&…

作者头像 李华
网站建设 2026/7/12 23:40:14

如何快速集成WaitingDots:3分钟实现弹跳点加载动画

如何快速集成WaitingDots&#xff1a;3分钟实现弹跳点加载动画 【免费下载链接】WaitingDots 项目地址: https://gitcode.com/gh_mirrors/wa/WaitingDots WaitingDots是一个轻量级Android库&#xff0c;提供生动的弹跳点加载动画效果&#xff0c;广泛应用于即时通讯应用…

作者头像 李华
网站建设 2026/7/12 23:36:44

定时任务“裂变”惨案:Spring Boot 分布式调度从互殴到握手言和

定时任务“裂变”惨案&#xff1a;Spring Boot 分布式调度从互殴到握手言和 你用 Spring Boot 的 Scheduled 注解轻松写了个定时任务&#xff0c;单机跑得稳稳的。随着业务增长&#xff0c;应用多实例部署&#xff0c;噩梦随之降临&#xff1a;凌晨 3 点对账任务重复执行了 4 次…

作者头像 李华