# MIT6.s081-2020 Lab9 File System
文件系统作为组织和存储数据的方式,通常支持共享用户和应用程序之间的数据,以及持久化,以便数据重新引导。在本模块将向 xv6 文件系统中大文件支持和符号链接出发,带你领悟文件系统的魅力。
# 准备
git fetch
git checkout fs
make clean
2
3
# Large files
引言
目前 xv6 文件限制为 268 个块,或 268 * BSIZE
字节(在 xv6 中 BSIZE 为 1024)。这个限制来自这样一个事实:一个 xv6 inode
包含 12 个direct
块号和一个singly-indirect
块号,这是指一个块最多可以容纳 256 个块号,总共 12+256=268
块。因此,本小节的目标是增加 xv6 文件的最大大小,以满足能够创建一个包含 65803 个块的文件。
实现
本小节需要借助doubly-indirect
块的方式,其中包含 256 个singly-indirect
地址,每个块最多可包含 256 个数据块地址,以此来实现对大文件的支持。最多可以包含 65803 个块,或 256*256+256+11
个块(11 个而不是 12 个,因为我们会为doubly-indirect
块牺牲一个direct
块号)。参考所给的HINT,即可顺利实现。
HINT1:需要修改NDIRECT
的定义,前 11 个元素ip->addrs[]
应该是直接块;第 12 个应该是一个单独的间接块(就像现在的块一样);第 13 个应该是你新的双重间接块。
kernel/fs.h
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define NDINDIRECT (NINDIRECT * NINDIRECT) //double-indirect
#define MAXFILE (NDIRECT + NINDIRECT + NDINDIRECT)
// On-disk inode structure
struct dinode {
//...
uint addrs[NDIRECT+2]; // Data block addresses
};
2
3
4
5
6
7
8
9
10
修改在file.h
中声明struct inode
的属性addrs[]
,确保 struct inode
和struct dinode
它们的元素个数相同addrs[]
数组。
kernel/file.h
// in-memory copy of an inode
struct inode {
//...
uint addrs[NDIRECT+2];
};
2
3
4
5
HINT2:修改bmap()
函数,参考indirect block
的加载方式,确保使用逻辑块编号索引双重间接块及其指向的间接块。
static uint
bmap(struct inode *ip, uint bn)
{
//...
bn -=NINDIRECT;
if(bn < NDINDIRECT){
// Load double indirect block, allocating if necessary.
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 idx = bn / (BSIZE / sizeof(uint));
bn %= (BSIZE / sizeof(uint));
if((addr = a[idx]) == 0){
a[idx] = addr = balloc(ip->dev);
log_write(bp);
}
bp2 = bread(ip->dev, addr);
a = (uint*)bp2->data;
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp2);
}
brelse(bp2);
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
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
HINT3:修改itrunc
,参考indirect block
的释放方式,确保其能够正确的释放文件doubly-indirect
的所有块。
void
itrunc(struct inode *ip)
{
//...
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]) {
bp2 = bread(ip->dev, a[j]);
a2 = (uint*)bp2->data;
for(k = 0; k < NINDIRECT; k++){
if(a2[k])
bfree(ip->dev, a2[k]);
}
bfree(ip->dev, a[j]);
brelse(bp2);
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT + 1]);
ip->addrs[NDIRECT + 1] = 0;
}
ip->size = 0;
iupdate(ip);
}
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
结果
启动xv6内核,运行bigfile
,可以顺利通过测试。
# Symbolic links
引言
符号链接(或软链接)是指通过路径名链接的文件;当一个符号链接被打开时,内核会跟随链接指向被引用的文件。符号链接类似于硬链接,但硬链接仅限于指向同一磁盘上的文件,而符号链接可以跨磁盘设备。本小节的目标是实现符号链接,即使得在路径上创建一个新的符号链接,该链接指向由目标命名的文件。参考所给的HINT,即可顺利实现。
实现
HINT1:首先,为symlink
创建一个新的系统调用号,在user/usys.pl
,user/user.h
中添加一个入口,在kernel/syscall.h
,kernel/syscall.c
中添加一个系统调用声明,将 symlinktest
添加到 Makefile
。
user/usys.pl
entry("uptime");
entry("symlink");
2
user/user.h
// system calls
//...
int uptime(void);
int symlink(char *, char *);
2
3
4
Makefile
$U/_zombie\
$U/_symlinktest\
2
kernel/syscall.h
#define SYS_close 21
#define SYS_symlink 22
2
kernel/syscall.c
extern uint64 sys_symlink(void);
static uint64 (*syscalls[])(void) = {
//...
[SYS_close] sys_close,
[SYS_symlink] sys_symlink,
};
2
3
4
5
6
HINT2:添加新的文件类型(T_SYMLINK
) 到 kernel/stat.h
来表示一个符号链接,
kernel/stat.h
#define T_DEVICE 3 // Device
#define T_SYMLINK 4 //SYM link
2
HINT3:在kernel/sysfile.c
中实现一个sys_symlink
以来在指向目标的路径上创建新的符号链接。请注意,系统调用成功时不需要存在目标。需要选择某个位置来存储符号链接的目标路径,例如,在 inode
的数据块中。symlink
应该返回一个表示成功 (0) 或失败 (-1) 的整数,类似于link
和unlink
。
kernel/sysfile.c
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 = 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;
}
//printf("symlink %p\n",ip->addrs[NDIRECT + 1]);
iunlockput(ip);
end_op();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HINT4:修改open
系统调用来处理路径引用符号链接的情况。并向 kernel/fcntl.h
添加一个新标志(O_NOFOLLOW
),以提供给open
系统调用使用。
uint64
sys_open(void)
{
//...
ilock(ip);
if(ip->type == T_SYMLINK && (omode & O_NOFOLLOW) == 0) {
char target[MAXPATH];
for(int i = 0; i < MAXFOLLOWDEPTH; i++) {
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);
if(ip->type != T_SYMLINK)
break;
}
if(ip->type == T_SYMLINK){
iunlockput(ip);
end_op();
return -1;
}
//printf("path: %s,target: %s\n",path,target);
}
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip);
end_op();
return -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
[注]:链接的深度达到某个阈值(例如,10),需要通过返回错误代码来近似此值。
kernel/param.h
#define MAXPATH 128 // maximum file path name
#define MAXFOLLOWDEPTH 10 //maximum depth of links
2
结果
启动xv6内核,运行symlinktest
,可以顺利通过测试。
运行make grade
,可以通过所有的测试用例。
# 总结
总的来说,本模块涉及的文件系统实现并不难,但受益匪浅。
就大文件支持而言,其核心需要把握的是对inode
、dinode
数据结构的设计,把握好原始定义,解决起来不算难。
就符号链接而言,其核心还是设计一个系统调用,整个流程和lab1相差不多,难点在于sys_open
函数里添加打开符号链接文件时的处理。需要读者多加思考并总结。