Nachos Lab4 文件系统

Lab4 文件系统

todo:makedep

【实习建议】

  1. 数据结构的修改和维护
  • 文件管理的升级基于对原有Nachos数据结构的修改。增加文件的描述信息需对文件头结构进行简单修改。多级目录中可创建目录也可创建文件,应根据实际的文件类型初始化文件头信息。
  1. 实现多级目录应当注意
  • 目录文件的含义。每个目录对应一个文件,通过此文件可了解其子目录及父目录的信息。

  • Nachos的目录文件大小是预先定义的,但实际上,目录文件的大小应根据内容确定,且能改变。

  • 实现多级目录后,添加、删除目录项要根据具体的路径,对树的遍历要有深刻的理解。

  1. 为了实现文件长度无限,可以采取混合索引的分配方式。

任务完成情况

ExercisesY/N
Exercise1Y
Exercise2Y
Exercise3Y
Exercise4Y
Exercise5Y
Exercise6Y
Exerciseu7Y
Challenge1Y
Challenge2Y

准备工作

我在main.cc中添加了-q(means quite)指令来关闭verbose变量,这样系统就不会每次都在halt时输出冗长的系统信息。为了测试文件系统,我写了一个脚本文件,主要用来测试文件系统的各种debug指令。在terminal中输入my_script.sh即可运行我放在code/filesys/test/my_script的脚本文件。这是脚本文件的内容:

# goto filesys/ in vagrant
cd /vagrant/nachos/nachos-3.4/code/filesys

# use -q to disable verbose machine messages
echo "=== format the DISK ==="
./nachos -q -f
echo "=== copies file \"big\" from UNIX to Nachos ==="
./nachos -q -cp test/big big
# Files: big
echo "=== copies file \"small\" from UNIX to Nachos ==="
./nachos -q -cp test/small small
# Files: big, small
echo "=== lists the contents of the Nachos directory ==="
./nachos -q -l
# big
# small
echo "=== remove the file \"big\" from Nachos ==="
./nachos -q -r big
echo "=== print the content of file \"small\" ==="
./nachos -q -p small
echo "=== prints the contents of the entire file system ==="
./nachos -q -D
echo "=== tests the performance of the Nachos file system ==="
./nachos -q -t

一、文件系统的基本操作

Exercise 1 源代码阅读

阅读Nachos源代码中与文件系统相关的代码,理解Nachos文件系统的工作原理。

code/filesys/filesys.h和code/filesys/filesys.cc

code/filesys/filehdr.h和code/filesys/filehdr.cc

code/filesys/directory.h和code/filesys/directory.cc

code /filesys/openfile.h和code /filesys/openfile.cc

code/userprog/bitmap.h和code/userprog/bitmap.cc

Nachos File System

+-----------------------------------+
|             FileSystem            |
+-----------------------------------+
|              OpenFile             |	 <-memory(i-node index)
+------------+----------+-----------+
| File Header| Directory|  Bitmap   |    <-disk(i-node)                 
+-----------------------------------+
|             SynchDisk             |
+-----------------------------------+
|                Disk               |
+-----------------------------------+
dick.cc disk.h

TODO

synchdisk.cc synchdisk.h

和其它设备一样,Nachos 模拟的磁盘是异步设备。当发出访问磁盘的请求后立刻返回,当从磁盘读出或写入数据结束后,发出磁盘中断,说明一次磁盘访问真正结束。

Nachos 是一个多线程的系统,如果多个线程同时对磁盘进行访问,会引起系统的混乱。所以必须作出这样的限制:

  • 同时只能有一个线程访问磁盘

  • 当发出磁盘访问请求后,必须等待访问的真正结束。 这两个限制就是实现同步磁盘的目的。

class SynchDisk
{
public:
  SynchDisk(char *name); // 生成一个同步磁盘
  ~SynchDisk();          // 析构磁盘
  void ReadSector(int sectorNumber, char *data); //同步读写磁盘,只有
  void WriteSector(int sectorNumber, char *data);//当真正读写结束才返回
  void RequestDone(); // 磁盘中断处理函数
private:
  Disk *disk;           // 物理异步磁盘设备
  Semaphore *semaphore; // 读写磁盘的信号量
  Lock *lock;           // 控制只有一个线程读写磁盘的锁
};

以ReadSector为例来说明同步磁盘的工作机制:

void SynchDisk::ReadSector(int sectorNumber, char *data)
{
    lock->Acquire(); // 一次只允许一个线程访问磁盘
    disk->ReadRequest(sectorNumber, data); //请求读取磁盘
    semaphore->P();  // 等待磁盘中断的到来
    lock->Release(); // 释放锁
}

当线程向磁盘设备发出读访问请求后,等待磁盘中断的到来。一旦磁盘中断来到,中断处理程序执行semaphore->V()操作,ReadSector得以继续运行。对磁盘同步写也基于同样的原理。

bitmap.cc bitmap.h

在 Nachos 的文件系统中,是通过位图来管理空闲块的。Nachos 的物理磁盘是以扇区为访问单位的,将扇区从 0 开始编号。所谓位图管理,就是将这些编号填入一张表,表中为 0 的地 方说明该扇区没有被占用,而非 0 位置说明该扇区已被占用。这部分内容是用 BitMap 类实现的。

class BitMap
{
public:
  BitMap(int nitems); // 初始化一个位图
  ~BitMap(); // 析构位图

  void Mark(int which);  // 将第n位设为1
  void Clear(int which); // 将第n位设为0
  bool Test(int which);  // 检查第n位是否为1
  int Find();            // 找第一个值为0的位,并置为1,如果找不到返回-1
  int NumClear(); // 返回值为0的位数
  void Print(); // 打印位图信息
  void FetchFrom(OpenFile *file); // 从Nachos磁盘读取数据到宿主主机内存
  void WriteBack(OpenFile *file); // 从宿主主机内存写回Nachos磁盘

private:
  int numBits;       // 位图的位数
  int numWords;      // 位图的字节数
  unsigned int *map; // bit以unsigned int的数据类型存储,这是bitmap的起点指针
};
filehdr.cc filehdr.h

文件头实际上就是 UNIX 文件系统中所说的 i-node 结构,它给出一个文件除了文件名之外的所有属性,包括文件长度、地址索引表等等(文件名属性在目录中给出)。所谓索引表,就是文件的逻辑地址和实际的物理地址的对应关系。Nachos 的文件头可以存放在磁盘上,也可以存放在宿主机内存中。在磁盘上存放时一个文件头占用一个独立的扇区。Nachos 文件 头的索引表只有直接索引。

文件头的定义和实现如下所示,由于目前 Nachos 只支持直接索引,而且文件长度一旦固定, 就不能变动。所以文件头的实现比较简单,这里不再赘述。

class FileHeader
{ //i-node
public:
  bool Allocate(BitMap *bitMap, int fileSize); // 通过文件大小初始化i-node(新)
  void Deallocate(BitMap *bitMap);             // 将一个文件所占用的数据空间释放(没有释放i-node的空间)
  void FetchFrom(int sectorNumber);            // 从磁盘中取出i-node(旧)
  void WriteBack(int sectorNumber);            // 将i-node写入磁盘
  int ByteToSector(int offset);                // 实现文件逻辑地址到物理地址的转换
  int FileLength();                            // 返回文件长度
  void Print();                                // 打印文件头信息(调试用)
private:
  int numBytes;               // 文件字节数
  int numSectors;             // 文件占的扇区数
  int dataSectors[NumDirect]; // 文件索引表
};

按照习惯,我将Nachos的fIleHeader称为i-node,下同。

openfile.cc openfile.h

该模块定义了一个打开文件控制结构。当用户打开了一个文件时,系统即为其产生一个打开文件控制结构,以后用户对该文件的访问都可以通过该结构。打开文件控制结构中的对文件操作的方法同 UNIX 操作系统中的系统调用。

class OpenFile {
  public:
    OpenFile(int sector);		// 打开文件初始化方法,sector为文件头i-node的扇区号
    ~OpenFile();			    // 关闭文件
    void Seek(int position); 		// 移动文件位置指针(从头文件开始)
    int Read(char *into, int numBytes); // 从文件中读入into缓冲
    int Write(char *from, int numBytes); //从from缓冲写入文件
    int ReadAt(char *into, int numBytes, int position);//从文件position开始读
    int WriteAt(char *from, int numBytes, int position);//写入文件position开始的位置
    int Length(); 			// 返回文件长度
  private:
    FileHeader *hdr;			// 该文件对应的文件头i-node(建立关系)
    int seekPosition;			// 当前文件位置指针
};
directory.cc directory.h

目录在文件系统中是一个很重要的部分,它实际上是一张表,将字符形式的文件名与实际文件的文件头相对应。这样用户就能方便地通过文件名来访问文件。 Nachos 中的目录结构非常简单,它只有一级目录,也就是只有根目录;而且根目录的大小是固定的,整个文件系统中只能存放有限个文件。这样的实现比较简单,这里只介绍目录的接口:

class Directory
{
public:
  Directory(int size); // 初始化一张空目录,size规定了目录中存放文件个数
  ~Directory();        // 析构目录
  void FetchFrom(OpenFile *file); // 从目录inode中读入目录内容到内存
  void WriteBack(OpenFile *file); // 将该目录内容从内存写回目录inode
  int Find(char *name); // 在目录中找文件名,返回文件的i-node的物理位置
  bool Add(char *name, int newSector); // 在目录中添加一个文件
  bool Remove(char *name); // 从目录中移除一个文件
  void List();  // 打印目录信息
  void Print(); // 详细打印目录信息
private:
  int tableSize;         // 目录项数
  DirectoryEntry *table; // 目录项表
  int FindIndex(char *name); // 根据文件名找出该文件在目录中的序号
};

Nachos默认初始化一张大小为10的目录,每个目录项的大小为10 * sizeof(char) + sizeof(int) + sizeof(bool) = 15, 十个目录项大小为150B, 超过了扇区大小128B,这是否是个bug?

filesys.cc filesys.h

读者在增强了线程管理的功能后,可以同时开展文件系统部分功能的增强或实现虚拟内存两部分工作。在 Nachos 中,实现了两套文件系统,它们对外接口是完全一样的:一套称作为 FILESYS_STUB,它是建立在 UNIX 文件系统之上的,而不使用 Nachos 的模拟磁盘,它主要用于读者先实现了用户程序和虚拟内存,然后再着手增强文件系统的功能;另一套是 Nachos 的文件系统,它是实现在 Nachos 的虚拟磁盘上的。当整个系统完成之后,只能使用第二套文件系统的实现。

class FileSystem
{
public:
	FileSystem(bool format);				  // 初始化文件系统
	bool Create(char *name, int initialSize); // 创造文件
	OpenFile *Open(char *name);				  // 打开文件
	bool Remove(char *name);				  // 删除文件
	void List();							  // 打印文件系统中的所有文件
	void Print();							  //详细列出文件和内容
private:
	OpenFile *freeMapFile;	 // 文件系统位图
	OpenFile *directoryFile; // 文件系统根目录
};

Exercise 2 扩展文件属性

增加文件描述信息,如“类型”、“创建时间”、“上次访问时间”、“上次修改时间”、“路径”等等。尝试突破文件名长度的限制。

背景知识:UNIX i-node

1.UNIX文件系统中的主要结构 i-node ,目录项中记录的是文件名和文件相对应的 i-node 在整个 i-node 区中的索引号,文件 i-node 结构中除了存放文件的属性外,最主要的是文件的索引表。

2.磁盘存储空间的安排 在 UNIX 文件系统中,磁盘块的作用分成两类:一类存放文件的 i-node,这一类磁盘块组织 在一起,形成 i-node 区;另一类存放文件内容本身,该类的集合形成存储数据区,如图 4.7。 图中, 0#块用来存放系统的自举程序; 1#块为管理块,管理本文件系统中资源的申请和回收。

  1. 每个Nachos 文件的 i-node 占用一个单独的扇区,分散在物理磁盘的任何地方,同一般存储扇区用同样的方式进行申请和回收。

    Nachos Disk Allocation Structure
    
    +----+----+---------------------+
    | 0# | 1# | Normal Storage Area |
    +----+----+---------------------+
      |     |
      |    1#: Root directory's i-node
      |
     0#: System bitmap file's i-node
    
  2. Nachos 则只有一级目录,也就是只有根目录,所有的文件都在根目录下。而且根目录中可以存放的文件数是有限的。Nachos 文件系统的根目录同样也是通过文件方式存放的,它的 i-node 占据了 1 号扇区。

  3. Nachos 同一般的 UNIX 一样,采用索引表进行物理地址和逻辑地址之间的转换,索引表存放在文件的 i-node 中。但是目前 Nachos 采用的索引都是直接索引,所以 Nachos 的最大文件长度不能大于4K。

新增变量

Nachos的file header等价于UNIX中的i-node,因此我将题述的几个变量加在code/filesys/filehdr.h的FileHeader类中,方便起见,设为public(虽然这样很不严谨,但是确实很省事):

class FileHeader
{
  ...
public:
  //lab4新增
  int createTime; //文件创造时间
  int lastVisitedTime; //文件上次被访问的时间
  int lastModifiedTime;//文件上次被修改的时间
  int type; //文件类型,0表示i-node,1表示普通文件
  char *path; //文件路径
};

新增宏:

//----------------------------------------------------------------------
//Lab4 Exercise2 新增成员变量
//----------------------------------------------------------------------
#define VAR_NUM  7//i-node中的变量数,变量的均占4B(int, char *), i-node中共7个变量,所以为7
#define NumDirect ((SectorSize - VAR_NUM * sizeof(int)) / sizeof(int)) // i-node中索引表大小,值为                                                                                    //(128 -  4*7)/4 = 25
#define MaxFileSize (NumDirect * SectorSize) //文件最大长度 25 * 128 = 3200B
维护成员变量

Nachos文件通过code/filesys/filesys.cc中的create函数创建,创建文件会调用FileHeader::Allocate()函数初始化一个i-node,应该在此函数内部增加对createTime的维护。每次访问文件(无论是读还是写),都会调用FileHeader::ByteToSector()函数来执行地址转换,所以应该在其中加入对lastVisitedTime的维护。而lastModifiedTime稍微复杂一点,只有对文件进行写操作时才会更新,所以应该在OpenFile::WriteAt()的结尾处增加对它的维护,表示一次写的结束。

以上的实现假设对i-node的修改不算作对文件本身的修改。

具体实现请查看code/filesys/openfile.cccode/filesys/filehdr.cc

突破文件名长度的限制

文件名位于目录项中,将char[]改为char*即可。

这样一来,一个directoryEntry的大小为sizeof(char*) + sizeof(bool) + sizeof(int) = 9;一个sector可以存放 128 / 9 = 14个目录项。

Exercise 3 扩展文件长度

改直接索引为间接索引,以突破文件长度不能超过4KB的限制。

Allocate()/Deallocate()

每次调用File system::Create()函数,都会调用FileHeader::Allocate()来分配物理空间。

在Exercise2中增加5个成员变量之后,文件的最大长度变为3200B,文件索引为25项。规定索引表的前24项为直接接索引,最后一项为一级索引,存放的是一级索引表对应扇区号。每个一级索引占一个扇区,可以容纳128/4 = 32个索引项:

//----------------------------------------------------------------------
//Lab4 Exercise3 改为间接索引
//----------------------------------------------------------------------
#define DERECT_NUM 24  //直接索引数,表示[0,24)块
#define PRIMARY_NUM (SectorSize / sizeof(int)) //一级索引块数,表示[24,56)块。文件最大长度 = 24 * 128 + 128/4 * 128 =  7168满足题意
//----------------------------------------------------------------------
// lab4 突破文件长度限制
// 根据文件长度分配内存需
// 要分为两步:先分配直接
// 索引,再分配一级索引。
//----------------------------------------------------------------------
bool FileHeader::Allocate(BitMap *freeMap, int fileSize)
{
    numBytes = fileSize;
    numSectors = divRoundUp(fileSize, SectorSize);
    if (freeMap->NumClear() < numSectors)
    {
        DEBUG('d', "There is not enough space.\tfile:%s\tline:%d\n", __FILE__, __LINE__);//d means disk
        return FALSE; // not enough space
    }
    //直接索引
    if (numSectors <= DIRECT_NUM)
    {
        DEBUG('d', "Using direct allocation.\n");
        for (int i = 0; i < numSectors; i++)
            dataSectors[i] = freeMap->Find();
    }
    //一级索引
    else if (numSectors <= DIRECT_NUM + PRIMARY_NUM)
    {
        DEBUG('d', "Using indirect allocation.\n");
        //直接索引
        for (int i = 0; i < DIRECT_NUM; i++)
            dataSectors[i] = freeMap->Find();
        //一级索引
        int primary[PRIMARY_NUM];
        //将物理块号存入一级索引表中
        for (int i = 0; i < numSectors - DIRECT_NUM; ++i)
            primary[i] = freeMap->Find();
        //为一级索引表分配物理空间
        int numSector = freeMap->Find();
        if (numSector == -1)
        {
            DEBUG('d', "No space for primary table.\tfile:%s\tline:%d\n", __FILE__, __LINE__);
            return FALSE;
        }
        //将一级索引表表号写入dataSectors中
        dataSectors[DIRECT_NUM] = numSector;
        //将一级索引表写入磁盘
        synchDisk->WriteSector(numSector, (char *)primary);
    }
    //文件长度超过限制
    else
    {
        DEBUG('d', "File length exceed!\tfile:%s\tline:%d\n", __FILE__, __LINE__);
        return FALSE;
    }
    createTime = stats->totalTicks; //lab4 文件创造时间
    return TRUE;
}

相应地,我们需要对Deallocate函数做出改变:

void FileHeader::Deallocate(BitMap *freeMap)
{
    //直接索引
    if (numSectors <= DIRECT_NUM)
    {
        for (int i = 0; i < numSectors; i++)
        {
            ASSERT(freeMap->Test((int)dataSectors[i])); // ought to be marked!
            freeMap->Clear((int)dataSectors[i]);
        }
    }
    //一级索引
    else if (numSectors <= DIRECT_NUM + PRIMARY_NUM)
    {
        //直接索引
        for (int i = 0; i < DIRECT_NUM; i++)
        {
            ASSERT(freeMap->Test((int)dataSectors[i])); // ought to be marked!
            freeMap->Clear((int)dataSectors[i]);
        }
        //在内存中初始化一个一级索引数组
        int primary[PRIMARY_NUM];
        //取出一级索引表表号
        int numSector = dataSectors[DIRECT_NUM];
        //将一级索引表读入内存
        synchDisk->ReadSector(numSector, (char *)primary);
        //清除一级索引表的磁盘空间
        ASSERT(freeMap->Test((int)numSector)); // ought to be marked!
        freeMap->Clear((int)numSector);
        //释放一级索引表中的空间
        for (int i = 0; i < numSectors - DIRECT_NUM; ++i)
        {
            ASSERT(freeMap->Test((int)primary[i])); // ought to be marked!
            freeMap->Clear((int)primary[i]);
        }
    }
}

可以发现:deallocate是allocate的一个逆向操作(清楚这一点可以大大降低编码的难度)。

ByteToSector()

每次执行地址转换的函数是ByteToSector(),因此我们还需要对它进行改变。

//lab4 改为间接索引
int FileHeader::ByteToSector(int offset)
{
    int v_index = offset / SectorSize; //虚拟块号
    //虚拟索引号不能大于直接索引+间接索引的总和
    ASSERT(v_index < DIRECT_NUM + PRIMARY_NUM);
    //直接索引
    if (v_index < DIRECT_NUM)
    {
        return dataSectors[v_index];
    }
    //间接索引
    else
    {
        //在内存中初始化一个一级索引数组
        int primary[PRIMARY_NUM];
        //取出一级索引表表号
        int numSector = dataSectors[DIRECT_NUM];
        //将一级索引表读入内存
        synchDisk->ReadSector(numSector, (char *)primary);
        return primary[v_index - DIRECT_NUM];
    }
}

还有一些琐碎的改动,请查看code/filesys/filehdr.cc;

Exercise 4 实现多级目录

todo

Exercise 5 动态调整文件长度

对文件的创建操作和写入操作进行适当修改,以使其符合实习要求。

二、文件访问的同步与互斥

Exercise 6 源代码阅读

a) 阅读Nachos源代码中与异步磁盘相关的代码,理解Nachos系统中异步访问模拟磁盘的工作原理。

filesys/synchdisk.h和filesys/synchdisk.cc

b) 利用异步访问模拟磁盘的工作原理,在Class Console的基础上,实现Class SynchConsole。

Exercise 7 实现文件系统的同步互斥访问机制,达到如下效果:

a) 一个文件可以同时被多个线程访问。且每个线程独自打开文件,独自拥有一个当前文件访问位置,彼此间不会互相干扰。

b) 所有对文件系统的操作必须是原子操作和序列化的。例如,当一个线程正在修改一个文件,而另一个线程正在读取该文件的内容时,读线程要么读出修改过的文件,要么读出原来的文件,不存在不可预计的中间状态。

c) 当某一线程欲删除一个文件,而另外一些线程正在访问该文件时,需保证所有线程关闭了这个文件,该文件才被删除。也就是说,只要还有一个线程打开了这个文件,该文件就不能真正地被删除。

三、Challenges (至少选做1个)

Challenge 1 性能优化

a) 例如,为了优化寻道时间和旋转延迟时间,可以将同一文件的数据块放置在磁盘同一磁道上

b) 使用cache机制减少磁盘访问次数,例如延迟写和预读取。

Challenge 2 实现pipe机制

重定向openfile的输入输出方式,使得前一进程从控制台读入数据并输出至管道,后一进程从管道读入数据并输出至控制台。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值