一、实验目的
- 通过编写程序实现进程或作业先来先服务、高优先权、按时间片轮转调度算法,进一步掌握进程调度的概念和算法,加深对处理机分配的理解。
- 了解进程(线程)的调度机制。
- 学习使用进程(线程)调度算法,掌握相应的与调度有关的 API 函数。
二、实验环境
Tencent 云服务器一台
三、实验内容
1 先来先服务算法
1.1 算法简介
先来先服务算法是最简单的调度算法,既可以用于作业调度 ,也可以用于程序调度,当作业调度中采用该算法时,系统将按照作业到达的先后次序来进行调度,优先从后备队列中,选择一个或多个位于队列头部的作业,把他们调入内存,分配所需资源、创建进程,然后放入“就绪队列”,直到该进程运行到完成或发生某事件堵塞后,进程调度程序才将处理机分配给其他进程。
1.2 算法流程
程序流程图如下:
1.3 代码实现
请参考 2.3
2 短作业优先算法
2.1 算法简介
对预计执行时间短的作业(进程)优先分派处理机。通常后来的短作业不抢先正在执行的作业。也就是说,不但要考虑进程的到达时间,还要考虑进程需要运行的时间。当一个进程正在运行时,假如有其他的进程到达,那么这些到达的进程就需要按照其需要运行的时间长短排序,运行时间短的在前,运行时间长的在后。
2.2 算法流程
程序流程图如下:
2.3 代码实现
我们按照 1 2 中的思路设计先来先服务算法和短作业优先算法,编写代码如下:
# include <stdio.h>
# include <stdlib.h>
# define MAX 100
int n;
float t,d; /*定义两个全局变量*/
struct /*定义一个结构体数组,包括进程的信息*/
{
int id;
float ArriveTime;
float RequestTime;
float StartTime;
float EndTime;
float RunTime;
float DQRunTime;
int Status;
}arrayTask[MAX]; /*定义初始化的结构体数组*/
void GetTask()/*给结构体数组赋值,输入到达,服务时间*/
{
int i;
float a;
printf("input the number of task: ");
scanf("%d",&n);
for(i=0;i<n;i++)
{
arrayTask[i].id=i+1;
printf("input the the ArriveTime of arrayTask[%d]: ",i); /*用户输入进程的时间,初始为零 */
scanf("%f",&a);
arrayTask[i].ArriveTime=a;
printf("input the RequestTime of arrayTask[%d]: ",i);
scanf("%f",&a);
arrayTask[i].RequestTime=a;
arrayTask[i].StartTime=0;
arrayTask[i].EndTime=0;
arrayTask[i].RunTime=0;
arrayTask[i].Status=0; /*开始默认的标志位零*/
}
}
int FCFS() /*定义 FCFS 中寻找未执行的进程的最先到达时间*/
{
int i,j,w=0;
/*在结构体数组中找到一个未执行的进程*/
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==0)
{
t=arrayTask[i].ArriveTime;
w=1;
}
if(w==1) break;
}
for(i=0;i<n;i++) /*查找数组中到达时间最小未执行的进程*/
{
if(arrayTask[i].ArriveTime<t&&arrayTask[i].Status==0) t=arrayTask[i].ArriveTime;
} /*返回最小到达时间的数组的下标*/
for(i=0;i<n;i++)
{
if (arrayTask[i].ArriveTime==t) return i;
}
}
int SFJ() /*定义 FCFS 中寻找未执行的进程的最先到达时间*/
{
int i,x=0,a=0,b=0; /*判断是不是第一个执行的进程*/
float g;
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==1)
{
g=arrayTask[i].EndTime;
x=1;
}
}
if(x==0) /*第一个执行的进程按 FCFS*/
{
t=arrayTask[0].ArriveTime;
for(i=0;i<n;i++)
{
if(arrayTask[i].ArriveTime<t)
{
t=arrayTask[i].ArriveTime;
a=i;
}
}
return a;
}
else
{
for(i=0;i<n;i++)
{
if(arrayTask[i].EndTime>g) g=arrayTask[i].EndTime;
}
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==0 && arrayTask[i].ArriveTime<=g)
{
t=arrayTask[i].RequestTime;
a=i;
b=1;
} /*判断有没有进程在前个进程完成前到达*/
}
if(b!=0) /*有进程到达则按 SFJ*/
{
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==0&&arrayTask[i].ArriveTime<=g&&arrayTask[i].RequestTime<t)
{
t=arrayTask[i].RequestTime;
a=i;
}
}
return a;
}
else
{
/*否则按 FCFS*/
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==0) t=arrayTask[i].ArriveTime;
}
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==0&&arrayTask[i].ArriveTime<t)
{
t=arrayTask[i].ArriveTime;
a=i;
}
}
return a;
}
}
}
void New(int s) /*定义执行进程后相关数据的修改*/
{
int i,g=0;
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==0) continue;
else
{
g=1;
break;
}
}
if(g==0) /*当处理的是第一个未执行的进程时执行*/
{
arrayTask[s].StartTime=arrayTask[s].ArriveTime;
arrayTask[s].EndTime=arrayTask[s].RequestTime+arrayTask[s].ArriveTime;
arrayTask[s].RunTime=arrayTask[s].RequestTime;
arrayTask[s].Status=1;
g=2;
}
else if(g==1) /*当处理的不是第一个未执行的进程时执行*/
{
arrayTask[s].Status=1;
for(i=0;i<n;i++)
{
if(arrayTask[i].Status==1)
d=arrayTask[i].EndTime; //用 d 保存作业结束时间
}
for(i=0;i<n;i++) /*查找最后执行的进程的完成时间*/
{
if(arrayTask[i].EndTime>d&&arrayTask[i].Status==1) d=arrayTask[i].EndTime;
}
/*判断修改的进程的到达时间是否在前一个执行的进程的完成时间前面 */
if(arrayTask[s].ArriveTime<d) arrayTask[s].StartTime=d;
else arrayTask[s].StartTime=arrayTask[s].ArriveTime;
arrayTask[s].EndTime=arrayTask[s].StartTime+arrayTask[s].RequestTime;
arrayTask[s].RunTime=arrayTask[s].EndTime-arrayTask[s].ArriveTime;
}
arrayTask[s].DQRunTime=arrayTask[s].RunTime / arrayTask[s].RequestTime;
}
void Printresult(int j) /*定义打印函数*/
{
printf("%d\t",arrayTask[j].id);
printf("%5.2f\t",arrayTask[j].ArriveTime);
printf("%5.2f\t",arrayTask[j].RequestTime);
printf("%5.2f\t",arrayTask[j].StartTime);
printf("%5.2f\t",arrayTask[j].EndTime);
printf("%5.2f\t",arrayTask[j].RunTime);
printf("%5.2f\n",arrayTask[j].DQRunTime);
}
void main()
{
int i,b,k,a,c=0;
int d[MAX];
printf("F. FCFS \n");
printf("S. SFJ \n");
printf("Q. EXIT \n");
for(i=0;;i++)
{
if(c) break;
printf("please choose one: ");
a=getchar();
getchar();
switch(a)
{
case 'Q':
c=1;
break;
case 'F':
GetTask();
printf("*****************************the result of FCFS*****************************\n");
printf("Number\tArrive\tServer\tStart\tFinish\tRun\tDQRuntime\n");
for(b=0;b<n;b++) /*调用两个函数改变结构体数的值*/
{
k=FCFS();
d[b]=k;
New(k);
}
for(b=0;b<n;b++)
Printresult(d[b]);/*调用打印函数打出结果*/
continue;
case 'S':
GetTask();
printf("******************************the result of SFJ*****************************\n");
printf("Number\tArrive\tRequest\tStart\tEnd\tRun\tDQRun time\n");
for(b=0;b<n;b++)
{
k=SFJ();
d[b]=k;
New(k);
}
for(b=0;b<n;b++)
{
Printresult(d[b]);
}
continue;
default:
printf("Number Error, please input another one!\n");
}
}
}
2.4 实验结果
我们运行以上代码,输入进程数目、到达时间、服务时间,如下表所示。同时我们也手动预先模拟了在先来先服务和短作业优先的情形下的进程调度情况,用于比对检验程序的正确性:
我们得出以下结果:
先来先服务结果
短作业优先结果
注意:程序中我们定义进程 ID 下标从 1 开始
通过比对以上两组实验结果和手动计算的结果,我们可以知道程序运行正确。
3 时间片轮转法
3.1 算法简介
时间片轮转法(Round-Robin,RR)主要用于分时系统中的进程调度。为了实现轮转调度,系统把所有就绪进程按先入先出的原则排成一个队列。新来的进程加到就绪队列末尾。每当执行进程调度时,进程调度程序总是选出就绪队列的队首进程,让它在CPU上运行一个时间片的时间。时间片是一个小的时间单位,通常为10~100ms数量级。当进程用完分给它的时间片后,系统的计时器发出时钟中断,调度程序便停止该进程的运行,把它放入就绪队列的末尾;然后,把CPU分给就绪队列的队首进程,同样也让它运行一个时间片,如此往复。
采用此算法的系统,其程序就绪队列往往按进程到达的时间来排序。进程调度程序总是选择就绪队列中的第一个进程,也就是说按照先来先服务原则调度,但一旦进程占用处理机则仅使用一个时间片。在使用先一个时间片后,进程还没有完成其运行,它必须释放出处理机给下一个就绪的进程,而被抢占的进程返回到就绪队列的末尾重新排队等待再次运行。
处理器同一个时间只能处理一个任务。处理器在处理多任务的时候,就要看请求的时间顺序,如果时间一致,就要进行预测。挑到一个任务后,需要若干步骤才能做完,这些步骤中有些需要处理器参与,有些不需要(如磁盘控制器的存储过程)。不需要处理器处理的时候,这部分时间就要分配给其他的进程。原来的进程就要处于等待的时间段上。经过周密分配时间,宏观上就象是多个任务一起运行一样,但微观上是有先后的,就是时间片轮换。
3.2 算法流程
时间片轮转算法的基本思想是,系统将所有的就绪进程按先来先服务算法的原则,排成一个队列,每次调度时,系统把处理机分配给队列首进程,并让其执行一个时间片。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序根据这个请求停止该进程的运行,将它送到就绪队列的末尾,再把处理机分给就绪队列中新的队列首进程,同时让它也执行一个时间片。
算法流程图如下:
3.3 代码实现
根据算法的流程及思想,我们编写代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct quen /*定义结构*/
{
char pname[8];
int time1;
int time2;
char state;
struct quen *next;
}QUEN;
void main()/*主程序*/
{
QUEN *q,*p,*head,*m;
char str[8],f;
int t,d,n;
printf("Enter the maxnumber of nodes(n):\n");
/*输入进程数*/
scanf("%d",&n);
d=n;
if(d>0)
{
printf("enter the pname:");
scanf("%s",str);
printf("enter the need time:");
scanf("%d",&t);
head=p=(QUEN*)malloc(sizeof(QUEN));
strcpy(p->pname,str);
p->time1=t;
p->time2=0;
p->state='R';
p->next=NULL;
head=p;
getchar();
--d;
}
while(d>0)
{
/*构建队列表*/
printf("enter the pname:");
scanf("%s",str);
printf("enter need time:");
scanf("%d",&t);
q=(QUEN *)malloc(sizeof(QUEN));
strcpy(q->pname,str);
q->time1=t;
q->time2=0;
q->state='R';
q->next=NULL;
p->next=q;
p=q; // 尾插法
--d;
p->next=head; // 循环队列
q=head;
}
printf("process name need time runned static\n");
do
{
printf("%s\t%d\t%d\t%c\n",q->pname,q->time1,q->time2,q->state);
q=q->next;
}while(q!=head);
printf("\n");
do
{
if(head->time2<head->time1)
{
head->time2++;
if(head->time2==head->time1)
{
head->state='E';
q=head;
printf("The running process is %s\n",q->pname);
printf("Pname\tLeft\tRunned\tStatus\n");
do
{
/*输入队列表*/
printf("%s\t%d\t%d\t%c\n",q->pname,q->time1,q->time2,q->state);
q=q->next;
}while(q!=head);
printf("\n");
head=head->next;
q=head;
p->next=head; // p 是队尾
}
else
{
printf("The running process is %s\n",q->pname);
printf("Pname\tLeft\tRunned\tStatus\n");
do
{
printf("%s\t%d\t%d\t%c\n",q->pname,q->time1,q->time2,q->state);
q=q->next;
}while(q!=head);
printf("\n");
head=head->next;
q=head;
p=p->next;
}
printf("Is it needing new process?(y or n)\n");
/*是否加入新的进程*/
getchar();
scanf("%c",&f);
if(f=='Y'||f=='y')
{
getchar();
printf("Enter the new pname:");
scanf("%s",str);
printf("Enter the new neededtime:");
scanf("%d",&t);
m=(QUEN *)malloc(sizeof(QUEN));
strcpy(m->pname,str);
m->time1=t;
m->time2=0;
m->state='R';
m->next=NULL;
/* 全部运行完成 */
if(q->next->state=='E')
{
p=m;
head=m;
p->next=head;
q=head;
}
else
{
p->next=m;
m->next=head;
p=m;
}
}
}
}while(q->next->state!='E');
printf("The processes are finished\n");
}
3.4 实验结果
我们共设置四个进程,这四个进程所需时间分别为2、5、4、3,之后我们又添加心得进程,录入所需时间 1,最终得到实验结果如下:
这里是用4个进程,分别需要2、5、1、6个单位时间运行,由运行过程可以看到,在一个时间片范围内,会选择一个进程执行,然后切换到下一个进程,所以说这种调度方式,是绝对公平的。虽说在某些情况下,带权周转时间可能不如其他的调度算法,但是,在实现人机交互上,这种算法是非常实用的。
4 优先数调度算法
4.1 算法简介
优先数调度算法常用于批处理系统中。在进程调度中,每次调度时,系统把处理机分配给就绪队列中优先数最高的进程。
它又分为两种:非抢占式优先数算法和抢占式优先数算法。
- 在非抢占式优先数算法下,系统一旦把处理机分配给就绪队列中优先数最高的进程后,这个进程就会一直运行,直到完成或发生某事件使它放弃处理机,这时系统才能重新将处理机分配给就绪队列中的另一个优先数最高的进程。
- 在抢占式优先数算法下,系统先将处理机分配给就绪队列中优先数最高的进程度让它运行,但在运行的过程中,如果出现另一个优先数比它高的进程,它就要立即停止,并将处理机分配给新的高优先数进程。
4.2 算法流程
算法流程如下:
4.3 代码实现
根据上述流程,我们编写代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct pcb/*定义结构*/
{
char name[5];
struct pcb *next;
int needtime;
int priority;
char state[5];
}NODE;
NODE *create_process(int n)/*创建队列*/
{
NODE *head,*s,*t;
int time,i=0,j;
char pname[5];
head=(NODE*)malloc(sizeof(NODE));
printf("please input process name:");
scanf("%s",pname);
strcpy(head->name,pname);
printf("please input process time:");
scanf("%d",&time);
head->needtime=time;
printf("please input priority:");
scanf("%d",&j);
head->priority=j;
strcpy(head->state,"ready");
head->next=NULL;
t=head;
for(i=1;i<n;i++)
{
s=(NODE*)malloc(sizeof(NODE));
printf("please input process name:");
getchar();
scanf("%s",pname);
strcpy(s->name,pname);
printf("please input need time:");
scanf("%d",&time);
s->needtime=time;
printf("please input priority:");
scanf("%d",&j);
s->priority=j;
strcpy(s->state,"ready");
s->next=NULL;
t->next=s; //尾插法
t=s;
}
return head;
}
void pri_process(NODE *p)/*输出进程队列*/
{
int i;
NODE *q;
q=p->next;
printf("name\tneedtime\tpriority\tstate\n");
while(q!=NULL)
{
printf("%s\t%d\t\t%d\t\t%s\n",q->name,q->needtime,q->priority,q->state);
q=q->next;
}
}
NODE *order(NODE *head_sort)/*对进程的优先级进行排序*/
{
NODE *p,*s,*q,*head,*r,*t;
int m,pr;
char name[5];
head=head_sort;
p=head->next;
r=p;
t=p;
q=p->next;
/*冒泡排序*/
while(r!=NULL)
{
while(q!=NULL)
{
if(p->priority<q->priority)
{
m=p->priority;
p->priority=q->priority;
q->priority=m;
strcmp(name,p->name);
strcmp(p->name,q->name);
strcmp(q->name,name);
pr=p->needtime;
p->needtime=q->needtime;
q->needtime=pr;
}
p=q;
q=q->next;
}
r=r->next;
p=t;
q=p->next;
}
return(head_sort);
}
void main()/*主程序*/
{
NODE *p=NULL,*head=NULL,*m=NULL,*z=NULL,*n=NULL;
int j,time,x=0;
char c,pname[5];
printf("please input process number!");
scanf("%d",&x);
p=create_process(x);
head=(NODE*)malloc(sizeof(NODE));
head->next=p;
pri_process(head);
getchar();
while(x>0)
{
order(head);
m=head->next;
strcpy(m->state,"run");
if(m->priority>=2)
m->priority--;
m->needtime--;
if(head->next!=NULL)
pri_process(head);
if(m->needtime==0)
{
head->next=m->next;
printf("%s has finished\n",m->name);
free(m);
x--;
}
getchar();
}
printf("over!");
getchar();
}
4.4 实验结果
我们运行以上代码,输入一组数据进行测试:
得到如下结果:
我们可以看到,程序自动按照优先级从大到小的顺序进行执行,当存在较大的优先级进程时,系统会先运行完成,之后再运行优先级小的进程,程序运行的结果符合我们的预期。