一、本节任务
二、Lab8: file system
在这一节,我们将为 xv6 的文件系统加入大文件和符号链接。
2.1 Large files (moderate)
这个部分需要我们增加 xv6 文件的大小上限,由于 inode 结构体中有 12 个直接映射项,1 个一级间接映射项,所以 xv6 文件系统中的最大文件大小为 (12 + (1024B / 4B)) 个块,即 268 个块,在 xv6 中,每个块大小为 1024B。而通过再增加一个二级间接映射项可以让我们的文件大小增加到 (11 + 256 + 256*256) = 65803 个块,而下面我们就是要实现这个二级间接映射项。
首先修改 fs.h 文件中的宏和 dinode 中 addrs 的大小,以及 file.h 文件中 inode 结构体中 addrs 的大小也要同步修改过来。这样做的目的是减少一个直接映射项,增加一个二级间接映射项。
#define NDIRECT 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 };
// 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.c 文件中的 bmap 和 itrunc 函数,bmap 函数的作用是通过逻辑块号 bn 来到 addrs 数组中找到其在磁盘上的真实块号;而 itrunc 函数的作用是将 inode 的 addrs 清空。直接映射项的真实块号就是 addrs[bn],而一级间接映射项的真实块号还需要从 addrs[NDIRECT] 指向的块上寻找真实块号,二级间接映射项需要从 addrs[NDIRECT+1] 指向的块上先找到对应一级间接映射项,然后再找到真实块号。
static uint bmap(struct inode *ip, uint bn) { uint addr, *a; struct buf *bp; if(bn < NDIRECT){ if((addr = ip->addrs[bn]) == 0){ addr = balloc(ip->dev); if(addr == 0) return 0; ip->addrs[bn] = addr; } return addr; } bn -= NDIRECT; if(bn < NINDIRECT){ // Load indirect block, allocating if necessary. if((addr = ip->addrs[NDIRECT]) == 0){ addr = balloc(ip->dev); if(addr == 0) return 0; ip->addrs[NDIRECT] = addr; } bp = bread(ip->dev, addr); a = (uint*)bp->data; if((addr = a[bn]) == 0){ addr = balloc(ip->dev); if(addr){ a[bn] = addr; log_write(bp); } } brelse(bp); return addr; } bn -= NINDIRECT; if(bn < NINDIRECT * NINDIRECT){ if((addr = ip->addrs[NDIRECT+1]) == 0){ addr = balloc(ip->dev); if(addr == 0) return 0; ip->addrs[NDIRECT+1] = addr; } bp = bread(ip->dev, addr); a = (uint*)bp->data; if((addr = a[bn/NINDIRECT]) == 0){ addr = balloc(ip->dev); if(addr == 0) return 0; a[bn/NINDIRECT] = addr; log_write(bp); } brelse(bp); bn = bn % NINDIRECT; bp = bread(ip->dev, addr); a = (uint*)bp->data; if((addr = a[bn]) == 0){ addr = balloc(ip->dev); if(addr){ a[bn] = addr; log_write(bp); } } brelse(bp); return addr; } panic("bmap: out of range"); }
void itrunc(struct inode *ip) { int i, j; struct buf *bp; struct buf *bp1; uint *a; uint *a1; 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; } 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]){ bp1 = bread(ip->dev, a[i]); a1 = (uint*)bp1->data; for(j = 0; j < NINDIRECT; j++){ if(a1[j]) bfree(ip->dev, a1[j]); } brelse(bp1); bfree(ip->dev, a[i]); a[i] = 0; } } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT+1]); ip->addrs[NDIRECT+1] = 0; } ip->size = 0; iupdate(ip); }
最后通过测试。
2.2 Symbolic links (moderate)
首先创建一个新系统调用 symlink,怎么添加系统调用在之前的 lab 中已经做过了,这里就不多赘述了。
下面是 symlink 系统调用的实现,首先使用 create 创建一个新文件,文件的类型为 T_SYMLINK,然后使用 writei 向文件的数据块写入 target 路径,这样使用 open 系统调用时如果发现文件是链接文件,则从文件的数据块中读取 target 文件的路径。最后记得使用 iunlockput(ip) 释放 inode 的锁和引用:
uint64 sys_symlink(void) { int n; char target[MAXPATH]; char path[MAXPATH]; struct inode *ip; if((n = argstr(0, target, MAXPATH)) < 0 || argstr(1, path, MAXPATH) < 0){ return -1; } begin_op(); ip = create(path, T_SYMLINK, 0, 0); if(ip == 0){ end_op(); return -1; } if(writei(ip, 0, (uint64)target, 0, MAXPATH) != MAXPATH){ iunlockput(ip); end_op(); return -1; } iunlockput(ip); end_op(); return 0; }
然后需要修改 open 系统调用,当识别到文件的类型为 T_SYMLINK 的时候,并且模式不为 O_NOFOLLOW,则循环地通过链接路径找到被链接的文件,若被链接的文件就是当前文件本身,形成的环路,或者链接的深度超过了 10,则报错:
uint64 sys_open(void) { char path[MAXPATH]; char target[MAXPATH]; int fd, omode; struct file *f; struct inode *ip; struct inode *tip; int n, depth; argint(1, &omode); if((n = argstr(0, path, MAXPATH)) < 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); if(ip->type == T_DIR && omode != O_RDONLY){ iunlockput(ip); end_op(); return -1; } } depth = 0; while(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){ if(readi(ip, 0, (uint64)target, 0, MAXPATH) != MAXPATH) panic("open symlink: readi"); // the links form a cycle if(strncmp(target, path, n) == 0){ iunlockput(ip); end_op(); return -1; } // target file does not exist if((tip = namei(target)) == 0){ iunlockput(ip); end_op(); return -1; } iunlock(ip); ilock(tip); ip = tip; depth++; if(depth >= 10){ iunlockput(ip); end_op(); return -1; } } ..... .....
最后通过所有测试。