操作系统文件结构的C++模拟

实验八 文件结构

第一题 MS-DOS中磁盘文件的存储结构

实验题目

模拟设计MS-DOS操作系统中磁盘文件的存储结构。

提示

(1)当用户对记录式文件采用顺序存以方式时,用户总是依次地访问一个个逻辑记录,即当访问了第i个记录后,下次总是访问第i+1个记录。所以,当用户采用顺序存取方式访问文件时,只要给出访问要求(读或写)而无需再指出要访问的记录号。

(2)采用链接文件结构,只有读出一个物理块信息后才能从链接字中得知下一个物理块号。所以,当用户要在文件中插入一些信息时,文件系统必须多次地请求启动磁盘读出信息才能做插入工作。

image-20211221110015455

  • MS-DOS操作系统对链接文件结构作了改进,它是把所有的链接指针集中在一起,存放在文件定位表FAT中。查找链接字时不必读出物理块信息可直接从FAT中得到。

  • 其设计思想是:
    假定磁盘上共有N个物理块可供使用,FAT就有N项,初始化时为全“0”,表示对应的物理块均可使用,当要存放文件时,从FAT中寻找为“0”的项,其对应的物理块用来存放文件信息,把文件的链接指针(指出物理块号)登记。在FAT中,文件第一块块号登记在文件目录中。

    image-20211221110148618

(3)假定磁盘存储空间共有32个物理块,模拟设计文件定位表FAT。文件定位表可以用一个一维数组FAT[031]来定义,其中一个元素与一个物理块对应。当FAT[i]=0时,表示第i块为空闲块;当FAT[i]=FFF时,表示链接文件到第i块结束;当在0~FFF时,其值指示链接文件中下一个物理块号。

(4)每个物理块只能存放一个逻辑记录,设计一个程序把文件的逻辑结构模拟转换成MS-DOS的链接结构。

  • 要求保存一个已经在主存中的文件时,给出文件名和文件的逻辑记录长度及个数,对一个已经保存的文件,允许用户插入新记录。用键盘输入来模拟用户的要求,输入信息为:
    “存” :文件名 逻辑记录个数;
    “插入” :文件名 逻辑记录号。

  • 模拟程序的算法如下图所示:

    image-20211221110505777

    (5)假设系统中已经有两个链接文件,其链接情况由FAT表指出(链接情况学生自定),现又要保存一个新文件,然后对已保存的文件插入一个新记录。运行你所设计的程序,观察其结果。

程序中使用的数据结构及符号说明

  1. 结构体及其数组(数据结构):文件目录信息

    1
    2
    3
    4
    5
    6
    7
    struct File //文件目录信息
    {
    string name; //文件名
    int start; //起始块号
    int length; //文件长度
    };
    File catalog[10001]; //文件目录
  2. 数组(数据结构):文件定位表

    1
    int FAT[10001];      //文件定位表
  3. 整型变量

    1
    2
    3
    int cnt = 0;         //已存的文件数
    int N; //物理块总数
    int rest; //空闲块个数
  4. 自定义函数

    • void init() :初始化
    • void store():存
    • void ins():插入(每次一块)
    • void disCatalog():打印文件目录
    • void disFAT():打印FAT表

源程序并附上注释

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#include <iostream>
#include <cstring>
#include <iomanip>
using namespace std;

struct File //文件目录信息
{
string name; //文件名
int start; //起始块号
int length; //文件长度
};
File catalog[10001]; //文件目录
int cnt = 0; //已存的文件数
int N; //物理块总数
int rest; //空闲块个数
int FAT[10001]; //文件定位表

//初始化
void init()
{
cout << "请输入物理块个数:";
cin >> N;
rest = N;
//FAT表初始化为全0
memset(FAT, 0, sizeof(FAT));
//注:FAT[0]固定为FDF;-1表示FFF
}

//存
void store()
{
string s;
cout << "请输入要存入的文件名:";
cin >> s;
//检查是否有同名文件
for (int i = 0; i < cnt; i++)
{
if (catalog[i].name == s)
{
cout << "已存在同名文件!" << endl;
return;
}
}
//若无同名文件:
int len;
cout << "请输入文件记录个数:";
cin >> len;
if (len > rest) //检查空闲块是否够
{
cout << "磁盘空间不够!" << endl;
return;
}
//通过检查后,文件目录记录文件信息:
catalog[cnt].name = s;
catalog[cnt].length = len;
int last; //记录上一个物理块的位置
//按链接结构填FAT表
for (int i = 0; i < len; i++)
{
//遍历FAT表
for (int j = 1; j <= N; j++)
{
if (FAT[j] == 0 && j != last) //若找到空闲块
{
if (i == 0) //文件起始块
{
catalog[cnt].start = j;
last = j;
}
else //不是文件起始块
{
//更新FAT表
FAT[last] = j;
last = j;
if (i == len - 1) //如果是文件最后一块
{
FAT[j] = -1; //-1表示FFF
}
}
rest--;
break;
}
}
}
cout << "文件保存完毕!" << endl;
cnt++; //文件保存完毕,已存文件数加1
return;
}

//插入(每次一块)
void ins()
{
string s;
cout << "请输入文件名:";
cin >> s;
bool flag = 0; //标记是否存在该文件
//检查是否有同名文件
int num; //插入记录的文件在目录中的位置
for (int i = 0; i < cnt; i++)
{
if (catalog[i].name == s)
{
num = i;
flag = 1;
break;
}
}
if (!flag) //不存在该文件
{
cout << "无此文件!" << endl;
return;
}
if (rest == 0)
{
cout << "无空闲块!" << endl;
return;
}
int ins_num; //逻辑记录编号
cout << "请输入 插入的逻辑记录在文件中的编号:";
cin >> ins_num;
if (ins_num > catalog[num].length + 1)
{
cout << "编号过大!";
return;
}
int free_block;
// 寻找空闲块
for (int i = 1; i <= N; i++)
{
if (FAT[i] == 0)
{
free_block = i;
break;
}
}
//更新文件目录
catalog[num].length++;
int temp_pos = 0; //记录遍历到的逻辑记录编号
int pre; //前一个逻辑记录的位置
//依次遍历该文件的逻辑记录在FAT表中的块
//i:当前位置 FAT[i]:下一位置
for (int i = catalog[num].start; FAT[i] != -1; i = FAT[i])
{
temp_pos++;
if (temp_pos == ins_num - 1) //到达插入位置的前一链接块
{
//修改链接指针(重点)
FAT[free_block] = FAT[i];
FAT[i] = free_block;
break;
}
}
if (ins_num == 1) //头部插入
{
catalog[num].start = free_block;
}
if (ins_num == catalog[num].length) //尾部插入
{
FAT[free_block] = -1;
}
cout << "插入完毕" << endl;
return;
}

//输出文件目录
void disCatalog()
{
cout << "文件目录如下:" << endl;
//表头
cout << "| 文件名 | 起始块号 |" << endl;
for (int i = 0; i < cnt; i++)
{
cout << "| " << setw(6) << catalog[i].name << " | " << setw(8) << catalog[i].start << " |" << endl;
}
}

//输出FAT表
void disFAT()
{
cout << "FAT表如下:" << endl;
for (int i = 0; i <= N; i++)
{
cout << left << setw(3) << i;
if (i == 0)
{
cout << "FDF" << endl;
}
else
{
if (FAT[i] == -1)
{
cout << "FFF" << endl;
}
else
{
cout << setw(3) << FAT[i] << endl;
}
}
}
return;
}

int main()
{
init();
int op = 5;
while (op)
{
cout << "----------------------" << endl;
cout << "1:存" << endl;
cout << "2:插入" << endl;
cout << "3:查看文件目录" << endl;
cout << "4:查看FAT表" << endl;
cout << "0:退出" << endl;
cout << "请输入操作:";
cin >> op;
cout << "----------------------" << endl;
if (op == 1)
store();
if (op == 2)
ins();
if (op == 3)
disCatalog();
if (op == 4)
disFAT();
if (op == 0)
cout << "程序结束!";
cout << "----------------------" << endl;
}
return 0;
}

运行结果

image-20211221112900663image-20211221113135367image-20211221113049802

第二题 模拟便于直接存取的索引文件结构

实验题目

模拟便于直接存取的索引文件结构。

提示

(1)索引文件像链接文件一样,文件的逻辑记录信息可存放在非连续的磁盘存储空间中。但这些存放逻辑记录的存储空间不是按链表的形式链接在一起的,而是采用索引表来指出逻辑记录存放的物理位置。

文件目录与索引表的关系如下图所示:

image-20211221113429122

(2)建立索引文件的过程是:寻找一些空闲物理块;逻辑记录存入这些物理块中;把逻辑记录与物理块的对应关系登记在索引表中。

程序中使用的数据结构及符号说明

  1. 整型变量

    1
    2
    int rest = 100000;        //磁盘剩余空间
    int user_cnt = 0; //用户数量
  2. 一维数组(数据结构)

    1
    2
    3
    bool disk[100001];        //磁盘物理块 0 表示未被使用,1 表示已经被使用
    string user[101]; //用户列表
    int file_cnt[101]; //每个用户的文件数
  3. 二维数组(数组结构)

    1
    2
    3
    int block_cnt[101][101];  //每个文件的块数量,用户 i 的第 j 个文件的块数量
    string file[101][101]; //文件名称,用户 i 的第 j 个文件的名称
    int table[101][101][101]; //文件索引表,用户 i 的文件 j 的第 k 块的物理地址
  4. 自定义函数

    • void init():初始化函数,重置磁盘和用户
    • int findUser(string username):搜索用户列表,判断用户是否已经存在
    • int findFile(string username, string file_name):搜索用户的文件列表,判断文件是否已经存在
    • void addUser():创建新用户
    • void addFile():添加新文件
    • void delFile():删除文件
    • void delUser():删除用户
    • void disUser():查看用户列表
    • void disFile():查看用户的文件目录
    • void disTable():查看某个用户的某个文件的索引表

源程序并附上注释

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#include <iostream>
#include <iomanip>
using namespace std;
int rest = 100000; //磁盘剩余空间
int user_cnt = 0; //用户数量
bool disk[100001]; //磁盘物理块 0 表示未被使用,1 表示已经被使用
string user[101]; //用户列表
int file_cnt[101]; //每个用户的文件数
int block_cnt[101][101]; //每个文件的块数量,用户 i 的第 j 个文件的块数量
string file[101][101]; //文件名称,用户 i 的第 j 个文件的名称
int table[101][101][101]; //文件索引表,用户 i 的文件 j 的第 k 块的物理地址

//初始化函数,重置磁盘和用户
void init()
{
//所有磁盘标记为没有使用
for (int i = 0; i < 100001; i++)
disk[i] = 0;
//用户数量置为 0
user_cnt = 0;
//输出提示信息
cout << "磁盘已重置" << endl;
}

//搜索用户列表,判断用户是否已经存在
int findUser(string username)
{
//在用户列表当中搜索同名的用户
for (int i = 1; i <= user_cnt; i++)
if (user[i] == username)
return i;
return 0;
}

//搜索用户的文件列表,判断文件是否已经存在
int findFile(string username, string file_name)
{
//在某个用户的文件列表中搜索是否有这个文件存在
int usr = findUser(username);
for (int i = 1; i <= file_cnt[usr]; i++)
if (file[usr][i] == file_name)
return i;
return 0;
}

//创建新用户
void addUser()
{
string username;
cout << "请输入用户名称:";
cin >> username;
//判断是否已经存在同名的用户
if (findUser(username) != 0)
{
cout << "该用户已存在,创建失败" << endl;
return;
}
//将这个用户存入用户列表
user[++user_cnt] = username;
//重置当前用户的文件数量
file_cnt[user_cnt] = 0;
cout << "用户创建成功" << endl;
return;
}

//添加新文件
void addFile()
{
string username;
cout << "请输入要添加文件的用户名称:";
cin >> username;
//判断是否有这个用户
if (findUser(username) == 0)
{
cout << "该用户不存在,文件添加失败" << endl;
return;
}
string file_name;
cout << "请输入要添加的文件的名称:";
cin >> file_name;
//判断该用户是否有同名的文件存在
if (findFile(username, file_name) != 0)
{
cout << "该用户已经存在同名文件,添加失败" << endl;
return;
}
int len;
cout << "请输入文件的长度:";
cin >> len;
//判断磁盘剩余空间是否够存储这个文件
if (len > rest)
{
cout << "磁盘空间不足,存储失败" << endl;
return;
}
//找到用户在用户列表中的位置
int user_pos = findUser(username);
//更新用户的文件数量
file_cnt[user_pos]++;
//将文件加入到用户文件列表中
file[user_pos][file_cnt[user_pos]] = file_name;
//更新这个文件占用的块数量
block_cnt[user_pos][file_cnt[user_pos]] = len;
int pos = 0;
//占用磁盘资源
for (int i = 1; i <= len; i++)
{
//寻找一个空的磁盘块
while (disk[pos] != 0)
pos++;
//占用这个磁盘块
disk[pos] = 1;
//记录索引表和磁盘间的映射关系
table[user_pos][file_cnt[user_pos]][i] = pos;
//更新剩余磁盘剩余块的数量
rest--;
}
//输出成功信息
cout << "文件存储成功" << endl;
}

//删除文件
void delFile()
{
string username;
cout << "请输入要删除的文件所属的用户:";
cin >> username;
//寻找是否有这个用户
if (findUser(username) == 0)
{
cout << "没有这个用户,删除失败" << endl;
return;
}
string file_name;
cout << "请输入要删除的文件名称:";
cin >> file_name;
//寻找是否有要删除的文件
if (findFile(username, file_name) == 0)
{
cout << "没有这个文件,删除失败" << endl;
return;
}
//记录用户在用户表中的位置
int user_pos = findUser(username);
//记录文件在文件目录中的位置
int file_pos = findFile(username, file_name);
//释放这个文件占用的磁盘资源
for (int i = 1; i <= block_cnt[user_pos][file_pos]; i++)
{
disk[table[user_pos][file_pos][i]] = 0;
rest++;
}
//清空文件索引表中这个文件的记录
for (int i = file_pos; i < file_cnt[user_pos]; i++)
for (int j = 1; j <= block_cnt[user_pos][i + 1]; j++)
table[user_pos][i][j] = table[user_pos][i + 1][j]; //清空这个文件的块数量记录
for (int i = file_pos; i < file_cnt[user_pos]; i++)
block_cnt[user_pos][i] = block_cnt[user_pos][i + 1];
//在文件目录中删除这个文件
for (int i = file_pos; i < file_cnt[user_pos]; i++)
file[user_pos][i] = file[user_pos][i + 1];
//文件数量减一
file_cnt[user_pos]--;
cout << "文件删除成功" << endl;
}

//删除用户
void delUser()
{
string username;
cout << "请输入要删除的用户名称:";
cin >> username;
int user_pos = findUser(username);
if (user_pos == 0)
{
cout << "没有这个用户,删除失败" << endl;
return;
}
//释放这个用户占用的所有磁盘资源
for (int i = 1; i <= file_cnt[user_pos]; i++)
{
for (int j = 1; j <= block_cnt[user_pos][i]; j++)
{
disk[table[user_pos][i][j]] = 0;
rest++;
}
}
//删除这个用户所有文件的块映射记录
for (int i = user_pos; i < user_cnt; i++)
for (int j = 1; j <= file_cnt[i + 1]; j++)
for (int k = 1; k <= block_cnt[i + 1][j]; k++)
table[i][j][k] = table[i + 1][j][k];
//删除这个用户的文件目录
for (int i = user_pos; i < user_cnt; i++)
for (int j = 1; j <= file_cnt[i + 1]; j++)
file[i][j] = file[i + 1][j];
//删除这个用户的每个文件的块数量
for (int i = user_pos; i < user_cnt; i++)
for (int j = 1; j <= file_cnt[i + 1]; j++)
block_cnt[i][j] = block_cnt[i + 1][j];
//用户文件数量中删除这个用户的记录
for (int i = user_pos; i < user_cnt; i++)
file_cnt[i] = file_cnt[i + 1];
//在用户列表里面删除这个用户的记录
for (int i = user_pos; i < user_cnt; i++)
user[i] = user[i + 1];
//更新删除以后的用户数量
user_cnt--;
cout << "用户删除成功" << endl;
}

//查看用户列表
void disUser()
{
cout << "用户列表如下:" << endl;
cout << "| 用户名 | 文件目录地址 |" << endl;
for (int i = 1; i <= user_cnt; i++)
{
cout << "| " << setw(6) << user[i] << " | " << setw(4) << 100 * (i - 1) << " |" << endl;
}
}

//查看用户的文件目录
void disFile()
{
string user_name;
cout << "请输入要查看文件目录的用户名称:";
cin >> user_name;
//找到用户在用户列表中的位置
int user_pos = findUser(user_name);
if (user_pos == 0)
{
cout << "该用户不存在!" << endl;
return;
}
//输出
cout << "用户" << user_name << "的文件目录如下:" << endl;
cout << "| 文件名 | 索引表地址 |" << endl;
for (int i = 1; i <= file_cnt[user_pos]; i++)
{
cout << "| " << setw(6) << file[user_pos][i] << " | " << setw(4) << 100 * (user_pos - 1) +
(i - 1) << " |" << endl;
}
}

//查看某个用户的某个文件的索引表
void disTable()
{
string user_name;
cout << "请输入要查看索引表的用户名称:";
cin >> user_name;
//找到用户在用户列表中的位置
int user_pos = findUser(user_name);
if (user_pos == 0)
{
cout << "该用户不存在!" << endl;
return;
}
string file_name;
cout << "请输入要查看索引表的文件名称:";
cin >> file_name;
//找到文件在文件目录中的位置
int file_pos = findFile(user_name, file_name);
if (file_pos == 0)
{
cout << "该文件不存在!" << endl;
return;
}
//输出
cout << "用户" << user_name << "的文件" << file_name << "的索引表如下:" << endl;
cout << "| 记录号 | 物理块号 |" << endl;
for (int i = 1; i <= block_cnt[user_pos][file_pos]; i++)
{
cout << "| " << setw(6) << i << " | " << setw(4) << table[user_pos][file_pos][i] << " |" << endl;
}
}

int main()
{
init();
int op = 5;
while (op)
{
cout << "----------------------------------" << endl;
cout << "1:创建新用户" << endl;
cout << "2:添加新文件" << endl;
cout << "3:删除用户" << endl;
cout << "4:删除文件" << endl;
cout << "5:查看用户列表" << endl;
cout << "6:查看某用户的文件目录" << endl;
cout << "7:查看某文件的索引表" << endl;
cout << "8:查看磁盘剩余空间" << endl;
cout << "0:退出" << endl;
cout << "请输入操作:";
cin >> op;
cout << "----------------------------------" << endl;
if (op == 1)
addUser();
if (op == 2)
addFile();
if (op == 3)
delUser();
if (op == 4)
delFile();
if (op == 5)
disUser();
if (op == 6)
disFile();
if (op == 7)
disTable();
if (op == 8)
cout << "磁盘剩余空间为:" << rest << endl;
if (op == 0)
cout << "程序结束!" << endl;
cout << "----------------------------------" << endl;
}
return 0;
}

运行结果

  1. 添加用户A及用户A的文件a。

    image-20211221130636419

  2. 查看磁盘剩余空间

    image-20211221130704608

  3. 添加用户B,查看用户列表

    image-20211221130729964

  4. 查看用户A的文件目录和文件a的索引表

    image-20211221130813499

  5. 删除文件a,再查看磁盘剩余空间

    image-20211221130854161

  6. 删除用户A,再查看用户列表,最后退出。

    image-20211221131215559

思考题

1

链表文件结构和索引文件结构各自的优缺点是什么?

链表文件结构:

优点

① 消除了外部碎片,故而显著地提高了外存空间的利用率;

② 因为是根据文件的当前需要,为它分配必需的盘块,当文件动态增长时,可动态地再为它分配盘块,故而无需事先知道文件的大小;

③ 对文件的增、删、改十分方便。

缺点

① 不能支持高效的直接存取。 要对一个较大的文件进行直接存取, 须首先在 FAT 中顺序地查找许多盘块号。

② FAT 需占用较大的内存空间。

索引文件结构:

优点

① 支持直接访问。当要读文件的第 i 个盘块时,可以方便地直接从索引块中找到第 i 个盘块的盘块号。

② 不会产生外部碎片。

缺点

可能要花费较多的外存空间,系统开销较大。

2

当文件较大时,使得索引表也较大,如果索引表的大小超过了一个物理块,如何进行索引表的存取?

① 采用多级索引方式来解决,使索引表所指的物理块中存储文件信息对应的物理块地址。这样对索引表的长度没有限制,但是空间上的开销比较大。

② 通过分页式内存管理进行存取,使较大的索引表存储时按照块来分割,一定程度上利用了内存空间。但是文件的读取和存储都是分块进行的,而且位置之间没有顺序性,所以存取的算法复杂度较高。

实验心得

​ 该实验使我加深了对两种文件结构的理解。其中在实现MS_DOS磁盘文件的存储结构时,要注意文件定位表实现链接的处理,在插入时把握好头插入、中间插入、尾插入三种情况。实现便于直接存取的索引文件结构时,使用的数组较多,在进行存、删操作的实现时,要把握好这各类数组之间的关系,处理好细节。总之,这次实验使我的动手编程能力有所提升。


本博客所有文章除特别声明外,均为博客作者本人编写整理,转载请联系作者!