目录
一、实验目的
- 理解在单处理器环境下的进程调度及状态转换的基本原理和关键点。
- 编程实现时间片轮转调度、短进程优先调度和多级反馈队列调度算法。
- 分析并比较不同调度算法的效率和公平性。
二、实验要求
- 模拟每一个进程,需要包含到达时间,运行时间,优先级等属性。
- 每一种调度算法都需要实现一个模拟函数,对进程结束时间进行仿真模拟。多级反馈队列调度算法至少包含三个队列,每个队列的时间片大小不同。
- 对给定的一组进程,当采用不同的调度算法时,需仿真模拟这组进程的状态转换过程。进程的状态至少有就绪、运行和阻塞三种状态。
- 当这组进程调度执行完成时,根据仿真模拟的结果,计算平均周转时间/平均带权周转时间,平均等待时间、平均响应时间、利用率等性能指标及公平性指标。
- 能够提供合适的测试数据集,对每一种算法进行测试,并分析其结果。
三、实验代码
下为实验完整c++代码:
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int N = 15;//PCB上限个数
const int M = 1005;//最大CPU运行终止时间
const int TSNUM = 2;//设定时间片大小
//PCB的状态
enum ProcessState {
READY,//就绪态
RUNNING,//运行态
WAITING,//等待态
TERMINATED//终止态
};
//定义PCB的数据结构
struct PCB{
int id;//进程编号
int priority;//优先级
int start;//到达时间
int runtime;//运行时间
int usedtime = 0;//已运行时间
int finish;//完成时间
int selected_time = -1;//响应时间,-1代表该进程还未响应
ProcessState status;//状态
//t时刻,给进程分配cpu
void selectPcb(int t)
{
if(selected_time == -1)//记录响应时间
selected_time = t;
status = RUNNING;
printf("在%d时刻,给%d号进程分配cpu,运行时间:%d,已运行时间:%d\n", t, id, runtime, usedtime);
}
}p[N];
int pcb_num;//进程个数(15以内)
vector<int> arrivedList[M];//保存某时刻到达的进程
//返回CPU运行终止时间
int GetEndtime()
{
int tmp1 = 0, tmp2 = 0;//分别代表最大到达时间和运行总时长
for(int i = 0; i < pcb_num; i++)
{
tmp1 = max(tmp1, p[i].start);
tmp2 += p[i].runtime;
}
return tmp1 + tmp2;
}
//RR算法
void RR()
{
queue<int> q;//就绪队列
int ts;//时间片
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
q.push(arrivedList[t][i]);
p[arrivedList[t][i]].status = READY;//进程变为就绪态
}
if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
{
if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
{
p[id].finish = t;
p[id].status = TERMINATED;
}
else//否则就将进程放入就绪队列的队尾(同一时刻如果一个进程下CPU,一个进程刚刚到达,我们默认刚到达的进程排在前面)
q.push(id);
if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
{
id = q.front();//取就绪队列队首的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.pop();//队首元素出队
ts = TSNUM;//时间片重置
}
else//否则,将id设为-1
id = -1;
}
}
else//就绪队列非空,且当前没有进程正在运行
{
id = q.front();//取就绪队列队首的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.pop();//队首元素出队
ts = TSNUM;//时间片重置
}
//将当前执行进程的已运行时间++,时间片--
if(id != -1)
{
p[id].usedtime++;
ts--;
}
}
}
//SJF算法(非抢占式)
void SJF()
{
vector<int> q;//就绪队列
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
q.push_back(arrivedList[t][i]);
p[arrivedList[t][i]].status = READY;//进程变为就绪态
}
if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
{
p[id].finish = t;
p[id].status = TERMINATED;
if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime < minn)
{
minn = p[q[i]].runtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
else//否则,将id设为-1
id = -1;
}
}
else//就绪队列非空,且当前没有进程正在运行
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime < minn)
{
minn = p[q[i]].runtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
//将当前执行进程的已运行时间++
if(id != -1)
p[id].usedtime++;
}
}
//SJF算法(抢占式)
void SJF1()
{
vector<int> q;//就绪队列
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
p[arrivedList[t][i]].status = READY;//进程变为就绪态
//如果新到达的进程剩余时间比当前运行的进程的剩余时间更短,新进程占用cpu,运行进程回到就绪队列
if(id != -1 && p[arrivedList[t][i]].runtime < p[id].runtime - p[id].usedtime)
{
q.push_back(id);
p[id].status = READY;
id = arrivedList[t][i];
p[id].selectPcb(t);//更改进程状态、输出信息
}
else
q.push_back(arrivedList[t][i]);
}
if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
{
p[id].finish = t;
p[id].status = TERMINATED;
if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime - p[q[i]].usedtime < minn)
{
minn = p[q[i]].runtime - p[q[i]].usedtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
else//否则,将id设为-1
id = -1;
}
}
else//就绪队列非空,且当前没有进程正在运行
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime - p[q[i]].usedtime < minn)
{
minn = p[q[i]].runtime - p[q[i]].usedtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
//将当前执行进程的已运行时间++
if(id != -1)
p[id].usedtime++;
}
}
//多级反馈队列调度算法
void MLFQ()
{
printf("三级就绪队列,时间片分配分别为1,2,4\n");
int level = 3;//就绪队列级数
queue<int> q[level];//三级就绪队列
int ts;//时间片
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int qnum = -1;//当前运行进程所在的就绪队列编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
p[arrivedList[t][i]].status = READY;//进程变为就绪态
//如果新到达的进程的优先级比当前运行的进程的优先级更高,新进程获得cpu,运行进程回到原级别就绪队列
if(id != -1 && qnum > 0)
{
q[qnum].push(id);
p[id].status = READY;
id = arrivedList[t][i];
qnum = 0;
printf("新到达的进程的优先级比当前运行的进程的优先级更高!\n");
p[id].selectPcb(t);//更改进程状态、输出信息
printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, 1);
}
else
q[0].push(arrivedList[t][i]);
}
// printf("%d时刻就绪队列状态:\n", t);
// for(int i = 0; i < level; i++)
// {
// printf("第%d级就绪队列:", i + 1);
// int qcnt = q[i].size();
// while(qcnt--)
// {
// int qtmp = q[i].front();
// q[i].pop();
// printf("%d ", qtmp);
// q[i].push(qtmp);
// }
// printf("\n");
// }
// printf("\n");
int pnum = 0;//目前所有就绪队列中的进程个数
for(int i = 0; i < level; i++)
pnum += q[i].size();
if(pnum == 0 && id == -1)//如果所有就绪队列为均为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
{
if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
{
p[id].finish = t;
p[id].status = TERMINATED;
}
else//否则就将进程放入下一级就绪队列的队尾(如果已经是最后一级,就仍然放到最后一级)
{
pnum++;
if(qnum < level - 1)
q[qnum + 1].push(id);
else
q[qnum].push(id);
}
if(pnum != 0)//如果就绪队列非空,就取出新的进程放入CPU
{
for(int i = 0; i < level; i++)
{
if(!q[i].empty())
{
printf("当前%d号进程执行完毕或时间片用完\n", id);
id = q[i].front();//取就绪队列队首的进程放入cpu
qnum = i;
p[id].selectPcb(t);//更改进程状态、输出信息
printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
q[i].pop();//队首元素出队
ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
if(i != 1)
ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
break;
}
}
}
else//否则,将id设为-1,qnum设为-1
{
id = -1;
qnum = -1;
}
}
}
else//就绪队列非空,且当前没有进程正在运行
{
for(int i = 0; i < level; i++)
{
if(!q[i].empty())
{
id = q[i].front();//取就绪队列队首的进程放入cpu
qnum = i;
printf("当前没有进程正在运行\n");
p[id].selectPcb(t);//更改进程状态、输出信息
printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
q[i].pop();//队首元素出队
ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
if(i != 1)
ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
break;
}
}
}
//将当前执行进程的已运行时间++,时间片--
if(id != -1)
{
p[id].usedtime++;
ts--;
}
}
}
//输入模块
void Input()
{
cout << "请输入将要执行的进程个数pcb_num:" << '\n';
cin >> pcb_num;
cout << "接下来pcb_num行,请分别输入每个进程的到达时间和运行时间:" << '\n';
for(int i = 0; i < pcb_num; i++)
{
int start, runtime;//到达时间、运行时间
cin >> start >> runtime;
p[i].id = i;
p[i].start = start;
p[i].runtime = runtime;
arrivedList[start].push_back(i);//将start时刻i进程到达的信息保存起来
}
}
//算法选择模块
void ChooseAlgorithm()
{
cout << "请选择将要使用的进程调度算法:" << '\n';
cout << "1.时间片轮转调度(RR)" << '\n';
cout << "2.短进程优先调度(SJF非抢占式)" << '\n';
cout << "3.短进程优先调度(SJF抢占式)" << '\n';
cout << "4.多级反馈队列调度(MLFQ)" << '\n';
int op;
cin >> op;
switch(op)
{
case 1:
RR();
break;
case 2:
SJF();
break;
case 3:
SJF1();
break;
case 4:
MLFQ();
break;
default:
break;
}
}
//统计数据模块
void ShowData()
{
double sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0;//总周转时间,总带权周转时间,总等待时间,总响应时间
cout << "结果统计如下:" << '\n';
printf("%2s %8s %8s %8s %8s %12s\n", "ID", "到达时间", "运行时间","完成时间", "周转时间", "带权周转时间");
for(int i = 0; i < pcb_num; i++)
{
printf("%-2d %-8d %-8d %-8d %-8d %-4lf\n", p[i].id, p[i].start, p[i].runtime, p[i].finish,
p[i].finish - p[i].start, (p[i].finish-p[i].start) * 1.0 / p[i].runtime);
sum1 += p[i].finish - p[i].start;
sum2 += (p[i].finish - p[i].start) * 1.0 / p[i].runtime;
sum3 += p[i].finish - p[i].start - p[i].runtime;
sum4 += p[i].selected_time - p[i].start;
}
cout << "平均周转时间: " << sum1 / pcb_num << '\n';
cout << "平均带权周转时间: " << sum2 / pcb_num << '\n';
cout << "平均等待时间: " << sum3 / pcb_num << '\n';
cout << "平均响应时间: " << sum4 / pcb_num << '\n';
}
int main()
{
Input();//输入要执行的进程
ChooseAlgorithm();//选择将要执行的调度算法
ShowData();//显示统计数据
return 0;
}
四、实验代码解读
4.1前言
模拟实验代码语言为c++。
4.2数据结构
4.2.1PCB的数据结构
//定义PCB的数据结构
struct PCB{
int id;//进程编号
int priority;//优先级
int start;//到达时间
int runtime;//运行时间
int usedtime = 0;//已运行时间
int finish;//完成时间
int selected_time = -1;//响应时间,-1代表该进程还未响应
ProcessState status;//状态
//t时刻,给进程分配cpu
void selectPcb(int t)
{
if(selected_time == -1)//记录响应时间
selected_time = t;
status = RUNNING;
printf("在%d时刻,给%d号进程分配cpu,运行时间:%d,已运行时间:%d\n", t, id, runtime, usedtime);
}
}p[N];
用一个结构体定义了PCB(进程控制块)。PCB保存进程的相关数据:进程编号、优先级、到达时间、运行时间、已运行时间、完成时间、响应时间和状态。该结构体内置selectPcb函数,当该进程被分配CPU时,执行该函数,并向用户展示相关信息。
4.2.2PCB的状态
//PCB的状态
enum ProcessState {
READY,//就绪态
RUNNING,//运行态
WAITING,//等待态
TERMINATED//终止态
};
ProcessState枚举出三种PCB状态:就绪态、等待态和终止态。
4.3主要函数
4.3.1获得CPU运行终止时间
//返回CPU运行终止时间
int GetEndtime()
{
int tmp1 = 0, tmp2 = 0;//分别代表最大到达时间和运行总时长
for(int i = 0; i < pcb_num; i++)
{
tmp1 = max(tmp1, p[i].start);
tmp2 += p[i].runtime;
}
return tmp1 + tmp2;
}
GetEndtime函数用来返回CPU运行终止时间。用运行总时长加上最大到达时间作为运行终止时间,当有相关错误出现时,可以及时被程序员发现。
4.3.2时间片轮转调度算法
//RR算法
void RR()
{
queue<int> q;//就绪队列
int ts;//时间片
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
q.push(arrivedList[t][i]);
p[arrivedList[t][i]].status = READY;//进程变为就绪态
}
if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
{
if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
{
p[id].finish = t;
p[id].status = TERMINATED;
}
else//否则就将进程放入就绪队列的队尾(同一时刻如果一个进程下CPU,一个进程刚刚到达,我们默认刚到达的进程排在前面)
q.push(id);
if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
{
id = q.front();//取就绪队列队首的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.pop();//队首元素出队
ts = TSNUM;//时间片重置
}
else//否则,将id设为-1
id = -1;
}
}
else//就绪队列非空,且当前没有进程正在运行
{
id = q.front();//取就绪队列队首的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.pop();//队首元素出队
ts = TSNUM;//时间片重置
}
//将当前执行进程的已运行时间++,时间片--
if(id != -1)
{
p[id].usedtime++;
ts--;
}
}
}
RR算法轮流让就绪队列的进程依次执行一个时间片(大小为2)。从0到endtime枚举时间t,模拟每个时刻发生的情况。首先将t时刻到达的所有进程放入就绪队列。然后分类讨论三种情况:1. 就绪队列为空,且当前没有进程正在运行。2.当前有进程在运行。3. 就绪队列非空,且当前没有进程正在运行用户交互。
4.3.3短进程优先调度算法(非抢占式)
//SJF算法(非抢占式)
void SJF()
{
vector<int> q;//就绪队列
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
q.push_back(arrivedList[t][i]);
p[arrivedList[t][i]].status = READY;//进程变为就绪态
}
if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
{
p[id].finish = t;
p[id].status = TERMINATED;
if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime < minn)
{
minn = p[q[i]].runtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
else//否则,将id设为-1
id = -1;
}
}
else//就绪队列非空,且当前没有进程正在运行
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime < minn)
{
minn = p[q[i]].runtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
//将当前执行进程的已运行时间++
if(id != -1)
p[id].usedtime++;
}
}
SJF(非抢占式)算法每次从就绪队列中选取运行时间最短的进程。模拟方式与RR算法类似,枚举时间t,然后分类讨论三种情况:1. 就绪队列为空,且当前没有进程正在运行。2.当前有进程在运行。3. 就绪队列非空,且当前没有进程正在运行用户交互。与RR算法不同的是,SJF(非抢占式)算法用vector模拟就绪队列,因为queue不支持遍历,使用vector可以方便查询运行时间最短的进程。
4.3.4短进程优先调度算法(抢占式)
//SJF算法(抢占式)
void SJF1()
{
vector<int> q;//就绪队列
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
p[arrivedList[t][i]].status = READY;//进程变为就绪态
//如果新到达的进程剩余时间比当前运行的进程的剩余时间更短,新进程占用cpu,运行进程回到就绪队列
if(id != -1 && p[arrivedList[t][i]].runtime < p[id].runtime - p[id].usedtime)
{
q.push_back(id);
p[id].status = READY;
id = arrivedList[t][i];
p[id].selectPcb(t);//更改进程状态、输出信息
}
else
q.push_back(arrivedList[t][i]);
}
if(q.empty() && id == -1)//如果就绪队列为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(p[id].usedtime == p[id].runtime)//如果该进程已运行完毕,就进行调度
{
p[id].finish = t;
p[id].status = TERMINATED;
if(!q.empty())//如果就绪队列非空,就取出新的进程放入CPU
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime - p[q[i]].usedtime < minn)
{
minn = p[q[i]].runtime - p[q[i]].usedtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
else//否则,将id设为-1
id = -1;
}
}
else//就绪队列非空,且当前没有进程正在运行
{
int tmp_qid = -1, minn = 0x3f3f3f3f;
for(int i = 0; i < q.size(); i++)//从就绪队列中选出运行时间最短的进程
{
if(p[q[i]].runtime - p[q[i]].usedtime < minn)
{
minn = p[q[i]].runtime - p[q[i]].usedtime;
tmp_qid = i;
}
}
id = q[tmp_qid];//将运行时间最短选出的进程放入cpu
p[id].selectPcb(t);//更改进程状态、输出信息
q.erase(q.begin() + tmp_qid);//在就绪队列中删除选出的进程
}
//将当前执行进程的已运行时间++
if(id != -1)
p[id].usedtime++;
}
}
SJF(抢占式)算法与SJF(非抢占式)算法的区别在于:新进程到达时就需要调度。于是在新进程到达时判断:新到达的进程剩余时间是否比当前运行的进程的剩余时间更短,其余部分与SJF(非抢占式)算法相同。
4.3.5多级反馈队列调度算法
//多级反馈队列调度算法
void MLFQ()
{
printf("三级就绪队列,时间片分配分别为1,2,4\n");
int level = 3;//就绪队列级数
queue<int> q[level];//三级就绪队列
int ts;//时间片
int id = -1;//当前运行进程的编号,-1代表没有进程正在运行
int qnum = -1;//当前运行进程所在的就绪队列编号,-1代表没有进程正在运行
int endtime = GetEndtime();//CPU运行终止时间
for(int t = 0; t <= endtime; t++)
{
//将t时刻到达的所有进程放入就绪队列
for(int i = 0; i < arrivedList[t].size(); i++)
{
p[arrivedList[t][i]].status = READY;//进程变为就绪态
//如果新到达的进程的优先级比当前运行的进程的优先级更高,新进程获得cpu,运行进程回到原级别就绪队列
if(id != -1 && qnum > 0)
{
q[qnum].push(id);
p[id].status = READY;
id = arrivedList[t][i];
qnum = 0;
printf("新到达的进程的优先级比当前运行的进程的优先级更高!\n");
p[id].selectPcb(t);//更改进程状态、输出信息
printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, 1);
}
else
q[0].push(arrivedList[t][i]);
}
printf("%d时刻就绪队列状态:\n", t);
for(int i = 0; i < level; i++)
{
printf("第%d级就绪队列:", i + 1);
int qcnt = q[i].size();
while(qcnt--)
{
int qtmp = q[i].front();
q[i].pop();
printf("%d ", qtmp);
q[i].push(qtmp);
}
printf("\n");
}
printf("\n");
int pnum = 0;//目前所有就绪队列中的进程个数
for(int i = 0; i < level; i++)
pnum += q[i].size();
if(pnum == 0 && id == -1)//如果所有就绪队列为均为空,且当前没有进程正在运行,则跳转到下一时间
continue;
if(id != -1)
{
if(ts == 0 || p[id].usedtime == p[id].runtime)//如果时间片用完了,或者该进程已运行完毕,就进行调度
{
if(p[id].usedtime == p[id].runtime)//如果该进程运行完毕,就设置完成时刻
{
p[id].finish = t;
p[id].status = TERMINATED;
}
else//否则就将进程放入下一级就绪队列的队尾(如果已经是最后一级,就仍然放到最后一级)
{
pnum++;
if(qnum < level - 1)
q[qnum + 1].push(id);
else
q[qnum].push(id);
}
if(pnum != 0)//如果就绪队列非空,就取出新的进程放入CPU
{
for(int i = 0; i < level; i++)
{
if(!q[i].empty())
{
printf("当前%d号进程执行完毕或时间片用完\n", id);
id = q[i].front();//取就绪队列队首的进程放入cpu
qnum = i;
p[id].selectPcb(t);//更改进程状态、输出信息
printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
q[i].pop();//队首元素出队
ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
if(i != 1)
ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
break;
}
}
}
else//否则,将id设为-1,qnum设为-1
{
id = -1;
qnum = -1;
}
}
}
else//就绪队列非空,且当前没有进程正在运行
{
for(int i = 0; i < level; i++)
{
if(!q[i].empty())
{
id = q[i].front();//取就绪队列队首的进程放入cpu
qnum = i;
printf("当前没有进程正在运行\n");
p[id].selectPcb(t);//更改进程状态、输出信息
printf("%d号进程先前所在就绪队列为第%d级就绪队列\n\n", id, i + 1);
q[i].pop();//队首元素出队
ts = TSNUM;//时间片重置,三级就绪队列的时间片分别为1,2,4
if(i != 1)
ts = (i > 1) ? TSNUM * 2 : TSNUM / 2;
break;
}
}
}
//将当前执行进程的已运行时间++,时间片--
if(id != -1)
{
p[id].usedtime++;
ts--;
}
}
}
多级反馈队列调度算法要求设置多级就绪队列,优先级从高到低,时间片从小到大。依然是枚举时间t,分三种情况讨论,与上述算法一致。设置三个queue类型的队列q,用来模拟三级就绪队列。时间片大小分别为1,2,4。设置时间片ts,在每个时刻考虑时间片是否用完。每个时刻从高优先级到低优先级访问就绪队列。
4.4用户交互
4.4.1输入模块
//输入模块
void Input()
{
cout << "请输入将要执行的进程个数pcb_num:" << '\n';
cin >> pcb_num;
cout << "接下来pcb_num行,请分别输入每个进程的到达时间和运行时间:" << '\n';
for(int i = 0; i < pcb_num; i++)
{
int start, runtime;//到达时间、运行时间
cin >> start >> runtime;
p[i].id = i;
p[i].start = start;
p[i].runtime = runtime;
arrivedList[start].push_back(i);//将start时刻i进程到达的信息保存起来
}
}
输入模块要求用户输入要执行的进程个数pcb_num。后面pcb_num行,每行输入各个进程的到达时间和运行时间。
4.4.2算法选择模块
//算法选择模块
void ChooseAlgorithm()
{
cout << "请选择将要使用的进程调度算法:" << '\n';
cout << "1.时间片轮转调度(RR)" << '\n';
cout << "2.短进程优先调度(SJF非抢占式)" << '\n';
cout << "3.短进程优先调度(SJF抢占式)" << '\n';
cout << "4.多级反馈队列调度(MLFQ)" << '\n';
int op;
cin >> op;
switch(op)
{
case 1:
RR();
break;
case 2:
SJF();
break;
case 3:
SJF1();
break;
case 4:
MLFQ();
break;
default:
break;
}
}
算法选择模块打印算法列表,并要求用户输入要使用的进程调度算法,以此来执行相应算法。
4.4.3统计数据模块
//统计数据模块
void ShowData()
{
double sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0;//总周转时间,总带权周转时间,总等待时间,总响应时间
cout << "结果统计如下:" << '\n';
printf("%2s %8s %8s %8s %8s %12s\n", "ID", "到达时间", "运行时间","完成时间", "周转时间", "带权周转时间");
for(int i = 0; i < pcb_num; i++)
{
printf("%-2d %-8d %-8d %-8d %-8d %-4lf\n", p[i].id, p[i].start, p[i].runtime, p[i].finish,
p[i].finish - p[i].start, (p[i].finish-p[i].start) * 1.0 / p[i].runtime);
sum1 += p[i].finish - p[i].start;
sum2 += (p[i].finish - p[i].start) * 1.0 / p[i].runtime;
sum3 += p[i].finish - p[i].start - p[i].runtime;
sum4 += p[i].selected_time - p[i].start;
}
cout << "平均周转时间: " << sum1 / pcb_num << '\n';
cout << "平均带权周转时间: " << sum2 / pcb_num << '\n';
cout << "平均等待时间: " << sum3 / pcb_num << '\n';
cout << "平均响应时间: " << sum4 / pcb_num << '\n';
}
统计数据模块会显示pcb中保存的部分数据,直观地告诉用户相关性能指标。
4.4.4主函数
int main()
{
Input();//输入要执行的进程
ChooseAlgorithm();//选择将要执行的调度算法
ShowData();//显示统计数据
return 0;
}
主函数依次执行输入模块、算法选择模块和显示统计数据模块。
五、实验结果
5.1时间片轮转调度算法测试结果
如下图所示:
测试用例:
4
0 5
2 4
4 1
5 6
5.2短进程优先调度算法(非抢占式)测试结果
如下图所示:
测试用例:
4
0 7
2 4
4 1
5 4
5.3短进程优先调度算法(抢占式)测试结果
如下图所示:
测试用例:
4
0 7
2 4
4 1
5 4
5.4多级反馈队列调度算法测试结果
如下图所示:
测试用例:
5
0 4
1 3
3 5
5 2
9 6
六、结语
本实验设计思路参考王道考研操作系统,90%以上代码为本人原创。如果有什么错误,欢迎大伙儿在评论区指正ヾ(●´▽‘●)ノ。