课程地址:https://pdos.csail.mit.edu/6.S081/2020/schedule.html
Lab 地址:https://pdos.csail.mit.edu/6.S081/2020/labs/fs.html
我的代码地址:https://github.com/Amroning/MIT6.S081/tree/fs
xv6手册:https://pdos.csail.mit.edu/6.S081/2020/xv6/book-riscv-rev1.pdf
相关翻译:https://xv6.dgs.zone/labs/requirements/lab9.html
参考博客:https://blog.miigon.net/posts/s081-lab9-file-system/
学习笔记记录,如有错误恳请各位大佬指正
Lab9: file system
增加XV6系统文件的最大大小(当前只支持268kb以下的文件);实现软链接
完整题目要求请去顶部链接查看
Large files(moderate)
在本作业中,您将增加xv6文件的最大大小。目前,xv6文件限制为268个块或
268*BSIZE
字节(在xv6中BSIZE
为1024)。此限制来自以下事实:一个xv6 inode包含12个“直接”块号和一个“间接”块号,“一级间接”块指一个最多可容纳256个块号的块,总共12+256=268个块。您将更改xv6文件系统代码,以支持每个inode中可包含256个一级间接块地址的“二级间接”块,每个一级间接块最多可以包含256个数据块地址。结果将是一个文件将能够包含多达65803个块,或256*256+256+11个块(11而不是12,因为我们将为二级间接块牺牲一个直接块号)
原本的XV6文件系统中,inode的结构如下:
// kernel/fs.h
#define NDIRECT 12
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + 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+1]; // Data block addresses
};
addrs
字段用来索引记录数据的所在盘块号。每个文件所占用的前 12 个盘块的盘块号是直接记录在 inode 中的(每个盘块 1024 字节),所以对于任何文件的前 12 KB 数据,都可以通过访问 inode 直接得到盘块号。这一部分称为直接记录盘块(addr[0~11],12个直接块)
对于大于 12 个盘块的文件,大于 12 个盘块的部分,会分配一个额外的一级索引表(addr[11],1024Byte),用于存储这部分数据的所在盘块号。一个索引项是4字节,一级索引表可以包含 BSIZE(1024) / 4 = 256 个盘块号,加上 inode 中的 12 个盘块号,一个文件最多可以使用 12+256 = 268 个盘块,也就是 268KB
该实验需要修改inode的结构,修改成11个直接块,1个一级间接块,1个二级间接块。所以可以先修改inode的结构体,注意这是磁盘inode的结构体:
// kernel/fs.h
#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]; // 0~10:直接索引 11:一级间接索引 12:二级间接索引
};
然后还需要修改内存中inode的副本结构体:
// kernel/fs.h
// 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];
};
直接块腾一个位置出来给二级间接块
需要修改的第一个函数是bmap
,这个函数是将inode中的第bn个逻辑块号和物理块号简历映射,其原理备注在源代码中(这里是解释原本的bmap
):
// kernel/fs.c
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
// 如果bn小于直接块数量,则按直接映射处理
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0) // 如果对应的物理块号为0,表示还没分配,则分配一个新的物理块,建立映射
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
// 到这里已经是间接块了,减去直接块的数量得到间接块的逻辑块号
bn -= NDIRECT;
if(bn < NINDIRECT){
// 如果间接块还没分配,分配一个
if((addr = ip->addrs[NDIRECT]) == 0) // 0~NDIRECT-1是直接块,NDIRECT是间接块
ip->addrs[NDIRECT] = addr = balloc(ip->dev); // 此时这个块可以映射256个块
// 获取刚分配的缓存块,检查bn对应的块,如果为0则没有分配,建立映射
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); // 建立映射结束,释放掉缓存块(可以通过ip->dev和bn找到这个块了)
return addr;
}
panic("bmap: out of range");
}
清楚了原理,加上刚才增加的二级映射,就很好修改了:
// kernel/fs.c
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
// 如果bn小于直接块数量,则按直接映射处理
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0) // 如果对应的物理块号为0,表示还没分配,则分配一个新的物理块,建立映射
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
// 到这里已经是一级间接块了,减去直接块的数量得到间接块的逻辑块号
bn -= NDIRECT;
if(bn < NINDIRECT){
// 如果间接块还没分配,分配一个
if((addr = ip->addrs[NDIRECT]) == 0) // 0~NDIRECT-1是直接块,NDIRECT是间接块
ip->addrs[NDIRECT] = addr = balloc(ip->dev); // 此时这个块可以映射256个块
// 获取刚分配的缓存块,检查bn对应的块,如果为0则没有分配,建立映射
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); // 建立映射结束,释放掉缓存块(可以通过ip->dev和bn找到这个块了)
return addr;
}
// 到这里已经是二级间接块了,减去一级间接块的数量得到二级件结块的逻辑块号
bn -= NINDIRECT;
// 原理很上面的一样
if (bn < NINDIRECT * 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[bn / NINDIRECT]) == 0) { // bn处在二级索引中间级的第 bn/NINDIRECT 个索引处
a[bn / NINDIRECT] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
// 最后一级索引
bn %= NINDIRECT;
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;
}
还有一个函数itrunc
,释放该inode所映射的所有数据块。原理类似,直接循环释放所有数据块:
// kernel/fs.c
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;
}
// 释放二级间接块的映射
if (ip->addrs[NDIRECT + 1]) {
bp = bread(ip->dev, ip->addrs[NDIRECT + 1]);
a = (uint*)bp->data;
for (int i = 0;i < NINDIRECT;++i) {
if (a[i]) {
struct buf* bp2 = bread(ip->dev, a[i]);
uint* a2 = (uint*)bp2->data;
for (int j = 0;j < NINDIRECT;++j) {
if (a2[j])
bfree(ip->dev, a2[j]);
}
brelse(bp2);
bfree(ip->dev, a[i]);
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT + 1]);
ip->addrs[NDIRECT + 1] = 0;
}
ip->size = 0;
iupdate(ip);
}
此时可以验证实验是否通过
Symbolic links(moderate)
您将实现
symlink(char *target, char *path)
系统调用,该调用在引用由target
命名的文件的路径处创建一个新的符号链接。有关更多信息,请参阅symlink
手册页(注:执行man symlink
)。要进行测试,请将symlinktest
添加到*Makefile*并运行它。当测试产生以下输出(包括usertests
运行成功)时,您就完成本作业了。
该实验需要实现软链接机制,实现一个软链接系统调用函数symlink
我习惯先把新的系统调用函数声明给做好,详细添加步骤参考Lab2笔记,涉及文件:syscall.h
、syscall.c
、usys.pl
、user.h
根据提示,添加新的文件类型T_SYMLINK
,表示文件为软链接文件:
// kernel/stat.h
#define T_DIR 1 // Directory
#define T_FILE 2 // File
#define T_DEVICE 3 // Device
#define T_SYMLINK 4 // 软链接
添加新的文件标志位O_NOFOLLOW
,该标志可用于open
系统调用:
// kernel/fcntl.h
#define O_RDONLY 0x000
#define O_WRONLY 0x001
#define O_RDWR 0x002
#define O_CREATE 0x200
#define O_TRUNC 0x400
#define O_NOFOLLOW 0x800
实现具体的sys_symlink
,使用create
创建一个加了锁的指向源文件的inode,再将链接目标文件地址通过writei
写入inode,操作完之后使用iunlockput
解锁创建的inode并使其引用计数-1,表示创建软链接的操作结束,释放锁:
// kernel/sysfile.c
// 软链接
uint64
sys_symlink(void) {
struct inode* ip;
char target[MAXPATH], path[MAXPATH];
if (argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0)
return -1;
begin_op();
ip = create(path, T_SYMLINK, 0, 0); // 创建一个新的inode,类型为T_SYMLINK,指向path文件
if (ip == 0) {
end_op();
return -1;
}
if (writei(ip, 0, (uint64)target, 0, strlen(target)) < 0) { // 将target路径写入inode
end_op();
return -1;
}
iunlockput(ip);
end_op();
return 0;
}
修改sys_open
函数,如果通过循环调用namei
获取对应的inode,如果指向的仍是软链接,就继续循环读取,直到找到真正指向的文件,或者超过了一定的链接深度:
// kernel/sysfile.c
uint64
sys_open(void)
{
......
if(omode & O_CREATE){
ip = create(path, T_FILE, 0, 0);
if(ip == 0){
end_op();
return -1;
}
}
else {
int symlink_depth = 0;
while (1) {
if ((ip = namei(path)) == 0) { // 解析路径,获取对应的inode
end_op();
return -1;
}
ilock(ip);
if (ip->type == T_SYMLINK && (omode & O_NOFOLLOW) == 0) { //如果当前指向的仍是软链接,则继续循环
if (++symlink_depth > 10) { // 链接深度超过10层就退出
iunlockput(ip);
end_op();
return -1;
}
if (readi(ip, 0, (uint64)path, 0, MAXPATH) < 0) { // 读取链接的目标路径
iunlockput(ip);
end_op();
return -1;
}
iunlockput(ip);
}
else
break;
}
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;
}
......
}
此时可以验证实验是否通过
标签:addr,Mit6,ip,bn,system,NDIRECT,dev,file,inode From: https://www.cnblogs.com/Amroning/p/18548087