感觉 xv6 的文件系统还是挺有深度的。这个 lab 带我们简单复习了下 inode 如何指向 blocks,以及通过路径名查找文件的方法。
Large files
这道题让我们增大 xv6 最大文件的大小。原本的 xv6 的每个 inode 支持 12 个普通块和一个间接块,那么原本的最大文件占据 12 + 256 = 268 个块。我们需要把 inode 修改为支持 11 个普通块、一个间接块、一个双重间接块,这样最大文件就能占据 11 + 256 + 256 * 256 = 65803 个块。
首先我们要去 fs.h 里修改 dinode 和一些常量的定义,再去 file.h 里修改 inode 的定义,主要是修改 addrs:
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define NDOUBLYINDIRECT (NINDIRECT * NINDIRECT)
#define MAXFILE (NDIRECT + NINDIRECT + NDOUBLYINDIRECT)
struct dinode {
// 其他内容保持不变
uint addrs[NDIRECT + 2];
};
struct inode {
// 其他内容保持不变
uint addrs[NDIRECT + 2];
};
然后我们可以画一画 ip->addrs 的结构,如下图所示:

对给定的 bn,如果它在 $[0,11)$ 之间,就查找普通块;如果在 $[11,11+256)$ 之间,就查找间接块;如果在 $[11+256,256+256\times256)$ 之间,就查找双重间接块。再参考 bmap 原有的代码就能完成修改了:
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 < NDOUBLYINDIRECT) {
// Load doubly 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;
}
// Load indirect block, allocating if necessary.
bp = bread(ip->dev, addr);
a = (uint *)bp->data;
if ((addr = a[(bn / NINDIRECT)]) == 0) {
addr = balloc(ip->dev);
if (addr) {
a[(bn / NINDIRECT)] = addr;
log_write(bp);
}
}
brelse(bp);
if (addr == 0) {
return 0;
}
// Find addr in block
bp = bread(ip->dev, addr);
a = (uint *)bp->data;
if ((addr = a[bn % NINDIRECT]) == 0) {
addr = balloc(ip->dev);
if (addr) {
a[bn % NINDIRECT] = addr;
log_write(bp);
}
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
我们还需要修改 itrunc,它释放传入的 inode 占用的所有数据块。仿照 itrunc 原有的代码加入释放双重间接块的代码就行:
void itrunc(struct inode *ip) {
int i, j, k;
struct buf *bp, *bbp;
uint *a, *aa;
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]) {
// Free doubly indirect block
bp = bread(ip->dev, ip->addrs[NDIRECT + 1]);
a = (uint *)bp->data;
for (j = 0; j < NINDIRECT; j++) {
if (a[j]) {
// Free indirect block j
bbp = bread(ip->dev, a[j]);
aa = (uint *)bbp->data;
for (k = 0; k < NINDIRECT; k++) {
if (aa[k])
bfree(ip->dev, aa[k]);
}
brelse(bbp);
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 加入符号链接,不过这里的符号链接是青春版。符号链接正统版应该能指向目录,我们的只能指向文件。
这里就不放配置系统调用的代码了(大家都已经品鉴过无数次了),我们先看看 man 手册是怎么介绍 symlink 的。这里我们主要看 man 2 symlink 和 man 7 symlink,前者是给使用者看的函数说明,后者是函数原理。
symlink(2): make new name for file - Linux man page
symlink(7): symbolic link handling - Linux man page
symlink 的签名是 int symlink(const char *oldpath, const char *newpath);,它创建一个名为 newpath 的指向 oldpath 的符号链接文件,这个文件里存储着 oldpath 这个字符串。
对照 hint,我们知道要去 stat.h 里新增一种叫作 T_SYMLINK 的文件类型,这就是符号链接类型。至此我们就能新增 sys_symlink 函数了。
uint64 sys_symlink(void) {
char old[MAXPATH], new[MAXPATH];
struct inode *ip;
int len;
begin_op();
if (argstr(0, old, MAXPATH) < 0 || argstr(1, new, MAXPATH) < 0 ||
(ip = create(new, T_SYMLINK, 0, 0)) == 0) {
end_op();
return -1;
}
len = strlen(old);
writei(ip, 0, (uint64)&len, 0, sizeof(len));
writei(ip, 0, (uint64)old, sizeof(len), len + 1);
iunlockput(ip);
end_op();
return 0;
}
我一开始对 iunlock、iunlockput 很疑惑,主要是不知道 iput 有什么用。其实 iput 和 inode 的工作方式密切相关,我们接下来简要分析一下。这里涉及到的文件是 fs.c。
考虑到我们只能直接和内存交互,而磁盘上又有很多 inode,我们会把有人在用的的 inode 保存在 itable.inode 中,并用 inode.ref 来记录有多少人在用。用完的人要调用 iput 说自己用完了,这样 ref 就会减少。未来我们在把磁盘 inode 放到内存中时会找到 ref == 0 的东西来替换。
举个例子,dirlookup 找路径对应的 inode,这种“找”最终总会调用 iget,iget 会检查想找的 inode 在不在内存中的 itable.inode 中,如果不在就在 itable.inode 里找个 ref == 0 的元素,把它替换成想找的 inode 并设置其 ref。相应地,调用者在用完 inode 后要负责调用 iput 说自己用完了,不然 itable.inode 的空间被占满后就不能加载新的 inode 了。
所以在 sys_symlink 的末尾我们要调用 iunlockput 而不是只调用 iunlock,毕竟我们在 sys_symlink 里不再使用这个 inode 了。
接下来我们看看 open 函数。首先照着 hint 在 fcntl.h 里加入 O_NOFOLLOW,然后对符号链接类型的文件,读取其 path 然后顺藤摸瓜就行:
uint64 sys_open(void) {
char path[MAXPATH];
int fd, omode;
struct file *f;
struct inode *ip;
int n, symcnt, len;
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 (omode & O_NOFOLLOW) {
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;
}
} else {
symcnt = 0;
if ((ip = namei(path)) == 0) {
end_op();
return -1;
}
ilock(ip);
while (ip->type == T_SYMLINK) {
symcnt++;
if (symcnt >= 10) {
iunlockput(ip);
end_op();
return -1;
}
readi(ip, 0, (uint64)&len, 0, sizeof(len));
readi(ip, 0, (uint64)path, sizeof(len), len + 1);
iunlockput(ip);
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;
}
}
if (ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)) {
iunlockput(ip);
end_op();
return -1;
}
if ((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0) {
if (f)
fileclose(f);
iunlockput(ip);
end_op();
return -1;
}
if (ip->type == T_DEVICE) {
f->type = FD_DEVICE;
f->major = ip->major;
} else {
f->type = FD_INODE;
f->off = 0;
}
f->ip = ip;
f->readable = !(omode & O_WRONLY);
f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
if ((omode & O_TRUNC) && ip->type == T_FILE) {
itrunc(ip);
}
iunlock(ip);
end_op();
return fd;
}
顺提一句,make grade 显示 Timeout FAIL 可能是因为后台的进程太多了导致 CPU 没有全力以赴。尽量把别的进程(比如浏览器)关掉只保留一个在跑 make grade 的终端。