注:
本文是四个调度算法的第一篇算法。
本文是根据CSDN上某一FCFS调度算法魔改来的,所以FCFS的算法不会发到网站。
我是个菜鸡,发文是为了纪念自己完成了代码,以及累计自己的经验。
如有知识错误或者算法有逻辑漏洞请各位大佬高抬贵手。
实验环境:win10、VS2019(C++)
PS:虽然是cpp文件,实际上是披着cpp的c
运行前,首先要在预处理器上插入_CRT_SECURE_NO_WARNINGS,避免运行错误,在插入前要用;隔开。
具体位置就在解决方案下面一栏(名字是自己起的),右键属性即可找到。
这个核心代码相对于之前的最短优先算法SJF来说在判断单元节点到达时间上是一致的,只不过多了等待时间变量wait以及响应比计算函数hrn,内容相对稀少,所以本篇会详细介绍响应比hrn函数以及在SJF中没提到的其他模板函数。
注:如果想了解SJF算法或是判断单元节点到达时间函数此处为链接:C语言 操作系统实验 四种调度(最短优先算法SJF)_key_149的博客-CSDN博客
好了直接进正题
最高响应比算法除了单元节点到达时间的思路外,只有一个核心:响应比的计算
先上代码:
void hrn(JCB* p)//响应比计算函数,第一轮计算时wait == p->tr
{
hr2 = hr;
hr = (wait - p->tr) / p->tn;
if (wait % p->tn)
{
hr = hr + 1;
}
}
响应比 = 响应时间 / 预计执行时间
响应时间 = 等待时间 + 预计执行时间
综上:响应比 = 1 + 作业等待时间 / 预计执行时间
而通过观察能发现,1是所有响应比都需要加的一个不变的常量,而在实际的代码编写当中可以省略,而关键的就在于会变的两个变量:等待时间、预计执行时间;
接下来就要对这两个变量进行分析:
预计执行时间:每个单元节点都有自己的预计执行时间当指针遍历某一单元节点时,就能获取当前节点的预计执行时间,而每个单元节点内的预计执行时间也是不会改变的。这样只需在遍历链表时直接计算响应比并进行记录便可。(在代码段中hr2代表的就是上一个进程的响应比,hr则是当前的响应比。)
等待时间:在第一个单元节点输出时,此时等待时间为0,是没有响应比的。所以在对链表头进行比较时,仅需对提交时间进行比较即可。在链表输出一次后,等待时间wait便是当前节点的执行时间 + 提交时间。但要注意的是这个等待时间并不包含与下一个单元节点的提交时间相交的部分,很有可能某个进程在上一个进程执行时的任意时刻到达,所以要减去它的提交时间,即 (wait - p->tr)出现的原因。
除了正常的公式计算,hrn函数中还包含了一个if语句,在C/C++中float类型的函数是不能被比较的,所以响应比的类型是int,而除出来的商也会是整数类型,在这种前提条件下极有可能出现响应比是小数的情况,进而使其和真正能整除的响应比混在一起,此时响应比+1以避免这种情况产生。
通过对这两个变量的总结可以得知:
1.在对链表头的的排序中,仅需要记录头结点的等待时间 + 执行时间;即wait = p->tr + p->tn;对于不断变换的头结点,wait也会相应的不断刷新。
2.至于头结点后的单元节点的排序是按提交时间排序即可。(与SJF思路类似,以便分层)
3.对于响应比会在输出每个单元节点后进行变化,所以响应比hrn函数应在遍历单元节点时逐一运行。
4.wait函数是在单元节点释放前加上当前的执行时间(头结点例外)
这样hrn的核心思路就捋清楚了,接下来上完整代码:
#include "stdio.h"
#include <stdlib.h>
#include <conio.h>
#define getpch(type) (type*)malloc(sizeof(type))
#define NULL 0
//定义作业jcb结构体
struct jcb
{
char name[100];
int tr;//作业提交时间
int tn;//作业运行时间
int ts;//作业开始时间
int tf;//作业完成时间
float ti;//作业周转时间
float wi;//作业带权周转时间
int flag;//顺序
struct jcb* link;
}*ready = NULL, * h, * p;
typedef struct jcb JCB;
JCB* first, * second, * last;
int time = 0;
int num = 0; //作业数量
int sum = 0;//目前总时间
float eti = 0.0;
float ewi = 0.0;
int wait;//等待时间
int hr = 0;
int hr2 = 0;
void hrn(JCB* p)//响应比计算函数,第一轮计算时wait == p->tr
{
hr2 = hr;
hr = (wait - p->tr) / p->tn;
if (wait % p->tn)
{
hr = hr + 1;
}
}
void linkpcb()//先将链表尾插法连接起来 //如果行不通就执行方案2 即将这些先按到达顺序排序后,通过dolevel进行分层,在用sort进行最短排序
{
int insert = 0;
if ((ready == NULL) || ((p->tr) <= (ready->tr) && p->tn < ready->tn)) /*提交 兼 提交 时间最小者,插入队首*/
{
p->link = ready;
ready = p;
wait = p->tr + p->tn;//计算首位的等待时间
}
else /* 进程比较提交时间,插入适当的位置中*/
{
first = ready;
second = first->link;
while (second != NULL)
{
if ((p->tr) < (second->tr)) /*若插入作业比当前作业提交时间小,*/
{
p->link = second;//p插入到second前面
first->link = p;
second = NULL;//second只能在连接好的链表上运行
insert = 1;
}
else /* 插入进程提交时间最高,则插入到队尾*/
{
first = first->link;
second = second->link;
}
}
if (insert == 0) first->link = p;//循环到最后若依旧没有比second大的,则first此时是p的前一个
}
}
void dolevel()
{
if (second->tr <= sum)
{
sum = sum + second->tn;
}
else
{
sum = second->tr + second->tn;
second->flag = 0;
}
second = second->link;
}
void destroy() /*建立作业撤消函数(进程运行结束,卸载作业)*/
{
free(p);
}
void running() /* 建立作业就绪函数(作业运行时间到,置就绪状态*/
{
if (p->link == NULL)
destroy(); /* 调用destroy函数*/
}
void disp(JCB* pr) /*建立作业显示函数,用于显示当前作业*/
{
printf(" %s\t", pr->name);
printf(" %d\t", pr->tr);
printf(" %d\t", pr->tn);
printf(" %d\t", pr->ts);
printf(" %d\t", pr->tf);
printf(" %-0.2f\t", pr->ti);
printf(" %-0.2f\t", pr->wi);
printf("\n");
}
void check() /* 建立作业查看函数 */
{
//累加变量time 记录当前用时 !!!
if (time >= p->tr)
{
p->ts = time;
p->tf = p->ts + p->tn;
time = p->tf;
}
else
{
p->ts = p->tr;
p->tf = p->ts + p->tn;
time = p->tf;
}
p->ti = p->tf - p->tr;
p->wi = p->ti / p->tn;
//!!!
}
void sort() /* 建立对作业进行提交时间排列函数,分出第一层*/
{
first = ready;//first归位
p = ready;
last = ready;
if (last->flag == 0)//置1
{
last->flag = 1;
}
while ((last->flag == 1 && last->link != NULL))//一层中找到最小的 最后一个会被强制退出
{
hrn(last);
if (hr > hr2)
{
p = last;
}
last = last->link;
if (last->link == NULL)//在最后一个被踢出前先进行比较
{
hrn(last);
if (hr > hr2)
{
p = last;
}
}
}
wait = wait + p->tn;
hr = 0;
hr2 = 0;
last = p->link;
if (ready == p)//防止最小可能是头单元节点造成ready成空指针
{
ready = ready->link;
}
first->link = last;
p->link = NULL;
check();
disp(p);
eti += p->ti;
ewi += p->wi;
running();
}
void input() /* 建立作业控制块函数*/
{
int i;
printf("\n 请输入即将运行的进程总数目");
scanf("%d", &num);
for (i = 0; i < num; i++)
{
p = getpch(JCB);
printf("\n 输入作业名:");
scanf("%s", p->name);
printf("\n 输入作业提交时间:");
scanf("%d", &p->tr);
printf("\n 输入作业运行时间:");
scanf("%d", &p->tn);
printf("\n");
p->ts = 0;
p->tf = 0;
p->ti = 0.0f;
p->wi = 0.0f;
p->link = NULL;
p->flag = 1;
wait = p->tr;
linkpcb();
}
p->link = NULL;
printf("\n 名称 \t 提交 \t 运行 \t 开始 \t 完成 \t 周转\t 带权周转 \n");
second = ready->link;//第二个
sum = ready->tr + ready->tn;
p = ready;
check();//对最先到达那个进行特殊输出
disp(p);
eti += p->ti;
ewi += p->wi;
ready = ready->link;
p->link = NULL;
running();
hrn(p);//记录当前响应比值
do
{
dolevel();
} while (second->link != NULL);//second此时是末尾节点
for (i = 1; i < num; i++)
{
sort(); /* 调用sort函数,对提交顺序和执行时间进行比较*/
}
}
int main() /*主函数*/
{
input();//此时已经按提交顺序排好了
eti /= num;
ewi /= num;
printf("\n该组作业平均周转时间为:%0.2f", eti);
printf("\n该组作业平均带权周转时间为:%0.2f", ewi);
return 0;
}
这里需要注意一点:dolevel函数在运行时是计算当前时间sum的,但 sum != wait;sum的作用是为链表分层,而wait是随响应比变化的单元节点等待时间。这也就意味着响应比小的节点不一定是提交时间早的节点。
到此HRN的思路就截止了,接下来是链表模板函数的分析:
1.结构体jcb,在结构体中定义了每一结构体内的项目,如提交时间、等待时间等。在结构体设置完后要设置结构体变量,此处是指针变量* ready、* p等。
2.typedef struct jcb JCB;此行代码是用typedef定义一个类型,类型是结构体struct,结构体的名字jcb,而这个用typedef定义的名为jcb的结构体怎么表示呢?在此处命名为JCB。同样的JCB定义的指针first、second以及last。而理论上这三个指针变量和ready、p是一样的。
3.定义这个类型,还需要做一些准备工作:#define getpch(type) (type*)malloc(sizeof(type))
此行代码宏定义了申请即getpch,申请的类型是type类型,即2中的typedef,malloc是向机器申请空间,空间的大小为sizeof测量的type类型的大小。
4.在input函数中设置循环,对每个申请的空间进行初始化以及输入工作。
5.check()函数负责计算周转时间和带权周转时间,周转时间有两个算法:
执行时间 + 等待时间;
完成时间 - 提交时间;
带权周转时间公式:周转时间 / 执行时间;
6.在disp输出后,p->NULL;作为一个标记,而running()则是通过p->NULL作为判断可以运行销毁函数destory;而running函数本身是作为进程工作而设立的函数,此处仅用于调用销毁函数。