【实验目的】
实验目的:
(1)通过编写程序实现进程或作业先来先服务、高优先权、按时间片轮转调度
算法,使学生进一步掌握进程调度的概念和算法,加深对处理机分配的理解。
(2)了解进程(线程)的调度机制。
(3)学习使用进程(线程)调度算法,掌握相应的与调度有关的 API 函数。
实验要求:
(1)经调试后程序能够正常运行。
(2)采用多进程或多线程方式运行,体现了进程或作业先来先服务、高优先权、
按时间片轮转调度的关系。
(3)程序界面美观。
【实验内容】
进程调度的算法:
1)先来先服务算法:如果早就绪的进程排在就绪队列的前面,迟就绪的进程排在就绪队列的后面,那么先来先服务(FCFS:first come first service)总是把当前处于就绪队列之首的那个进程调度到运行状态。
2)轮转法就是按一定时间片(记为 q)轮番运行各个进程。如果 q 是一个定值,则轮转法是一种对各进程机会均等的调度方法。
3)优先级调度的基本思想是,把当前处于就绪队列中优先级最高的进程投入运行,而不管各进程的下一个 CPU 周期的长短和其他因素。
【实验环境】(含主要设计设备、器材、软件等)
Pc电脑一台,虚拟机下的linux系统
【实验步骤、过程】(含原理图、流程图、关键代码,或实验过程中的记录、数据等)
先来先服务调度算法(FCFS):按照作业进入后备作业队列的先后顺序选择作业进入内存,即先进入后备作业队列的作业被优先选择进入内存。高级调度时,为选中的作业创建进程并分配该作业所需资源;低级调度时,FCFS调度算法每次从内存的进程/线程就绪队列中选择一个最先进入的进程/线程,然后由进程/线程调度程序将CPU分配给它并使其运行。
短作业优先调度算法(SJF):按照作业的运行时间长短来安排作业的执行顺序,即运行时间短的作业先执行。
时间片轮转调度算法:将所有就绪进程按照FCFS原则排成一个队列,每个进程被分配一个时间片,当时间片用完时,该进程被放到队列的末尾,然后下一个进程被调度并分配时间片。
优先级调度算法:为每个进程分配一个优先级,优先级高的进程先执行。
算法 | 优点 | 缺点 |
先来先服务调度算法 | 简单易实现 | 可能会导致长作业饥饿 |
短作业优先调度算法 | 可以减少平均等待时间和平均周转时间 | 可能会导致长作业饥饿 |
时间片轮转调度算法 | 可以保证所有进程公平地使用CPU | 可能会导致上下文切换的开销增加 |
优先级调度算法 | 可以保证高优先级进程的响应时间 | 可能会导致低优先级进程饥饿 |
内容:
1.使用一个包含进程信息的结构体数组arrayTask[4],其中每个元素表示一个进程的信息,包括进程ID、到达时间、服务时间、开始时间、结束时间、运行时间、带权周转时间以及状态。
GetTask函数:用户输入每个进程的到达时间和服务时间,将这些信息存储在结构体数组中。
FCFS算法:在fcfs函数中,首先遍历数组找到第一个未执行的进程,记录其到达时间为t。再次遍历数组,找到到达时间最小且未执行的进程,更新t。返回具有最小到达时间的进程在数组中的下标。更新进程的开始时间、结束时间、运行时间和状态。循环执行,直到所有进程都被执行完毕。
SJF算法:在sjf函数中,判断是否是第一个执行的进程(x标志)。如果是第一个,按照FCFS的原则选择执行。如果不是第一个执行的进程,首先找到上一个执行进程的结束时间(g标志)。再次遍历数组,找到在上一个执行进程完成前到达且服务时间最短的进程,更新t。如果找到了进程在前一个进程完成前到达的情况,再次遍历数组,找到在上一个执行进程完成前到达且服务时间最短的进程,更新t。如果没有进程在前一个进程完成前到达,按照FCFS的原则选择执行。更新进程的开始时间、结束时间、运行时间和状态。循环执行,直到所有进程都被执行完毕。
new函数:如果是第一个未执行的进程,直接更新开始时间、结束时间、运行时间和状态。如果不是第一个未执行的进程,找到上一个执行进程的结束时间,然后判断修改的进程的到达时间是否在前一个执行的进程的完成时间前面,根据判断更新开始时间、结束时间、运行时间和状态。
Printresult函数:用于打印每个进程的信息,包括进程ID、到达时间、服务时间、开始时间、结束时间、运行时间和带权周转时间。
main函数通过循环,提供用户选择FCFS、SJF或退出的选项。根据用户的选择调用相应的函数进行进程调度,并输出结果。
流程图:
#include<stdio.h>
float t,d;
struct
{
int id;
float ArriveTime;
float RequestTime;
float StartTime;
float EndTime;
float RunTime;
float DQRunTime;
int Status;
}arrayTask[4];
void GetTask()
{
int i;
float a;
for(i=0;i<4;i++)
{
arrayTask[i].id=i+1;
printf("input the number");
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<4;i++)
{
if(arrayTask[i].Status==0)
{
t=arrayTask[i].ArriveTime;
w=1;
}
if(w==1)
break;
}
for(i=0;i<4;i++)
{
if(arrayTask[i].ArriveTime<t&&arrayTask[i].Status==0)
t=arrayTask[i].ArriveTime;
}
for(i=0;i<4;i++)
{
if (arrayTask[i].ArriveTime==t)
return i;
}
}
int sjf()
{
int i,x=0,a=0,b=0;
float g;
for(i=0;i<4;i++)
{
if(arrayTask[i].Status==1)
{
g=arrayTask[i].EndTime;
x=1;
}
}
if(x==0)
{
t=arrayTask[0].ArriveTime;
for(i=0;i<4;i++)
{
if(arrayTask[i].ArriveTime<t)
{
t=arrayTask[i].ArriveTime;
a=i;
}
}
return a;
}
else
{
for(i=0;i<4;i++)
{
if(arrayTask[i].EndTime>g)
g=arrayTask[i].EndTime;
}
for(i=0;i<4;i++)
{
if(arrayTask[i].Status==0&& arrayTask[i].ArriveTime<=g)
{
t=arrayTask[i].RequestTime;
a=i;
b=1;
}
}
if(b!=0)
{
for(i=0;i<4;i++)
{
if(arrayTask[i].Status==0&&arrayTask[i].ArriveTime<=g&&arrayTask[i].RequestTime<t)
{
t=arrayTask[i].RequestTime;
a=i;
}
}
return a;
}
else
{
for(i=0;i<4;i++)
{
if(arrayTask[i].Status==0)
t=arrayTask[i].ArriveTime;
}
for(i=0;i<4;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<4;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;
}
if(g==1)
{
arrayTask[s].Status=1;
for(i=0;i<4;i++)
{
if(arrayTask[i].Status==1)
d=arrayTask[i].EndTime;
}
for(i=0;i<4;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);
}
int main()
{
int i,b,k,c=0;
char a;
int d[4];
printf("\t F. FCFS \n");
printf("\t S. SJF \n");
printf("\t Q. EXIT \n");
for(i=0;;i++)
{
if(c)
break;
printf("please input the number a:\n");
scanf("%s",&a);
switch(a)
{
case 'Q': c=1;
break;
case 'F':printf("please input the different-ArriveTime of arrayTasks\n");
GetTask();
printf("*****************************the result of fcfs\n");
printf("Number\tArrive\tServer\tStart\tFinish\tTurnove\tTake power turnover time\n");
for(b=0;b<4;b++)
{
k=fcfs();
d[b]=k;
new(k);
}
for(b=0;b<4;b++)
Printresult(d[b]);
continue;
case 'S': printf("please input the different-RequestTime of arrayTasks\n");
GetTask();
printf("******************************the result of sjf\n");
printf("Number\tArrive\tRequest\tStart\tEnd\tRun\tDQRun time\n");
for(b=0;b<4;b++)
{
k=sjf();
d[b]=k;
new(k);
}
for(b=0;b<4;b++)
Printresult(d[b]);
continue;
default:printf("the number Error.please input another number!\n");
}
}
}
2.时间片轮转法
代码定义了一个结构体QUEN,用于存储进程的名称、需要的时间、已运行的时间和状态。然后,使用malloc函数动态分配内存空间,构建一个循环队列,用head指针指向队首,p指针指向队尾,q指针用于遍历队列。接着,使用do-while循环,每次检查队首的进程是否已经运行完毕,如果是,就将其状态改为’E’,并输出队列的情况,然后将head指针后移,断开原队首的链接,如果不是,就将其已运行时间加一,然后输出队列的情况,然后将head和p指针都后移,实现轮转。最后,每次循环结束后,询问用户是否需要加入新的进程,如果是,就使用malloc函数分配一个新的结点,插入到队尾,如果不是,就继续循环,直到队列中只剩下一个状态为’E’的进程,表示所有进程都执行完毕,退出循环,输出结束信息。
流程图:
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
typedef struct quen /*定义结构*/
{
char pname[8];
int time1;
int time2;
char state;
struct quen *next;
} QUEN;
int 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 thepname:");
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%d %d %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("process name left time runned static\n");
do
{
printf("%s %d %d %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;
}
else
{
printf("The running process is %s\n",q->pname);
printf("process name left time runned static\n");
do
{
printf("%s%d%d %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.优先级调度方法
定义一个结构体 NODE,用于存储进程的名称、需要的时间、优先级、状态和指向下一个进程的指针。
函数 create_process:用于创建一个进程队列,接收一个参数 n,表示进程的数量。
函数 pri_process:用于输出进程队列的信息,循环遍历队列,每次输出一个进程的名称、需要的时间、优先级和状态。
函数 order:使用冒泡排序的方法,对进程的优先级进行排序,优先级高的进程排在前面,优先级相同的进程按照先来先服务的原则排列。
main函数:从队列中取出第一个进程,将其状态改为运行,如果其优先级大于等于2,则将其优先级减一,如果其需要运行的时间大于0,则将其需要运行的时间减一。然后,输出当前的进程队列的情况,包括每个进程的名字、需要运行的时间、优先级和状态。接着,判断当前运行的进程是否已经完成,如果是,则将其从队列中移除,并输出其名字和完成的信息,如果否,则将其重新插入到队列中,按照优先级重新排序。重复上述步骤,直到队列为空,输出结束的信息。
流程图:
实验步骤:
(1)需求分析:了解基本原理,确定程序的基本功能,查找相关资料,画出基
本的数据流图;
(2)概要设计:确定程序的总体结构、模块关系和总体流程;
(3)详细设计:确定模块内部的流程和实现算法;
(4)上机编码和调试;
(5)运行测试;
(6)编写实验报告。
程序说明及实现:
1)先来先服务调度算法:
高响应比优先实现进程调度.(用 C 语言实现),
2) 优先级调度程序:
该程序由主程序、构造队列子程序、打印子程序、运行子程序构成。
3) 时间片轮转法程序:
在此程序中由于程序比较小,未进行分模块设计。直接采用单一模块。
【实验结果或总结】(对实验结果进行相应分析,或总结实验的心得体会,并提出实验的改进意见)
1.FCFS算法:
输入F,表示进程执行FCFS算法。FCFS算法是按照进程的到达时间顺序,先到先服务,不考虑进程的优先级和执行时间。开始时,输入了4个进程的信息,它们的到达时间分别是5、8、10、12,它们的请求时间分别是4、3、5、6。根据FCFS算法,进程的调度顺序是:P1 -> P2 -> P3 -> P4,因此第一个到的进程的开始时间就是它到达的时间,其他进程的开始时间=上一个进程的开始时间+上一个进程的请求时间,各进程的结束时间=各进程的开始时间+各进程的请求时间,周转时间=各进程的结束时间-各进程的到达时间,最后一列是带权周转时间=周转时间/进程请求的时间,用于衡量系统对进程的调度效率,如上图所示。
SJF算法:
输入S,表示进程执行SJF算法。SJF算法的基本思想是:在就绪队列中,优先调度预计执行时间最短的进程。开始时,输入了4个进程的信息,它们的到达时间分别是4、6、7、9,它们的请求时间分别是3、4、2,5。当进程1到达时,此时只有它一个进程,所以此时执行进程1,当进程1执行完成后进程2、3都已到达,比较进程的请求时间,2<4,所以此时执行进程3,在进程3执行完成后,进程4也到达,对比进程的请求时间,4<5,所以执行进程2,在进程2执行完成后,只剩下进程4还未执行,此时执行进程4,开始时间是进程开始执行的时间,结束时间是进程完成的时间,运行时间是进程的结束时间-到达时间,如上图所示。
输入Q,退出进程。
2.时间片轮转法
轮转调度算法将所有就绪的进程按照到达时间排成一个循环队列,然后每次从队首取出一个进程执行一个时间片,如果该进程还没有执行完毕,就将它放到队尾,继续执行下一个进程,直到所有进程都执行完毕为止。
开始时,输入了2个进程的信息,分别是a、b,它们的需要的时间分别是2、3,它们的状态都是R。输出当前的进程队列,如上图。
如果head的已运行时间小于需要的时间,将已运行时间加一,输出正在运行的进程和当前的进程信息,如下:
询问是否需要新的进程,输入了y,表示需要,输入了进程c需要的时间为2,将状态设为’R’,如果head的已运行时间小于需要的时间,将已运行时间加一,输出正在运行的进程和当前的进程信息,如下:
询问是否需要新的进程,输入了n,表示不需要,各进程的状态仍不为’E’,此时继续执行,此时进程a的已运行时间等于需要的时间,将状态设为’E’,输出运行的进程和队列的情况,如下:
判断a的状态是否为’E’,发现是,将其从队列中移除,继续循环执行。
发现队列为空,输出结束的信息,如下:
3.优先级调度方法
开始时,输入了3个进程的信息,分别是a、b、c,它们的需要运行的时间分别是2、1、1,它们的优先级分别是3、2、1,它们的状态都是就绪。
然后,对进程队列进行排序,得到的顺序是a、b、c,因为a的优先级最高,b和c的优先级相同,但是b先输入,输出当前的进程队列。取出第一个进程a,将其状态改为运行,将其优先级减一,变为2,将其需要运行的时间减一,变为1,输出当前的进程队列,如下:
判断a是否已经完成,发现没有,将其重新插入到队列中,重新排序,得到的顺序是a、b、c,因为a和b的优先级相同,但是a先运行。取出第一个进程a,将其状态改为运行,将其优先级减一,变为1,将其需要运行的时间减一,变为0,输出当前的进程队列,如下:
判断a是否已经完成,发现是,将其从队列中移除,并输出其名字和完成的信息。
对剩下的进程队列进行排序,得到的顺序是b、c,因为b的优先级高于c。取出第一个进程b,将其状态改为运行,将其优先级减一,变为1,将其需要运行的时间减一,变为0,输出当前的进程队列,如下:
判断b是否已经完成,发现是,将其从队列中移除,并输出其名字和完成的信息。
对剩下的进程队列进行排序,得到的顺序是c,因为只剩下一个进程。取出第一个进程c,将其状态改为运行,将其优先级减一,变为0,将其需要运行的时间减一,变为0,输出当前的进程队列,如下:
判断c是否已经完成,发现是,将其从队列中移除,并输出其名字和完成的信息。
发现队列为空,输出结束的信息。
通过本实验,我对作业调度算法有了更深入的理解和掌握,它们都是基于不同的优先级规则来安排作业的执行顺序,从而影响系统的整体性能。我发现,这三种算法都只考虑了作业的到达时间和运行时间,而没有考虑作业的响应时间,即从作业提交到开始执行的时间,也不区分任务的紧急程度。