Lab: file system
实验准备
切换到fs分支
$ git fetch
$ git checkout fs
$ make clean
Large files
需求
在这个任务中,你将增加一个xv6文件的最大大小。
目前,xv6文件被限制在268个块或268 * BSIZE字节(BSIZE在xv6中为1024)。这个限制来自于xv6 inode包含12个"直接"块号和一个"单间接"块号,它引用一个可以容纳256个更多块号的块,总计12+256=268个块。你将修改xv6文件系统代码,以支持每个inode中的一个"双间接"块,其中包含256个单间接块的地址,每个单间接块可以包含最多256个数据块的地址。结果将是一个文件最多由65803个块组成,即256*256+256+11个块(11个而不是12个,因为我们将牺牲一个直接块号来放置双间接块)。
你需要修改bmap()函数,以实现双间接块的功能,除了直接块和单间接块外。为了给新的双间接块腾出位置,你需要将ip->addrs[]的前11个元素作为直接块,第12个元素作为单间接块(与当前的设置相同),第13个元素作为新的双间接块。当bigfile写入65803个块,并且usertests -q成功运行时,你就完成了这个练习。
The solution
首先去fs.h中添加双间接块对应的直接块总数宏定义,并修改文件最大大小的宏定义:
#define NDIRECT 12
#define NINDIRECT (BSIZE / sizeof(uint))
#define DINDIRECT (NINDIRECT*NINDIRECT)//双间接块对应的直接块总数
#define MAXFILE (NDIRECT + NINDIRECT + DINDIRECT - 1)//要减去被占用的直接块
然后在kernel/fs.c中修改bmap定义,添加用双间接块获取指定物理块号的代码:
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
if(bn < NDIRECT -1){//判断是否为前11个直接块
if((addr = ip->addrs[bn]) == 0){
addr = balloc(ip->dev);
if(addr == 0)
return 0;
ip->addrs[bn] = addr;
}
return addr;
}
bn -= (NDIRECT - 1);//减去11
if(bn < NINDIRECT){//判断是否可由“单间接”块找到
// Load indirect block, allocating if necessary.
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]) == 0){
addr = balloc(ip->dev);
if(addr){
a[bn] = addr;
log_write(bp);
}
}
brelse(bp);
return addr;
}
bn -= NINDIRECT;//减256
if(bn < DINDIRECT){
if((addr = ip->addrs[NDIRECT]) == 0){//判断“双间接”块是否已分配
addr = balloc(ip->dev);
if(addr == 0)
return 0;
ip->addrs[NDIRECT] = addr;
}
uint an = bn / NINDIRECT;//判断是第几个单间接块
bp = bread(ip->dev,addr);
a = (uint*)bp->data;
if((addr = a[an]) == 0){
addr = balloc(ip->dev);
if(addr){
a[an] = addr;//将单间接块地址放入双间接块中
log_write(bp);
}
//else return 0;
}
brelse(bp);
an = bn % NINDIRECT;//获取直接块
bp = bread(ip->dev,addr);
a = (uint*)bp->data;
if((addr = a[an]) == 0){
addr = balloc(ip->dev);
if(addr){
a[an] = addr;//将直接块地址放入单间接块中
log_write(bp);
}
//else return 0;
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
然后修改itrunc的定义以释放双间接块有关物理块,确保释放文件所有的块:
void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp,*ap;
uint *a,*b;
for(i = 0; i < NDIRECT - 1; i++){//释放前11个直接块
if(ip->addrs[i]){
bfree(ip->dev, ip->addrs[i]);
ip->addrs[i] = 0;
}
}
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])
bfree(ip->dev, a[j]);
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT-1]);//释放单间接块
ip->addrs[NDIRECT-1] = 0;
}
if(ip->addrs[NDIRECT]){//判断双间接块是否存在
bp = bread(ip->dev, ip->addrs[NDIRECT]);//若存在则读取
a = (uint*)bp->data;
for(i = 0; i < NINDIRECT; i++){
if(a[i]){//判断单间接块是否存在
ap = bread(ip->dev,a[i]);
b = (uint*)ap->data;
for(j = 0; j < NINDIRECT; j++){
if(b[j])//判断直接块是否存在
bfree(ip->dev,b[j]);
}
brelse(ap);
bfree(ip->dev, a[i]);//释放单间接块
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT]);//释放双间接块
ip->addrs[NDIRECT] = 0;
}
ip->size = 0;
iupdate(ip);
}
由于我们未修改NDIRECT的宏定义而是在逻辑上认为前11块为直接块,第12块为单间接块,第13块为双间接块,故我们要注意依赖NDIRECT的代码,确保它符合我们规定的逻辑,发现建立初始文件系统的mkfs/mkfs.c中的iappend函数中涉及NDIRECT,我们要对其进行修改:
void
iappend(uint inum, void *xp, int n)
{
...
if(fbn < NDIRECT - 1){
if(xint(din.addrs[fbn]) == 0){
din.addrs[fbn] = xint(freeblock++);
}
x = xint(din.addrs[fbn]);
} else {
if(xint(din.addrs[NDIRECT - 1]) == 0){
din.addrs[NDIRECT - 1] = xint(freeblock++);
}
rsect(xint(din.addrs[NDIRECT - 1]), (char*)indirect);
if(indirect[fbn - NDIRECT + 1] == 0){
indirect[fbn - NDIRECT + 1] = xint(freeblock++);
wsect(xint(din.addrs[NDIRECT - 1]), (char*)indirect);
}
x = xint(indirect[fbn-NDIRECT + 1]);
}
...
}
Symbolic links
需求
在这个练习中,你将向xv6添加符号链接。符号链接(或软链接)通过路径名引用链接的文件;当打开符号链接时,内核会跟随链接到被引用的文件。符号链接类似于硬链接,但是硬链接限制只能指向同一磁盘上的文件,而符号链接可以跨越磁盘设备。尽管xv6不支持多个设备,但实现这个系统调用是一个很好的练习,可以理解路径名查找的工作原理。
你将实现 symlink(char *target, char *path) 系统调用,该调用在 path 路径下创建一个新的符号链接,指向 target 所命名的文件。更详细的信息可以参考 symlink 的 man 页面。为了进行测试,需要将 symlinktest 添加到 Makefile 中并运行它。
The solution
预处理
- 在user/user.h中为该系统调用添加用户态的声明
int symlink(char *target, char *path); - 在user/usys.pl中为其添加一个脚本生成项
entry("symlink") - 在kernel/syscall.h中为其添加一个宏定义
#define SYS_symlink 22 - 在kernel/syscall.c中的函数指针数组syscalls中添加一条
[SYS_symlink] sys_symlink,
sys_symlink实现
为了让系统能区分出软链接inode,需要先在kernel/stat.h添加一个新的文件类型宏定义#define T_SYMLINK 4 // symlink
然后在kernel/sysfile.c中定义sys_symlink函数:
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();
if((ip = namei(path)) != 0){//path下已经存在该符号链接
end_op();
return -1;
}
ip = create(path, T_SYMLINK, 0, 0);//在path下创建一个新的符号链接
if(ip == 0){
end_op();
return -1;
}
if(writei(ip, 0, (uint64)target, 0, MAXPATH) < 0){//将target写入inode
iunlockput(ip);
end_op();
return -1;
}
iunlockput(ip);//create会返回一个锁定的inode,故要记得解锁
end_op();
return 0;
}
为了让open系统调用确定是要跟随软链接还是单纯打开软链接,我们要在kernel/fcntl.h中添加一个新的标志#define O_NOFOLLOW 0x004
最后我们修改kernel/sysfile.c中sys_open的定义,当inode类型为T_SYMLINK,并且用户未设置O_NOFOLLOW标志时我们就可以跟踪该软链接,直至获取到文件。这里需要注意的是软链接可能会出现循环链接的情况,我们要限制循环查找次数
uint64
sys_open(void)
{
...
if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
iunlockput(ip);
end_op();
return -1;
}
int tag = 0;
while(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){//可跟踪
readi(ip, 0, (uint64)target, 0, MAXPATH);//读取target
iunlockput(ip);
if(++tag > 10){//循环查找
end_op();
return -1;
}
ip = namei(target);//链接到下一inode
if(ip == 0){
end_op();
return -1;
}
ilock(ip);
}
if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
...
}