1. 实验目的
1.了解三级调度的基本概念;
2.通过对先来先服务算法、时间片轮转算法和短作业优先算法的模拟实现掌握它们的工作原理。
2. 三级调度的基本概念
现在的操作系统大多是多道程序系统,在同一时段内,允许用户同时执行多个作业进(或进程)。一个作业从提交到执行,通常都要经历很多种调度,如高级调度(即作业调度)、低级调度(即进程调度)、中级调度(即内外存对交换进程的选择)和I/O调度等。而系统运行的性能,如吞吐量的大小、周转时间的长短、响应的及时性等,很大程度上都取决于调度。
(1)高级调度
高级调度,又称作业调度或长程调度。其主要功能是根据一定的算法,从输入的一批作业中选出若干个作业,分配必要的资源,如内存、外设等,为它建立相应的用户作业进程和为其服务的系统进程(如输入、输出进程),最后把它们的程序和数据调入内存,等待进程调度程序对其执行调度,并在作业完成后完成善后处理工作。
(2)中级调度
中级调度,又称交换调度或中程调度。为了使内存中同时存放的进程不至于太多,有时就需要把某些进程从内存中移到外存上,以减少多道程序的数目,为此设立了中级调度。在采用虚拟存储技术的系统或分时系统中,往往会增加中级调度以选择需要对换的进程。
(3)低级调度
低级调度,又称进程调度或短程调度。它决定就绪队列中的哪个进程将获得处理机,然后由分配程序执行把处理机分配给该进程的操作。进程调度的运行频率很高,在分时系统中通常是几十毫秒就要执行一次。
3. 常用的进程调度算法模拟
一、先来先服务(First Come First Served,FCFS)
先来先服务算法按照进程进入就绪队列的先后顺序来调度进程,更早到达的进程有更高的优先级。获得处理机的进程,在未遇到其他情况时将会一直运行下去,系统只需要具备一个先进先出的队列。这是一种最简单的不可抢占方式的调度算法,其优点就是实现简单,但由于其不可抢占的特点,因此该算法的缺点是后进入就绪队列的进程较长时间等待处理机。该算法最早用于批处理系统的作业调度,功能是按照作业进入系统后备作业队列的先后次序挑选作业进入内存,为之创建用户进程、分配资源,然后进入就绪队列。
二、短作业优先(Shortest Job First,SJF)
短作业优先算法总是选取估计运行时间最短的作业投入运行,更短的作业总是拥有更高的优先级。这是一种不可抢占方式的调度算法,是批处理系统较早使用的一种简单有效的作业调度算法。该算法的优点是系统吞吐量大、实现简单、克服了先来先服务算法偏爱长作业的不足,但正因如此也带来了一个问题,也就是该算法的缺点,长作业等待的时间可能会很长,容易出现饥饿现象。
三、时间片轮转(Round Robin,RR)
时间片轮转算法是一种非常适合与分时系统的调度算法,是先来先服务算法的一个变形策略。系统吧所有就绪进程按先后次序排列,处理机总是优先分配给就绪队列中的第一个就绪进程,并分配给它一个固定的时间片。当该运行进程用完规定的时间片时,被迫释放处理机给下一个处于就绪队列中的第一个进程,为这个进程分配一个相同的时间片。每当运行进程运行完一个时间片,而且未遇到任何阻塞时,就会回到就绪队列队尾,等待下次轮到它时继续投入运行。因此,处于就绪队列中的所有进程总是可以分配到处理机投入运行。
4. 常用进程调度算法模拟实验提高实验
一、先来先服务算法
请你右键使用Visual Studio Code打开fcfshard.c文件:
在这个文件中我们定义了进程控制块pcb数据结构:
1 typedef struct pcb{
2 char name[10]; //进程名字
3 char state; //进程状态
4 int ntime; //进程需要运行的时间
5 int rtime; //进程已经运行的时间
6 struct pcb *link;
7 }PCB;
我们已经知道进程控制块描述了进程十分重要的状态信息,在这个结构体中,各成员的具体含义见代码中的注释,需要说明的是,最后一个成员struct pcb *link指向的是在某个进程队列中该进程的下一个进程。
继续浏览代码,你可以看到我们定义了geti()、fcfs()、input()、disp(PCB *pr)、check()、destroy()、running()和main()函数,各个函数的功能后面进行介绍。
现在需要你补全fcfs()函数,实则为将新进入进程放入就绪队列队尾。
1 void fcfs()//插入进程
2 {
3 if(!ready ){
4 //待补全
5 }
6 else{
7 //待补全
8 }
9 }
在我们的程序中,main()函数为入口,调用input()函数接受输入,在我们正确的输入进程信息时,调用fcfs()函数将输入的进程放进就绪队列,你可以看到fcfs()函数出需要你来实现,在这个调度算法中,我们仅需简单的将新输入的进程放在就绪队列队尾部。接着不断接受键入字符,主函数的while()循环每次取队首的进程执行,而在此期间如果接收的字符为“i”或者“I”的时候说明将插入新的进程。每接受一个回车,相当于时间流失一个单位,在这个单位时间流失期间,我们调用了check()函数和running()函数,前者的功能是打印当前正在执行的进程信息和就绪队列中所有进程的信息,后者的功能就是执行队首进程。我们查看一下running()函数的代码:
1 void running()
2 {
3 (ready -> rtime)++;
4 if(ready->rtime == ready->ntime){
5 destroy();
6 return;
7 }
8 }
|
注意: running()函数始终运行队首ready指针指向的进程,因此正确的fcfs()函数才能保证正确的进程执行顺序。 |
二、短作业优先算法
请你打开sjfhard.c文件:
在这个文件中我们同样定义了进程控制块pcb数据结构,不再做累赘叙述。你可以看到文件中SJF()函数需要由你来实现,由于running()函数同样每次都执行ready指针指向的进程,你需要做的是将不断创建的进程按照其运行时间正确插入已按运行时间由小到大排列的队列中。请补全下面的代码并编译运行。
1 void SJF()//Shortest Job First
2 {
3 if(!ready ){
4 //待补全
5 }
6 else{
7 PCB *tp=ready,*tempp;
8 if(ready->oncerun == 1)
9 tp = tp->link;
10 while(tp!=NOTHING &&p->ntime >= tp->ntime){
11 tempp = tp;
12 tp = tp->link;
13 }
14
15
16 if(tp == ready){
17 if(ready->oncerun == 0){
18 //待补全
19 }
20 else{
21 //待补全
22 }
23 }
24 else if(tp == NOTHING){
25 //待补全
26 }
27 else{
28 //待补全
29 }
30 }
31 }
|
注意: 本次实验希望你实现的是非抢占的sjf调度,因此你需要注意在新插入进程的时候队首是否已经处于运行状态,再将其插入争取的位置。 |
|
思考: 如果让你实现抢占的sjf调度,该如何修改代码?与非抢占的sjf调度相比,在给定的框架下哪一个实现起来更简单? |
在完成SJF()函数后编译并运行,验证你的函数是否编写正确。
三、轮转调度算法
请你打开rrhard.c文件:
我们仍然先查看定义的进程控制块pcb:
1 typedef struct pcb{
2 char name[10]; //进程名字
3 char state; //进程状态
4 int ntime; //进程需要运行的时间
5 int rtime; //进程已经运行的时间
6 int etime;
7 struct pcb *link;
8 }PCB;
|
思考: 结合running()中etime==0条件满足时的情况,思考增加的etime描述的为何信息? |
继续浏览代码,你可以看到insert()和rr()函数是需要你来实现的。前者函数只需要简单的将新创建的进程插入就绪队列,而后者需要你在每次执行running()前将需要执行的正确的进程放在队首,时间片用完的进程放在队尾。现在请根据轮转调度算法的原理补全rr()函数。
1 void rr()//调整进程队列
2 {
3 if(!ready->link) return;
4 //待补全
5 }
然后编译运行rrhard.c,验证你的函数是否正确。
代码补全如下:

2870

被折叠的 条评论
为什么被折叠?



