file system
这个lab难度不大,主要理解file system中几个较高层的函数就可以做
笔者用时约3h
Large files
这一部分要求我们扩容xv6文件系统能够支持的最大文件大小,在我们修改代码之前,xv6的文件系统限制为12个直接块和1个一级间接块,最大文件大小为(12+256)*1024字节。
我们需要实现的是,将1个直接块替换为1个二级间接块,其中,二级间接块的意思是,该块中存储256个一级简接块的块号,也就是说,最大文件大小变为(11+256+256*256)*1024字节。
具体实现上,首先我们在fs.h文件中修改并添加一些宏定义(这一步非常重要,因为一开始的宏定义不仅仅在我们需要修改的函数中使用,还在其他地方有使用,最简单的方法就是根据其定义修改其值),如下所示。其中,NDIRECT表示直接块数量,NINDIRECT1表示一级间接块可以表示的块号数量(256),NINDIRECT2表示二级间接块可以表示的块号数量(256*256),NINDIRECT表示所有间接块可以表示的块号数量。
#define NDIRECT 11
#define NINDIRECT1 (BSIZE / sizeof(uint))
#define NINDIRECT2 (NINDIRECT1 * NINDIRECT1)
#define NINDIRECT (NINDIRECT1 + NINDIRECT2)
然后需要修改dinode结构体(定义在kernel/fs.h:34)中addrs数组的大小以及inode结构体(定义在kernel/file.h:17)中addrs数组的大小。
// fs.h
// 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
};
// file.h
// 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];
};
然后根据实验文档,我们修改bmap函数(定义在kernel/fs.c:378),参考一级间接块的处理方案,首先需要判断当前请求的文件块是第几块,如果是二级间接块的话,那么需要获取两个偏移量,分别是二级间接块的偏移量(bn / NINDIRECT1)和该偏移量所指向一级间接块的偏移量(bn % NINDIRECT1)。然后先看二级间接块是否存在,不存在则分配,并跑到对应的一级间接块中,查看一级间接块是否存在,不存在则分配,最后跑到目标块中,查看目标块是否存在,不存在则分配。
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0)
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
bn -= NDIRECT;
if(bn < NINDIRECT1){
// Load indirect block, allocating if necessary.
if((addr = ip->addrs[NDIRECT]) == 0)
ip->addrs[NDIRECT] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
bn -= NINDIRECT1;
if (bn < NINDIRECT2) {
if ((addr = ip->addrs[NDIRECT + 1]) == 0)
ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*) bp->data;
if ((addr = a[bn / NINDIRECT1]) == 0) {
a[bn / NINDIRECT1] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
bp = bread(ip->dev, addr);
a = (uint*) bp->data;
if ((addr = a[bn % NINDIRECT1]) == 0) {
a[bn % NINDIRECT1] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
同样的,我们需要修改itrunc函数(定义在kernel/fs.c:431),比较简单。
void
itrunc(struct inode *ip)
{
int i, j, k;
struct buf *bp, *bp2;
uint *a, *a2;
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 < NINDIRECT1; 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(j = 0; j < NINDIRECT1; j++){
if (a[j]) {
bp2 = bread(ip->dev, a[j]);
a2 = (uint*)bp2->data;
for(k = 0; k < NINDIRECT1; 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;
}
ip->size = 0;
iupdate(ip);
}
Symbolic links
这一部分要求我们为xv6实现添加符号链接的系统调用,那么首先我们需要理解符号链接是什么。符号链接,也叫做软链接,可以理解为windows上面的快捷方式,只是原始文件或文件夹的别名,访问或者修改符号链接会直接导向指向的文件或文件夹。
根据文档提示,我们可以将原始文件的路径存放在链接文件的block中,可以理解为软链接文件中存放着原始文件的路径。
具体实现上,根据文档提示,首先我们在kernel/fcntl.h文件中添加以下文件权限码(与O_RDONLY等类似),当该文件码被指定时,直接将软链接视为一个文件打开,而不往后寻找其真正指向。
#define O_NOFOLLOW 0x004
然后,在kernel/stat.h文件中添加以下文件类型,表示文件为软链接。
#define T_SYMLINK 4
在此之后,我们在kernel/sysfile.c文件中添加sys_symlink函数,如下所示(此处省略系统调用的添加方法,详细见系统调用实验Lab2)。函数中,我们首先从用户空间获取path字符串和target字符串,分别表示软链接文件的路径与目标文件(也就是源文件)的路径。然后,我们需要用begin_op
函数开启一个事务(与日志有关),并调用create
函数来创建这个链接文件,指定TYPE
为T_SYMLINK
。最后调用writei
函数将target字符串也就是源文件路径写入该链接文件中即可。注意在退出函数时需要将ip
(指向该文件inode的指针)所指向的inode释放,并调用end_op
函数将事务提交。
uint64
sys_symlink(void)
{
char path[MAXPATH], target[MAXPATH];
struct inode* ip;
if (argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
return -1;
begin_op();
if ((ip = create(path, T_SYMLINK, 0, 0)) == 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;
}
最后,我们需要在sys_open
函数中添加打开软链接文件的处理代码(在获取该路径对应的inode指针之后),主循环以文件的type
与open的打开权限是否为O_NOFOLLOW
作为循环条件,设置一个循环深度,如果超过10次则跳出循环(防止循环软链接),在循环中,读取inode中的内容(长度为MAXPATH),将ip
设置为新文件的inode指针(注意需要将原始ip
所指向的inode释放)。这样子当跳出循环后,ip
就指向我们最终想要打开文件的inode了。
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) < MAXPATH) {
iunlockput(ip);
end_op();
return -1;
}
iunlockput(ip);
if ((ip = namei(path)) == 0) {
end_op();
return -1;
}
ilock(ip);
}