实验六 作业调度算法模拟
一、实验目的
(1)掌握周转时间、等待时间、平均周转时间等概念及其计算方法。
(2)理解五种常用的进程调度算法(FCFS、SJF、HRRF、HPF、RR),区分算法之间的差异性,并用C语言模拟实现各算法。
(3)了解操作系统中高级调度、中级调度和低级调度的区别和联系。
二、实验环境
硬件环境:计算机一台,局域网环境;
软件环境:Windows或Linux操作系统,C语言编程环境。
三、实验内容和步骤
-
实验说明
-
基本概念
-
程序:程序是指静态的指令集合,它不占用系统的运行资源,可以长久地保存在磁盘中。
-
进程:进程是指进程实体(由程序、数据和进程控制块构成)的运行过程,是系统进行资源分配和调度的一个独立单位。进程执行程序,但进程与程序之间不是一一对应的。通过多次运行,一个程序可以包含多个进程;通过调用关系,同一进程可以被多个程序包含(如一个DLL文件可以被多个程序运用)。
-
作业:作业由一组统一管理和操作的进程集合构成,是用户要求计算机系统完成的一项相对独立的工作。作业可以是完成了编译、链接之后的一个用户程序,也可以是各种命令构成的一个脚本。
-
作业调度:作业调度是在资源满足的条件下,将处于后备状态的作业调入内存,同时生成与作业相对应的进程,并为这些进程提供所需要的资源。作业调度适用于多道批处理系统中的批处理作业。根据作业控制块中的信息,检查系统是否满足作业的资源要求,只有在满足作业调度的资源需求的情况下,系统才能进行作业调度。
-
-
基本调度算法
-
1)先来先服务(First-Come First-Served,FCFS)调度算法
先来先服务调度算法遵循按照进入后备队列的顺序进行调度的原则。该算法是一种非抢占式的算法,是到目前为止最简单的调度算法,其编码实现非常容易。该算法仅考虑了作业到达的先后顺序,而没有考虑作业的执行时间长短、作业的运行特性和作业对资源的要求。
-
2)短作业优先(Shortest-Job-First,SJF)调度算法
短作业优先调度算法根据作业控制块中指出的执行时间,选取执行时间最短的作业优先调度。本实验中规定,该算法是非抢占式的,即不允许立即抢占正在执行中的长进程,而是等当前作业执行完毕再进行调度。
-
3)响应比高者优先(HRRF)调度算法
FCFS调度算法只片面地考虑了作业的进入时间,短作业优先调度算法考虑了作业的运行时间而忽略了作业的等待时间。响应比高者优先调度算法为这两种算法的折中。响应比为作业的响应时间与作业需要执行的时间之比。作业的响应时间为作业进入系统后的等待时间与作业要求处理器处理的时间之和。
-
4)优先权高者优先(Highest-Priority-First,HPF)调度算法
优先权高者优先调度算法与响应比高者优先调度算法十分相似,根据作业的优先权进行作业调度,每次总是选取优先权高的作业优先调度。作业的优先权通常用一个整数表示,也叫优先数。优先数的大小与优先权的关系由系统或者用户规定。优先权高者优先调度算法综合考虑了作业执行时间和等待时间的长短、作业的缓急度,作业对外部设备的使用情况等因素,根据系统设计目标和运行环境而给定各个作业的优先权,决定作业调度的先后顺序。
-
本实验所选用的调度算法均默认为非抢占式调度。实验所用的测试数据如下表所示。本实验所用的测试数据如下表所示
-
-
实验内容
要求:
1、通过程序的打印信息来检查作业信息的读入是否正确。
2、运行FCFS算法,检查其运算结果是否正确
3、根据下图所示补充短作业优先代码,并计算其等待时间和周转时间。
4、参考以上算法的实现方法,编写高响应比优先算法和优先权高者优先算法。
/* * @Description: * @version: * @Author: * @Date: 2021-05-14 13:25:09 * @LastEditors: Please set LastEditors * @LastEditTime: 2021-05-14 19:08:11 */ #include <stdbool.h> #include <stdio.h> #include <string.h> #include <stdlib.h> // 最大作业数量 #define MAXJOB 50 // 作业的数据结构 typedef struct node { int number; // 作业号 int reach_time; // 作业抵达的时间 int need_time; // 作业的执行需要的时间 int privilege; // 作业优先级 // 以上是从文件中输入的信息 float excellent; // 作业响应比 int start_time; // 作业开始时间 int wait_time; // 作业等待时间 int visited; // 作业是否已经被访问过 } job; // 作业序列 job jobs[MAXJOB]; // 作业数量 int quantity; // 初始化所有作业 void initial_jobs() { int i; for (i = 0; i < MAXJOB; i++) { jobs[i].number = 0; jobs[i].reach_time = 0; jobs[i].privilege = 0; jobs[i].excellent = 0; jobs[i].start_time = 0; jobs[i].wait_time = 0; jobs[i].visited = 0; } quantity = 0; } // 重置所有作业的信息 void reset_jinfo() { int i; for (i = 0; i < MAXJOB; i++) { jobs[i].start_time = 0; jobs[i].wait_time = 0; jobs[i].visited = 0; } } // 读取作业的基本信息 void readJobdata() { FILE *fp; char fname[20]; int i; printf("Please input jobs data file name\n"); scanf("%s", fname); if ((fp = fopen(fname, "r")) == NULL) printf("error,open file failed,please check filename:\n"); else { // 读取文件中的作业信息 while (!feof(fp)) { if (fscanf(fp, "%d %d %d %d", &jobs[quantity].number, &jobs[quantity].reach_time, &jobs[quantity].need_time, &jobs[quantity].privilege) == 4) quantity++; } // 打印作业信息 printf("output the origin jobs data\n"); printf("---------------------------------\n"); printf("\tjobID\treachtime\tneedtime\tprivilege\n"); for (i = 0; i < quantity; i++) printf("\t%-8d\t%-8d\t%-8d\t%-8d\n", jobs[i].number, jobs[i].reach_time, jobs[i].need_time, jobs[i].privilege); } } // 找出当前时间的已到达的 执行时间最短的作业 int findminjob(job jobs[], int current_time) { int mincost = 1000; int minloc = -1; // 找到当前时间已到达的且且未执行 且需要时间最小的作业 for (int i = 0; i < quantity; i++) { if (jobs[i].reach_time <= current_time && jobs[i].visited == 0) { if (jobs[i].need_time < mincost) { mincost = jobs[i].need_time; minloc = i; } } } // 在当前时间未找到满足要求的作业,即无未执行的作业 if (minloc == -1) { // 从所有作业找到一个作业,最早到达的未执行的最短作为返回值 int early_time = 10000; for (int i = 0; i < quantity; i++) { // 找到最早到达且未执行 if (jobs[i].reach_time < early_time && jobs[i].visited == 0) { early_time = jobs[i].reach_time; mincost = jobs[i].need_time; minloc = i; } // 找到与当前最早时间相同,且更短的作业 else if (jobs[i].reach_time == early_time && jobs[i].need_time < mincost && jobs[i].visited == 0) { mincost = jobs[i].need_time; minloc = i; } } } return minloc; } // 找到到达最早的作业 int findearlyjob(job jobs[], int count) { int earlyloc = -1; int earlyjob = -1; for (int i = 0; i < count; i++) { // 找到第一个最早且未被执行过的作业 if (earlyloc == -1) { if (jobs[i].visited == 0) { earlyloc = i; earlyjob = jobs[i].reach_time; } } else if (earlyjob > jobs[i].reach_time && jobs[i].visited == 0) { earlyjob = jobs[i].reach_time; earlyloc = i; } } return earlyloc; } // 更新响应比 int updateExcellent(job jobs[], int current_time) { int next = -1; int max_excellent = -1; int i = 0; // 设置所有已到达作业且未执行作业的响应比,因为已经按照到达时间排序,所以当循环到作业的到达时间大于当前时间后就无须处理之后的作业了,因为必定此时未到达 for (i = 0; i < quantity && jobs[i].reach_time <= current_time; i++) { if (jobs[i].visited == 0) { jobs[i].wait_time = current_time - jobs[i].reach_time; // 计算响应比:响应时间 比 执行时间 jobs[i].excellent = 1 + (double)jobs[i].wait_time / (double)jobs[i].need_time; // 更新最大响应比的作业 if (jobs[i].excellent > max_excellent) { max_excellent = jobs[i].excellent; next = i; } } } // 若在当前时间,所有作业中没有未执行的作业 if (max_excellent == -1) { int early_time = 10000; for (int i = 0; i < quantity; i++) { // 找到未执行的最早到达的作业,更新其响应比,因为已经按照到达时间排序,所以满足要求的第一个作业就是 if (jobs[i].reach_time < early_time && jobs[i].visited == 0) { early_time = jobs[i].reach_time; next = i; jobs[i].excellent = 1 + (double)jobs[i].wait_time / (double)jobs[i].need_time; break; } } } return next; } // 先来先服务算法 void FCFS() { int i; int current_time = 0; int loc; int total_waittime = 0; int total_roundtime = 0; // 获取最近到达的作业 loc = findearlyjob(jobs, quantity); printf("\n\nFCFS算法作业流\n"); printf("---------------------------\n"); printf("\tjobID\treachtime\tstarttime\twaittime\troundtime\n"); current_time = jobs[loc].reach_time; // 每次循环找到最先到达的作业并打印相关信息 for (i = 0; i < quantity; i++) { // 如果当前获取到的最早到达的作业比当前时间还要大,设置作业开始执行的时间为作业到达的时间,重新设置当前时间为这个作业的到达时间 if (jobs[loc].reach_time > current_time) { jobs[loc].start_time = jobs[loc].reach_time; current_time = jobs[loc].reach_time; } // 当前时间,找到的那个最早到达的作业已经到达了,直接设置开始时间为当前时间 else jobs[loc].start_time = current_time; // 设置进程的等待时间,为当前时间-到达时间 jobs[loc].wait_time = current_time - jobs[loc].reach_time; // 输出信息 printf("\t%-8d\t%-8d\t%-8d\t%-8d\t%-8d\n", loc + 1, jobs[loc].reach_time, jobs[loc].start_time, jobs[loc].wait_time, jobs[loc].wait_time + jobs[loc].need_time); // 设置已执行 jobs[loc].visited = 1; // 当前时间更新为 +=作业执行时间 current_time += jobs[loc].need_time; // 更新等待时间的和 与 周转时间的和 total_waittime += jobs[loc].wait_time; total_roundtime += (jobs[loc].wait_time + jobs[loc].need_time); // 获取剩余作业中的最早到达的作业 loc = findearlyjob(jobs, quantity); } printf("总等待时间:%-8d 总周转时间:%-8d\n", total_waittime, total_roundtime); printf("平均等待时间: %4.2f 平均周转时间: %4.2f\n", (float)total_waittime / (quantity), (float)total_roundtime / (quantity)); } // 短作业优先 void SFJschdulejob() { int i; int current_time = 0; int total_waittime = 0; int total_roundtime = 0; // 找到最早到达的作业 int earlyest = findearlyjob(jobs, quantity); // 在最早的作业达到的情况下,找到此时最短的作业,当然是第一个到达的作业 int loc = findminjob(jobs, jobs[earlyest].reach_time); printf("\n\nSFJ算法作业流\n"); printf("----------------------\n"); printf("\tjobID\treachtime\tstarttime\twaittime\troundtime\n"); for (i = 0; i < quantity; i++) { // 若当前最短的作业比当前时间还大 if (jobs[loc].reach_time > current_time) { // 该作业开始时间就是执行时间 jobs[loc].start_time = jobs[loc].reach_time; current_time = jobs[loc].reach_time; } else { jobs[loc].start_time = current_time; jobs[loc].wait_time = current_time - jobs[loc].reach_time; } printf("\t%-8d\t%-8d\t%-8d\t%-8d\t%-8d\n", loc + 1, jobs[loc].reach_time, jobs[loc].start_time, jobs[loc].wait_time, jobs[loc].wait_time + jobs[loc].need_time); total_waittime += jobs[loc].wait_time; total_roundtime += jobs[loc].wait_time + jobs[i].need_time; current_time += jobs[loc].need_time; jobs[loc].visited = 1; loc = findminjob(jobs, current_time); } printf("总等待时间:%-8d 总周转时间:%-8d\n", total_waittime, total_roundtime); printf("平均等待时间: %4.2f 平均周转时间: %4.2f\n", (float)total_waittime / (quantity), (float)total_roundtime / (quantity)); } // 高响应比调度算法 void HRRFschdulejob() { int i, j; job temp[MAXJOB]; // 保存副本 for (int i = 0; i < quantity; i++) temp[i] = jobs[i]; // 对到达时间进行排序 for (i = 0; i < quantity - 1; i++) { for (j = 0; j < quantity - 1 - i; j++) { if (jobs[j].reach_time > jobs[j + 1].reach_time) { job temp = jobs[j]; jobs[j] = jobs[j + 1]; jobs[j + 1] = temp; } } } int current_time = 0; int total_waitime = 0; int total_roundtime = 0; // 第二个参数传最早到达作业的时间为当前时间,更新所有作业的响应比,第一个执行的作业肯定是先到达的。 int loc = updateExcellent(jobs, jobs[0].reach_time); printf("高响应比调度算法\n"); printf("------------------------------------\n"); printf("\tjobID\treachtime\tstarttime\twaittime\troundtime\n"); // 这里处理类似于前两个算法 for (i = 0; i < quantity; i++) { if (jobs[loc].reach_time > current_time) { jobs[loc].start_time = jobs[loc].reach_time; jobs[loc].wait_time = 0; current_time = jobs[loc].start_time + jobs[loc].need_time; } else { jobs[loc].start_time = current_time; jobs[loc].wait_time = current_time - jobs[loc].reach_time; current_time += jobs[loc].need_time; } printf("\t%-8d\t%-8d\t%-8d\t%-8d\t%-8d\n", jobs[loc].number, jobs[loc].reach_time, jobs[loc].start_time, jobs[loc].wait_time,jobs[loc].wait_time + jobs[loc].need_time); total_waitime += jobs[loc].wait_time; total_roundtime += jobs[loc].wait_time + jobs[loc].need_time; jobs[loc].visited = 1; // 更新当前时间下的未执行作业的响应比,并找到最大响应比 loc = updateExcellent(jobs, current_time); } printf("总等待时间:%-8d 总周转时间:%-8d\n", total_waitime, total_roundtime); printf("平均等待时间: %4.2f 平均周转时间: %4.2f\n", (float)total_waitime / (quantity), (float)total_roundtime / (quantity)); // 还原数据 for (int i = 0; i < quantity; i++) jobs[i] = temp[i]; } // 优先权高者优先调度算法 void HPF() { int i, j; // 按照到达时间排序 for (i = 0; i < quantity - 1; i++) { for (j = 0; j < quantity - 1 - i; j++) { if (jobs[j].reach_time >= jobs[j + 1].reach_time) { job temp = jobs[j]; jobs[j] = jobs[j + 1]; jobs[j + 1] = temp; } } } // 到达时间相同的情况下,排序优先级 for (i = 0; i < quantity - 1; i++) { for (j = 0; j < quantity - 1 - i; j++) { if (jobs[j].reach_time == jobs[j + 1].reach_time && jobs[j].privilege <= jobs[j + 1].privilege) { job temp = jobs[j]; jobs[j] = jobs[j + 1]; jobs[j + 1] = temp; } } } int current_time = 0; int total_waitime = 0; int total_roundtime = 0; // 因为已经排好序,所以直接使用0位置作业,是最先到达的作业,并且是在相同时间下优先权最高的 int loc = 0; printf("优先权高者优先调度算法\n"); printf("---------------------------------\n"); printf("\tjobID\treachtime\tstarttime\twaittime\troundtime\n"); for (i = 0; i < quantity; i++) { if (jobs[loc].reach_time > current_time) { jobs[loc].start_time = jobs[loc].reach_time; current_time = jobs[loc].start_time + jobs[loc].need_time; } else { jobs[loc].start_time = current_time; current_time += jobs[loc].need_time; } jobs[loc].wait_time = jobs[loc].start_time - jobs[loc].reach_time; printf("\t%-8d\t%-8d\t%-8d\t%-8d\t%-8d\n", jobs[loc].number, jobs[loc].reach_time, jobs[loc].start_time, jobs[loc].wait_time,jobs[loc].wait_time + jobs[loc].need_time); total_waitime += jobs[loc].wait_time; total_roundtime += jobs[loc].wait_time + jobs[loc].need_time; jobs[loc].visited = 1; // 以上操作与前几个算法相似 // 下面开始寻找已到达的优先权最高的作业 int next = -1; int max_privilege = -1; // 因为已经按照到达时间排序,所以当前时间小于到达时间的作业及以后的作业必定未到达,不用再处理了 for (j = 0; j < quantity && current_time >= jobs[j].reach_time; j++) { // 找到未执行且优先权最大的作业 if (jobs[j].visited == 0 && jobs[j].privilege > max_privilege) { max_privilege = jobs[j].privilege; next = j; } } // 没有找到满足要求的作业的话,直接执行下一个到达的作业 if (next == -1) { next = loc + 1; } loc = next; } printf("总等待时间:%-8d 总周转时间:%-8d\n", total_waitime, total_roundtime); printf("平均等待时间: %4.2f 平均周转时间: %4.2f\n", (float)total_waitime / (quantity), (float)total_roundtime / (quantity)); } int main() { initial_jobs(); readJobdata(); FCFS(); reset_jinfo(); SFJschdulejob(); reset_jinfo(); HRRFschdulejob(); reset_jinfo(); HPF(); reset_jinfo(); system("pause"); return 0; }
四、实验总结
-
由四种算法的测试数据来看,算法思想不同,所需的等待时间和周转时间也不同。
-
表1 算法与等待时间、执行时间、优先级的关系
作业调度算法 | 等待时间 | 执行时间 | 优先权 |
---|---|---|---|
FCFS | √ | ||
SJF | √ | ||
HRRF | √ | √ | |
HPF | √ |
-
由表1得出FCFS算法仅考虑作业的等待时间,等待时间长的优先考虑;SJF算法主要考虑作业的执行时间,执行时间短的优先考虑;HRRF算法同时考虑了作业的等待时间和执行时间,是FCFS和SJF算法的折中;HPF算法仅考虑作业的优先权,优先权高者先执行。
-
我们实验结果中可以发现对测试数据而言,并非HRRF算法的平均等待时间和平均周转时间最短。对于这组作业,SJF算法的平均等待时间和平均周转时间比 HRRF算法和HPF算法的短,说明最适合这个作业的调度算法是SJF。
-
由此可以得出判断算法的好坏要根据具体的作业,如果对于a作业A算法的平均等待时间和周转时间是最短的,那说明A算法是最适合a作业的调度算法。
-
请总结一下本次实验的收获、教训和感受,结合课本内容谈一下你对操作系统中各种作业调度算法优缺点的理解。
-
FCFS算法比较有利于长作业,而不利于短作业,有利于CPU繁忙型作业,而不利于I/O繁忙型作业。并且 用于批处理系统,不适于分时系统。
-
SJF算法易于实现,照顾了短进程,缩短了短进程的等待时间,体现了短进程优先原则,改善了平均周转时间和平均带权周转时间,有利于提高系统的吞吐量。但是对长进程不利,甚至会导致长进程长时间无法得到关注而使得系统整体性能下降,完全未考虑进程的急迫程度,因而不能保证紧迫性进程会被及时处理,进程的运行时间很难精确估计,进程在运行前不一定能真正做到短进程被优先调度。
-
HRRF算法既照顾了短作业又照顾了长作业,同时也照顾了先到达进程。但是调度之前需要计算各个进程的响应比,增加了系统开销,导致对实时进程无法做出及时反映。
-
HPF算法调度灵活,能适应多种调度需求。但是进程优先级的划分和确定每个进程优先级比较困难。
-