Lab8:File System
本 lab 的任务是使 xv6 文件系统支持大文件和符号链接。
详细要求及提示见链接:
https://pdos.csail.mit.edu/6.1810/2021/labs/fs.html
参考文章:
主要参考:xv6-lab9-fs
xv6 6.S081 Lab8: fs
阅读指路:
xv6book:Chapter 8 File system
Large files (moderate)
当前xv6文件被限制为最大268blocks,因为xv6 inode有12个直接映射的块号以及1个"singly-indirect"块号,内部再指向256个块,一共12+256=268块
以下是xv6book chapter8中对于inode数据结构的示意:
目标:
增加xv6系统中文件的最大长度,创建一个大小为65803 blocks的文件(发现正好是11+256+256*256,即减少一个直接映射块,修改为二级映射即可)
【Support a “doubly-indirect” block in each inode】
方法:
kernel/fs.h中的 struct dinode 定义了磁盘上inode的数据结构。
kernel/fs.c 中的 bmap() 函数:在磁盘上找到文件数据,在读和写文件时都会调用,写入文件时,分配足够的块来装入文件内容(包括分配 indirect block)
bmap() 处理两种块号(block numbers),一种是 “逻辑块号”(和文件起始块的相对位置),另一种是 真实的磁盘块号(ip->addrs[],以及bread()的参数都是真实的磁盘块号)
bmap() 函数实现从文件的 逻辑块号 到 磁盘块号 的映射。
Hints:
- 理解 bmap() 函数是关键√
- 理解为什么加入"doubly-indirect" 块可以增加256*256(-1)个块的文件大小√
- 想清楚如何对二级映射块做 index索引,找到逻辑块号√
- 修改NDIRECT的大小,就需要修改file.h 中的struct inode的变量addrs[],保证它们有相同的元素个数√
- 修改NDIRECT后,需要重新创建fs.img,因为mkfs需要用NDIRECT来构建文件系统√
- 对于每个调用bread()的块,都需要调用brelse()(这一点结合Lab7更好理解)√
- 对于一级和二级映射的块,和原始一样,采用需要多少给多少的策略√
- itrunc() 函数需要释放所有的块(包括一级和二级映射的块)√
主要工作:
fs.h 修改:
#define NDIRECT 11 // 【DIRECT直接映射的块号,由12变为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
// NDIRECT+1 变为 【NDIRECT+2】
};
在 file.h 中同样需要修改addrs[],保证大小一致
// 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() 函数:
// Return the disk block address of the nth block in inode ip.
// If there is no such block, bmap allocates one.
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
// direct blocks(0-10)
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0) // 对应地址为0,则为未分配磁盘块
ip->addrs[bn] = addr = balloc(ip->dev); // 分配
return addr;
}
/*
*当NDIRECT <= bn < NINDIRECT
*先从对应的磁盘块读取目录
*检查一级目录是否存在(若不存在,分配磁盘块)
*在一级目录中寻找对应块
*存在返回,不存在分配并用log_wirte()写
*调用了bread(),需要调用brelse()
*/
// log_write() replaces bwrite(); a typical use is:
// bp = bread(...)
// modify bp->data[]
// log_write(bp)
// brelse(bp)
// singly-indirect blocks(11~11+255=266)
bn -= NDIRECT; //(0~255)
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); // 调用了bread就要记得brelse
return addr;
}
/* My Code: */
// (模仿以上结构)
// doubly-indirect blocks(267~266+256*256=65802)
bn -= NINDIRECT; //(0~256*256-1)
if(bn < NINDIRECT * NINDIRECT){
uint index_1 = bn / NINDIRECT; // 第几个一级映射表
uint index_2 = bn % NINDIRECT; // 第几个二级映射项
if((addr = ip->addrs[NDIRECT+1]) == 0)
ip->addrs[NDIRECT+1] = addr = balloc(ip->dev); // 分配
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[index_1]) == 0){
a[index_1] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
bp = bread(ip->dev, addr); // 继续读取第二级映射目录
a = (uint*)bp->data;
if((addr = a[index_2]) == 0){
a[index_2] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
后面紧接着的 itrunc() 函数:释放inode下的所有数据块
// Truncate inode (discard contents).
// Caller must hold ip->lock.
void
itrunc(struct inode *ip)
{
int i, j, k;
struct buf *bp, *bp_doub;
uint *a, *b;
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;
}
/*My code start*/
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]){
bp_doub = bread(ip->dev, a[j]);
b = (uint*)bp_doub->data;
for (k = 0; k<NINDIRECT;k++){
if(b[k])
bfree(ip->dev, b[k]);
}
brelse(bp_doub);
bfree(ip->dev, a[j]);
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT+1]);
ip->addrs[NDIRECT+1] = 0;
}
/*My code end*/
ip->size = 0;
iupdate(ip);
}
Symbolic links (moderate)
符号链接(软链接)是通过路径名链接的文件;当打开一个符号链接时,内核会根据该链接指向所引用的文件。符号链接类似于硬链接,但硬链接仅限于指向同一磁盘上的文件,而符号链接可以跨磁盘设备。实现这个系统调用,可以更好地理解路径名查找的工作原理。
目标:
实现系统调用 symlink() ,即软链接功能。
Hints:
-
首先是创建一个新的系统调用的准备工作
-
在 kernel/stat.h 中添加一个新的文件类型 T_SYMLINK
-
在 kernel/fcntl.h 中添加一个新标志 O_NOFOLLOW,该标志可用于系统调用open(请注意,传递给open的标志使用位运算符OR,因此新标志不应与任何现有标志重叠)
【表示将symbolic link文件视为普通文件打开】 -
在symlink中【实现指向目标文件的软链接】需要选择一个地方储存你的目标路径,比如在 inode 的数据块里
-
修改系统调用open,增加处理软链接的情况(如果该文件不存在,open返回一个fail;如果标志位O_NOFOLLOW为true,open应该打开这个软链接,而不是FOLLOW这个链接)
-
如果标志位O_NOFOLLOW为false,系统调用open应该递归,找到不是软链接的文件将其打开,需要设置一个递归深度上限防止链接的循环
-
其他系统调用不允许FOLLOW软链接
准备工作
增加系统调用步骤:
user/usys.pl:
entry("symlink");
user/user.h:
/** Symbol link */
int symlink(char *target, char *path);
kernel/syscall.h:
/** Symbol link */
#define SYS_symlink 22
kernel/syscall.c:
[SYS_symlink] sys_symlink
加入文件类型:
kernel/stat.h:
#define T_SYMLINK 4
加入标志位信息:
kernel/fcntl.h:
#define O_NOFOLLOW 0x800
主要工作:
kernel/sysfile.c:
symlink需要实现的主要功能:
- 取回两个地址:目标地址和虚拟地址
- 将目标地址储存在某个地方,你必须在open 时能够根据虚拟地址找到目标地址
那么我们直接在根据虚拟地址创建一个 struct inode 然后把目标地址储存在 inode 的磁盘块里。
这里主要关注两个操作inode 的函数: create 和 writei
/* create a new symbolic link at path that refers to target */
uint64
sys_symlink(void){
char target[MAXPATH];
char path[MAXPATH];
// 读入target和path
if(argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
return -1;
begin_op(); // called at the start of each FS system call.
struct inode *ip; // 【create an inode】
if((ip = create(path, T_SYMLINK, 0, 0)) == 0) // 根据虚拟地址创建inode
{
end_op(); // called at the end of each FS system call.
return -1; // failed
}
// [Write data to inode->data]
// Caller must hold ip->lock.
// If user_src==1, then src is a user virtual address;
// otherwise, src is a kernel address.
// Returns the number of bytes successfully written.
// If the return value is less than the requested n,
// there was an error of some kind.
// int
// writei(struct inode *ip, int user_src, uint64 src, uint off, uint n)
if(writei(ip, 0, (uint64)target, 0, MAXPATH) < MAXPATH) //将目标地址存在inode的磁盘块里
{
iunlockput(ip);
end_op(); // called at the end of each FS system call.
return -1;
}
/*
*
*【create默认上锁返回,记得解锁】
*
*/
iunlockput(ip);
end_op(); // called at the end of each FS system call.
return 0;
}
sys_open():
在具体操作文件前先通过软链接递归找到真正的地址
【读到的 path 转换为 ip 模仿 sys_open() 开始的操作即可】
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);
/*mycode start*/
// 文件类型是SYMLINK,并且O_NOFOLLOW是false,应该递归寻找到不是软链接的文件(否则按照正常文件打开)
if(!(omode & O_NOFOLLOW) && ip->type == T_SYMLINK) {
char path[MAXPATH];
// [Read data from inode]
// Caller must hold ip->lock.
// If user_dst==1, then dst is a user virtual address;
// otherwise, dst is a kernel address.
// int
// readi(struct inode *ip, int user_dst, uint64 dst, uint off, uint n)
for(int i = 0; i < 12; i++) { // 【设定12是最大递归次数】
if(readi(ip, 0, (uint64)path, 0, MAXPATH) < MAXPATH){ // #define MAXPATH 128
iunlockput(ip);
end_op();
return -1;
}
iunlockput(ip);
if((ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip);
if(ip->type != T_SYMLINK) // 找到不是软链接类型的文件inode,继续正常打开
break;
}
if(ip->type == T_SYMLINK){ // 如果超出阈值,返回fail
iunlockput(ip);
end_op();
return -1;
}
}
/*mycode end*/
//...
iunlock(ip);
end_op();
return fd;
}