文章目录
- Large files
- task
- hints
- 思路
- Symbolic links
- task
- hints
- 思路
- sys_symlink
- sys_open
Large files
目标:11+256+256*256个block
inode的格式在fs.h
的struct dinode
中被定义,你需要特别注意以下几点
NDIRECT
NINDIRECT
MAXFILE
addrs[]
在磁盘上找一个文件数据是通过fs.c
中的bmap()
实现的
- 无论是读还是写文件,都调用了
bmap
- 在写文件时,
bmap()
分配了新的block去容纳文件内容,在必要的时候,会去分配一个非直接映射块
bmap
处理了两种块号
bn
参数是一个逻辑块号,是在一个文件中相对于文件起始位置的块号- 而
ip->addrs[]
和bread()
的参数中的块号,都是磁盘块号 - 你可以将
bmap
看做是逻辑块号到物理块号的映射
task
- 修改
bmap
使其通过addrs[]
支持11+256+256*256个磁盘块 - 如果能通过
bigfile
和usertests
测试,就说明成功
hints
- 保证你理解了
bmap()
。画图理清楚inode中不同类型的块的指向和作用 - 想一下你如何通过逻辑块号索引一级地址块和直接地址块
- 如果你改变了
NDIRECT
,你可能需要去改变file.h
中struct inode
中的addrs[]
的声明。保证struct inode
和struct dinode
在addrs数组中有相同数量的元素 - 如果你改变了
NDIRECT
的定义,保证你创造了一个新的fs.img
,即make clean 然后make qemu - 对任何一个block进行
bread
之后都要记得brelse
- 你应该只在必要的时候分配一级地址和二级地址
- 保证
itrunc
将一个文件所有的block都free了,包括新增的二级地址
思路
文件系统这一块,感觉学的很难,各种函数很多,但是这个task这一块是不太难,不过我也做了好久。。。
这个task只需要修改bmap
和itrunc
两个函数,以及一些宏常量,之所以只修改这么点东西就可以给一个文件扩容,应该是因为其他函数都是通过bmap
来获取逻辑块对应的物理块号的,它们只负责要和写,根本不管到底使用了多少block
首先,需要修改一些宏常量,并且将dinode
和inode
的addrs
数组长度修改
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define N2INDIRECT (NINDIRECT * NINDIRECT)
#define MAXFILE (NDIRECT + NINDIRECT + N2INDIRECT)uint addrs[NDIRECT + 2];
然后,修改bitmap函数,首先可以看一下bitmap如何处理直接地址和一级地址,学习一下基本的思路,我们这里基本就是嵌套一下一级地址的情况。
具体实现如下:
-
首先,将逻辑块号减去一级地址的块数
-
然后这里使用了一个search函数
uint search(struct inode *ip, uint index, uint bn, uint *addrs)
这个函数的意思是,目前寻找的文件的inode是ip,现在要去addrs数组的index项指向的那个多级地址块上的第bn个block的地址,如果第bn块处没有地址,那么就创建一个。所以这个本质上就是一个一级地址的情况,通过两次调用这个函数,就可以完成我们二级地址的查找
代码如下
uint search(struct inode *ip, uint index, uint bn, uint *addrs) {uint addr, *a;struct buf *bp;if ((addr = addrs[index]) == 0) {addrs[index] = 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;
}static uint
bmap(struct inode *ip, uint bn) {uint addr, *a;struct buf *bp;if (bn < NDIRECT) {if ((addr = ip->addrs[bn]) == 0)ip->addrs[bn] = addr = balloc(ip->dev);return addr;}bn -= NDIRECT;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;if (bn < N2INDIRECT) {int index = bn / NINDIRECT;int nbn = bn % NINDIRECT;addr = search(ip, NDIRECT + 1, index, ip->addrs);bp = bread(ip->dev, ip->addrs[NDIRECT + 1]);addr = search(ip, index, nbn, (uint *)bp->data);brelse(bp);return addr;}panic("bmap: out of range");
}
Symbolic links
task
- 增加一个系统调用
symlink(char *target, char *path)
- 需要通过
symlinktest
,usertests
hints
-
增加系统调用的流程
- Makefile,加入的不是
symlink
,而是symlinktest
- user/usys.pl
- user/user.h
- kernel/sysfile.c
- syscall.h && syscall.c
- Makefile,加入的不是
-
在
kernel/stat.h
中增加一个新的文件类型T_SYMLINK
代表软链接 -
在
kernel/fcntl.h
中增加一个标志位O_NOFOLLOW
,因为open文件的标志位是or起来的,因此你不能和已有的发生重叠 -
你需要找一个位置去存储软链接的目标地址,例如在
inode数据块
symlink
应该返回0表示成功,返回-1表示失败 -
修改
open
系统调用去处理一个路径指向软链接的情况如果文件不存在,open必须失败
当一个进程在open中指定了
O_NOFOLLOW
,则说明不是打开target,而是打开软链接 -
如果被链接的文件也是一个软链接,你必须递归地访问,直到访问一个正确的文件
你可以定义最大递归的层数,比如10
-
Other system calls (e.g., link and unlink) must not follow symbolic links; these system calls operate on the symbolic link itself.
-
你不需要处理软链接到目录的情况
思路
这玩意看着很抽象,但是其实搞清楚以下几件事就行了
-
访问文件就是先访问得到inode,然后通过inode去写对应的文件
通过readi就可以读取path对应的inode
通过writei就可以在inode对应的文件中去写
-
软链接的作用
在open它的时候,它会直接导向target
-
创建软链接,分为以下几步
- 首先创建一个文件,即获得一个inode,这个可以通过create函数实现
- 将我们的target写入这个inode,我们就将target存在第一个文件数据块就行了
-
打开软链接对应的文件,分为以下几步
- 在open中获取软链接对应的真实的inode
- 然后就让open对这个inode进行分配fd和file的操作即可
代码很少,但是思路真的很有意思
sys_symlink
这里可以先看看create和open的代码是如何使用xv6提供的一些api的,主要是
create
,writei
,readi
- 首先,我们需要将target和path这两个参数从寄存器中读出来,使用argstr即可
- 然后,我们需要创建inode,使用create函数,第一个参数是软链接的路径,第二个参数是文件类型,我们这里当然是新建的那个,后面两个参数不知道啥意思,模仿其他函数的使用,填0
- 将target写入软链接文件,也就是写入数据块,使用writei函数
- 第一个参数是inode的指针
- 第三个参数是我们写入的东西的地址,这里就是target的地址
- 第四个参数是写到文件的哪里,其实就是使用一个偏移量完成,我们软链接文件没其他的文件内容,就写到偏移量为0的地方,也就是文件的起始位置
- 最后一个参数是写入多少个字节
- 注意,这里如果操作失败了,需要将这个inode的锁给解开了
bug:没有正确判断函数的返回值,说的就是writei,主要是因为writei的参数太多了,当我一个一个填完参数之后,就忘记判断它的返回值是否小于0了
sys_symlink(void) {char target[MAXPATH], path[MAXPATH];if (argstr(0, target, MAXPATH) < 0) {return -1;}if (argstr(1, path, MAXPATH) < 0) {return -1;}struct inode *ip;// 创建软链接文件的inodebegin_op();if ((ip = create(path, T_SYMLINK, 0, 0)) == 0) {end_op();return -1;}// 将target写入软链接的数据块中if (writei(ip, 0, (uint64)target, 0, strlen(target)) < 0) {iunlockput(ip);end_op();return -1;}iunlockput(ip);end_op();return 0;
}
这里还有一些细节,比如begin_op
和end_op
,比如create
之后是会自动给inode
上锁的
sys_open
这一个函数的修改就是对应我们真正使用软链接的情况
如果我们设置了O_NOFOLLOW,那说明不是访问target,就是想访问这个软链接,那就正常open就行了
而如果我们没设置,说明实际上要访问的是target,在这种情况下,我们只需要在open函数分配fd和file之前,将ip指针切换成target的ip地址即可,因此,找一个适当的位置截胡即可。我这里选择的是在获取已有文件的inode时进行的
- 首先,如果进入了else分支,都进入这个while循环,这个while循环走来就读取path的inode,如果不是软链接或者不是需要target的情况,那就直接break,这样的话就和之前的open一样了
- 如果需要找target,那就会读出当前软链接文件的target,然后解锁当前inode,进入下一轮while循环,获取target的inode,如果还是软链接,则递归操作,这里是通过迭代代替递归
- 这中间关键的函数是readi函数,我看了下实现,具体的操作其实看不太懂。这里有个小问题,那就是最后一个参数应该传入的是我们想读入的path的长度,但是我们这里不知道path多长,只能传入MAXPATH。这样有没有可能多读了呢?我估计是因为这些数据块的没有被write的地方都是0,那么多读一点正好还给path当结尾0了
if (omode & O_CREATE) {ip = create(path, T_FILE, 0, 0);if (ip == 0) {end_op();return -1;}} else {int depth = 0;while (1) {if ((ip = namei(path)) == 0) {end_op();return -1;}ilock(ip);if ((ip->type == T_SYMLINK) && (!(omode & O_NOFOLLOW))) {if (++depth > 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;}}
这个lab说简单也简单,说难也难,主要是我人菜还不愿意慢慢学