操作系统课程设计报告总结(下)

实验六 银行家算法的模拟与实现

实验目的

(1) 进一步了解进程的并发执行。
(2) 加强对进程死锁的理解,理解安全状态与不安全状态的概念。
(3) 掌握使用银行家算法避免死锁问题。

总体设计

背景知识

银行家算法是一个避免死锁的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法。它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。在银行中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要,在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。
我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。

基本原理

操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。
银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。
安全状态:
如果存在一个由系统中所有进程构成的安全序列P1,…,Pn,则系统处于安全状态。安全状态一定是没有死锁发生。安全序列是指一个进程序列{P1,…,Pn}是安全的,即对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和。
不安全状态:
不存在一个安全序列。不安全状态不一定导致死锁。

模块介绍

(1)宏定义及相关数组的定义

#define p 5                           //进程数量
#define Resource 3                    //资源种类数量
int max[p][Resource]={{7,5,3},{3,2,2},{9,0,2},{2,2,2},{4,3,3}};//进程所需资源最大数量
int Allocation[p][Resource]={{0,1,0},{2,0,0},{3,0,2},{2,1,1},{0,0,2}};//进程已有资源数量
int Need[p][Resource]={{7,4,3},{1,2,2},{6,0,0},{0,1,1},{4,3,1}};//进程目前所需资源数量
int Available[Resource]={3,3,2};//银行空闲的各类资源数量
bool flag[p]={false,false,false,false,false};//获得资源则将相应进程代号为下标的数组单元值置为true

(2)相关自定义函数

void initPrint();//打印抬头资源分配表
void print_processForm();//打印资源分配表
bool compare(int p0,int* maxx);// 比较函数:如果Available数组单元的每个数均大于Need数组相应进程单元的每个数则返回ture
int getProcessRes();//输入请求资源的具体进程
int* getResArr(int p0);// 输入具体进程请求的各类资源数量
void SearchSecProcess(int numProcess);// 递归查找可以安全得到资源的进程
void CheckSecurity(int* initNeed,int p0);// 进行安全性检测并输出安全序列

详细设计

关键代码及分析

① 比较函数:比较单个进程对应的Need数组和Available数组相同下标的数组单元的值,当Available数组单元的每个数均大于Need数组相应进程单元的每个数则返回ture,否则返回false。

bool compare(int p0,int* maxx){
	for(int i=0;i<Resource;i++){
		if(maxx[i]>Available[i]) return false;
	}
	return true;
}

② 在函数内部分配给初始进程相应资源后更新资源分配表,调用SearchSecProcess()函数进行安全性检测。

void CheckSecurity(int* initNeed,int p0){
	for(int i=0;i<Resource;i++){//更新资源分配表
		Available[i]-=initNeed[i];
		Allocation[p0][i]+=initNeed[i];
		Need[p0][i]-=initNeed[i];
		if(Need[p0][i]<0) Need[p0][i]=0;
	}
	cout<<"分配给"<<p0<<"进程资源后,资源分配表更新如下:"<<endl;
	print_processForm();//打印资源分配表
	if(compare(p0,Need[p0])){
		flag[p0]=true;//该进程已加入安全序列中
		cout<<"安全序列为:"<<"进程"<<p0<<"  ";
		for(int j=0;j<Resource;j++){
			Available[j]+=Allocation[p0][j];
		}
	}
	SearchSecProcess(p);
}

③ 递归查找未分配资源的进程,分配资源后将相应进程的flag数组对应的数组单元置为true值,检测是否陷入死锁,并在控制台上打印出检测结果。

/*递归查找可以安全得到资源的进程*/
void SearchSecProcess(int numProcess){
	int i;
	for(i=0;i<numProcess;i++){
		if(flag[i]==false && compare(i,Need[i])){
			flag[i]=true;
			for(int j=0;j<Resource;j++){
				Available[j]+=Allocation[i][j];
			}
			cout<<"进程"<<i<<"  ";
			break;
		}
	}
	if(i<numProcess) SearchSecProcess(numProcess);
	else if(flag[0]==false||flag[1]==false||flag[2]==false||flag[3]==false||flag[4]==false)
		cout<<"死锁检测情况:陷入死锁!"<<endl;
	else cout<<"死锁检测情况:未陷入死锁。"<<endl;
}

实验结果与分析

(1)将资源分配表初始赋值后,输入请求资源的具体进程代号:0,后输入该进程请求的A,B,C资源数量,此时表示进程0请求资源A,B,C数量分别为2,3,0;初始分配资源后,更新资源分配表,将进程0请求的资源A,B,C从Available中分出去,并在Allocation和Need两栏中也有所显示。后进行死锁检测,检查分配资源后是否存在安全序列,由于Available目前所拥有的资源A,B,C数量分别为1,0,2,均无法满足任何进程所需的A,B,C资源数,因此此时陷入死锁,在控制台上显示出结果,死锁检测结果:陷入死锁!运行结果如分析图6-1.1所示。
在这里插入图片描述
(2)将资源分配表初始赋值后,输入相关信息,此时表示进程3请求资源A,B,C数量分别为2,3,0;初始分配资源并更新资源分配表后,进行死锁检测,检查分配资源后是否存在安全序列,此种情况不会陷入死锁,在分配给进程3相应的资源后,Available值减少为1,0,2,并回收进程3中已经拥有的资源A,B,C,后变为5,4,3,再进行死锁检测,未陷入死锁,然后再分配给其他进程资源,依次分配资源回收资源,最终确定安全序列为进程3、进程1、进程0、进程2、进程4,并在控制台上打印出来。运行结果如分析图6-1.2所示。
在这里插入图片描述
(3)若输入的进程请求资源数大于该进程所真正需求的资源量,表面进程请求资源不合理,在控制台上输出信息,输入资源不合理,所请求资源数大于真正需求数!运行结果如分析图6-1.3(1)所示。若输入的请求资源数大于Available的相应值,则表示银行家无法提供给进程这么多资源,在控制台上输出输入请求资源不合理,请重新输入的信息,并可以重新输入该进程请求的A,B,C资源数量。运行结果如分析图6-1.3(2)所示。
在这里插入图片描述
在这里插入图片描述

小结与心得体会

通过此次银行家算法的实验,我进一步了解了进程的并发执行,关于此次实验的难点我认为主要在于安全序列的递归检查以及死锁的检测部分,在此思考良久,递归功底并不是很好,后续还要多加锻炼!同时,通过这个实验,加强了我对进程死锁的理解,理解安全状态和不安全状态的概念,也深刻地认识到了银行家算法是避免死锁问题的一种很好的解决方法,此次实验收获很大。
本实验不足之处:当检测出陷入死锁时,笔者并未设计算法去还原到分配资源前的状态,其实设计此算法十分简单,只需将每次更新的资源分配表等相关数组拷贝进新的数组,并使用if判断语句判断当陷入死锁时,不再进行拷贝,并将拷贝数组的各单元值重新赋值给资源分配表等相关函数即可,后续笔者会对换页算法进行设计。

银行家算法源码

#include<iostream>
#include<iomanip>
using namespace std;
#define p 5                           //进程数量
#define Resource 3                    //资源种类数量
int Max[p][Resource]={{7,5,3},{3,2,2},{9,0,2},{2,2,2},{4,3,3}};//进程所需资源最大数量
int Allocation[p][Resource]={{0,1,0},{2,0,0},{3,0,2},{2,1,1},{0,0,2}};//进程已有资源数量
int Need[p][Resource]={{7,4,3},{1,2,2},{6,0,0},{0,1,1},{4,3,1}};//进程目前所需资源数量
int Available[Resource]={3,3,2};//银行空闲的各类资源数量
bool flag[p]={false,false,false,false,false};//获得资源则将相应进程代号为下标的数组单元值置为true
//打印抬头资源分配表
void initPrint(){
	cout<<"*************************************"<<endl;
	cout<<"*         银行家算法C++实现         *"<<endl;
	cout<<"*************************************"<<endl;
	cout<<endl;
}
//打印资源分配表
void print_processForm(){
	cout<<std::left<<setw(9)<<"进程";
	cout<<std::left<<setw(9)<<"Max";
	cout<<std::left<<setw(14)<<"Allocation";
	cout<<std::left<<setw(9)<<"Need";
	cout<<std::left<<setw(12)<<"Available"<<endl;  //表的第一行输出完毕
	for(int i=0;i<p;i++){
		cout<<"进程"<<i<<":"<<"  ";
		for(int j=0;j<Resource;j++){
			cout<<std::left<<setw(2)<<Max[i][j];
		}
		cout<<"      ";
	    for(int k=0;k<Resource;k++){
			cout<<std::left<<setw(2)<<Allocation[i][k];
		}
		cout<<"      ";
		for(int r=0;r<Resource;r++){
			cout<<std::left<<setw(2)<<Need[i][r];
		}
		if(i==0) cout<<"     "<<std::left<<setw(2)<<Available[0]<<std::left<<setw(2)<<Available[1]<<std::left<<setw(2)<<Available[2]<<endl;
		else cout<<endl;
	}
}
//比较函数:如果Available数组单元的每个数均大于Need数组相应进程单元的每个数则返回ture
bool compare(int p0,int* maxx){
	for(int i=0;i<Resource;i++){
		if(maxx[i]>Available[i]) return false;
	}
	return true;
}
//输入请求资源的具体进程
int getProcessRes(){
	cout<<endl;
	cout<<"请输入请求资源的具体进程代号:";
	int p0;
	cin>>p0;
	return p0;
}
//输入具体进程请求的各类资源数量
int* getResArr(int p0){
	cout<<"请输入进程"<<p0<<"请求的A,B,C资源数量:";
	int *arr;
	arr = new int[Resource];
	for(int i=0;i<Resource;i++) cin>>arr[i];
	return arr;
}
//递归查找可以安全得到资源的进程
void SearchSecProcess(int numProcess){
	int i;
	for(i=0;i<numProcess;i++){
		if(flag[i]==false && compare(i,Need[i])){
			flag[i]=true;
			for(int j=0;j<Resource;j++){
				Available[j]+=Allocation[i][j];
			}
			cout<<"进程"<<i<<"  ";
			break;
		}
	}
	if(i<numProcess) SearchSecProcess(numProcess);
	else if(flag[0]==false||flag[1]==false||flag[2]==false||flag[3]==false||flag[4]==false)
		cout<<"死锁检测情况:陷入死锁!"<<endl;
	else cout<<"死锁检测情况:未陷入死锁。"<<endl;
}
//进行安全性检测并输出安全序列
void CheckSecurity(int* initNeed,int p0){
	for(int i=0;i<Resource;i++){//更新资源分配表
		Available[i]-=initNeed[i];
		Allocation[p0][i]+=initNeed[i];
		Need[p0][i]-=initNeed[i];
		if(Need[p0][i]<0){
			cout<<"输入资源数不合理,所请求资源数大于真正需求的资源数!"<<endl;
			return ;
		}
	}
	cout<<"分配给"<<p0<<"进程资源后,资源分配表更新如下:"<<endl;
	print_processForm();//打印资源分配表
	if(compare(p0,Need[p0])){
		flag[p0]=true;//该进程已加入安全序列中
		cout<<"安全序列为:"<<"进程"<<p0<<"  ";
		for(int j=0;j<Resource;j++){
			Available[j]+=Allocation[p0][j];
		}
	}
	SearchSecProcess(p);
}
int main(){
	initPrint();                  //打印资源表抬头信息
	print_processForm();
	int p0 = getProcessRes();     //获取输入请求资源的具体进程代号
	int* initNeed;
	while(1){
		initNeed=getResArr(p0);  //获取此具体进程请求的各类资源数量
		if(compare(p0,initNeed)) break;
		else {cout<<"输入请求资源数不合理!请重新输入....."<<endl;cout<<endl;}//输入的进程请求数太大,不符合要求。
	}
	CheckSecurity(initNeed,p0);
	return 0;
}

实验七 磁盘调度算法的模拟与实现

实验目的

(1) 了解磁盘结构以及磁盘上数据的组织方式。
(2) 掌握磁盘访问时间的计算方式。
(3) 掌握常用磁盘调度算法及其相关特性。

总体设计

背景知识

在过去的40年中,处理器速度和内存速度的提高远远超过了磁盘访问速度的提高:处理器和内存的速度提高了两个数量级,磁盘访问的速度只提高了一个数量级。因此,当前磁盘的速度比内存至少慢了4个数量级,这一差距在未来仍将继续存在。因此,磁盘存储子系统的性能至关重要。
磁盘是一种高速、大量旋转型、可直接存取的存储设备。它作为计算机系统的辅助存储器,负担着繁重的输入输出任务,在多道程序设计系统中,往往同时会有若干个要求访问磁盘的输入输出请示等待处理。系统可采用一种策略,尽可能按最佳次序执行要求访问磁盘的诸输入输出请求,这就叫磁盘调度,使用的算法称磁盘调度算法。磁盘调度能降低为若干个输入输出请求服务所须的总时间,从而提高系统效率。

基本原理

磁盘驱动器工作时,磁盘以某个恒定的速度旋转。为了读或写,磁头必须定位于指定的磁道和该磁道中指定扇区的开始处。磁道选择包括在活动头系统中移动磁头,或在固定头系统中电子选择一个磁头。在活动头系统中,磁头定位到磁道所需要的时间称为寻道时间。在任何情况下,一旦选择好磁道,磁盘控制器就开始等待,直到适当的扇区旋转到磁头处。磁头到达扇区开始位置的时间称为旋转延迟。寻道时间和旋转延迟的总和为存取时间,这是达到读或写位置所需要的时间。一旦磁头定位完成,磁头就通过下面旋转的扇区,开始执行读操作或写操作,这正是操作的数据传送部分。传输所需的时间是传输时间。以下为几种重要的磁盘调度策略:
先进先出(FIFO):最简单的调度是先进先出(FIFO)调度,它按顺序处理队列中的项目,该策略的优点是公平,每个请求都会得到处理,且按接收到的顺序处理。
最短服务时间优先算法(SSTF):最短服务时间优先策略选择使磁头臂从当前位置开始移动最少的磁盘I/O请求。因此,SSTF策略总是选择导致最小寻道时间的请求。当然,总是选择最小寻道时间并不能保证平均寻道时间最小,但能提供比FIFO更好的性能。由于磁头臂可沿两个方向移动,因此能使用一种随机选择算法解决距离相等的情况。
电梯算法(SCAN):SCAN要求磁头臂仅沿一个方向移动,并在途中满足所有未完成的请求,直到它到达这个方向上的最后一个磁道,或者在这个方向上没有其他请求为止,后一种改进有时称为LOOK策略。接着反转服务方向,沿相反方向扫描,同样按顺序完成所有请求。
循环扫描算法(C_SCAN):C_SCAN策略把扫描限定在一个方向上。因此,当访问到沿某个方向的最后一个磁道时,磁头臂返回到磁盘相反方向末端的磁道,并再次开始扫描。这就减少了新请求的最大延迟。

模块介绍

(1)相关头文件及相关数组的定义

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
int Sequence[8]={98,138,37,122,14,124,65,67};       //磁盘I/O请求序列数组
int length=sizeof(Sequence)/sizeof(Sequence[0]);    //length为请求序列数组的长度
int decideNum=0;                                    //选择的磁盘调度算法编号
int NowDiskNum=0;                                   //当前的磁道号

(2)相关自定义函数

void InitPrint();//打印抬头信息
void MenuPrint();//打印操作菜单信息
void FIFO();//执行先来先服务算法输出访问的磁道顺序
void SSTF();//执行最短寻道时间优先算法输出访问的磁道顺序
void SCAN();//执行电梯算法输出访问的磁道顺序
void C_SCAN();//执行循环扫描算法输出访问的磁道顺序
void VisitList(int dN);// 根据选择的磁盘调度算法编号执行相应的算法

详细设计

关键代码及分析

① 执行先来先服务算法输出访问的磁道顺序,首先定义sum值存放磁头移动的总磁道数,将sum赋初值为初始磁道到执行FIFO算法后的要到的第一个磁道需要移动的磁道数,循环遍历Sequence数组,按顺序输出Sequence数组中元素的值,在循环体内设置中间值tmp存放磁头移动到下一磁道跨越的磁道数,然后使用sum+=tmp;语句将中间值加入到总磁道数sum当中,最后输出sum值。

void FIFO(){
	cout<<endl;cout<<"访问的磁道顺序为:";
	int sum=(Sequence[0]-NowDiskNum)>0?Sequence[0]-NowDiskNum:NowDiskNum-Sequence[0];         //移动的总磁道数
	for(int i=0;i<length-1;i++){
		cout<<Sequence[i]<<" ";
		int tmp=(Sequence[i+1]-Sequence[i])>0?Sequence[i+1]-Sequence[i]:Sequence[i]-Sequence[i+1];   //移动到下一磁道跨越的磁道数
		sum+=tmp;
	}cout<<Sequence[length-1]<<endl;
	cout<<"移动的总磁道数为:"<<sum<<endl;
}

② 执行最短寻道时间优先算法输出访问的磁道顺序,首先定义VectSeq容器和arrSeq数组分别用于存放Sequence数组中的元素值,VectSeq容器方便后续删除元素,arrSeq数组是用于防止使用sort数组后改变了原请求序列。进行拷贝和排序后,定义sum存放移动的总磁道数,sum的初始值为初始磁道到执行算法后第一个请求磁道所移动的磁道数(注意此处的分情况讨论),后设计while循环体,在循环体内部根据相邻两数组元素之差的大小判断磁头下一个移动到的磁道数组下标。并使用vector中的erase函数将已经访问过的磁道移除,直到移除到VectSeq容器为空,跳出循环,输出移动的总磁道数sum值。(注意针对初始磁道数的值进行分情况讨论)

void SSTF(){
	cout<<endl;
	cout<<"访问的磁道顺序为:";
	vector<int> VectSeq;
	int arrSeq[8];
	for(int k=0;k<length;k++){arrSeq[k]=Sequence[k];}
	sort(arrSeq,arrSeq+length);
	int i;
	int index=0;
	for(int j=0;j<length;j++){VectSeq.push_back(arrSeq[j]);}   //向vector容器中添加排序后的数组元素
	for(i=0;i<length;i++){
		if(arrSeq[i]>NowDiskNum) break;
	}
	int sum;
	if(i!=0 && i!=length)sum=(arrSeq[i]-NowDiskNum)<(NowDiskNum-arrSeq[i-1])?arrSeq[i]-NowDiskNum:NowDiskNum-arrSeq[i-1];
	else if(i==0) sum=arrSeq[i]-NowDiskNum;
	else if(i==length){sum=NowDiskNum-arrSeq[i-1];i=i-1;}
	index=i;
	while(!VectSeq.empty()){
		int tmp;
		if(i!=0 && i!=VectSeq.size()-1) {
	tmp=(VectSeq[i+1]-VectSeq[i])<(VectSeq[i]-VectSeq[i-1])?VectSeq[i+1]-VectSeq[i]:VectSeq[i]-VectSeq[i-1];   //计算出离此时磁道数最近的磁道数
			index=(VectSeq[i+1]-VectSeq[i])<(VectSeq[i]-VectSeq[i-1])?i:i-1; //记录i应该指向的下一磁道数数组下标
		}
		else if(i==0){
			tmp=VectSeq[i+1]-VectSeq[i];
			index=i;
		}
		else if(i==VectSeq.size()-1){
			tmp=VectSeq[i]-VectSeq[i-1];
			index=i-1;
		}
		cout<<VectSeq[i]<<" ";
		VectSeq.erase(VectSeq.begin()+i,VectSeq.begin()+i+1); //将i号下标数组元素移除
		if(VectSeq.empty()) break;
		sum+=tmp;
		i=index;
	}
	cout<<"移动的总磁道数为:"<<sum<<endl;
}

③ 执行电梯算法输出访问的磁道顺序,首先为了防止使用sort函数而改变了原序列的顺序定义arrSeq数组,进行数组拷贝且排序后,同样定义sum并赋初值。后从排序后的请求序列数组中第一个大于初始磁道数的元素(记录此下标)开始遍历数组,依次输出后直至到数组末尾,并将相应移动的磁道数累加到sum当中,后从记录的下标开始逆着遍历数组,逆着输出剩余数组元素,仍然不断将移动磁道数累加到sum当中,最终将sum值输出。(注意针对初始磁道数的值进行分情况讨论)

void SCAN(){
	cout<<endl;cout<<"访问的磁道顺序为:";
	int arrSeq[8];
	for(int k=0;k<length;k++){arrSeq[k]=Sequence[k];}    //定义新的数组变量,并将Sequence数组中的单元值拷贝进新数组arrSeq当中,防止数组排序将原数组值改变影响其他算法的执行
	sort(arrSeq,arrSeq+length);
	int i;
	for(i=0;i<length;i++){
		if(arrSeq[i]>NowDiskNum) break;
	}
	int sum;
	if(i<length) sum=arrSeq[i]-NowDiskNum;
	else{sum=NowDiskNum-arrSeq[0];i=0;}   //如果NowDiskNum是请求序列中最大的数,则指向序列中最小的磁道数
	for(int j=i;j<length-1;j++){
		cout<<arrSeq[j]<<" ";
		int tmp=arrSeq[j+1]-arrSeq[j];
		sum+=tmp;
	}
	cout<<arrSeq[length-1]<<" ";
	if(i!=0) sum=sum+arrSeq[length-1]-arrSeq[i-1];
	for(int r=i-1;r>0;r--){
		cout<<arrSeq[r]<<" ";
		int tmp=arrSeq[r]-arrSeq[r-1];
		sum+=tmp;
	}
	if(r==0) cout<<arrSeq[0];
	cout<<"移动的总磁道数为:"<<sum<<endl;
}

④ 执行循环扫描算法输出访问的磁道顺序,同样定义arrSeq数组并将该数组排序,定义sum并赋初值,后开始for循环遍历数组,输出记录下标后的所有数组元素并完成累加,然后从下标0开始遍历到记录下标,输出这期间的数组元素并完成累加,最终输出sum值。(注意针对初始磁道数的值进行分情况讨论)

void C_SCAN(){
	cout<<endl;cout<<"访问的磁道顺序为:";
	int arrSeq[8];
	for(int k=0;k<length;k++){arrSeq[k]=Sequence[k];}    //定义新的数组变量,并将Sequence数组中的单元值拷贝进新数组arrSeq当中,防止数组排序将原数组值改变影响其他算法的执行
	sort(arrSeq,arrSeq+length);
	int i;
	for(i=0;i<length;i++){
		if(arrSeq[i]>NowDiskNum) break;
	}
	int sum;
	if(i<length) sum=arrSeq[i]-NowDiskNum;
	else{sum=NowDiskNum-arrSeq[0];i=0;}                  //如果NowDiskNum是请求序列中最大的数,则指向序列中最小的磁道数
	for(int j=i;j<length-1;j++){
		cout<<arrSeq[j]<<" ";
		int tmp=arrSeq[j+1]-arrSeq[j];
		sum+=tmp;
	}
	cout<<arrSeq[length-1]<<" ";
	if(i!=0) sum=sum+arrSeq[length-1]-arrSeq[0];
	for(int r=0;r<i-1;r++){
		cout<<arrSeq[r]<<" ";
		int tmp=arrSeq[r+1]-arrSeq[r];
		sum+=tmp;
	}
	if(i!=0) cout<<arrSeq[i-1];
	cout<<"移动的总磁道数为:"<<sum<<endl;
}

实验结果与分析

(1)选择使用执行的磁盘调度算法为先进先出算法FIFO,将decideNum输入为1后传参调用VisitNum()函数,在函数内部,由于操作数为1,调用FIFO()函数执行先进先出算法,在FIFO函数中循环遍历Sequence数组按顺序输出数组元素,并定义sum存放移动的总磁道数,最终将sum值输出到控制台上,运行结果如分析图7-1.1所示。
在这里插入图片描述
(2)选择使用执行的磁盘调度算法为最短寻道时间优先算法(SSTF),同样赋初值给decideNUM后,调用VisitNum()函数,继而调用SSTF()函数,在SSTF()函数体内部,定义VectSeq容器和arrSeq数组,经过排序和拷贝,找到寻道时间最优的下一磁道输出并将每次移动的磁道数累加到sum值中,通过对VectSeq容器内部元素不断移除(注意下标问题),当VectSeq容器为空时,跳出循环输出sum值。(具体算法分析见上文关键代码及分析)运行结果如分析图7-1.2所示。
在这里插入图片描述
(3)选择使用执行的磁盘调度算法为电梯算法(SCAN),赋初值给decideNum后,传参到VisitNum()函数中,继而调用SCAN()函数,进行拷贝和排序后,从排序后的请求序列数组中第一个大于初始磁道数的元素(标记)开始遍历数组并依次输出后续数组元素直至数组末尾,后从标记处逆着遍历数组并输出元素,在这个过程中将每次移动的磁道数累加到sum中,最终将sum值输出(具体算法分析见上文关键代码及分析),运行结果如分析图7-1.3所示。
在这里插入图片描述
(4)选择使用执行的磁盘调度算法为循环扫描算法输出访问的磁道顺序,给decideNum赋初值后传参调用VisitNum()函数,继而调用C_SCAN()函数,在函数内部定义arrSeq数组,拷贝并排序后,定义sum并赋初值,两次for循环满足扫描规则将数组元素依次输出,并将每次移动的磁道数累加到sum中,最后输出sum,运行结果如分析图7-1.4所示。
在这里插入图片描述

小结与心得体会

通过此次实验,我很好地了解了磁盘结构以及磁盘上数据的组织方式,掌握了磁盘访问时间的计算方式以及常用的磁盘调度算法和其相关特性。对先进先出算法,最短寻道时间优先算法,电梯算法,循环扫描算法进行了逐一分析,自感觉最短寻道时间优先算法的代码相对而言较为难写,所有算法中均需要对由于初始磁道大小引起的数组下标问题进行分类讨论,不过从这个过程中,我对各个算法的执行过程有了更深的了解。
本实验不足之处:当磁头初始所在磁道号正好是请求序列数组中的元素时,控制台上输出的移动磁道数并不正确,以及输出的访问磁道顺序中,也将初始访问的磁道号输出重复,解决方法即在每个算法函数中加判断语句,判断初始磁道号是否被包含在请求序列中,然后在序列中移除此元素即可,思路虽简单,因为遍历数组涉及到下标问题,实现过程着实麻烦,笔者后续会对相应算法进行更新改良。

磁盘调度算法源码

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
int Sequence[8]={98,138,37,122,14,124,65,67};       //磁盘I/O请求序列数组
int length=sizeof(Sequence)/sizeof(Sequence[0]);    //length为请求序列数组的长度
int decideNum=0;                                    //选择的磁盘调度算法编号
int NowDiskNum=0;                                   //当前的磁道号
//打印抬头信息
void InitPrint(){
	cout<<"*************************************"<<endl;
	cout<<"*      磁盘调度算法C++模拟与实现    *"<<endl;
	cout<<"*************************************"<<endl;
	cout<<endl;
	cout<<"磁盘I/O请求的序列数组为:";
	for(int i=0;i<length;i++){                      //打印磁盘I/O请求序列数组
		cout<<Sequence[i]<<" ";
	}
}
//打印操作菜单信息
void MenuPrint(){
	cout<<endl;cout<<endl;
	cout<<"磁盘调度算法菜单如下:"<<endl;
	cout<<"1、先进先出算法(FIFO)  "<<"2、最短寻道时间优先算法(SSTF)"<<endl;
	cout<<"3、电梯算法(SCAN)      "<<"4、循环扫描算法(C-SCAN)  "<<"5、退出"<<endl;
	cout<<"请选择想要使用的磁盘调度算法:";
	cin>>decideNum;
	if(decideNum>5||decideNum<1) cout<<"您输入的操作数有误!请重新输入:"<<endl;
	if(decideNum==5){
		cout<<"退出成功!"<<endl;
		exit(0);
	}
	else{
		cout<<"请输入当前的磁道号:";
		cin>>NowDiskNum;
	}
}
//执行先来先服务算法输出访问的磁道顺序
void FIFO(){
	cout<<endl;cout<<"访问的磁道顺序为:";
	int sum=(Sequence[0]-NowDiskNum)>0?Sequence[0]-NowDiskNum:NowDiskNum-Sequence[0];         //移动的总磁道数
	for(int i=0;i<length-1;i++){
		cout<<Sequence[i]<<" ";
		int tmp=(Sequence[i+1]-Sequence[i])>0?Sequence[i+1]-Sequence[i]:Sequence[i]-Sequence[i+1];   //移动到下一磁道跨越的磁道数
		sum+=tmp;
	}cout<<Sequence[length-1]<<endl;
	cout<<"移动的总磁道数为:"<<sum<<endl;
}
//执行最短寻道时间优先算法输出访问的磁道顺序
void SSTF(){
	cout<<endl;
	cout<<"访问的磁道顺序为:";
	vector<int> VectSeq;
	int arrSeq[8];
	for(int k=0;k<length;k++){arrSeq[k]=Sequence[k];}
	sort(arrSeq,arrSeq+length);
	int i;
	int index=0;
	for(int j=0;j<length;j++){VectSeq.push_back(arrSeq[j]);}   //向vector容器中添加排序后的数组元素
	for(i=0;i<length;i++){
		if(arrSeq[i]>NowDiskNum) break;
	}
	int sum;
	if(i!=0 && i!=length)sum=(arrSeq[i]-NowDiskNum)<(NowDiskNum-arrSeq[i-1])?arrSeq[i]-NowDiskNum:NowDiskNum-arrSeq[i-1];
	else if(i==0) sum=arrSeq[i]-NowDiskNum;
	else if(i==length){sum=NowDiskNum-arrSeq[i-1];i=i-1;}
	index=i;
	while(!VectSeq.empty()){
		int tmp;
		if(i!=0 && i!=VectSeq.size()-1) {
			tmp=(VectSeq[i+1]-VectSeq[i])<(VectSeq[i]-VectSeq[i-1])?VectSeq[i+1]-VectSeq[i]:VectSeq[i]-VectSeq[i-1];   //计算出离此时磁道数最近的磁道数
			index=(VectSeq[i+1]-VectSeq[i])<(VectSeq[i]-VectSeq[i-1])?i:i-1;                                             //记录i应该指向的下一磁道数数组下标
		}
		else if(i==0){
			tmp=VectSeq[i+1]-VectSeq[i];
			index=i;
		}
		else if(i==VectSeq.size()-1){
			tmp=VectSeq[i]-VectSeq[i-1];
			index=i-1;
		}
		cout<<VectSeq[i]<<" ";
		VectSeq.erase(VectSeq.begin()+i,VectSeq.begin()+i+1);             //将i号下标数组元素移除
		if(VectSeq.empty()) break;
		sum+=tmp;
		i=index;
	}
	cout<<"移动的总磁道数为:"<<sum<<endl;
}
//执行电梯算法输出访问的磁道顺序
void SCAN(){
	cout<<endl;cout<<"访问的磁道顺序为:";
	int arrSeq[8];
	for(int k=0;k<length;k++){arrSeq[k]=Sequence[k];}    //定义新的数组变量,并将Sequence数组中的单元值拷贝进新数组arrSeq当中,防止数组排序将原数组值改变影响其他算法的执行
	sort(arrSeq,arrSeq+length);
	int i;
	for(i=0;i<length;i++){
		if(arrSeq[i]>NowDiskNum) break;
	}
	int sum;
	if(i<length) sum=arrSeq[i]-NowDiskNum;
	else{sum=NowDiskNum-arrSeq[0];i=0;}                  //如果NowDiskNum是请求序列中最大的数,则指向序列中最小的磁道数
	for(int j=i;j<length-1;j++){
		cout<<arrSeq[j]<<" ";
		int tmp=arrSeq[j+1]-arrSeq[j];
		sum+=tmp;
	}
	cout<<arrSeq[length-1]<<" ";
	if(i!=0) sum=sum+arrSeq[length-1]-arrSeq[i-1];
	for(int r=i-1;r>0;r--){
		cout<<arrSeq[r]<<" ";
		int tmp=arrSeq[r]-arrSeq[r-1];
		sum+=tmp;
	}
	if(r==0) cout<<arrSeq[0];
	cout<<"移动的总磁道数为:"<<sum<<endl;
}
//执行循环扫描算法输出访问的磁道顺序
void C_SCAN(){
	cout<<endl;cout<<"访问的磁道顺序为:";
	int arrSeq[8];
	for(int k=0;k<length;k++){arrSeq[k]=Sequence[k];}    //定义新的数组变量,并将Sequence数组中的单元值拷贝进新数组arrSeq当中,防止数组排序将原数组值改变影响其他算法的执行
	sort(arrSeq,arrSeq+length);
	int i;
	for(i=0;i<length;i++){
		if(arrSeq[i]>NowDiskNum) break;
	}
	int sum;
	if(i<length) sum=arrSeq[i]-NowDiskNum;
	else{sum=NowDiskNum-arrSeq[0];i=0;}                  //如果NowDiskNum是请求序列中最大的数,则指向序列中最小的磁道数
	for(int j=i;j<length-1;j++){
		cout<<arrSeq[j]<<" ";
		int tmp=arrSeq[j+1]-arrSeq[j];
		sum+=tmp;
	}
	cout<<arrSeq[length-1]<<" ";
	if(i!=0) sum=sum+arrSeq[length-1]-arrSeq[0];
	for(int r=0;r<i-1;r++){
		cout<<arrSeq[r]<<" ";
		int tmp=arrSeq[r+1]-arrSeq[r];
		sum+=tmp;
	}
	if(i!=0) cout<<arrSeq[i-1];
	cout<<"移动的总磁道数为:"<<sum<<endl;
}
//根据选择的磁盘调度算法编号执行相应的算法
void VisitList(int dN){
	switch(dN){
	case 1:
		FIFO();
		break;
	case 2:
		SSTF();
		break;
	case 3:
		SCAN();
		break;
	case 4:
		C_SCAN();
		break;
	case 5:
		exit(0);
		break;
	}
}
int main(){
	InitPrint();
	while(1){
		MenuPrint();
		VisitList(decideNum);
	}
	return 0;
}

实验九 基于信号量机制的并发程序设计

实验目的

(1) 回顾操作系统进程、线程的有关概念,针对经典的同步、互斥、死锁与饥饿问题进行并发程序设计。
(2) 了解互斥体对象,利用互斥与同步操作编写读者-写者问题的并发程序,加深对P(即semWait)、V(即semSignal)原语以及利用 P、V原语进行进程间同步与互斥操作的理解。
(3) 理解Linux支持的信息量机制,利用 IPC 的信号量系统调用编程实现哲学家进餐问题。

总体设计

背景知识

在设计同步和并发机制时,若能与一个著名的问题关联,检测该问题的解决方案对原问题是否有效,则这种方法是非常有用的。很多文献中都有一些频繁出现的重要问题,它们不仅是普遍性的设计问题,而且具有教育价值。
读者/写者问题定义如下:存在一个多个进程共享的数据区,该数据区可以是一个文件或一块内存空间,甚至可以是一组寄存器;有些进程只读取这个数据区中的数据,有些进程只往数据区中写数据。

基本原理

读者/写者问题,必须满足以下条件:
1、 任意数量的读进程可同时读这个文件。
2、 一次只有一个写进程可以写文件。
3、 若一个写进程正在写文件,则禁止任何读进程读文件。
也就是说,读进程不需要排斥其他读进程,而写进程需要排斥其他所有进程,包括读进程和写进程。
读者优先:信号量wsem用于实施互斥,只要一个写进程正在访问共享数据区,其他写进程和读进程就都不能访问它。读进程也使用wsem实施互斥,但为了允许多个读进程,没有读进程正在读时,第一个试图读的读进程需要在wsem上等待。当至少已有一个读进程在读时,随后的读进程无须等待,可以直接进入全局变量readcount用于记录进程的数量,信号量x用于确保readcount被正确地更新。

模块介绍

(1)相关头文件及宏和变量的定义

#include<windows.h>
#include<iostream>
using namespace std;
HANDLE Mutex;//用于线程间互斥
HANDLE rw;//用于保证读进程/写进程互斥访问文件
int readerCount=0;//用于判断读者数量,保证多个读者进程同时读
bool p_ccontinue = true;//控制进程结束
int PageCount=0;//读者读第几个文件或写者写第几个文件
#define readerMax 30
#define readerNum 4
#define writerNum 2
DWORD WINAPI reader(LPVOID);//将要调用的新线程函数声明成DWORD WINAPI ThreadProc(lpvoid lpParameter)
DWORD WINAPI writer(LPVOID); 

(2)相关自定义函数

void Writing();//写操作函数
DWORD WINAPI writer(LPVOID lpPara);//写者函数
void Reading();//读操作函数
DWORD WINAPI reader(LPVOID lpPara)//读者函数

详细设计

关键代码及分析

① 定义写操作函数以及写者函数,写操作函数中简单地定义了输出函数以区别不同的读者。写者函数中调用windowsAPI函数WaitForSingleObject()函数和ReleaseSemaphore()函数分别进行p()和v()操作构建临界区,后使用Sleep()函数防止短时间内创建大量进程。

void Writing(){//写操作函数
	PageCount++;
	cout<<"写者在写文件中........:"<<PageCount<<endl;
}
DWORD WINAPI writer(LPVOID lpPara){ //写者函数
	while(1){
		WaitForSingleObject(rw,INFINITE);         //p(rw)写者申请访问文件
		Writing();       //写者向文件中写入
		ReleaseSemaphore(rw,1,NULL);         //v(rw)释放文件
		Sleep(4000);
	}
}

② 定义读操作函数和读者函数,在读者函数中使用多个p和v操作构成临界区,并使用多个信号控制使得多个读者可读,且多个读者不可同时访问文件申请资源,最后使用Sleep()函数防止短时间内创建大量进程。

void Reading(){//读操作函数
	cout<<"一个读者在读文件中........:"<<PageCount<<endl;
}
DWORD WINAPI reader(LPVOID lpPara){ //读者函数
	while(1){
		WaitForSingleObject(Mutex,INFINITE); //P(Mutex)当第一个读者进入时申请访问文件,此时第二个读者无法进入
		if(readerCount==0) WaitForSingleObject(rw,INFINITE); //p(rw)第一个读者进入判断体申请访问文件,第二个无需
		readerCount++;                      //记录当前读者数量,以致第二个读者无需申请访问文件即可读文件
		ReleaseSemaphore(Mutex,1,NULL);                           //v(Mutex)当第一个读者已经申请访问文件后,释放访问信号量,标志第二个读者可以进入此临界区
		Reading();                            //调用读函数,读者读文件l
		WaitForSingleObject(Mutex,INFINITE);                           //p(Mutex)有读者在时,第二个读者无法进入,防止readerCount同时自减出现负数的情况
		readerCount--;                      //读者读文件结束,将读者数量减一
		if(readerCount==0) ReleaseSemaphore(rw,1,NULL);           //v(rw)如果读者数量为0,则释放访问的文件,标志写者可访问文件
		ReleaseSemaphore(Mutex,1,NULL);                           //v(Mutex)读者离开时,将访问信号量释放,标志第二个读者可以进入
		Sleep(2000);
	}
	return 0;
}	cout<<"移动的总磁道数为:"<<sum<<endl;
}

实验结果与分析

通过互斥信号量和pv操作保证写时不读,只有一个写者写操作,多个读者无法同时申请文件资源,但多个读者可以同时读操作,在控制台上输出的读者和写者后面均有序号以区别不同的读者和不同的写者。运行结果如分析图9-1所示。
在这里插入图片描述

小结与心得体会

通过此次实验,我进一步了解了操作系统进程,线程的相关概念,对经典的同步、互斥、死锁与饥饿问题的并发设计有了更深刻的认识,同时了解了互斥体对象,利用互斥与同步pv操作编写了读者-写者问题的并发程序。此实验与实验四生产者与消费者验证性实验的代码有相似之处,编写代码的过程中也借鉴了实验四的代码,收获了很多。
本实验不足之处:虽然已经对进程的认识进一步加深,但相关的windowsAPI函数还是认识欠缺,不能做到灵活使用,在使用API函数前仍然要搜索资料,笔者在期末复习过程中会再一次加深对API函数的理解很认识。

读者写者问题源码

#include<windows.h>
#include<iostream>
using namespace std;

HANDLE Mutex;//用于线程间互斥
HANDLE rw;//用于保证读进程/写进程互斥访问文件
int readerCount=0;//用于判断读者数量,保证多个读者进程同时读
bool p_ccontinue = true;//控制进程结束
int PageCount=0;//读者读第几个文件或写者写第几个文件

#define readerMax 30
#define readerNum 4
#define writerNum 2

DWORD WINAPI reader(LPVOID);//将要调用的新线程函数声明成DWORD WINAPI ThreadProc(lpvoid lpParameter)。
DWORD WINAPI writer(LPVOID);//将要调用的新线程函数声明成DWORD WINAPI ThreadProc(lpvoid lpParameter)。

//写操作函数
void Writing(){
	PageCount++;
	cout<<"写者在写文件中........:"<<PageCount<<endl;
}
//写者函数
DWORD WINAPI writer(LPVOID lpPara){
	while(1){
		WaitForSingleObject(rw,INFINITE);         //p(rw)写者申请访问文件
		Writing();       //写者向文件中写入
		ReleaseSemaphore(rw,1,NULL);         //v(rw)释放文件
		Sleep(6000);
	}
}
//读操作函数
void Reading(){
	cout<<"一个读者在读文件中........:"<<PageCount<<endl;
}
//读者函数
DWORD WINAPI reader(LPVOID lpPara){
	while(1){
		
		WaitForSingleObject(Mutex,INFINITE);                           //P(Mutex)当第一个读者进入时申请访问文件,此时第二个读者无法进入
		if(readerCount==0) WaitForSingleObject(rw,INFINITE);           //p(rw)第一个读者进入判断体申请访问文件,第二个无需
		readerCount++;                      //记录当前读者数量,以致第二个读者无需申请访问文件即可读文件
		ReleaseSemaphore(Mutex,1,NULL);                           //v(Mutex)当第一个读者已经申请访问文件后,释放访问信号量,标志第二个读者可以进入此临界区

		Reading();                            //调用读函数,读者读文件
		
		WaitForSingleObject(Mutex,INFINITE);                           //p(Mutex)有读者在时,第二个读者无法进入,防止readerCount同时自减出现负数的情况
		readerCount--;                      //读者读文件结束,将读者数量减一
		if(readerCount==0) ReleaseSemaphore(rw,1,NULL);           //v(rw)如果读者数量为0,则释放访问的文件,标志写者可访问文件
		ReleaseSemaphore(Mutex,1,NULL);                           //v(Mutex)读者离开时,将访问信号量释放,标志第二个读者可以进入

		Sleep(2000);
	}
	return 0;
}
int main(){
	//创建各个互斥信号
	//注意,互斥信号量和同步信号量的定义方法不同,互斥信号量调用的是CreateMutex函数,同步信号量调用的是CreateSemaphore函数,函数的返回值都是句柄。
    Mutex = CreateMutex(NULL,FALSE,NULL);
	rw = CreateSemaphore(NULL,1,readerMax,NULL);

    HANDLE hThreads[readerNum+writerNum]; //各线程的handle
    DWORD readerID[readerNum]; //读者线程的标识符
    DWORD writerID[writerNum]; //写者线程的标识符

	//创建读者线程
    for (int i=0;i<readerNum;++i){
        hThreads[i]=CreateThread(NULL,0,reader,NULL,0,&readerID[i]);
        if (hThreads[i]==NULL) return -1;
    }
    //创建写者线程
    for (int j=0;j<writerNum;++j){
        hThreads[readerNum+j]=CreateThread(NULL,0,writer,NULL,0,&writerID[j]);
        if (hThreads[j]==NULL) return -1;
    }

    while(p_ccontinue){
        if(getchar()){ //按回车后终止程序运行
            p_ccontinue = false;
        }
    }
    return 0;
}

总结

以上为操作系统课程设计设计类实验部分,三个实验均是笔者独创,和网上其他博主的代码对比了一下,笔者代码行数较简短,实现基本功能没什么问题,但可能对比其他博主,代码并不是很全面,不足之处每个实验之后笔者也有注明,有不详或不正确的地方,敬请大家批评和指正!!

  • 3
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
华工操作系统课程设计时,通常会涉及到Unix文件系统的学习和设计。Unix文件系统是一种层次化的文件系统结构,以目录、文件和链接为基础,并支持对文件和目录的创建、读取、写入和删除等操作。 在设计Unix文件系统时,首先需要确定文件系统的组织结构。通常,文件系统被组织成一系列的目录,这些目录可以包含其他目录和文件。每一个文件和目录都有一个唯一的路径标识符,用于在文件系统中定位它们。 其次,需要确定文件和目录的属性和权限。每个文件和目录都会有一些属性,如大小、创建时间、修改时间等。此外,文件和目录还有访问权限,用于控制哪些用户可以对它们进行读取、写入和执行操作。 另外,还需要实现文件和目录的基本操作功能。文件和目录可以通过系统调用进行创建、读取、写入和删除等操作。这些操作涉及到对文件和目录的物理位置和大小进行管理,还包括文件的读写缓冲区和文件指针的管理等。 此外,还要考虑文件系统的性能和安全性。为了提高文件系统的性能,可以采用缓存和预读取策略,减少磁盘的访问次数。为了保障文件系统的安全性,可以通过访问控制列表和文件权限等机制来限制用户对文件的操作。 总结来说,设计Unix文件系统需要考虑文件系统的组织结构、文件和目录的属性和权限、基本操作功能的实现以及性能和安全性的考虑。通过理解和掌握Unix文件系统的原理和设计方法,可以更好地设计和实现一个高效、安全的文件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

近景_

打赏拉满,动力满满

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值