实验简介
本实验要求在模拟的I/O系统之上开发一个简单的文件系统。用户通过create, open, read等命令与文件系统交互。文件系统把磁盘视为顺序编号的逻辑块序列,逻辑块的编号为0至L − 1。I/O系统利用内存中的数组模拟磁盘。
实验背景
- IO系统
实际物理磁盘的结构是多维的:有柱面、磁头、扇区等概念。I/O系统的任务是隐藏磁盘的结构细节,把磁盘以逻辑块的面目呈现给文件系统。逻辑块顺序编号,编号取值范围为0至L−1,其中L表示磁盘的存储块总数。实验中,我们可以利用数组ldisk[C][H][B]构建磁盘模型,其中CHB 分别表示柱面号,磁头号和扇区号。每个扇区大小为512字节。I/O系统从文件系统接收命令,根据命令指定的逻辑块号把磁盘块的内容读入命令指定的内存区域,或者把命令指定的内存区域内容写入磁盘块。文件系统和I/O系统之间的接口由如下两个函数定义:
read_block(int i, char *p);
该函数把逻辑块i的内容读入到指针p指向的内存位置,拷贝的字符个数为存储块的长度B。
write block(int i, char *p);
该函数把指针p指向的内容写入逻辑块i,拷贝的字符个数为存储块的长度B。此外,为了方便测试,我们还需要实现另外两个函数:一个用来把数组ldisk 存储到文件;另一个用来把文件内容恢复到数组。 - 文件系统
文件系统位于I/O系统之上。 - 用户与文件系统之间的接口
文件系统需提供如下函数;create, destroy, open, read, write。
create(filename): 根据指定的文件名创建新文件。
destroy(filename): 删除指定文件。
open(filename): 打开文件。该函数返回的索引号可用于后续的read, write, lseek,或close操作。
close(index): 关闭制定文件。
read(index, mem_area, count): 从指定文件顺序读入count个字节memarea指定的内存位置。读操作从文件的读写指针指示的位置开始。
write(index, mem_area, count): 把memarea指定的内存位置开始的count个字节顺序写入指定文件。写操作从文件的读写指针指示的位置开始。
lseek(index, pos): 把文件的读写指针移动到pos指定的位置。pos是一个整数,表示从文件开始位置的偏移量。文件打开时,读写指针自动设置为0。每次读写操作之后,它指向最后被访问的字节的下一个位置。lseek能够在不进行读写操作的情况下改变读写指针能位置。
directory: 列表显示所有文件及其长度。 - 文件系统的组织
磁盘的前k个块是保留区,其中包含如下信息:位图和文件描述符。位图用来描述磁盘块的分配情况。位图中的每一位对应一个逻辑块。创建或者删除文件,以及文件的长度发生变化时,文件系统都需要进行位图操作。前k个块的剩余部分包含一组文件描述符。每个文件描述符包含如下信息:
• 文件长度,单位字节
• 文件分配到的磁盘块号数组。该数组的长度是一个系统参数。在实验中我们可以把它设置为一个比较小的数,例如3。
实验过程
主要数据结构
#define B 10 //存储块长度
#define L 500 //存储块总数
#define K 100 //保留区大小
#define BUSY 1
#define FREE 0
#define OK 1
#define ERROR 0
#define FILE_BLOCK_LENGTH (B-3) //文件分配磁盘块号数组长度
#define FILE_NAME_LENGTH (B-1) //最长文件名长度
#define FILE_SIGN_AREA ((L-1-K)/B+1) //保留区中文件标识符起始块号位图之后
#define FILE_NUM FILE_BLOCK_LENGTH //目录内最多文件数目
#define BUFFER_LENGTH 25 //打开文件表目中的缓冲区长度
#define INPUT_LENGTH 100 //写文件时最大输入长度
#define OUTPUT_LENGTH 100 //读文件时最大读出长度
struct filesign { //文件描述符
int file_length; //文件长度
int filesign_flag; //占用标识位
int file_block; //文件分配磁盘块号数组实际长度
int file_block_ary[FILE_BLOCK_LENGTH]; //文件分配磁盘块号数组
};
struct contents { //目录项
char filename[FILE_NAME_LENGTH]; //文件名
int filesignnum; //文件描述符序号
};
struct openfilelist { //打开文件表表目
char buffer[BUFFER_LENGTH]; //读写缓冲区
int pointer[2]; //读写指针文件内容的位置
int filesignnum; //文件描述符
int flag; //占用符
};
char ldisk[L][B]; //用字符数组模拟磁盘
openfilelist open_list[FILE_NUM]; //打开文件表
主要函数声明
//辅助函数
int show_openlist(); //显示打开文件表,返回打开文件个数
void directory(); //显示目录文件详细信息
void show_help(); //该文件系统的帮助
void show_ldisk(); //显示磁盘内容(辅助用)
//核心函数
void read_block(int, char *); //文件系统与IO设备的接口函数,读取块
void write_block(int, char *); //文件系统与IO设备的接口函数,写入块
void Init(); //初始化文件系统
int create(char *); //创建文件
int destroy(char *); //删除文件
int open(char *); //打开文件
int close(int); //关闭文件
int read(int, int, int); //读文件
int write(int, int, int); //写文件
int write_buffer(int, int); //把缓冲区内容写入文件
int lseek(int, int); //定位文件指针
void Init_block(char, int); //初始化字符数组块(辅助)
命令菜单
显示所有实现的功能
目录
我们的文件系统中仅设置一个目录,该目录包含文件系统中的所有文件。除了不需要显示地创建和删除之外,目录在很多方面和普通文件相像。目录对应0号文件描述符。初始状态下,目录中没有文件,所有,目录对应的描述符中记录的长度应为0,而且也没有分配磁盘块。每创建一个文件,目录文件的长度便增加一分。目录文件的内容由一系列的目录项组成,其中每个目录项由如下内容组成:
• 文件名
• 文件描述符序号
主要代码
void directory()
//列表显示所有文件及其长度。
{
int i;
int filenum;
int filelength;
char filename[FILE_NAME_LENGTH];
char temp[B];
char tempd[B];
char temps[B];
read_block(FILE_SIGN_AREA, temp);
filenum = temp[1]; //实际存在的文件个数
printf("\n");
if (filenum == 0)
{
printf("\t\t\t\t该目录下没有文件\n");
}
for (i = 0; i < FILE_NUM; i++)
{
read_block(temp[3 + i], tempd); //读取目录项
if (tempd[0] != 0)
{
read_block(tempd[0] + FILE_SIGN_AREA, temps); //读取文件描述符
if (temps[0] == BUSY && tempd[0] != 0)
{
filelength = temps[1];
strcpy(filename, tempd + 1);
printf("%-10s\t\t%-2d字节\n", filename, filelength);
}
}
}
if (filenum != 0)
{
printf("\t\t\t\t共%d个文件\n", filenum);
}
}
运行演示结果
文件的创建与删除
创建文件时需要进行如下操作;
• 找一个空闲文件描述符(扫描ldisk [0]~ldisk [k - 1])
• 在文件目录里为新创建的文件分配一个目录项(可能需要为目录文件分配新的磁盘块)
• 在分配到的目录项里记录文件名及描述符编号.
• 返回状态信息(如有无错误发生等)
删除文件时需要进行如下操作(假设文件没有被打开):
• 在目录里搜索该文件的描述符编号
• 删除该文件对应的目录项并更新位图
• 释放文件描述符
• 返回状态信息
主要代码以及运行截图
- 文件的创建
int create(char filename[])
{
int i;
int frees;
int freed;
int freed2;
char temps[B];
char tempc[B];
char temp[B];
//**************
for (i = K; i < K + FILE_NUM; i++)
{
read_block((i - K) / B, temp);
if (temp[(i - K) % B] == BUSY)
{
read_block(i, temp);
if (strncmp(temp + 1, filename, FILE_NAME_LENGTH) == 0)
{
printf("该目录已经存在文件名为%s的文件\n", filename);
return ERROR;
}
}
}
//*************
for (i = FILE_SIGN_AREA; i < K; i++)
{
read_block(i, temp);
if (temp[0] == FREE)
{
frees = i;
break;
}
}
if (i == K)
{
printf("没有空闲的文件描述符\n");
return ERROR;
}
//*************
for (i = K; i < K + FILE_NUM; i++)
{
read_block((i - K) / B, temp);
if (temp[(i - K) % B] == FREE)
{
freed = i;
break;
}
}
if (i == K + FILE_NUM)
{
printf("文件个数已达上限\n");
return ERROR;
}
//*****************
for (i = K + FILE_NUM; i < L; i++)
{
read_block((i - K) / B, temp);
if (temp[(i - K) % B] == FREE)
{
freed2 = i;
break;
}
}
if (i == L)
{
printf("磁盘已满,分配失败\n");
return ERROR;
}
filesign temp_filesign;
contents temp_contents;
//*******
temp_filesign.filesign_flag = 1;
temp_filesign.file_length = 0;
temp_filesign.file_block = 1;
Init_block(temps, B);
temps[0] = temp_filesign.filesign_flag;
temps[1] = temp_filesign.file_length;
temps[2] = temp_filesign.file_block;
temps[3] = freed2;
for (i = 4; i < FILE_BLOCK_LENGTH; i++)
{
temps[i] = '\0';
}
write_block(frees, temps);
//**************
temp_contents.filesignnum = frees - FILE_SIGN_AREA;
strncpy(temp_contents.filename, filename, FILE_NAME_LENGTH);
Init_block(tempc, B);
tempc[0] = temp_contents.filesignnum;
tempc[1] = '\0';
strcat(tempc, temp_contents.filename);
write_block(freed, tempc);
//****************
read_block((freed - K) / B, temp);
temp[(freed - K) % B] = BUSY;
write_block((freed - K) / B, temp);
read_block((freed2 - K) / B, temp);
temp[(freed2 - K) % B] = BUSY;
write_block((freed2 - K) / B, temp);
read_block(FILE_SIGN_AREA, temp);
temp[1]++;
write_block(FILE_SIGN_AREA, temp);
return OK;
}
运行截图
- 文件的删除
int destroy(char * filename)
{
int i;
int dtys;
int dtyd;
int use_block;
int index;
char temp[B];
char tempd[B];
for (i = K; i < K + FILE_NUM; i++)
{
read_block((i - K) / B, temp);
if (temp[(i - K) % B] == BUSY)
{
read_block(i, temp);
if (strncmp(temp + 1, filename, FILE_NAME_LENGTH) == 0)
{
dtyd = i;
dtys = temp[0] + FILE_SIGN_AREA;
index = temp[0];
break;
}
}
}
if (i == K + FILE_NUM)
{
printf("没有找到该文件\n");
return ERROR;
}
//**
int list = -1;
for (i = 0; i < FILE_NUM; i++)
{
if (open_list[i].filesignnum == index)
{
list = i;
break;
}
}
if (open_list[list].flag == BUSY && list != -1)
{
printf("该文件已经被打开,需要关闭才能删除\n");
return ERROR;
}
//***********
//**********
read_block(dtys, temp);
use_block = temp[2];
for (i = 0; i < use_block; i++)
{
read_block((temp[i + 3] - K) / B, tempd);
tempd[(temp[i + 3] - K) % B] = FREE;
write_block((temp[i + 3] - K) / B, tempd);
}
//***********
Init_block(temp, B);
write_block(dtys, temp);
//************
Init_block(temp, B);
write_block(dtyd, temp);
//***************
read_block((dtyd - K) / B, temp);
temp[(dtyd - K) % B] = FREE;
write_block((dtyd - K) / B, temp);
//**********
read_block(FILE_SIGN_AREA, temp);
temp[1]--;
write_block(FILE_SIGN_AREA, temp);
return OK;
}
文件的打开与关闭
文件系统维护一张打开文件表.打开文件表的长度固定,其表目包含如下信息:
• 读写缓冲区
• 读写指针
• 文件描述符号
文件被打开时,便在打开文件表中为其分配一个表目;文件被关闭时,其对应的表目被释放。读写缓冲区的大小等于一个磁盘存储块。打开文件时需要进行的操作如下:
• 搜索目录找到文件对应的描述符编号
• 在打开文件表中分配一个表目
• 在分配到的表目中把读写指针置为0,并记录描述符编号
• 读入文件的第一块到读写缓冲区中
• 返回分配到的表目在打开文件表中的索引号
关闭文件时需要进行的操作如下:
• 把缓冲区的内容写入磁盘
• 释放该文件在打开文件表中对应的表目
• 返回状态信息
主要代码以及运行截图
- 文件打开
主要代码
open(char * filename)
{
int i;
int opd;
int ops;
int list;
char temp[B];
int index;
//************
for (i = K; i < K + FILE_NUM; i++)
{
read_block((i - K) / B, temp);
if (temp[(i - K) % B] == BUSY)
{
read_block(i, temp);
if (strncmp(temp + 1, filename, FILE_NAME_LENGTH) == 0)
{
opd = i;
ops = temp[0];
// printf("opd: %d,ops: %d\n",opd,ops);
break;
}
}
}
if (i == K + FILE_NUM)
{
printf("没有找到该文件\n");
return ERROR;
}
//***********
for (i = 0; i < FILE_NUM; i++)
{
if (open_list[i].filesignnum == ops && open_list[i].flag == BUSY)
{
printf("该文件已经被打开\n");
return ERROR;
}
}
//**********
for (i = 0; i < FILE_NUM; i++)
{
if (open_list[i].flag != BUSY)
{
list = i;
break;
}
}
//**************
open_list[list].filesignnum = ops;
open_list[list].flag = BUSY;
index = open_list[list].filesignnum;
lseek(index, 0);
Init_block(open_list[list].buffer, BUFFER_LENGTH);
read_block(open_list[list].pointer[0], temp);
strncpy(open_list[list].buffer, temp, BUFFER_LENGTH);
return OK;
}
实验截图(bjtu文件已删除打不开)
- 文件关闭
主要代码
int close(int index)
/***************************关闭文件
***************************/
{
int i;
int list = -1;
char temp[B];
//***************根据index找表目
for (i = 0; i < FILE_NUM; i++)
{
if (open_list[i].filesignnum == index)
{
list = i;
break;
}
}
if (list == -1) //没找到
{
printf("没找到当前索引号文件,操作失败...\n");
return ERROR;
}
if (open_list[list].flag != BUSY) //输入的index对应文件没被打开
{
printf("输入的索引号有误,操作失败...\n");
return ERROR;
}
//****************将缓冲区的内容写入磁盘
/* //测试用
for(i = 0 ; i < BUFFER_LENGTH-1; i++ )
{
open_list[list].buffer[i] = 5;
}
*/
write_buffer(index, list); //将当前list打开文件表对应的缓冲区入index索引号的文件
//****************清楚操释放表目
Init_block(open_list[list].buffer, BUFFER_LENGTH); //清除缓冲区
open_list[list].filesignnum = FREE; //清除文件描述符
open_list[list].flag = FREE; //清除占用标志位
open_list[list].pointer[0] = NULL; //清楚指针
open_list[list].pointer[1] = NULL;
return OK;
}
实验截图
读写
创建文件时需要进行如下操作;
• 找一个空闲文件描述符(扫描ldisk [0]~ldisk [k - 1])
• 在文件目录里为新创建的文件分配一个目录项(可能需要为目录文件分配新的磁盘块)
• 在分配到的目录项里记录文件名及描述符编号.
• 返回状态信息(如有无错误发生等)
删除文件时需要进行如下操作(假设文件没有被打开):
• 在目录里搜索该文件的描述符编号
• 删除该文件对应的目录项并更新位图
• 释放文件描述符
• 返回状态信息
主要代码以及运行截图
- 读文件
主要代码
int read(int index, int mem_area, int count)
/*
从指定文件顺序读入count 个字
节mem_area 指定的内存位置。读操作从文件的读写指针指示的位置
开始。
*/
{
int i;
int list = -1;
char temp[B];
//***************根据index找打开文件表表目
for (i = 0; i < FILE_NUM; i++)
{
if (open_list[i].filesignnum == index)
{
list = i;
break;
}
}
if (list == -1) //没找到
{
printf("没找到当前索引号文件,操作失败...\n");
return ERROR;
}
if (open_list[list].flag != BUSY) //输入的index对应文件没被打开
{
printf("输入的索引号有误,操作失败...\n");
return ERROR;
}
//***************根据index找文件描述符
char temp_output[OUTPUT_LENGTH];
Init_block(temp_output, OUTPUT_LENGTH);
char output[OUTPUT_LENGTH];
Init_block(output, OUTPUT_LENGTH);
read_block(FILE_SIGN_AREA + index, temp);
int file_length = temp[1]; //文件长度
int file_block = temp[2]; //文件实际使用块
int file_area;
//**********************拷贝文件内容至temp_output
for (i = 0; i < file_block - 1; i++)
{
read_block(FILE_SIGN_AREA + index, temp);
read_block(temp[3 + i], temp);
strncpy(temp_output + i * B, temp, B);
}
read_block(FILE_SIGN_AREA + index, temp);
read_block(temp[3 + i], temp);
strncpy(temp_output + i * B, temp, B);
//******************当前文件读写坐标
int x = open_list[list].pointer[0];
int y = open_list[list].pointer[1];
for (i = 0; i < file_block; i++)
{
read_block(FILE_SIGN_AREA + index, temp);
if (temp[3 + i] == x)
{
break;
}
}
file_area = i * B + y; //转换文件内相对位置
for (i = 0; i < count; i++)
{
output[i + mem_area] = temp_output[i + file_area];
}
printf("%s\n", output + mem_area);
return OK;
}
- 写文件
主要代码
int write(int index, int mem_area, int count)
/*
把mem_area 指定的内存位置开
始的ount 个字节顺序写入指定文件写操作从文件的读写指针指示
的位置开始。
*/
{
int i;
int list = -1;
int input_length;
char temp[B];
//*************根据index找到文件打开表
for (i = 0; i < FILE_NUM; i++)
{
if (open_list[i].filesignnum == index)
{
list = i;
break;
}
}
if (list == -1) //没找到
{
printf("没找到当前索引号文件,操作失败...\n");
return ERROR;
}
if (open_list[list].flag != BUSY) //输入的index对应文件没被打开
{
printf("输入的索引号有误,操作失败...\n");
return ERROR;
}
char input[INPUT_LENGTH];
Init_block(input, INPUT_LENGTH);
i = 0;
fflush(stdin);
while (scanf("%c", &input[i]))
{
if (input[i] == '\n') //回车时终止读入
{
input[i] = '\0';
break;
}
i++;
}
input_length = i;
//*******************考虑输入串的长度需要写入的长度为input_length - mem_area
//缓冲区容量可写入输入内容不需要再分配
if (count <= BUFFER_LENGTH)
{
strncat(open_list[list].buffer, input + mem_area, count); //存入缓冲区
}
//大于缓冲区长度分次写入,需要分块写入
else
{
int rest; //当前缓冲区空闲容量
for (i = 0; i < BUFFER_LENGTH; i++)
{
if (open_list[list].buffer[i] == FREE)
{
rest = BUFFER_LENGTH - i;
break;
}
}
//第一部分,缓冲区有一定容量rest 将缓冲区写入文件,清空缓冲区
strncat(open_list[list].buffer + BUFFER_LENGTH - rest, input + mem_area, rest);
write_buffer(index, list);
Init_block(open_list[list].buffer, BUFFER_LENGTH);
//第二部分,循环(input_length - mem_area)/BUFFER_LENGTH 块缓冲区写入文件
for (i = 0; i < (count / BUFFER_LENGTH) - 1; i++)
{
strncpy(open_list[list].buffer, (input + mem_area) + rest + i * BUFFER_LENGTH, BUFFER_LENGTH);
write_buffer(index, list);
Init_block(open_list[list].buffer, BUFFER_LENGTH);
}
//第三部分,(count%BUFFER_LENGTH)剩余未满一块的写入缓冲区
Init_block(open_list[list].buffer, BUFFER_LENGTH);
strncpy(open_list[list].buffer, (input + mem_area) + rest + i * BUFFER_LENGTH, count%BUFFER_LENGTH);
int buffer_start;
}
return OK;
}
实验截图
实验代码
GitHub地址:https://github.com/notdz56/16281023-/tree/master/操作系统第五次实验代码