操作系统实验1—实现单处理机下的进程调度程序

操作系统实验1—实现单处理机下的进程调度程序

实验描述

实验内容:

编写一个单处理机下的进程调度程序,模拟操作系统对进程的调度。

实验目的:

进程是操作系统中最基本、最重要的概念,进程调度又是操作系统的核心模块。本实验要求学生独立设计并实现进程调度模拟程序,以加深对进程控制块概念和各种进程调度算法的理解。

实验要求:

  1. 可以随机输入若干进程,支持先来先服务、短作业优先、最短剩余时间优先、时间片轮转、动态优先级调度算法,能够输出进程的调度过程。具体信息见测试用例格式说明。
  2. 每个进程由一个进程控制块表示。
  3. 实现先来先服务调度算法:进程到达时间可由进程创建时间表示。进程到达时间相同时,优先处理进程号小的进程。
  4. 实现短作业优先调度算法:可指定进程要求的运行时间。进程运行时间相同时,按照先来先服务原则进行处理。
  5. 实现最短剩余时间优先调度算法:可指定进程要求的运行时间。进程运行时间相同时,按照先来先服务原则进行处理。
  6. 实现时间片轮转调度算法:可指定生成时间片大小。进程到达时间相同时,优先处理进程号小的进程;进程执行完一个时间片进入就绪队列时,其优先级低于首次进入就绪队列的进程。
  7. 实现动态优先级调度算法:可指定进程的初始优先级(优先级与优先数成反比,优先级最高为0),优先级改变遵循下列原则:进程在就绪队列中每停留一个时间片(停留时间>0),优先级加1,进程每运行一个时间片,优先级减3。进程到达时间相同时,优先处理进程号小的进程,且仅在时间片完或进程运行结束时发生进程调度。

测试用例格式如下:

输入:
调度算法
进程号/到达时间/运行时间/优先级/时间片

其中调度算法选项为:
1----先来先服务,
2----短作业优先,
3----最短剩余时间优先,
4----时间片轮转,
5----动态优先级

输出:
调度顺序/进程号/开始运行时间/结束运行时间/优先级

测试输入期待的输出时间限制内存限制额外进程
测试用例 11
1/0/24/1/1
2/0/3/1/1
3/0/3/1/1
1/1/0/24/1
2/2/24/27/1
3/3/27/30/1
1秒64M0
测试用例 22
1/0/7/1/1
2/2/4/1/1
3/4/1/1/1
4/5/4/1/1
1/1/0/7/1
2/3/7/8/1
3/2/8/12/1
4/4/12/16/1
1秒64M0
测试用例 33
1/0/7/1/1
2/2/4/1/1
3/4/1/1/1
4/5/4/1/1
1/1/0/2/1
2/2/2/4/1
3/3/4/5/1
4/2/5/7/1
5/4/7/11/1
6/1/11/16/1
1秒64M0
测试用例 44
1/0/53/1/20
2/0/17/1/20
3/0/68/1/20
4/0/24/1/20
1/1/0/20/1
2/2/20/37/1
3/3/37/57/1
4/4/57/77/1
5/1/77/97/1
6/3/97/117/1
7/4/117/121/1
8/1/121/134/1
9/3/134/154/1
10/3/154/162/1
1秒64M0
测试用例 55
1/0/3/1/2
2/0/4/1/2
3/0/2/3/2
4/2/1/1/2
5/2/4/4/2
1/1/0/2/4
2/2/2/4/3
3/4/4/5/3
4/3/5/7/3
5/1/7/8/4
6/2/8/10/3
7/5/10/12/3
8/5/12/14/6
1秒64M0

设计思路

因为每一个进程输入的时候需要输入进程号、到达时间、运行时间、优先级、时间片五个属性,想到使用结构体来存储这些属性;同时进程输出的时候又要有调度顺序、开始运行时间、结束运行时间三个属性,以及短作业优先算法中还要判断进程是否运行结束,所以将进程需要用到的全部属性都存储在结构体里面。

时间片轮转算法中需要用到队列+结构体数组的形式来实现算法,其余四个算法使用结构体数组的形式实现算法。

struct process
{
	int pid;		//进程号
	int comeTime;	//到达时间
	int runTime;	//运行所需时间
	int beginTime;	//开始运行的时间
	int endTime;	//结束运行的时间
	int order;		//调度顺序
	int priority;	//优先级
	int slot;		//时间片
	int finish;		//结束标志
}que[1010];

queue<process>q;	//就绪队列,用于时间片轮转算法

程序概要设计如下图所示:
概要设计

  1. main()函数是主程序的入口,接受程序输入,并按照输入的调度信号选择相应的算法模块进行运行。
  2. FCFS()函数是先来先服务算法,按照到来时间/进程号排序,先来到的进程首先处理,并将处理结果输出。
  3. SJF()函数是非剥夺的短作业优先算法,按照到来时间/运行时间/进程号排序,每次找出运行时间最短的进程运行,注意要按断进程是否已经运行结束,最后将结果输出。
  4. SRTF()函数是剥夺式的短作业优先算法,按照到来时间/运行时间/进程号排序,每次找出运行时间最短的进程运行,同时剥夺掉当前在运行的进程,注意要按断进程是否已经运行结束,最后将结果输出。
  5. RR()函数是时间片轮转算法,按照到来时间/进程号排序,每个进程运行一个时间片时间,注意判断进程是否运行结束,并将处理结果输出。
  6. DPSA()是动态优先级调度算法,按照到来时间/进程号排序,每个进程根据优先级分配运行时间,同时要动态调整进程的优先级,注意判断进程是否运行结束,并将处理结果输出。
  7. cmp1()是排序函数 1,按照到来时间/进程号排序。
  8. cmp2()是排序函数 2,按照到来时间/运行时间/进程号排序。
int main();		//主程序入口
void FCFS();	//先来先服务算法
void SJF();		//不可剥夺的短作业优先算法
void SRTF();	//可剥夺式的短作业优先算法
void RR();		//时间片轮转算法
void DPSA();	//动态优先级算法
bool cmp1(process, process);//排序函数1,用于FCFS、RR、DPSA 
bool cmp2(process, process);//排序函数 2,用于 SJF、SRTF

上机代码

代码使用 C++ 语言进行编写

#include<iostream>
#include<cstdlib> 
#include<cstdio> 
#include<cmath> 
#include<algorithm> 
#include<queue>
using namespace std;
struct process
{
	int pid;//进程号 
	int comeTime;//到达时间 
	int runTime;//运行所需时间 
	int beginTime;//开始运行的时间 
	int endTime;//结束运行的时间 
	int order;//调度顺序 
	int priority;//优先级 
	int slot;//时间片 
	int finish;//结束标志 
}que[1010];
void FCFS();//先来先服务 
void SJF();//不可剥夺的短作业优先算法 
void SRTF();//可剥夺式的短作业优先算法 
void RR();//时间片轮转 
void DPSA();//动态优先级 
bool cmp1(process, process);//FCFS、RR、DPSA 
bool cmp2(process, process);//SJF、SRTF 
int num;//输入的总进程数 
const int IsEnd = 1;//进程已经结束 
const int NoEnd = 0;//进程还未结束 
int main()
{
	//freopen("osin.txt", "r", stdin); 
	//freopen("osout.txt", "w", stdout); 
	int sig = 0;
	cin >> sig;   //算法选择标志 

	 //读入数据 
	num = 0;
	while (~scanf("%d/%d/%d/%d/%d", &que[num].pid, &que[num].comeTime, &que[num].runTime, &que[num].priority, &que[num].slot))
	{
		que[num].beginTime = que[num].endTime = que[num].order = 0;
		que[num].finish = NoEnd;
		num++;
	}

	//选择算法 
	switch (sig)
	{
		case 1:FCFS(); break;
		case 2:SJF(); break;
		case 3:SRTF(); break;
		case 4:RR(); break;
		case 5:DPSA(); break;
	}
	return 0;
}
void FCFS()
{
	sort(que, que + num, cmp1);//先来后到排好序 
	for (int i = 0; i < num; i++)
	{
		que[i].order = i + 1;
		if (i == 0)//第一个进程特判一下 
		{
			que[i].beginTime = que[i].comeTime;
		}
		else
		{
			que[i].beginTime = max(que[i].comeTime, que[i - 1].endTime);
		}
		que[i].endTime = que[i].beginTime + que[i].runTime;

		//输出 
		printf("%d/%d/%d/%d/%d\n", que[i].order, que[i].pid, que[i].beginTime, que[i].endTime, que[i].priority);
	}
}
void SJF()
{
	sort(que, que + num, cmp2);
	int lastTime = que[0].comeTime;//当前时间 
	int tmp = 0;//每次选中的短作业 
	for (int i = 0; i < num; i++)//筛选num次,每次选出最佳的短作业 
	{
		bool isBool = false;//判断当前时间是否有短作业在就绪队列中 
		while (1)
		{
			if (i == 0)
			{
				//排序后的第一个肯定满足条件,特判 
				break;
			}
			else
			{
				for (int j = 1; j < num; j++)//for一遍的复杂度为O(n),比排序O(nlogn)快 
				{
					if (que[j].finish == IsEnd)//当前短作业已经结束就跳过 
						continue;
					if (que[j].comeTime <= lastTime)//当前短作业在就绪队列中 
					{
						if (isBool == false)//没有标记最优短作业 
						{
							isBool = true;
							tmp = j;
						}
						else//已经标记了最优短作业 
						{
							//比较,更新最优短作业 
							 //当短作业运行时间相等时,优先调度进程号小的短作业执行 
							if (que[j].runTime < que[tmp].runTime)
								tmp = j;
							else if (que[j].runTime == que[tmp].runTime&&que[j].pid < que[tmp].pid)
								tmp = j;
						}
					}
				}
				if (isBool)//如果存在短进程满足条件就输出 
					break;
				else//不存在就把时间后移再寻找 
					lastTime++;
			}
		}
		que[tmp].order = i + 1;
		que[tmp].beginTime = max(que[tmp].beginTime, lastTime);
		que[tmp].endTime = que[tmp].beginTime + que[tmp].runTime;
		printf("%d/%d/%d/%d/%d\n", que[tmp].order, que[tmp].pid, que[tmp].beginTime, que[tmp].endTime, que[tmp].priority);

		lastTime = que[tmp].endTime;//更新当前时间 
		que[tmp].finish = IsEnd;//标记短作业结束 
	}
}
void SRTF()
{
	sort(que, que + num, cmp2);
	int lastTime = que[0].comeTime;
	int ID = 1;//输出顺序 
	int tmp = 0, counts = 0;//当前进程,输出次数 
	int isRun = -1, start = 0;//当前是否有进程运行,运行开始时间 
	while (counts < num)
	{
		while (que[tmp].comeTime <= lastTime && tmp < num)//当前时间内的进程 
		{
			tmp++;
		}
		int minx = 0x3f3f3f, minId = -1;//最短时间和下标 
		for (int i = 0; i < tmp; i++)//寻找当前进程中剩余运行时间最短的进程 
		{
			if (que[i].runTime < minx && que[i].finish == NoEnd)
			{
				minx = que[i].runTime;
				minId = i;
			}
		}
		if (minId == -1)//如果当前时间进程都结束了就等待下一个进程 
		{
			lastTime = que[tmp].comeTime;
			continue;
		}
		if (isRun == -1)//当前没有进程在运行 
		{
			isRun = minId;
			start = max(que[isRun].comeTime, lastTime);//运行刚找到的进程 
		}
		//如果找到进程的剩余运行时间小于当前进程的剩余运行时间 
		if (que[minId].runTime < que[isRun].runTime)
		{
			que[isRun].order = ID++;
			que[isRun].beginTime = start;
			que[isRun].endTime = lastTime;
			printf("%d/%d/%d/%d/%d\n", que[isRun].order, que[isRun].pid, que[isRun].beginTime, que[isRun].endTime, que[isRun].priority);
			isRun = minId;
			start = lastTime;
		}
		//运行进程 
		int run = que[tmp].comeTime - lastTime;
		//进程能运行完就运行完 
		if (run >= que[isRun].runTime || run <= 0)
		{
			lastTime += que[isRun].runTime;
			que[isRun].order = ID++;
			que[isRun].beginTime = start;
			que[isRun].endTime = lastTime;
			printf("%d/%d/%d/%d/%d\n", que[isRun].order, que[isRun].pid, que[isRun].beginTime, que[isRun].endTime, que[isRun].priority);

			que[isRun].runTime = 0;
			que[isRun].finish = IsEnd;
			counts++;
			isRun = -1;
			start = lastTime;
		}
		//进程不能运行完就运行一个时间片 
		else
		{
			lastTime += run;
			que[isRun].runTime -= run;
		}
	}
}
void RR()
{
	sort(que, que + num, cmp1);
	queue<process>q;//就绪队列 
	int lastTime = 0;//当前时间 
	int ID = 1;//输出顺序 
	int tmp = 0, counts = 0;//当前进程,输出次数 
	while (counts < num)
	{
		if (q.empty())//队列为空则等待进程到来 
		{
			lastTime = que[tmp].comeTime;
			while (que[tmp].comeTime <= lastTime && tmp < num)//当前时间内的进程 
			{
				if (que[tmp].finish == NoEnd)//还没完成的进程入队 
				{
					q.push(que[tmp]);
				}
				tmp++;
			}
		}
		else
		{
			process ans = q.front();//取出处于队列头的进程 
			q.pop();
			//进程可以运行完 
			if (ans.runTime <= ans.slot)
			{
				//输出当前进程调度 
				ans.order = ID++;
				ans.beginTime = lastTime;
				ans.endTime = ans.beginTime + ans.runTime;
				printf("%d/%d/%d/%d/%d\n", ans.order, ans.pid, ans.beginTime, ans.endTime, ans.priority);

				lastTime = ans.endTime;//更新当前时间 
				ans.finish = IsEnd;//标记短作业结束 
				counts++;

				while (que[tmp].comeTime <= lastTime && tmp < num)//当前时间内的进程 
				{
					if (que[tmp].finish == NoEnd)//还没完成的进程入队 
					{
						q.push(que[tmp]);
					}
					tmp++;
				}
			}
			//进程不能运行完 
			else
			{
				//输出当前进程调度 
				ans.order = ID++;
				ans.beginTime = lastTime;
				ans.endTime = ans.beginTime + ans.slot;
				printf("%d/%d/%d/%d/%d\n", ans.order, ans.pid, ans.beginTime, ans.endTime, ans.priority);

				lastTime = ans.endTime;
				ans.runTime -= ans.slot;

				while (que[tmp].comeTime <= lastTime && tmp < num)//当前时间内的进程 
				{
					if (que[tmp].finish == NoEnd)//还没完成的进程入队 
					{
						q.push(que[tmp]);
					}
					tmp++;
				}
				q.push(ans);//先让新进程入队再让当前进程入队 
			}
		}
	}
}
void DPSA()
{
	sort(que, que + num, cmp1);
	int lastTime = que[0].comeTime;//当前时间 
	int ID = 1;//输出顺序 
	int tmp = 0, counts = 0;//当前进程,输出次数 
	int start = lastTime;//当前进程开始时间 
	int minId, Smin;
	while (counts < num)
	{
		while (que[tmp].comeTime <= lastTime && tmp < num)//当前时间内的进程 
		{
			if (que[tmp].comeTime > start && que[tmp].comeTime < lastTime)
			{
				que[tmp].priority = max(que[tmp].priority - 1, 0);//等待时优先级加1 
			}
			tmp++;
		}
		//寻找最高优先级进程 
		minId = -1;
		Smin = 0x3f3f3f;
		for (int i = 0; i < tmp; i++)
		{
			if (que[i].priority < Smin && que[i].finish == NoEnd)
			{
				Smin = que[i].priority;
				minId = i;
			}
		}
		if (minId == -1)//如果当前时间进程都结束了就等待下一个进程 
		{
			lastTime = que[tmp].comeTime;
			continue;
		}
		//运行进程 
		start = lastTime;
		//进程能运行完 
		if (que[minId].runTime <= que[minId].slot)
		{
			que[minId].order = ID++;
			que[minId].beginTime = lastTime;
			que[minId].endTime = lastTime + que[minId].runTime;
			printf("%d/%d/%d/%d/%d\n", que[minId].order, que[minId].pid, que[minId].beginTime, que[minId].endTime, que[minId].priority + 3);

			counts++;
			lastTime += que[minId].runTime;
			que[minId].finish = IsEnd;
		}
		//进程不能运行完 
		else
		{
			que[minId].order = ID++;
			que[minId].beginTime = lastTime;
			que[minId].endTime = lastTime + que[minId].slot;
			printf("%d/%d/%d/%d/%d\n", que[minId].order, que[minId].pid, que[minId].beginTime, que[minId].endTime, que[minId].priority + 3);

			lastTime += que[minId].slot;
			que[minId].runTime -= que[minId].slot;
		}
		//改变进程优先级 
		for (int i = 0; i < tmp; i++)
		{
			if (que[i].finish == NoEnd)
			{
				if (i == minId)//运行优先级减3 
				{
					que[i].priority += 3;
				}
				else//等待优先级加1 
				{
					que[i].priority = max(que[i].priority - 1, 0);
				}
			}
		}
	}
}
bool cmp1(process a, process b)
{
	if (a.comeTime != b.comeTime)
		return a.comeTime < b.comeTime;
	else
		return a.pid < b.pid;
}
bool cmp2(process a, process b)
{
	if (a.comeTime != b.comeTime)
		return a.comeTime < b.comeTime;
	else if (a.runTime != b.runTime)
		return a.runTime > b.runTime;
	else
		return a.pid < b.pid;
}

测试结果

程序采用黑盒测试的方式,提交到乐学的 OJ 系统上进行在线评测
测试
可以看到,OJ 的测试用例全部通过

心得体会

通过本次实验,上机代码模拟实现了五种进程调度算法,对操作系统内部的进程调度方式有了更深刻的认识和感受。对于非剥夺的短作业优先算法而言,因为每次都要检测当前时刻的短作业,排序其实可有可无;对于其余四种算法而言,排序则显得尤为重要。