在这一节,我们将为 xv6 的文件系统加入大文件和符号链接。
这个部分需要我们增加 xv6 文件的大小上限,由于 inode 结构体中有 12 个直接映射项,1 个一级间接映射项,所以 xv6 文件系统中的最大文件大小为 (12 + (1024B / 4B)) 个块,即 268 个块,在 xv6 中,每个块大小为 1024B。而通过再增加一个二级间接映射项可以让我们的文件大小增加到 (11 + 256 + 256*256) = 65803 个块,而下面我们就是要实现这个二级间接映射项。
首先修改 fs.h 文件中的宏和 dinode 中 addrs 的大小,以及 file.h 文件中 inode 结构体中 addrs 的大小也要同步修改过来。这样做的目的是减少一个直接映射项,增加一个二级间接映射项。
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT * NINDIRECT)
// 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
};
// 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];
};
然后再修改 fs.c 文件中的 bmap 和 itrunc 函数,bmap 函数的作用是通过逻辑块号 bn 来到 addrs 数组中找到其在磁盘上的真实块号;而 itrunc 函数的作用是将 inode 的 addrs 清空。直接映射项的真实块号就是 addrs[bn],而一级间接映射项的真实块号还需要从 addrs[NDIRECT] 指向的块上寻找真实块号,二级间接映射项需要从 addrs[NDIRECT+1] 指向的块上先找到对应一级间接映射项,然后再找到真实块号。
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 < NINDIRECT * NINDIRECT){
if((addr = ip->addrs[NDIRECT+1]) == 0){
addr = balloc(ip->dev);
if(addr == 0)
return 0;
ip->addrs[NDIRECT+1] = addr;
}
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[bn/NINDIRECT]) == 0){
addr = balloc(ip->dev);
if(addr == 0)
return 0;
a[bn/NINDIRECT] = addr;
log_write(bp);
}
brelse(bp);
bn = bn % NINDIRECT;
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;
}
panic("bmap: out of range");
}
void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp;
struct buf *bp1;
uint *a;
uint *a1;
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]){
bp = bread(ip->dev, ip->addrs[NDIRECT+1]);
a = (uint*)bp->data;
for(i = 0; i < NINDIRECT; i++){
if(a[i]){
bp1 = bread(ip->dev, a[i]);
a1 = (uint*)bp1->data;
for(j = 0; j < NINDIRECT; j++){
if(a1[j])
bfree(ip->dev, a1[j]);
}
brelse(bp1);
bfree(ip->dev, a[i]);
a[i] = 0;
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT+1]);
ip->addrs[NDIRECT+1] = 0;
}
ip->size = 0;
iupdate(ip);
}
最后通过测试。
首先创建一个新系统调用 symlink,怎么添加系统调用在之前的 lab 中已经做过了,这里就不多赘述了。
下面是 symlink 系统调用的实现,首先使用 create 创建一个新文件,文件的类型为 T_SYMLINK,然后使用 writei 向文件的数据块写入 target 路径,这样使用 open 系统调用时如果发现文件是链接文件,则从文件的数据块中读取 target 文件的路径。最后记得使用 iunlockput(ip) 释放 inode 的锁和引用:
uint64 sys_symlink(void)
{
int n;
char target[MAXPATH];
char path[MAXPATH];
struct inode *ip;
if((n = argstr(0, target, MAXPATH)) < 0 || argstr(1, path, MAXPATH) < 0){
return -1;
}
begin_op();
ip = create(path, T_SYMLINK, 0, 0);
if(ip == 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;
}
然后需要修改 open 系统调用,当识别到文件的类型为 T_SYMLINK 的时候,并且模式不为 O_NOFOLLOW,则循环地通过链接路径找到被链接的文件,若被链接的文件就是当前文件本身,形成的环路,或者链接的深度超过了 10,则报错:
uint64
sys_open(void)
{
char path[MAXPATH];
char target[MAXPATH];
int fd, omode;
struct file *f;
struct inode *ip;
struct inode *tip;
int n, depth;
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((ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip);
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip);
end_op();
return -1;
}
}
depth = 0;
while(ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)){
if(readi(ip, 0, (uint64)target, 0, MAXPATH) != MAXPATH)
panic("open symlink: readi");
// the links form a cycle
if(strncmp(target, path, n) == 0){
iunlockput(ip);
end_op();
return -1;
}
// target file does not exist
if((tip = namei(target)) == 0){
iunlockput(ip);
end_op();
return -1;
}
iunlock(ip);
ilock(tip);
ip = tip;
depth++;
if(depth >= 10){
iunlockput(ip);
end_op();
return -1;
}
}
.....
.....
最后通过所有测试。