轮转调度算法Round-Robin(RR)
在分时系统中都采用时间片轮转算法进行进程调度。
时间片是指一个较小的时间间隔,通常为10-100毫秒。
1 轮转法的基本原理
【百度百科】在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则,排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片.时间片的大小从几ms到几百ms.当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片.这样就可以保证就绪队列中的所有进程,在一给定的时间内,均能获得一时间片的处理机执行时间.
【整理版】系统将所有的就绪进程按先来先服务规则排成一个队列,每次调度时,将CPU分配给队首进程,并令其执行一个时间片,在这个时间片内如果进程执行结束了,那么把进程从进程队列中删去,如果进程没有结束,那么把该进程停止然后改为等待状态,放到进程队列的尾部,再把CPU分配给就绪队列中新的队首进程,同时也让它执行一个时间片。如此调度,直到所有的进程都已执行完毕。
2 进程的切换
时间片够用:意思就是在该时间片内,进程可以运行至结束,进程运行结束之后,将进程从进程队列中删除,然后启动新的时间片。
时间片不够用:意思是在该时间片内,进程只能完成它的一部分任务,在时间片用完之后,将进程的状态改为等待状态,将进程放到进程队列的尾部,等待cpu的调用。
3 关于时间片大小的选择
时间片过小,则进程频繁切换,会造成cpu资源的浪费。
假如进程切换(process switch) - 有时称为上下文切换(context switch),需要5毫秒,再假设时间片设为20毫秒,则在做完20毫秒有用的工作之后,CPU将花费5毫秒来进行进程切换。CPU时间的20%被浪费在了管理开销上。
时间片过大,则轮转调度算法就退化成了先来先服务算法。
为了提高CPU效率,我们可以将时间片设为500毫秒。这时浪费的时间只有1%。但考虑在一个分时系统中,如果有十个交互用户几乎同时按下回车键,将发生什么情况?假设所有其他进程都用足它们的时间片的话,最后一个不幸的进程不得不等待5秒钟才获得运行机会。多数用户无法忍受一条简短命令要5秒钟才能做出响应。
结论可以归结如下:
时间片设得太短会导致过多的进程切换,降低了CPU效率;
而设得太长又可能引起对短的交互请求的响应变差。
将时间片设为100毫秒通常是一个比较合理的折中。
优点 :CPU分配相对公平;平均响应时间较短
缺点:不利于紧急作业,而且当进程的运行时间都相近时,平均的等待时间比较长,甚至不如先来先服务算法。
4 实战举例
4.1 RR时间片轮转调度算法~不同到达时间
视频解析
借助甘特图分析;
如何决定下一个被调度的进程,还需要就绪队列;
1 时间片=1
周转时间 = 完成时间 - 到达时间;
带权周转时间 = 周转时间 / 服务时间;
可以看到完成时间是17 = 所有进程服务总时间,这也是验证对错的方法。
2 时间片=4
当时间片为4 时,可以看到最大服务时间 <=4,该算法就退化成先来先服务;
那么就按照先来先服务算法来做,就可以直接写了。
A到达时间0,服务时间4,所以完时间 0 + 4 =4;
此时,t=4,BCDE全到了;
B开始时间4,服务时间3,所完成时间 4 + 3 =7;
C开始时间7,服务时间4,所完成时间 7 + 4 =11;
.
.
.
4.2 RR时间片轮转调度算法 ~相同到达时间
0时刻全部达到,所有进程同时到达。
1 时间片 q= 1
所有进程同时到达,刚好按照ABCDE的顺序;
当时间片q = 1 时,
第一轮,所有进程都获得1个时间片,总时长5;
第二轮,所有进程都获得2个时间片,总时长10;此时 t = 9,D结束,就绪队列删除D;
第三轮,ABCE都获得3个时间片,总时长14;此时 t = 12,B结束;
.
.
.
2 时间片 q= 4
当时间片为4 时,可以看到最大服务时间 <=4,该算法就退化成先来先服务;
4.3
例:考虑5个进程P1、P2、P3、P4、P5,如下表,试计算在采用下述时间片轮转调度算法时各个进程周转时间和带权周转时间。假设忽略进程的调度时间。
5 c实现
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define TAKEIN "takein"//对应的进程状态
#define WAIT "wait"
#define RUN "run"
#define FINISH "finish"
#define PNUMBER 5//进程个数
#define TRUE 1
#define FALSE 0
typedef struct pcb
{
char processName[20];//进程名称
int arriveTime;//进程到达时间
int startTime;//进程开始时间
int endTime;//进程结束时间
int runTime;//进程运行时间大小
int turnOverTime;//周转时间
int userweightTurnOverTime;//带权周转时间
char provessStatus[10];//进程状态
int runTimeed; //进程已经运行的时间
} pcb;
pcb pcbs[PNUMBER];//进程数组
int currentTime=0;//时间
int processIndex=0;//进程的编号
int cpuTime=2;//时间片
int size=PNUMBER;//进程数组中的有效值
void createPcbs()//进程初始化函数
{
freopen("input.txt","r",stdin);//以只读操作读文件
printf("进程名\t到达时间\t运行时间\n");
int index;
for(index=0; index<PNUMBER; index++)//遍历所有进程,给进程赋初值
{
scanf("%s",pcbs[index].processName);
scanf("%d",&pcbs[index].arriveTime);
scanf("%d",&pcbs[index].runTime);
pcbs[index].endTime=0;
pcbs[index].startTime=0;
pcbs[index].turnOverTime=0;
pcbs[index].userweightTurnOverTime=0;
strcpy( pcbs[index].provessStatus,TAKEIN);
printf("%s \t%d \t%d\n", pcbs[index].processName, pcbs[index].arriveTime, pcbs[index].runTime);
}
printf("\n***********************************************\n");
}
void movePcbs(int pindex)
{
int index=0;
pcb temp=pcbs[pindex];//需要移动的进程
for(index=pindex;index<size-1;index++)
{
pcbs[index]=pcbs[index+1];//后面的进程全部前移一位
}
pcbs[size-1]=temp;//目标进程移到数组尾部
}//享受过cpu服务的进程移到进程数组的尾部,采用队列实现
void printfPcbsInfo()//打印所有进程的所有信息
{
int index=0;
printf("当前时间为:%d时各进程的信息.....\n\n",currentTime);
printf("进程名\t到达时间\t运行时间\t开始时间\t结束时间\t周转时间\t带权周转时间\t状态\n");
for(index=0; index<PNUMBER; index++)
{
printf("%s\t%8d\t%8d\t%8d\t%8d\t%8d\t%8d\t%4s\n",pcbs[index].processName,pcbs[index].arriveTime,pcbs[index].runTime,pcbs[index].startTime,pcbs[index].endTime,pcbs[index].turnOverTime,pcbs[index].userweightTurnOverTime,pcbs[index].provessStatus);
}
}
void sortPcbs()//按到达时间的升序排序
{
int minIndex=0,minValue=0,i,j;
for(i=0; i<PNUMBER; i++)
{
minIndex=i;
minValue=pcbs[i].arriveTime;
for(j=i; j<PNUMBER; j++)
{
if(pcbs[j].arriveTime<minValue)
{
minValue=pcbs[j].arriveTime;//保存最小的
minIndex=j;
}
}
pcb temp=pcbs[minIndex];//交换
pcbs[minIndex]=pcbs[i];
pcbs[i]=temp;
}
}
int selNextProcess()//选择下一个进程,要求,最近的等待进程
{
int result=-1;
int index=0;
for(index=0;index<size;index++)
{
if(strcmp(pcbs[index].provessStatus,WAIT)==0)
{
return index;
}
}
return result;
}
void removeFromPcbs(int pindex)//删除完成任务的进程
{
movePcbs(pindex);
size--;//数组逻辑长度缩小,达到移除效果
}
int isHasProcessArrive()//检查在某一个时间点有没有进程到达
{
int result=-1;
int index=0;
for(index=0; index<PNUMBER; index++)
{
if(pcbs[index].arriveTime==currentTime)//某个进程的到达时间等于当前时间
{
result=index;
strcpy(pcbs[index].provessStatus,WAIT);//改变进程状态
}
}
return result;
}
int proIsEnd(int pindex)//判断一个进程是否完成
{
if(pcbs[pindex].runTime==pcbs[pindex].runTimeed)
{
currentTime++;//当前时间走到
isHasProcessArrive();//判断是否有新到达进程
strcpy(pcbs[pindex].provessStatus,FINISH);//进程相关信息的改变
pcbs[pindex].turnOverTime=pcbs[pindex].endTime-pcbs[pindex].arriveTime;
pcbs[pindex].userweightTurnOverTime=pcbs[pindex].turnOverTime*1.0/pcbs[pindex].runTime;
removeFromPcbs(pindex);//移走完成的进程
processIndex++;//准备下一个进程
printfPcbsInfo();//打印此时所有进程的信息
return TRUE;
}
return FALSE;
}
void runProcess(int pindex)
{
int index=0;
int end=FALSE;
pcbs[pindex].startTime=currentTime;//进程开始运行时间为当前时间
strcpy(pcbs[pindex].provessStatus,RUN);//进程状态改为运行态
pcbs[pindex].runTimeed++;//进程已经运行时间加一
printfPcbsInfo();
if(proIsEnd(pindex)==TRUE)//检查在当前时间片下进程是否已经结束
{
currentTime--;
return ;
}
if(cpuTime-1>=1)//时间片没有用完
{
for(index=0;index<cpuTime-1;index++)
{
currentTime++;
isHasProcessArrive();
pcbs[index].runTimeed++;
printfPcbsInfo();
if(proIsEnd(pindex)==TRUE)
{
pindex=size;
end=TRUE;
}
}
}
if(end==FALSE)//如果在当前时间片下面进程没有结束
{
currentTime++;
strcpy(pcbs[pindex].provessStatus,WAIT);
movePcbs(pindex);
printfPcbsInfo();
}
currentTime--;
}
void startProcess()
{
int firstArriveTime=pcbs[0].arriveTime;
int nextIndex=0;
while(1)
{
currentTime++;
isHasProcessArrive();//检查当前时间有没有进程到达
if(currentTime<firstArriveTime)
{
printfPcbsInfo();
}else if(currentTime==firstArriveTime)
{
runProcess(0);//执行进程
}else
{
nextIndex=selNextProcess();
if(nextIndex!=-1)
{
runProcess(nextIndex);
}else
{
printfPcbsInfo();
}
}
if(processIndex==PNUMBER)
break;
}
}
int main()
{
createPcbs();
sortPcbs();
startProcess();
return 0;
}