目录
- Task 1:Large files
- Task 2:Symbolic links
- 2.1 增加一个系统调用 symlink
- 2.2 新增文件类型
- 2.3 新增 NOFOLLOW 标志位
- 2.4 实现 sys_symlink 系统调用
- 2.5 修改 sys_open 函数
- 2.6 测试
Task 1:Large files
现在的 xv6 系统中,一个文件的 data block 的索引目录最多到一级间接索引:
而这个 task 为了增大可支持的最大文件大小,要求扩展出二级间接索引,如下图:
代码实现上,主要是扩展 fs.c 文件中的 bmap()
和 itrunc()
函数:
bmap()
:传入相对的 block number(比如这个文件的第一个 block 的相对 block number 就是 0),返回这个 block 的地址。如果没有的话需要分配创建。itrunc()
:释放一个文件的所有 blocks
直接索引表上有 13 个元素位置,原本是 12 个直接索引 + 1 个一级间接索引,现在需要改成 11 个直接索引 + 1 个一级间接索引 + 1 个二级间接索引。
修改 fs.h
文件中的相关参数:
相应的修改 dinode 和 inode 中 addrs 的元素数量(因为宏 NDIRECT 数值变了,所以这两个地方需要相应地修改一下):
在 bmap()
函数中增加对耳机间接索引的支持:
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
// 检查 bn 是否可以走直接索引
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0) // 如果发现 bn 的 block 还不存在,则 alloc 一个 block
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
bn -= NDIRECT;
// 检查 bn 是否可以走一级间接索引
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;
// 检查 bn 是否可以走二级间接索引
if (bn < N_DOUBLE_INDIRECT) {
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 level1 = bn / NINDIRECT; // 在一级索引目录中的 index
if ((addr = a[level1]) == 0) {
a[level1] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
// 再从二级间接索引中找
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
uint level2 = bn % NINDIRECT; // 在二级索引目录中的 index
if ((addr = a[level2]) == 0) {
a[level2] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
在 itrunc()
函数中增加对二级间接索引的支持:
void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp;
uint *a;
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;
}
// 释放二级间接索引目录下的 blocks
if (ip->addrs[NDIRECT + 1]) {
// 从直接索引中记录的指针,访问一级间接索引
struct buf *bp1 = bread(ip->dev, ip->addrs[NDIRECT + 1]); // 一级间接索引的 block
uint *a1 = (uint*)bp1->data;
for(j = 0; j < NINDIRECT; j++) { // 遍历一级间接索引的所有索引项
if (a1[j]) {
// 从一级间接索引中记录的指针,访问二级间接索引
struct buf *bp2 = bread(ip->dev, a1[j]); // 二级间接索引的 block
uint *a2 = (uint*)bp2->data;
for(int k = 0; k < NINDIRECT; k++) { // 遍历二级间接索引的所有索引项
if (a2[k])
bfree(ip->dev, a2[k]); // 释放这个 data block
}
brelse(bp2);
bfree(ip->dev, a1[j]); // 释放二级间接索引的 block
}
//释放完了二阶指针块,
}
brelse(bp1);
bfree(ip->dev, ip->addrs[NDIRECT + 1]); // 释放一级间接索引的 block
ip->addrs[NDIRECT + 1] = 0;
}
ip->size = 0;
iupdate(ip);
}
测试:
usertests 的结果:
Task 2:Symbolic links
这个 task 目标是新增一个系统调用,支持创建符号连接,就是一种新的文件类型,它链接到了另一个实际的文件。实现的思路就是,增加一种新的文件类型 “symlink”,创建 symlink 时,在文件内容中写入实际链接的文件名,这样就可以实现链接过去了。
2.1 增加一个系统调用 symlink
在 syscall.h 中增加一个新的系统调用号:
在 user/usys.pl 中增加一个 entry:
在 user/user.h 中增加声明:
在 kernel/syscall.c 中的系统调用表中增加这个系统调用:
2.2 新增文件类型
我们需要新增一种 SYMLINK 的文件类型:
2.3 新增 NOFOLLOW 标志位
按照 lab 官网给出的提示:
Add a new flag to kernel/fcntl.h, (O_NOFOLLOW), that can be used with the open system call. Note that flags passed to open are combined using a bitwise OR operator, so your new flag should not overlap with any existing flags. This will let you compile user/symlinktest.c once you add it to the Makefile.
我们在 kernel/fcntl.h 中增加这个 flag:
2.4 实现 sys_symlink 系统调用
在 kernel/sysfile.c 中,我们实现 sys_symlink()
函数,用来在 path 上创建一个链接到 target 的 SYMLINK 文件:
uint64 sys_symlink(void)
{
char target[MAXPATH], path[MAXPATH]; // 在 path 上创建一个 symlink 指向 target
struct inode *dp, *ip;
// 校验并绑定参数
if (argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0) {
return -1;
}
begin_op(); // 开始一个文件系统操作
// 校验 target 是否合法
if ((ip = namei(target)) != 0 && ip->type == T_DIR) {
end_op();
goto bad;
}
// 创建 symlink 文件
dp = create(path, T_SYMLINK, 0, 0); // 这一步会同时为 inode 加锁
if (dp == 0) {
goto bad;
}
// 在 symlink 文件中写入 target 信息
int targetLen = strlen(target);
if (writei(dp, 0, (uint64)target, 0, targetLen) != targetLen) {
panic("symlink: writei"); // 模仿 sys_unlink() 函数中的写法
}
iunlockput(dp); // 释放 inode 的 lock
end_op();
return 0;
bad:
end_op();
return -1;
}
2.5 修改 sys_open 函数
我们需要修改 sys_open() 函数,官网给出的提示是:
- Modify the open system call to handle the case where the path refers to a symbolic link. If the file does not exist, open must fail. When a process specifies O_NOFOLLOW in the flags to open, open should open the symlink (and not follow the symbolic link).
- If the linked file is also a symbolic link, you must recursively follow it until a non-link file is reached. If the links form a cycle, you must return an error code. You may approximate this by returning an error code if the depth of links reaches some threshold (e.g., 10).
该函数的完整代码如下:
uint64
sys_open(void)
{
char path[MAXPATH];
int fd, omode;
struct file *f;
struct inode *ip;
int n;
if((n = argstr(0, path, MAXPATH)) < 0 || argint(1, &omode) < 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((ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip);
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip);
end_op();
return -1;
}
}
// 当没有 NOFOLLOW 标志时,对于 SYMLINK 文件,默认的行为是打开的链接过去的 target 文件
if ((omode & O_NOFOLLOW) == 0) {
struct inode *dp;
char target[MAXPATH];
int i;
for (i = 0; i < 10 && ip->type == T_SYMLINK; i++) {
// 从 inode 读取数据
if (readi(ip, 0, (uint64)target, 0, MAXPATH) == 0) {
iunlockput(ip);
end_op();
return -1;
}
// 读取 target 的 inode
dp = namei(target);
if (dp == 0) {
iunlockput(ip);
end_op();
return -1;
}
iunlockput(ip);
ip = dp;
ilock(ip);
}
// 如果 10 轮循环后也没找到 target 原文件
if (i == 10) {
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;
}
2.6 测试
完成以上代码实现后,测试就可以跑通过了: