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[NDIRECT1]; 实现存储的是磁盘逻辑块号分为11个直接块和一个一级间接块映射。可表示121kb/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[NDIRECT2]; // 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[NDIRECT2]; //数据块地址数组存储了文件内容所在的磁盘块地址包括直接块和间接块 };修改 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[NDIRECT1]){ // 如果二级间接块存在 bp bread(ip-dev, ip-addrs[NDIRECT1]); 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[NDIRECT1]); ip-addrs[NDIRECT1] 0; } ip-size 0; iupdate(ip); }bmap 按逻辑块号范围逐层寻址0~NDIRECT-1 直接从 inode 直接块条目取物理块号超出部分进入一级间接块索引再超出则进入二级间接块先通过高位索引定位一级间接块再通过低位索引定位最终数据块每层缺块时自动分配。itrunc 遵循从底向上的释放顺序先释放所有直接数据块再依次释放一级间接块及其指向的数据块、二级间接块下所有一级子块的数据与索引最后释放二级间接块本身完成后更新 inode 并写回磁盘。2.Symbolic links符号链接软链接的核心设计本质是一个特殊类型的 inode新增文件类型T_SYMLINK它本身也是一个独立的 inode有自己的 inode 号、元数据。文件内容存目标路径字符串软链接的数据块里不存普通文件内容只存它指向的目标文件的路径字符串。两套核心操作sys_symlink创建软链接 → 新建一个T_SYMLINK类型的 inode把目标路径写进它的数据区。sys_open改造打开文件时自动识别软链接 → 读出目标路径 → 递归解析路径 → 最终打开真实文件。硬链接目录项直接指向目标 inode和原文件完全等价目标文件删除后链接依然有效。软链接存路径字符串目标文件删除后链接就失效悬空链接好处是可以跨文件系统、可以指向目录。实现sys_symlinkint 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\
xv6 lab9 fs
发布时间:2026/7/12 23:52:23
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[NDIRECT1]; 实现存储的是磁盘逻辑块号分为11个直接块和一个一级间接块映射。可表示121kb/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[NDIRECT2]; // 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[NDIRECT2]; //数据块地址数组存储了文件内容所在的磁盘块地址包括直接块和间接块 };修改 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[NDIRECT1]){ // 如果二级间接块存在 bp bread(ip-dev, ip-addrs[NDIRECT1]); 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[NDIRECT1]); ip-addrs[NDIRECT1] 0; } ip-size 0; iupdate(ip); }bmap 按逻辑块号范围逐层寻址0~NDIRECT-1 直接从 inode 直接块条目取物理块号超出部分进入一级间接块索引再超出则进入二级间接块先通过高位索引定位一级间接块再通过低位索引定位最终数据块每层缺块时自动分配。itrunc 遵循从底向上的释放顺序先释放所有直接数据块再依次释放一级间接块及其指向的数据块、二级间接块下所有一级子块的数据与索引最后释放二级间接块本身完成后更新 inode 并写回磁盘。2.Symbolic links符号链接软链接的核心设计本质是一个特殊类型的 inode新增文件类型T_SYMLINK它本身也是一个独立的 inode有自己的 inode 号、元数据。文件内容存目标路径字符串软链接的数据块里不存普通文件内容只存它指向的目标文件的路径字符串。两套核心操作sys_symlink创建软链接 → 新建一个T_SYMLINK类型的 inode把目标路径写进它的数据区。sys_open改造打开文件时自动识别软链接 → 读出目标路径 → 递归解析路径 → 最终打开真实文件。硬链接目录项直接指向目标 inode和原文件完全等价目标文件删除后链接依然有效。软链接存路径字符串目标文件删除后链接就失效悬空链接好处是可以跨文件系统、可以指向目录。实现sys_symlinkint 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\