Fy J
CS专业扫雷学深造学者互联网冲浪一级选手
FRIENDS
jhn

操作系统五:文件管理系统

06-16-2019 22:40:05 操作系统
Word count: 4.1k | Reading time: 16min

原创文章,转载、引用请注明出处!
代码、文章及图片挂载地址:https://github.com/MoyangSensei/OS

题目

简介

本实验要求在模拟的I/O系统之上开发一个简单的文件系统。用户通过create,open,read等命令与文件系统交互。文件系统把磁盘视为顺序编号的逻辑块序列,逻辑块的编号为0至L−1。I/O系统利用内存中的数组模拟磁盘。

I/O系统

实际物理磁盘的结构是多维的:有柱面、磁头、扇区等概念。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。

目录

我们的文件系统中仅设置一个目录,该目录包含文件系统中的所有文件。除了不需要显示地创建和删除之外,目录在很多方面和普通文件相像。目录对应0号文件描述符。初始状态下,目录中没有文件,所有,目录对应的描述符中记录的长度应为0,而且也没有分配磁盘块。每创建一个文件,目录文件的长度便增加一分。目录文件的内容由一系列的目录项组成,其中每个目录项由如下内容组成:

• 文件名
• 文件描述符序号

文件的创建与删除

创建文件时需要进行如下操作;

• 找一个空闲文件描述符(扫描ldisk [0]~ldisk [k - 1])
• 在文件目录里为新创建的文件分配一个目录项(可能需要为目录文件分配新的磁盘块)
• 在分配到的目录项里记录文件名及描述符编号.
• 返回状态信息(如有无错误发生等)

删除文件时需要进行如下操作(假设文件没有被打开):

• 在目录里搜索该文件的描述符编号
• 删除该文件对应的目录项并更新位图
• 释放文件描述符
• 返回状态信息

文件的打开与关闭

文件系统维护一张打开文件表.打开文件表的长度固定,其表目包含如下信息:
• 读写缓冲区
• 读写指针
• 文件描述符号

文件被打开时,便在打开文件表中为其分配一个表目;文件被关闭时,其对应的表目被释放。读写缓冲区的大小等于一个磁盘存储块。打开文件时需要进行的操作如下:

• 搜索目录找到文件对应的描述符编号
• 在打开文件表中分配一个表目
• 在分配到的表目中把读写指针置为0,并记录描述符编号
• 读入文件的第一块到读写缓冲区中
• 返回分配到的表目在打开文件表中的索引号

关闭文件时需要进行的操作如下:

• 把缓冲区的内容写入磁盘
• 释放该文件在打开文件表中对应的表目
• 返回状态信息

读写

文件打开之后才能进行读写操作.读操作需要完成的任务如下:

  1. 计算读写指针对应的位置在读写缓冲区中的偏移

  2. 把缓冲区中的内容拷贝到指定的内存位置,直到发生下列事件之一:

• 到达文件尾或者已经拷贝了指定的字节数。这时,更新读写指针并返回相应信息
• 到达缓冲区末尾。这时,把缓冲区内容写入磁盘,然后把文件下一块的内容读入磁盘。最后返回第2步。

其他操作请同学们自己考虑。

测试

为了能够对我们的模拟系统进行测试,请编写一个操纵文件系统的外壳程序或者一个菜单驱动系统。


解答

Liunx视角下的文件

在LINUX系统中有一个重要的概念:一切都是文件。其实这是UNIX哲学的一个体现,而Linux是重写UNIX而来,所以这个概念也就传承了下来。在UNIX系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。

Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。

对于Linux系统来说,从物理磁盘到文件系统有如下概念:

我们知道文件最终是保存在硬盘上的。硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等。每个盘片有两面,都可记录信息。盘片被分成许多扇形的区域,每个区域叫一个扇区,每个扇区可存储128×2的N次方(N=0.1.2.3)字节信息。在DOS中每扇区是128×2的2次方=512字节,盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。硬盘中,不同盘片相同半径的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆,在许多场合,磁道和柱面可以互换使用,我们知道,每个磁盘有两个面,每个面都有一个磁头,习惯用磁头号来区分。扇区,磁道(或柱面)和磁头数构成了硬盘结构的基本参数,帮这些参数可以得到硬盘的容量,基计算公式为:

存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数

基本要点有:

(1)硬盘有数个盘片,每盘片两个面,每个面一个磁头
(2)盘片被划分为多个扇形区域即扇区
(3)同一盘片不同半径的同心圆为磁道
(4)不同盘片相同半径构成的圆柱面即柱面
(5)存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
(6)信息记录可表示为:××磁道(柱面),××磁头,××扇区

那么这些空间又是怎么管理起来的呢?unix/linux使用了一个简单的方法。它将磁盘块分为以下三个部分:

(1)超级块,文件系统中第一个块被称为超级块。这个块存放文件系统本身的结构信息。比如,超级块记录了每个区域的大小,超级块也存放未被使用的磁盘块的信息。

(2)I-切点表。超级块的下一个部分就是i-节点表。每个i-节点就是一个对应一个文件/目录的结构,这个结构它包含了一个文件的长度、创建及修改时间、权限、所属关系、磁盘中的位置等信息。一个文件系统维护了一个索引节点的数组,每个文件或目录都与索引节点数组中的唯一一个元素对应。系统给每个索引节点分配了一个号码,也就是该节点在数组中的索引号,称为索引节点号

(3)数据区。文件系统的第3个部分是数据区。文件的内容保存在这个区域。磁盘上所有块的大小都一样。如果文件包含了超过一个块的内容,则文件内容会存放在多个磁盘块中。一个较大的文件很容易分布上千个独产的磁盘块中。

而Linux正统的文件系统(如ext2、ext3)一个文件由目录项、inode和数据块组成:

目录项:包括文件名和inode节点号。
Inode:又称文件索引节点,是文件基本信息的存放地和数据块指针存放地。
数据块:文件的具体内容存放地。

总体设计与代码

磁盘分布

磁盘第0块对应于文件描述符位图,共328bit;第1块到第4块,共324*8 = 1024bit对应于1024个数据块位图;接下来100块,每块一个文件描述符;接下来有1024块为数据块。

1
2
3
4
5
6
7
8
9
10
#define FILEBLOCK 1
#define DATABLOCK 4
#define FILEDESSIZE 100
#define L 1129
#define B 32
#define KEEPSIZE 105
char ldisk[L][B];
char map[8] = {127,64,32,16,8,4,2,1};
char temp_block[B];
int noofblock;

文件系统组织

文件系统的组织磁盘的前K个块是保留区,其中包含如下信息:位图和文件描述符。位图用来描述磁盘块的分配情况。位图中的每一位对应一个逻辑块。创建或者删除文件,以及文件的长度发生变化时,文件系统都需要进行位图操作。前K个块的剩余部分包含一组文件描述符。每个文件描述符包含如下信息:

  • 文件长度,单位字节

  • 文件分配到的磁盘块号数组,该数组的长度是一个指定的系统参数。

主函数

主函数中包括系统的初始化和输入指令选择功能。功能选择模块使用输出string字符串并与关键字进行匹配来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
int main(){
InitFMS();
load();
printTips();
int opCounter=0;
for(;;){
cout<<endl;
cout<<"["<<++opCounter<<"]:";
string op="";
cin>>op;

if(!op.compare("exit")){
savedata();
return 0;
}
else if(!op.compare("create")){
printf("File Name: ");
char filename1[10];
scanf("%s",filename1);
if(!create(filename1)){
exit(0);
}
getchar();
}
else if(!op.compare("delete")){
printf("File Name: ");
char filename[10];
scanf("%s",filename);
getchar();
destroy(filename);
}
else if(!op.compare("open")){
printf("File Name: ");
char filename[10];
scanf("%s",filename);
getchar();
if(!open(filename)){
cout<<"ERROR: 请输入正确的文件名!"<<endl;
continue;
}
printf("接下来的指令需为读写指令,请输入:");
cin>>op;
if(!op.compare("read")){
getchar();
char temp[B];
read(NULL,temp,B);
printf("%s\n",temp);
close(filename);
}
else if(!op.compare("write")){
getchar();
char str[100];
printf("请输入写入内容:\n");
scanf("%s",str);
write(NULL,str,strlen(str)+1);
close(filename);
getchar();
}
}
else if(!op.compare("dir")){
directory();
printf("\n");

}
else if(!op.compare("clean")){
InitFMS();
}
else if(!op.compare("help")){
printTips();
}
else{
cout<<"ERROR: 请正确输入指令!"<<endl;
}
}
return 0;
}

指令功能

本文件系统提供以下指令功能:

指令名称 对应的函数名 函数位置 功能描述
exit - - 退出文件管理系统(自动保存文件)
help void printTips() AuxiliaryFunction.h 显示指令功能的操作提示信息
dir bool destroy(char *filename) SystemFunction.h 显示文件目录
create bool create(char *filename) SystemFunction.h 创建未存在的文件
delete bool destroy(char *filename) SystemFunction.h 删除已存在的文件
open bool open(char *filename) SystemFunction.h 打开已存在的文件
close bool close(char *index) SystemFunction.h 关闭已打开的文件
write bool write(char *index, char *mem_area, int count) SystemFunction.h 读取已存在的文件
clean void InitFMS() SystemFunction.h 清空所有存储内容

部分重点实现的函数如下:

create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool create(char *filename){
char *p = filename;
int length=0;
if((length = strlen(filename))>10)
{
printf("文件名太长!\n");
return false;
}
int index = getfileheadblock();
setfileheadblock();
p=filename;
filedes fd;
memcpy(&fd.name,filename,length+1);
fd.length = 0;
for (int i=0; i<10; i++)
{
fd.allo[i]=-1;
}
fd.allo[0] = getfreedatablock();
setfileheadblock();
memset(temp_block,-1,B);
write_block(fd.allo[0],temp_block);
write_block(FILEBLOCK+DATABLOCK+index,(char*)&fd);
addfile(index);
return true;
}

open

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
bool open(char *filename)
{
filedes fd;
read_block(DATABLOCK+FILEBLOCK,(char*)&fd);
for (int i=0; i<10; i++)
{
if(fd.allo[i]==-1)
{
printf("文件不存在!\n");
return false;
}
read_block(fd.allo[i],temp_block);
for (int j=0; j<B; j++)
{
if(temp_block[j]!=-1)
{
filedes temp_fd;
read_block(DATABLOCK+FILEBLOCK+temp_block[j],(char*)&temp_fd);
if(memcmp(filename,temp_fd.name,strlen(filename))==0)
{
openFileTable.id = temp_block[j];
openFileTable.index = 0;
if(temp_fd.allo[0]==-1)
{
openFileTable.p==NULL;
}
read_block(temp_fd.allo[0],openFileTable.Buffer);
openFileTable.p = openFileTable.Buffer;
openFileTable.dsc = temp_fd;
return true;
}

}
}
}
return false;
}

write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
bool write(char *index, char *mem_area, int count){
int left=B-(openFileTable.p-openFileTable.Buffer);
while (count)
{
if(count-left>0) //当前缓冲区不能满足请求,再取
{
count-=left;
if(openFileTable.p==NULL)
{
printf("访问超过范围\n");
return false;
}
memcpy(openFileTable.Buffer,mem_area,left);
write_block(openFileTable.dsc.allo[openFileTable.index],openFileTable.Buffer);
openFileTable.index++;
if(openFileTable.dsc.allo[openFileTable.index]==-1)
{
openFileTable.p=NULL;
}
openFileTable.p = openFileTable.Buffer;

left = B;
}
else
{
if(openFileTable.p==NULL)
{
printf("访问超过范围\n");
return false;
}
memcpy(openFileTable.p,mem_area,count);
write_block(openFileTable.dsc.allo[openFileTable.index],openFileTable.Buffer);
openFileTable.p+=count;
openFileTable.dsc.length+=count;
write_block(DATABLOCK+FILEBLOCK+openFileTable.id,(char*)&openFileTable.dsc);
return true;
}
}
return false;
}

dir

1
2
3
4
5
6
7
8
9
10
11
12
13
void printTips(){
cout<<"----------------------------------------"<<endl;
for(int i=0;i<10;i++){
cout<<TIPSNAME[i];
for(int j=0;j<10-TIPSNAME[i].length();j++){
cout<<" ";
}
cout<<TIPSINFO[i];
cout<<endl;
}
cout<<"----------------------------------------"<<endl;
cout<<endl;
}

测试运行结果

创建新文件

打开新文件并写入内容

删除文件

help指令和clean指令


参考链接

Linux文件系统详解:https://www.cnblogs.com/alantu2018/p/8461749.html

< PreviousPost
“一场战争”
NextPost >
深度学习笔记(2):使用神经网络识别手写数字
CATALOG
  1. 1. 题目
    1. 1.1. 简介
    2. 1.2. I/O系统
    3. 1.3. 文件系统
      1. 1.3.1. 用户与文件系统之间的接口
      2. 1.3.2. 文件系统的组织
      3. 1.3.3. 目录
      4. 1.3.4. 文件的创建与删除
      5. 1.3.5. 文件的打开与关闭
      6. 1.3.6. 读写
    4. 1.4. 测试
  2. 2. 解答
    1. 2.1. Liunx视角下的文件
    2. 2.2. 总体设计与代码
      1. 2.2.1. 磁盘分布
      2. 2.2.2. 文件系统组织
      3. 2.2.3. 主函数
      4. 2.2.4. 指令功能
        1. 2.2.4.1. create
        2. 2.2.4.2. open
        3. 2.2.4.3. write
        4. 2.2.4.4. dir
    3. 2.3. 测试运行结果
  3. 3. 参考链接