操作系统实验:文件系统(xv6的i节点混合索引)

目录

一、编写测试程序,测试XV6中一个文件能占用的最大磁盘块数。

二、分析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分支的操作逻辑是一样的了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值