文章目录
1. 实验目的
理解并掌握主要的移动头磁盘调度算法的基本设计思想和编程实现要旨。
2. 实验内容
编程设计与实现关于移动头磁盘调度的先来先服务调度算法(FCFS)、最短寻道时间优先调度算法(SSTF)、电梯调度算法(SCAN)、循环式单向电梯调度算法(CSCAN)、双队列电梯调度算法(FSCAN),并随机发生一组磁盘访问事件(磁道号)序列开展有关算法的测试及性能比较。
3. 实验要求
本实验课题功能设计要求如下:
(1)编程设计实现先来先服务调度算法、最短寻道时间优先调度算法、电梯调度算法、
循环式单向电梯调度算法、双队列单向电梯调度算法;
(2)编程设计实现移动头磁盘访问事件序列的随机发生机制,其中所生成的磁道号取值区间为[0, 199];
(3)基于相同的一组移动头磁盘访问事件序列(长度为 100,即 100 个磁道号),进行有关算法的测试,并显示输出基于相应调度算法的磁道访问的先后次序及平均寻道数;
(4)变换上述移动头磁盘访问事件序列实施多次测试,统计分析和比较有关算法的性能(譬如平均寻道数)。
4. 实验思路
实验按照要求进行。本实验,主要使用C++STL库进行编写代码,已获得任课老师首肯。
实验的所有设定参数由文件读入,本人给定了config的格式,保证了输入的正确性。
一开始我们可以设定一个固定的随件数种子,用以生成相同的随机数列作为磁道访问序列。这样方便判断验证实验是否正确和结果的可重复性。
主要函数包括要求的五个核心算法函数和pre_set函数。Pre_set函数主要功能为实验的基本参数读入和随件访问序列的生成,基本的信息输出验证。
5. 程序实现
5.1. 相关环境介绍
操作系统:window 10 21H2
开发环境:Clion-2022.2.1-Windows
编译器:mwing-10.0
5.2. 主要数据结构
01:vector<int> trace_accessed;
02:int total_num_trace_access;
03:int random_seed;
04:int min_random;
05:int max_random;
06:int initial_now;
主要数据结构定义为全局变量,方便参数传递。Trace_accessed为生成的待访问序列。
Total_num_trace_access为访问队列总长度。Random_seed随机序列生成的种子,min_random和max_random为随机数生成的范围,initial_now为初始化磁头位置。
上述参数部分由config文件读入,部分是随机数生成,保证了程序的鲁棒性。
5.3. Pre_set函数 随机队列生成
依次读入总队列长度,随机数种子,随机数范围。然后生成队列,最后把生成的队列和初始磁头位置输出。
图表 5 1 config文件超参数设置
图表 5 2 结果输出
07:int pre_set(string& file_path){
08: fstream fin;
09: fin.open(file_path);
10: if(!fin){
11: cout << file_path << " file open error\n";
12: return -1;
13: }
14:
15: fin >> total_num_trace_access;
16: fin >> random_seed;
17: fin >> min_random;
18: fin >> max_random;
19: fin.close();
20: srand(random_seed);
21: int num;
22:// (rand() % (b-a+1))+ a
23: for(int i=0; i<total_num_trace_access; ++i){
24: num = (rand() % (max_random-min_random+1)) + min_random;
25: trace_accessed.emplace_back(num);
26: }
27: initial_now = (rand() % (max_random - min_random +1)) + min_random;
28://output
29: cout << "initial trace head location\t" << initial_now << "\n";
30: cout << "source trace access queue\n";
31: for(auto item:trace_accessed){
32: cout << item << "\t";
33: }
34: cout << "\n\n";
35:}
36:
5.4. FIFO先来先服务
基本思路按照ppt。直接拷贝待访问队列即可。再加入开销计算代码。
结果验证
图表 5 3 FIFO算法验证
可以看出通过手动分析,实验结果正确。
37:int FIFO(){
38: vector<int>access_result = trace_accessed;
39: int total_move=0;
40: int now = initial_now;
41: double mean;
42: for(auto item:access_result){
43: total_move += abs(item - now);
44: now = item;
45: }
46: mean = (double)total_move / total_num_trace_access;
47: cout << "FIFO\n";
48: cout << "accessed queue\n";
49: for(auto item: access_result){
50: cout << item << "\t";
51: }
52: cout << "\nmean trace cost\t" << mean << "\n\n";
53:}
5.5. SSTF 最短服务时间
基本框架和FIFO一样。寻找下一个访问对象时,首先待访问队列进行排序,然后求得大于磁头位置的待访问磁道的下标,进行上下验证,取更近的进行访问,更新待访问队列。周而复始,直到队列为空。
结果验证:
图表 5 4 SSTF算法验证
可以看出通过手动分析,实验结果正确。平均磁头移动相比FIFO极大的减少了。
54:int SSTF(){
55: vector<int> access_result = trace_accessed;
56: std::sort(access_result.begin(), access_result.end());
57: vector<int> result;
58: int total_move =0 ;
59: int now = initial_now;
60: double mean;
61: int b_index;
62: int a_index;
63: while(!access_result.empty()){
64: b_index = lower_bound(access_result.begin(), access_result.end(), now) - access_result.begin();
65: a_index = b_index-1;
66: if(b_index == 0){
67: total_move += abs(access_result[b_index] - now);
68: now = access_result[b_index];
69: access_result.erase(access_result.begin());
70: }
71: else if(b_index == access_result.size()){
72: total_move += abs(access_result[b_index-1] - now);
73: now = access_result[b_index-1];
74: access_result.erase(access_result.end()-1);
75: }
76: else{
77: int tem1 = abs(access_result[b_index] - now);
78: int tem2 = abs(access_result[a_index]- now);
79: if(tem1 >= tem2){
80: total_move += tem2;
81: now = access_result[a_index];
82: access_result.erase(access_result.begin() + a_index);
83: }
84: else{
85: total_move +=tem1;
86: now = access_result[b_index];
87: access_result.erase(access_result.begin() + b_index);
88: }
89: }
90: result.push_back(now);
91: }
92: mean = (double) total_move / total_num_trace_access;
93: cout << "SSTF\n";
94: cout << "accessed queue\n";
95: for(auto item:result){
96: cout << item << "\t";
97: }
98: cout << "\nmean trace cost\t" << mean << "\n\n";
99:}
100:
5.6. SCAN 电梯算法
模拟类似电梯的访问方法,单次只单向运行。
在结合数据结构的基础上,可以把待访问队列进行排序,按初始磁头位置,把队列分开,分两个反向进行入队操。
结果验证:
图表 5 5 SCAN算法
可以看出通过手动分析,实验结果正确。
101:int SCAN(){
102: vector <int>access_result = trace_accessed;
103: std::sort(access_result.begin(), access_result.end());
104: vector<int>result;
105: int total_move =0 ;
106: int now = initial_now;
107: double mean;
108:
109: auto b_index = std::lower_bound(access_result.begin(), access_result.end(), now);
110: result.insert(result.end(), b_index, access_result.end());
111: reverse_iterator<vector<int>::iterator> tem_reverse(b_index);
112:// pointer the left of b_index
113: result.insert(result.end(), tem_reverse, access_result.rend());
114:
115: for(auto item:result){
116: total_move += abs(item - now);
117: now = item;
118: }
119: mean = (double)total_move / total_num_trace_access;
120: cout << "SCAN\n";
121: cout << "accessed queue\n";
122: for(auto item:result){
123: cout << item << "\t";
124: }
125: cout << "\nmean trace cost\t" << mean << "\n\n";
126:// return now;
127:}
128:
5.7. CSCAN 循环电梯
循环电梯算法和电梯算法区别在于,转向后电梯是否从最远端开始访问。其实,按照磁盘原型设计,这种循环电梯算法才是合理的,因为其磁道天然是首尾相连的,这样其实是没有可以实现磁头真正的单向寻道,减少空转。同时,其寻道开销计算方法也有点不同,特别是转向后的变化,课堂教授时没有做区分,我们的计算策略还是以上课为主。
结果验证:
图表 5 6 CSCAN算法验证
结果正确。
129:int CSCAN(){
130: vector<int> access_result = trace_accessed;
131: std::sort(access_result.begin(), access_result.end());
132: vector<int>result;
133: int total_move =0 ;
134: int now = initial_now;
135: double mean;
136:
137: auto b_index = std::lower_bound(access_result.begin(), access_result.end(), now);
138: result.insert(result.end(), b_index, access_result.end());
139:
140:// pointer the left of b_index
141: result.insert(result.end(), access_result.begin(), b_index);
142:
143: for(auto item:result){
144: total_move += abs(item - now);
145: now = item;
146: }
147: mean = (double)total_move / total_num_trace_access;
148: cout << "CSCAN\n";
149: cout << "accessed queue\n";
150: for(auto item:result){
151: cout << item << "\t";
152: }
153: cout << "\nmean trace cost\t" << mean << "\n\n";
154:}
155:
5.8. FSCAN 双队列单向电梯算法
这里为了模拟两次访问,即第一个队列开始访问后,新到的访问请求放到第二队列,我们把随机生成的访问队列可以对半分为两部分,做前后访问,然后对两个访问队列依次使用SCAN算法,进行运算。
为了凸显两个队列的模拟,我们在code中把函数名设置为DFSCAN。首先把两次访问队列输出,在输出最终的访问队列和开销。
编程结果验证如下:
图表 5 7 FSCAN算法验证
结果正确。
156:int DFSCAN(){
157: vector<int> access_result = trace_accessed;
158: vector<int> access_result1(trace_accessed.begin(), trace_accessed.begin()+int(total_num_trace_access/2));
159: vector<int> access_result2(trace_accessed.begin()+int(total_num_trace_access/2), trace_accessed.end());
160:
161: vector<int>result;
162: int total_move =0 ;
163: int now = initial_now;
164: double mean;
165:
166: for(int i=0;i<2;++i){
167: vector<int> tem;
168: if(i==0) tem = access_result1;
169: else if(i==1) tem = access_result2;
170: std::sort(tem.begin(), tem.end());
171:
172: auto b_index = std::lower_bound(tem.begin(), tem.end(), now);
173: result.insert(result.end(), b_index, tem.end());
174: reverse_iterator<vector<int>::iterator> tem_reverse(b_index);
175:// pointer the left of b_index
176: result.insert(result.end(), tem_reverse, tem.rend());
177:
178:// second scan begin
179: now = result.back();
180: }
181:
182: now = initial_now;
183:// to compute cost
184: for(auto item:result){
185: total_move += abs(item - now);
186: now = item;
187: }
188: mean = (double)total_move / total_num_trace_access;
189: cout << "DFSCAN\n";
190: cout << "first scan queue\n";
191: for(auto item: access_result1){
192: cout<< item << "\t";
193: }
194: cout << "\n";
195: cout << "second scan queue\n";
196: for(auto item:access_result2){
197: cout << item << "\t";
198: }
199: cout << '\n';
200: cout << "accessed queue\n";
201: for(auto item:result){
202: cout << item << "\t";
203: }
204: cout << "\nmean trace cost\t" << mean << "\n\n";
205:
206:}
207:
5.9. 平均寻道时间对比
我们通过控制变量法,对5个算法进行性能对比,主要比较平均寻道长度。
表格 5 1 算法平均寻道长度对比
同时,实验要求中的显示寻道次序和平均寻道时间,在算法验证中已经给出,不表。
从表中可以看出FIFO只实现了表面上的公平,但对于寻道整体效果并没有太多助力。最短寻道和电梯法已经电梯法变种之间时间其实比较接近。同时考虑到电梯法改进并不是为了时间上的考量,并且电梯法在排序和访问页面管理中的开销比较大,这一点也在编程的过程中本人深有体会,综合考虑,电梯法也没有比最短寻道算法慢太多。
在访问磁道队列为100时,其他方法会下降到个位数,考虑到随机磁道数生成的范围是0到199,所以会有大量同时重复页面,这样的结果也在合理解释范围之内。
根据以上实验,对于平均寻道长度指标,有以下结论,越小代表性能越好。
FIFO > FSCAN > CSCAN > SCAN > SSTF
6. 实验汇总
###6.1. 实验要求完成情况
成功完成实验要求。
6.2. 技术难点及解决方案
发现输出语句中字符串紧跟一个’\t’,是有几率不能生成制表符的,但是多加一个空格就可以正确生成了,还可以自动对齐。
显示函数的格式美化和对齐实验了许多次。
一开始,随机数种子是按时间设定的,每次实验结果不同,无法复现比较,后固定随机数种子,便于比较实验。
主要还是部分函数性质不熟悉的问题。
6.3. 实验感想和经验总结
算法结果对比过程中,对排序算法的不稳定性有了更多的理解,同时,也知道了一个算法的性能是多方面的,更多的是看实际中的倾向。
计算数组长度时,没注意返回的是 size_t格式的,计算的结果千奇百怪。
使用lower_bound函数时,找不到满足条件的数,返回的是array.end()迭代器。
由基础迭代器创建反向迭代器,反向迭代器指向基础迭代器左侧。
6.4. 参考链接
- ZGSOS操作系统实验指导《实验课题19_移动头磁盘调度算法模拟实现与比较》