操作系统实验五 文件系统
16281049 王晗炜 计科1601
实验简介
本实验要求在模拟的I/O系统之上开发一个简单的文件系统。用户通过create, open, read等命令与文件系统交互。文件系统把磁盘视为顺序编号的逻辑块序列,逻辑块的编号为0至L − 1。I/O系统利用内存中的数组模拟磁盘。
系统设计
-
I/O系统设计
实际物理磁盘的结构是多维的:有柱面、磁头、扇区等概念。I/O系统的任务是隐藏磁盘的结构细节,把磁盘以逻辑块的面目呈现给文件系统。逻辑块顺序编号,编号取值范围为0至L−1,其中L表示磁盘的存储块总数。
为了简化实验的流程,我们在设计磁盘结构时不考虑磁盘的盘片结构,直接使用一个二维字符串数组来表示磁盘结构。如我们所知,C语言中一个字符变量占用一个字节,因此二维字符数组的长度等价于磁盘的字节数大小。这里我们设置磁盘块数量L为4096,磁盘块大小B为512.易知整个磁盘的大小为2MB。
为完成I/O系统的基本功能和与文件系统的交互,我们在此设计了四个接口:
void read_block(int i,char* p)
:该函数把逻辑块i的内容读入到指针p指向的内存位置,拷贝的字符个数为存储块的长度B。void write_block(int i,char* p)
:该函数把指针p指向的内容写入逻辑块i,拷贝的字符个数为存储块的长度B。bool read_file()
:该函数从指定文件中读取磁盘的状态,用于磁盘状态的恢复bool write_file()
:该函数将磁盘的状态存至指定文件中,用于磁盘状态的保存
-
文件系统设计
文件系统的设计是本次实验的核心部分,其建立在之前设计的I/O系统的设计之上。在设计具体的数据结构和函数接口之前,我们先对文件系统的结构做一个说明。
文件系统在使用之前需要对磁盘进行格式化的操作:磁盘的前k个块是保留区,其中包含如下信息:位图和文件描述符。位图用来描述磁盘块的分配情况。位图中的每一位对应一个逻辑块。创建或者删除文件,以及文件的长度发生变化时,文件系统都需要进行位图操作。前k个块的剩余部分包含一组文件描述符。
除此之外,我们需要设置一个目录,该目录包含文件系统中的所有文件。除了不需要显示地创建和删除之外,目录在很多方面和普通文件相像。目录对应0号文件描述符。初始状态下,目录中没有文件,所有,目录对应的描述符中记录的长度应为0,而且也没有分配磁盘块。每创建一个文件,目录文件的长度便增加一分。目录文件的内容由一系列的目录项组成,目录项中储存着对应文件的相关信息。
最后,我们需要维护一张打开文件表,包含文件系统当前打开文件的一些信息。
之后我们可以开始设计文件系统中具体涉及到的数据结构:
-
文件描述符:
File_Descriptor
结构成员:
- len:该成员代表文件的长度,单位为字节,变量类型为int,占四个字节,初始值为0
- block_index[7]:该成员代表文件分配的磁盘块号,其长度代表了文件系统单个文件的最大长度,这里我们将其设置为int类型的数组,长度为7,占28个字节,初始值为-1,不难得知该系统下文件的最大长度为3.5KB.
-
目录结构项:
Directory
结构成员:
- name[28]:该成员代表文件名,变量类型为char型数组,长度为28,由此易知其占用28个字节
- index:该成员代表文件描述符的序号,变量类型为int,易知其占用4个字节
-
打开文件表项:
Opening_File_Table
:结构成员:
- buf:该成员代表打开文件的缓冲区,变量类型为字符指针,可见缓冲区的大小不固定
- p:该成员代表打开文件的读写指针,变量类型为int
- index:该成员代表打开文件的文件描述符编号,变量类型为int
之后在设计具体的文件操作函数接口之前,为了简化代码结构,我们先设计如下几个内部接口:
int get_decimal(char* mem)
:将内存中读取的二进制数据转化为十进制,返回值为一个int型的整数。因为本实验涉及到磁盘的读写,结合其物理结构需要进行大量的二进制转换操作,需要频繁调用该函数,下一函数同理。void get_binary(int num, char* mem)
:将输入的十进制数据转换为二进制,并存入mem内存段中。int get_block()
:申请一个空闲的磁盘块,返回申请磁盘块的序号。void destory_block(int index)
:清空指定序号的磁盘块。void add_dir(char* filename, int index)
:将指定文件名和文件描述符序号添加至目录中。int remove_dir(char* filename)
:根据传入的文件名将文件从目录中删除。int find_file(char* filename)
:根据传入的文件名返回对应的文件描述符序号。
完成以上内部接口后我们开始正式设计文件系统操作的相关函数接口:
int create(char* filename)
:根据指定的文件名创建新文件,返回文件对应的文件描述符序号。int destory(char* filename)
:根据指定的文件名删除文件,返回文件对应的文件描述符序号。int open(char* filename)
:根据指定的文件名打开文件,返回文件对应的文件描述符序号。int close(char* filename)
:根据指定的文件名关闭文件,返回文件对应的文件描述符序号。int read(char* filename, char* mem_area, int count)
:从指定的文件顺序读入count个字节至mem_area指定的内存位置,读操作从文件的读写指针指示的位置开始。int write(char* filename, char* mem_area, int count)
:把mem_area指定的内存位置开始的count个字节顺序写入指定文件,写操作从文件的读写指针指示的位置开始。int lseek(char* filename, int pos)
:把文件的读写指针移动到pos指定的位置。pos是一个整数,表示从文件开始位置的偏移量。文件打开时,读写指针自动设置为0。每次读写操作之后,它指向最后被访问的字节的下一个位置。lseek能够在不进行读写操作的情况下改变读写指针能位置。void directory()
:打印出目录中的文件的文件名和文件描述符序号。void directory_open()
:打印出打开文件的文件名和读写指针位置。
实验过程
-
I/O系统实现
相较于文件系统,I/O系统的实现较为简单,具体如下:
-
read_block(int i, char* p)
:读取磁盘块//读取磁盘块 void read_block(int i, char *p) { memcpy(p, ldisk[i], B); }
-
write_block(int i,char* p)
:写入磁盘块//写入磁盘块 void write_block(int i, char *p) { memcpy(ldisk[i], p, B); }
-
read_file()
:从文件中恢复磁盘状态//从文件中恢复磁盘状态 bool read_file() { FILE *fp; if ((fp = fopen( "ldisk.txt", "r")) == NULL) { cout << "打开文件时出错!" << endl; return false; } for (int i = 0; i < L; i++) fread(ldisk[i], sizeof(char), B, fp); fclose(fp); return true;
-
write_file()
:将磁盘状态保存至文件中//将磁盘状态保存至文件中 bool write_file() { FILE *fp; if ((fp = fopen("ldsik.txt", "w+")) == NULL) { cout << "打开文件时出错!" << endl; return false; } for (int i = 0; i < L; i++) fwrite(ldisk[i], sizeof(char), B, fp); fclose(fp); return true; }
-
-
文件系统实现
在文件系统实现之前,我们需要对磁盘进行格式化(初始化),根据之前的设计,我们可以得知磁盘共有4096块,每一块的大小为512B。首先在磁盘的最初我们需要使用64个字节来表示位图,这里我们将第0块的前64个字节留给位图。除了位图所占的空间,我们还需预留一些空间给文件描述符,这里我们预留64块磁盘给系统,因为每项文件描述符的大小为32B,其最多可容纳1024个文件描述符。然后我们将目录也当作一个文件,因此将第0块的前32个字节当作目录文件标识符,将其初始长度设置为0,分配磁盘块均初始化为-1。综合以上我们已经可以给出以下的初始化程序:
#include <string.h> #include <iostream> #include <stdio.h> #include "io.h" using namespace std; char ldisk[L][B]; int main(){ // 先将所有字符初始化为0 FILE *fp = fopen("ldisk.txt","w+"); memset(ldisk,0,sizeof(ldisk)); // 前64个盘块为文件描述符,已经被系统占用,位图置为1 for(int i = 0 ; i < 8 ; i++){ ldisk[0][i] = 0xff; } // 目录文件描述符已被占用,其余均初始化为-1 for(int i = 1 ; i < L ; i++){ for(int j = 0 ; j < B ; j++){ if(i==1&&j<4)continue; ldisk[i][j] = 0xff; } } // 将初始化的磁盘信息写入文件中 for(int i = 0 ; i < L ; i++){ fwrite(ldisk[i],sizeof(char),B,fp); } fclose(fp); return 0; }
完成了磁盘的初始化之后我们正式开始编写文件系统中的函数:
-
内部接口函数
-
int get_decimal(char* mem)
:该函数将二进制数转化为十进制,由于文件系统中所定义的整数均为int型,因此我们需要将连续的4个字节转化为一个整数,不同的字节自然对应不同的高低位,这里我们使用移位操作将这四个字节的二进制数据转化为十进制,具体代码如下:// 将从内存中读取的二进制数据转化为十进制 int get_decimal(char *mem) { int ans = 0; ans += ((unsigned char)mem[0]) << 24; ans += ((unsigned char)mem[1]) << 16; ans += ((unsigned char)mem[2]) << <
-
-
-