一、进程调度算法的背景
在多道程序环境下,内存中存在着多个进程,其数目往往多于处理机数目。这就要求系统能按某种算法,动态地将处理机分配给处于就绪状态的一个进程,使之执行。分配处理机的任务是由处理机调度程序完成的。对于大型系统运行时的性能,如系统吞吐量、资源利用率、作业周转时间或响应的及时性等,在很大程度上都取决于处理机调度性能的好坏。因而,处理机调度便成为OS中至关重要的部分。
了解进程调度算法可以更好的理解操作系统是如何将资源合理的分配给各个正在运行的进程。理解三种进程调度算法可以充分利用计算机系统中的CPU资源,让计算机系统能够多快好省地完成我们让它做的各种任务。为此,可在内存中可存放数目远大于计算机系统内CPU个数的进程,让这些进程在操作系统的进程调度器的调度下,能够让进程高效、及时、公平地使用CPU。为此调度器可设计不同的调度算法来选择进程,这体现了进程调度的策略,同时还需并进一步通过进程的上下文切换来完成进程切换,这体现了进程调度的机制。了解上述细节,也就可以说是了解了进程调度。
二、进程点调度算法的原理
1.先来先服务算法(FCFS)
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。当在作业调度中采用该算法时,每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。在进程调度中采用FCFS算时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。
2.短作业优先算法(SJF)
短作业(进程)优先调度算法SJ(P)F,是指对短作业或短进程优先调度的算法。它们可以分别用于作业调度和进程调度。短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。而短进程优先(SPF)调度算法则是从就绪队列中选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或发生某事件而被阻塞放弃处理机时再重新调度。
3.时间片轮转算法(RR)
在时间片轮转调度算法中,系统根据先来先服务的原则,将所有的就绪进程排成一个就绪队列,并且每隔一段时间产生一次中断,激活系统中的进程调度程序,完成一次处理机调度,把处理机分配给就绪队列队首进程,让其执行指令。当时间片结束或进程执行结束,系统再次将cpu分配给队首进程。
三、各个算法的实验数据
1.FCFS算法:
进程:ABCDE,
到达时间分别为:0,1,2,3,4
服务时间分别为:4,3,4,2,4
执行结果如下:
2.SJF算法:
测试(1):
进程:A,B,C
到达时间分别为:0,1,5,
服务时间分别为:3,1,2,
执行结果如下
测试(2):
进程:A,B,C,
到达时间分别为:0,3,3
服务时间分别为:2,4,2
执行结果如下:
测试(3):
进程:ABCDE
到达时间分别为:0,1,2,3,4
服务时间分别为:4,3,4,2,4
执行结果如下:
3.RR算法:
测试(1):
时间片 q =1时
进程:ABCDE
到达时间分别为:0,1,2,3,4
服务时间分别为:4,3,4,2,4
执行结果如下:
测试(2):
时间片 q =4
进程:ABCDE
到达时间分别为:0,1,2,3,4
服务时间分别为:4,3,4,2,4
执行结果如下:
四、算法的优劣比较
对于测试用例:
进程:ABCDE
到达时间分别为:0,1,2,3,4
服务时间分别为:4,3,4,2,4
三个算法都测试过了,下面是各个算法的对比。
可见,在此测试用例中,平均周转时间最短的是SJF算法,平均周转时间最长的是RR算法(q=1)。
1、先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。
2、短作业优先算法(SJF):由上图中可以看出,采用SJF算法后,不论是平均周转时间还是平均带权周转时间,都有较明显的改善,尤其是对短作业,其周转时间减小,这说明SJF调度算法能有效地降低作业的平均等待时间,提高系统吞吐量。SJF调度算法也存在不容忽视的缺点:该算法对长作业不利,如果有一长作业(进程)进入系统的后备队列(就绪队列),由于调度程序总是优先调度那些(即使是后进来的)短作业(进程),将导致长作业(进程)长期不被调度。该算法完全未考虑作业的紧迫程度,因而不能保证紧迫性作业(进程)会被及时处理。由于作业(进程)的长短只是根据用户所提供的估计执行时间而定的,而用户又可能会有意或无意地缩短其作业的估计运行时间,致使该算法不一定能真正做到短作业优先调度。
3、时间片轮转调度算法(RR):在轮转调度算法中时间片的大小对系统的性能有很大的影响。若时间片很小,将有利于短作业,其能够在这个时间片内完成。时间片过小意味着会进行频繁的进程切换,这将增大系统的开销。若时间片选择太长,时间片轮转调度算法将退化为先来先服务的进程调度算法。属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。
五、代码实现
Pcb.h
#pragma once
#include<iostream>
#include<string>
#include<map>
#include<queue>
#include<fstream>
#include<functional>
using namespace std;
#define BEREADY 0 //就绪状态
#define RUNNING 1 //运行状态
#define WAITING 2 //等待状态
class PCB
{
private:
string m_PidName; //进程名
int m_Arrival; //进程到达时间
int m_Server; //进程运行时间
int m_Finish;//进程完成时间
int m_Turnaround;//周转时间
float m_Weighted;//带权周转时间
int m_Status;//运行状态
static float m_s_AverTurnaround;//平均周转时间
static float m_s_AverWeighted;//平均带权周转时间
static int m_s_Num;//进程数量
public:
friend void FCFS();
friend void SJF();
friend void RR();
friend void ReadData();
static void OutputPid(PCB* P, int num);
};
void ReadData();//读入数据
void FCFS();
void SJF();
void RR();
Pcb.cpp
#include"PCB.h"
float PCB::m_s_AverTurnaround = 0;//平均周转时间
float PCB::m_s_AverWeighted = 0;//平均带权周转时间
int PCB::m_s_Num = 0;//进程数量
PCB *Pid;
void ReadData()//读入数据
{
ifstream readData;
readData.open("data.txt");
readData >> PCB::m_s_Num;//读入分区数量
Pid = new PCB[PCB::m_s_Num];//开空间
for (int i = 0; i < PCB::m_s_Num; i++)//读入进程名称
{
readData >> Pid[i].m_PidName;
}
for (int i = 0; i < PCB::m_s_Num; i++)//读入进程到达时间
{
readData >> Pid[i].m_Arrival;
}
for (int i = 0; i < PCB::m_s_Num; i++)//读入进程运行时间
{
readData >> Pid[i].m_Server;
}
readData.close();
}
void PCB::OutputPid(PCB* P, int num)
{
printf("\n%-5s %-5s %-5s %-5s %-5s %-5s %-5s %-5s\n\n", "进程名", "到达时间", "运行时间", "完成时间", "周转时间", "带权周转时间", "平均周转时间", "平均带权周转时间");
for (int i = 0; i < num; i++)
{
cout << P[i].m_PidName << "\t " << P[i].m_Arrival << "\t " << P[i].m_Server << "\t " << P[i].m_Finish << "\t " << P[i].m_Turnaround << "\t " << P[i].m_Weighted;
if (i + 1 == num)
{
printf(" \t\t%3.2f\t %3.2f\n\n", PCB::m_s_AverTurnaround, PCB::m_s_AverWeighted);
}
cout << endl;
}
}
void FCFS()
{
int num, T;
cout << "-------------FCFS算法-------------\n\n\n";
ReadData();
num = PCB::m_s_Num;
multimap<int, PCB*> m;
for (int i = 0; i < num; i++)
{
m.insert(make_pair(Pid[i].m_Arrival, Pid + i));
}
T = Pid[0].m_Arrival;
for (auto& ip = m.begin(); ip != m.end();)
{
PCB* tmp = ip->second;
if (T >= tmp->m_Arrival)//上一个进程进行中这个进程已经到了内存
{
tmp->m_Finish = T + tmp->m_Server;
T += tmp->m_Server;
}
else//map里的进程空了,直接执行下一个,T要加上等待时间(m_Arrival - T)
{
tmp->m_Finish = T + tmp->m_Server + (tmp->m_Arrival - T);
T += tmp->m_Server + (tmp->m_Arrival - T);
}
tmp->m_Turnaround = tmp->m_Finish - tmp->m_Arrival;
tmp->m_Weighted = (float)tmp->m_Turnaround / (float)tmp->m_Server;
PCB::m_s_AverTurnaround += tmp->m_Turnaround;
PCB::m_s_AverWeighted += tmp->m_Weighted;
cout << "时刻" << T << ",进程" << tmp->m_PidName << "完成,退出" << endl;
ip = m.erase(ip);
}
PCB::m_s_AverTurnaround /= (float)num;
PCB::m_s_AverWeighted /= (float)num;
PCB::OutputPid(Pid, num);
}
void SJF()
{
int num, T;
cout << "-------------SJF算法-------------\n\n\n";
ReadData();
num = PCB::m_s_Num;
multimap<int, PCB*> ma;//ma--按到达时间排序
for (int i = 0; i < num; i++)
{
ma.insert(make_pair(Pid[i].m_Arrival, Pid + i));//最先进入时间
}
T = ma.begin()->second->m_Arrival;//开始时刻是先到达的那个进程的到达时间
for (auto& ip = ma.begin(); !ma.empty();)//跳出条件是m2为空
{
PCB* tmp = ip->second;//记录ip->second的值,减少代码的冗余,进入后ip改变,不能使用tmp
if (T >= tmp->m_Arrival)//进程到达
{
auto ip1 = ip;
int MinServer1 = ip->second->m_Server;
ip++;
while (ip != ma.end() && T >= ip->second->m_Arrival)//找到已到达进程中的最小服务时间
{
if (ip->second->m_Server < MinServer1)
{
MinServer1 = ip->second->m_Server;
ip1 = ip;
}
else
{
ip++;
}
}
ip = ip1;
tmp = ip->second;//记录ip->second的值,减少代码的冗余
tmp->m_Finish = T + tmp->m_Server;
T += tmp->m_Server;
}
else
//运行完进程A之后,其他进程还未到达等待队列中且满足是最小服务时间
{
auto ip2 = ip;
int MinServer2 = ip->second->m_Server;
int t = ip->second->m_Arrival;
ip++;
while (ip != ma.end() && t == ip->second->m_Arrival)//找到已到达进程中的最小服务时间
{
if (ip->second->m_Server < MinServer2)
{
MinServer2 = ip->second->m_Server;
ip2 = ip;
}
else
{
ip++;
}
}
ip = ip2;
tmp = ip->second;//记录ip->second的值,减少代码的冗余
tmp->m_Finish = T + tmp->m_Server + (tmp->m_Arrival - T);
//完成时间等于上一个进程的完成时间+要等待的时间(tm->m_Arrival-T)+需要运行的时间
T += tmp->m_Server + (tmp->m_Arrival - T);//时刻T也要加上等待时间
}
tmp->m_Turnaround = tmp->m_Finish - tmp->m_Arrival;
tmp->m_Weighted = (float)tmp->m_Turnaround / (float)tmp->m_Server;
PCB::m_s_AverTurnaround += tmp->m_Turnaround;
PCB::m_s_AverWeighted += tmp->m_Weighted;
cout << "时刻" << T << ",进程" << tmp->m_PidName << "完成,退出" << endl;
ip = ma.erase(ip);
ip = ma.begin();//每次从ma的头部(即最先到达的进程找起)
}
PCB::m_s_AverTurnaround /= (float)num;
PCB::m_s_AverWeighted /= (float)num;
PCB::OutputPid(Pid, num);
}
void RR()
{
int q = 0;
int num, T;
queue<PCB*> qu;
PCB* cur;
typedef struct
{
int Arrival;
int Server;
}tmp;
map<string, tmp*> ma;
cout << "-------------RR算法-------------\n\n\n";
ReadData();
num = PCB::m_s_Num;
tmp * t = new tmp[num];
cout << "设置时间片:";
cin >> q;
for (int i = 0; i < num; i++)//先使所有进程处于等待状态
{
t[i].Arrival = Pid[i].m_Arrival;
t[i].Server = Pid[i].m_Server;
ma.insert(make_pair(Pid[i].m_PidName,t + i));//将所有到达时间保存,在进程运行时会修改,运行完重新赋回去
Pid[i].m_Status = WAITING;
}
T = Pid[0].m_Arrival;//取第一个到达内存的进程p
qu.push(Pid);
while (!qu.empty())//ma为空,轮转完毕
{
cur = qu.front();
cur->m_Status = RUNNING;//拿到时间片状态标志位改变为RUNNING
if (cur->m_Server - q == 0)//刚好减完
{
T += q;
cout << "时刻" << T << ",进程" << cur->m_PidName << "完成,退出" << endl;
cur->m_Finish = T;
cur->m_Turnaround = cur->m_Finish - ma[cur->m_PidName]->Arrival;
cur->m_Weighted = (float)(cur->m_Turnaround) / (float)(ma[cur->m_PidName]->Server);
PCB::m_s_AverTurnaround += cur->m_Turnaround;
PCB::m_s_AverWeighted += cur->m_Weighted;
cur->m_Server = 0;//输出完以后置为0,防止再次调用
}
else if (cur->m_Server - q > 0)
{
T += q;
cur->m_Arrival += q;//到达时间也跟着变化
cur->m_Server -= q;
}
else//剩余服务时间超过时间片
{
T += cur->m_Server;
cout << "时刻" << T << ",进程" << cur->m_PidName << "完成,退出" << endl;
cur->m_Finish = T;
cur->m_Turnaround = cur->m_Finish - ma[cur->m_PidName]->Arrival;
cur->m_Weighted = (float)(cur->m_Turnaround) / (float)(ma[cur->m_PidName]->Server);
cur->m_Server = 0;//输出完以后置为0,防止再次调用
PCB::m_s_AverTurnaround += cur->m_Turnaround;
PCB::m_s_AverWeighted += cur->m_Weighted;
}
qu.pop();//运行完就pop掉
for (int i = 0; i < num ; i++)
//将在上一个进程运行时间内和运行完这段时间中所有到达的进程push进队
{
if (T >= Pid[i].m_Arrival && Pid[i].m_Status == WAITING)
{
qu.push(Pid + i);
qu.back()->m_Status = BEREADY;//让其处于就绪队列中
}
}
if (cur->m_Server != 0)//这个进程运行完后判断是否结束,没结束就加入队尾
{
qu.push(cur);
cur->m_Status = BEREADY;//让其处于就绪队列中
}
}
for (int i = 0; i < num; i++)
{
Pid[i].m_Arrival = ma[Pid[i].m_PidName]->Arrival;//将保存的值赋回去
Pid[i].m_Server = ma[Pid[i].m_PidName]->Server;
}
PCB::m_s_AverTurnaround /= (float)num;
PCB::m_s_AverWeighted /= (float)num;
PCB::OutputPid(Pid, num);
}
Main.cpp
#include"PCB.h"
#include<iostream>
using namespace std;
int chose;
while (1)
{
cout << "-------进程调度算法-------" << endl << endl << endl;
cout << "------1.FCFS" << endl;
cout << "------2.SJF" << endl;
cout << "------3.RR" << endl;
cin >> chose;
switch (chose)
{
case 1:FCFS();
break;
case 2:SJF();
break;
case 3:RR();
break;
default:
cout << "输入错误!!!" << endl;
}
}
system("pause");
return 0;
}