MIT6.S081 Lab-9: File System

倒数第三个实验,这次的实验只有两个难度都为moderate\text{moderate}的任务,然后最后两个实验就都只有一个难度为hard\text{hard}的大任务。。。所以这大概是最后的温柔了。

不过做完这个实验暂时就不碰系统级编程了。。。眼下Graphics\text{Graphics}的事情越来越紧急,反正系统级编程是啥时候都能做的,最后两个实验什么时候补回来也不成问题。

这次的主题是文件系统。

1. 大文件\text{1. 大文件}

目前的xv6\text{xv6}对于文件的大小是有限制的,至多268268块。这是由于现在的xv6\text{xv6}使用了1212个直接块,以及一个块指向256256个间接块。我们希望能够修改xv6\text{xv6}使得它能使用1111个直接块,256256个间接块以及256×256256\times 256个二级间接块名字我瞎翻译的,或者说,256256个块,每个块指向一个保存了256256个块的块。

说到底,还是个树形结构,然后类似于虚拟页表,我们用低88位来作为偏移,高位数值作为块号。

首先要修改NDIRECT宏的定义,改成1111,因为现在我们多拿一个块作为二级间接块,直接块自然就要少一个,当然,在各种定义里我们就应该也注意使用了NDIRECT数值的地方,比如,addr[NDIRECT + 1]我们就应该修改为addr[NDIRECT + 2]

然后就是修改bmap,实话说。。。没啥难的,一遍过。我们在panic前面加上对于二级间接块的处理即可,这可以跟着原来间接块的代码照葫芦画瓢————首先判断一下二级间接块有没有分配,没分配就分配。然后访问二级间接块指向的块,这里应该保存着256256个间接块,同样判断是否分配,没分配就分配,最后访问间接块,看我们需要的块是否分配,没分配就分配。。。就这样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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 < NINDIRECT){
// 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 -= NINDIRECT;
if(bn < NDOUB_INDIRECT)
{
uint singly_bn = bn >> 8;
// if we haven't allocated block for doubly indirect block, alloc it
if((addr = ip->addrs[NDIRECT + 1]) == 0)
ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev);
// we first load the corresponding singly indirect block
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[singly_bn]) == 0){
a[singly_bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
// we then load and write to the doubly indirect block
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
bn &= 255; // take the offset
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}

panic("bmap: out of range");
}

这里NDOUB_INDIRECT的定义是

#define NDOUB_INDIRECT (NINDIRECT * NINDIRECT)

直到做完了才发现自己没看指南里给出的提示。。。

2. 符号链接\text{2. 符号链接}

实现一个symlink系统调用,用来创建符号链接(软链接)文件。软链接不同于硬链接,软链接是一个文件里面保存了实际的文件路径。它的好处是可以跨磁盘引用这个名词也是我瞎翻译的,代价是会慢一些。

提示已经写得非常完善了。添加系统调用的标准流程自不必说,除此以外,我们首先加一种文件类型T_SYMLINK,再加一个O_NOFOLLOW标签,用来表示打开符号链接文件时不跟踪符号链接而是直接操作文件本身。然后我们可以将链接的目标路径以字符串形式写入符号链接文件对应的文件块中。

在调用系统调用symlink时,我们创建文件,然后在其中写入目标路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uint64 sys_symlink(void)
{
char target[MAXPATH], path[MAXPATH];
if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
return -1;
begin_op();
struct inode *ip = create(path, T_SYMLINK, 0, 0);
if(ip == 0)
{
end_op();
return -1;
}
uint n = strlen(target);
uint len = writei(ip, 0, (uint64)target, 0, n);
iunlock(ip);
end_op();
return len == n ? 0 : -1;
}

然而,这里有几个小坑点,一个是create函数在创建inode成功后会自动加锁,因此不需要你在调用完之后还给新建的inode上锁,这样反而会导致死锁。

然后,有可能存在一种情况是要创建的符号链接文件已经存在,我们当然不用再创建新的。但是xv6\text{xv6}里对于这种情况并没有管(因为xv6\text{xv6}本来没考虑符号链接),需要你自己加上这个判断。

1
2
3
4
5
6
7
8
9
10
11
if((ip = dirlookup(dp, name, 0)) != 0)
{
iunlockput(dp);
ilock(ip);
if(type == T_FILE && (ip->type == T_FILE || ip->type == T_DEVICE))
return ip;
if(type == T_SYMLINK && ip->type == T_SYMLINK) // add this judgement
return ip;
iunlockput(ip);
return 0;
}

最后就是修改sys_open使得open系统调用能够跟踪符号链接。我们只需要在sys_open创建文件指针之前跟踪符号链接并及时修改inode指针即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)) 
{
char target[MAXPATH];
uint len = readi(ip, 0, (uint64)target, 0, MAXPATH);
struct inode *ret_ip;
if(len == 0 || follow_symlink(&ret_ip, target, 0) < 0)
{
printf("follow symlink failed\n");
iunlock(ip);
end_op();
return -1;
}
iunlock(ip);
ip = ret_ip;
ilock(ip);
}

这里我们使用follow_symlink函数(实现见后)跟踪符号链接文件直到我们遇到一个真正的文件,并将其inode指针保存在第一个参数对应的地址中。这样就得到了实际文件的inode,用这个inode指针替换原来的inode指针即可,当然注意加锁。

follow_symlink函数通过符号链接文件跟踪其指向的文件。由于可能指向另一个符号链接文件,我们需要递归进行访问。当然,符号链接的指向关系可能会出现循环的情况,为了防止这种情况造成无限递归,同时也为了防止递归深度过深,只要设定一个递归最大层数即可。如此一来就没什么问题了,唯一要注意的就是记得及时加锁以及及时释放锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static 
int follow_symlink(struct inode **ipp, char *path, uint depth)
{
if(depth >= LINK_THREASHOLD) return -1;
struct inode *ip;
// if the file doesn't exist, we return
if((ip = namei(path)) == 0)
return -1;
ilock(ip);
if(ip->type != T_SYMLINK)
{
*ipp = ip;
iunlock(ip);
return 0;
}
char target[MAXPATH];
uint len = readi(ip, 0, (uint64)target, 0, MAXPATH);
// if we cannot read anything from the file, we return
if(len == 0)
{
iunlock(ip);
return -1;
}
int ret = follow_symlink(ipp, target, depth + 1);
iunlock(ip);
return ret;
}

然后很奇怪的是运行symlinktest的时候老是会因为inode空间用光了而panic\text{panic},实话说我也没明白啥情况,最后把inode的空间调大了一点点就莫名其妙地过了。。。

于是这次实验就做完了。后面两个实验看来难度都较大,系统级编程到此暂时结束了,等到血源诅咒上PC了有时间再来补上最后两个实验吧。