目录
一、编写测试程序,测试XV6中一个文件能占用的最大磁盘块数。
三、修改XV6文件系统的相关代码,使得文件所能占用的最大磁盘块数有所增加,给出并分析你的代码。
实验内容:
由于磁盘块地址的数量是有限的,所以一个文件所能占用的最大磁盘块数也是有限的。本次实验中,要完成的任务如下:
一、设计一个测试方法并编写测试程序,测试XV6中一个文件所能占用的最大磁盘块数。
二、分析XV6文件系统的相关代码,分析上一步的测试结果。
三、修改XV6文件系统的相关代码,使得文件所能占用的最大磁盘块数有所增加,给出并分析你的代码。
四、重新第一步中的测试程序,再次观察文件所能占用的最大磁盘块数,并分析所得结论。
要做好这个实验,首先要了解什么是i节点。i节点是用来存放文件的属性和文件磁盘块地址等信息,XV6的i-节点结构如下:
一、编写测试程序,测试XV6中一个文件能占用的最大磁盘块数。
1. 编写程序代码:
#include "types.h"
#include "stat.h"
#include "user.h"
#include "unistd.h"
#include "fcntl.h"
#include "param.h"
#define BUF_SIZE 3000
int main() {
int file = open("data.txt", O_RDWR | O_CREATE); // 创建文件data.txt
int readMe = open("README", O_RDONLY); // 读取文件README
char buf[BUF_SIZE]; // 读写缓冲区
int bytes_read = read(readMe, buf, BUF_SIZE);
while(1) {
int bytes_write = write(file, buf, BUF_SIZE);
if(bytes_write < 0) { // 文件空间写不进去3000字节了
printf(2, "Error writing to data.txt\n");
break;
}
}
if (bytes_read < 0) {
printf(2, "Error reading from source file README\n");
}
close(file);
close(readMe);
return 0;
}
原理:该代码是创建一个data.txt的文件,用来读取README文件,每次读取3000字节(是用#define BUF_SIZE 3000来定义每次读取的字节数),当文件剩余的空间数小于3000字节,也就是没法再读下一次时,就抛出异常情况提示:【Error writing to data.txt\n】。
2. 测试运行结果
终端中cd进入xv6,输入命令
sudo make qemu
进入qemu,接着输入命令
ls
可以看到xv6中的所有文件,运行刚刚编写代码的文件,再次输入命令ls,就会看到data.txt文件已经创造出来了,并且已经快读满了,详细内容在下一步中结合xv6的源码进行分析:
二、分析XV6文件系统的相关代码,分析上一步的测试结果。
1. fs.h文件代码分析(相关代码部分逐行分析)
#define BSIZE 512
一个块大小为512字节
#define NDIRECT 12
最多可以存储12个直接地址。
#define NINDIRECT (BSIZE / sizeof(uint))
一次间接。BSIZE=512,sizeof(uint) 是占4个字节,(BSIZE / sizeof(uint))= 512/4 = 128,即NINDIRECT=128,也就是一个一次间接最多能存128个块号。
#dedine MAXFILE (NDIRECT + NINDIRECT)
意思是最大文件长度=直接地址+一次间接,上文得知,最大文件长度=直接地址NDIRECT+一次间接NINDIRECT=12+512=524。总量是(12+512)* 512=72240。
2. 上一步中的测试结果分析
由代码分析得知最大文件空间为72240,而写入的data.txt文件在70536时停止了。因为我的代码写的是每次读取3000字节存入,到存到70536时,剩余的空间不足3000了,所以停止存入,和代码分析得出的空间最大为72240是一致的。
三、修改XV6文件系统的相关代码,使得文件所能占用的最大磁盘块数有所增加,给出并分析你的代码。
思路:要使得文件所能占用的最大磁盘块数有所增加,就是要改变间接方式。比如直接地址,有12个直接地址就是12个页表项对应12个页面,每个页面大小4M的话,就有12*4M大小的空间。若改成直接地址+一次间接,比如12个直接地址+1个一次间接,就有(12*4M)+(1*1024*4M)空间了。类似的,我们也可以增加二次间接等等。
那么如何修改间接方式呢,我们需要改file.h,fs.h,fs.c这3个文件。
1. 修改fs.h
上一步中已经分析过fs.h文件中相关代码的含义了,下面就来看看如何修改
源码:
#dedine MAXFILE (NDIRECT + NINDIRECT)
意思是 最大文件长度 = 直接地址 + 一次间接,如果要修改长度
修改方法1:增加一次间接,修改为
#dedine MAXFILE (NDIRECT + (NINDIRECT*2))
修改方法2:增加二次间接,修改为
#dedine MAXFILE (NDIRECT + NINDIRECT + (NINDIRECT*NINDIRECT))
修改方法3:修改为直接地址(减少长度),则一个文件长度最大为12
#dedine MAXFILE (NDIRECT)
2. 修改file.h
源码:(i结点)
struct inode {
...
uint addrs[NDIRECT+1] // 是原本的地址
}
修改方法1:增加一个一次间接
Uint addrs[NDIRECT+1+1]
因为addrs 数组是用于存储文件数据块的直接块地址和间接块地址,因此如果增加了一个一次间接块,就需要在数组也再增加一个位置来存放增加的一次间接块地址,也就是将 addrs 数组的大小再加1,即从 NDIRECT+1 修改为 NDIRECT+1+1。原理如图所示:
3. 修改fs.c
要实现文件最大长度为直接地址加两个一次间接的功能,需要更改与地址映射有关的 bmap 函数。bmap 函数是将给定的逻辑块号映射到相应的磁盘块号,目前源码中已经处理了直接块和一个一次间接块,需要增加一个if分支处理第二个一次间接块。
源码:
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;
}
panic("bmap: out of range");
}
修改代码:
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 < 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]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
原本的bmap函数通过if(bn < NDIRECT)处理了直接块,又通过if(bn < NINDIRECT)处理了一次间接块。为什么一次间接块是if(bn < NINDIRECT)而不是if(bn < NDIRECT+NINDIRECT)呢?这是我一开始困惑的点,后来我发现是因为处理完直接块后,bn-= NDIRECT了,也就是说如果给定的块号不是直接块,也就是没有进入第一个分支if(bn < NDIRECT)的话,就直接减掉了直接块的地址,剩下的bn就直接从一次间接块开始了,所以bn -= NDIRECT再if(bn < NINDIRECT)就相当于if(bn < NDIRECT+NINDIRECT)。因此我在增加处理第二个一次间接块时,延用了这个方法。如果给定的块号不是第一个一次间接块,就在减掉了直接块数量的基础上,把一次间接块的数量减掉,也就是bn -= NINDIRECT,再让if(bn < NINDIRECT),就能直接处理到第二个一次间接块了。
因为都是在处理一次间接块,只是顺序不同,在数组中所处的位置不同,操作都是相同的,因此只需要改变一下数组的索引,也就是将addrs[NDIRECT] 变为addrs[NDIRECT + 1],第三个if分支剩下的操作就都和第二个if分支的操作逻辑是一样的了。