C语言 操作系统实验 四种调度(时间片轮转法 RR)

6 篇文章 0 订阅
1 篇文章 0 订阅

 注:

本文是四个调度算法的第一篇算法。

本文是根据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博客

        注:如果想了解HRN算法或是算法中链表的模板函数此处为链接:

C语言 操作系统实验 四种调度(最高响应比优先算法 HRN)_key_149的博客-CSDN博客

        前三个调度算法FCSF、FJS、HRN都是通过一定顺序对进程进行排序输出,这也就导致了这三个除了思路意外,代码几乎都能通用,而时间片轮转法不论从思路还是代码都不同于前三个算法,这也导致了时间片RR的程序段需要从头开始整理思路。

        时间片RR算法首先划分时间片的大小,当有进程运行时,其进程的执行时间不超过时间片的大小,当时间片结束后,进程暂停运行,而下一个进程将开始运行,运行时间为时间片大小。直到最后一个进程用完时间片后,再次从第一个进程开始循环运行,直至当前进程运行完毕,进程退出队列。

        这种雨露均沾的做法不会有排序的机会出现,但时间片RR的算法是循环进行,也意味着其可用for循环来做,初始变量假设为0,每轮加上固定的时间片数,直至超过当前单元节点的运行时间,单元节点退出当前链表即可。

核心思路整理完毕,直接上代码:

void timeslicet()//时间片函数
{
	p->tc--;
	if (p->flag ==1)
	{
		p->ts = T;
		p->flag--;
	}
	if (p->tc == 0)
	{
		if (numcopy >= 2)//因循环链表的需求,最后两个链表特殊对待
		{
			first->link = p->link;//将p的下一个节点地址赋值给first的下一个节点
			p->link = NULL;
		}
		if (p->tn % time != 0)
		{
			T = T + p->tn % time;//在时间片内结束增加运行时间
			p->tf = T;//差值
		}
		else
		{
			T = T + time;//刚好在时间片用完时结束
			p->tf = T;
		}
	}
	else
	{
		T = T + time;//进程未运行完时,时间按时间片照常增加
	}
}

        此处tc为当前单元节点所需要的轮数。

        不同进程到达时间不一样,如不记录到达时间,有可能会发生晚到的进程却和提前到的进程在同一轮进行作业,所以需要记录当前的提交时间进行额外判断,此外在计算周转时间时,采用的是完成时间 - 提交时间的算法,所以记录提交时间是非常重要的。在链表连接时没有经过排序,所以单元节点的提交时间参差不齐,等待时间不便计算,故不采用。

        当前圈所需轮数转为空时,即tc == 0;时进入单元节点的输出和摘除阶段,因为循环遍历的原因,链表设置为循环链表,即头结点和尾结点是单向连接的,所以如果输出到只剩最后一个节点了,那其对应的多个指针在释放时会出现野指针的情况。为了避免这种情况出现,设立了if (numcopy >= 2)判断。

        在tc == 0:后first会作为当前节点p的前一个节点,连接p的下一个节点,即first->link = p->link;p->link = NULL:将p的指针域置空作为标记,以便释放。

        在摘出节点p后,应计算其完成时间。完成时间有两种,一种是在时间片内完成的,另一种则是在时间片执行完成时结束的,故需要执行时间 % 时间片计算有没有在时间片内完成的情况发生,T是到前一节点运行结束后的时间,如果当前节点在时间片内结束,则加上执行时间 % 时间片的余值,即T = T + p->tn % time;反之则加上时间片时间即可,T = T + time;

除了时间片函数RR外还有一部分核心思路在主函数的循环中。

	while (ready !=NULL)
	{
		p = ready;
		ready = p->link;//此处ready为下一个节点,first为上一个节点,p为当前节点

		if (T >= p->tr)
		{
			timeslicet(); //此函数将当前节点p从链表中摘除
		}
		else
		{
			T++;//时间每次循环+1
		}


		if (p->tc == 0)
		{
			check();//计算此时刻的周转时间
			disp(p);//输出此节点的值
			numcopy--;
			eti += p->ti;
			ewi += p->wi;
			p->link = NULL;
			running();
			//摘除p后first不动
		}
		else
		{
			first = p;//将first转移至当前节点处(当前没有摘除p)
		}
	}

        如上所示,当ready所指节点不为空时,即当前链表存在时,进行循环遍历,总时间T如果大于等于当前节点的提交时间,则执行时间片函数,否则自增一表示自增一秒的时间,这个判断防止当前时间没到提交时间却能执行的逻辑漏洞。

完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#define getpch(type) (type*)malloc(sizeof(type))
#define NULL 0

int num = 0;
int numcopy = 0;
int time = 0;
int T = 0;

struct jcb {
	char name[100];
	int tn;//运行
	int tf;//完成
	int ts;//开始
	int tr;//提交
	int tc;//自己所需转的圈数
	int flag;
	float ti;//周转
	float wi;//带权周转
	struct jcb* link;
}*ready = NULL, * h, * p;
typedef struct jcb JCB;
JCB* first;

void linkpcb()//尾插法将队列连接起来
{
	if (ready == NULL)
	{
		ready = p;
		first = p;
	}
	else
	{
		first->link = p;
		first = p;
	}
}

void timeslicet()//时间片函数
{
	p->tc--;
	if (p->flag ==1)
	{
		p->ts = T;
		p->flag--;
	}
	if (p->tc == 0)
	{
		if (numcopy >= 2)//因循环链表的需求,最后两个链表特殊对待
		{
			first->link = p->link;//将p的下一个节点地址赋值给first的下一个节点
			p->link = NULL;
		}
		if (p->tn % time != 0)
		{
			T = T + p->tn % time;//在时间片内结束增加运行时间
			p->tf = T;//差值
		}
		else
		{
			T = T + time;//刚好在时间片用完时结束
			p->tf = T;
		}
	}
	else
	{
		T = T + time;//进程未运行完时,时间按时间片照常增加
	}
}

void input()
{
	int i;
	printf("\n 请输入运行的进程总数目:");
	scanf("%d", &num);
	numcopy = num;//将num的值赋给copy,在链表的最后两个单元节点时有用	
	printf("\n 请输入时间片长度:");
	scanf("%d", &time);
	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->tf = 0;
		p->ts = 0;
		if (p->tn % time)//判断自己转圈所需的个数,最后一圈转不完的按一圈算
		{
			p->tc = p->tn / time + 1;
		}
		else
		{
			p->tc = p->tn / time;
		}
		p->wi = 0.0f;
		p->ti = 0.0f;
		p->flag = 1;
		p->link = NULL;
		linkpcb();
	}
	first->link = ready;//首尾连接做循环函数
}

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("%f  ", pr->ti);
	printf("%f\t", pr->wi);
	printf("\n");
}

void check()
{
	p->ti = p->tf - p->tr;//周转
	p->wi = p->ti / p->tn;//带权周转
}

void destory()
{
	free(p);//同一单元节点只需要释放一次
	if (numcopy == 0)
	{
		first = NULL;
		ready = NULL;//清空指针,避免野指针出现
	}
}

void running()
{
	if (p->link == NULL)
	{
		destory();
	}
}

int main()
{
	float eti = 0.0;//平均周转时间
	float ewi = 0.0;//平均带权周转时间
	input();
	printf("\n 名称 \t 提交 \t 运行 \t 开始 \t 完成 \t 周转 \t 带权周转 \n");
	while (ready !=NULL)
	{
		p = ready;
		ready = p->link;//此处ready为下一个节点,first为上一个节点,p为当前节点

		if (T >= p->tr)
		{
			timeslicet(); //此函数将当前节点p从链表中摘除
		}
		else
		{
			T++;//时间每次循环+1
		}


		if (p->tc == 0)
		{
			check();//计算此时刻的周转时间
			disp(p);//输出此节点的值
			numcopy--;
			eti += p->ti;
			ewi += p->wi;
			p->link = NULL;
			running();
			//摘除p后first不动
		}
		else
		{
			first = p;//将first转移至当前节点处(当前没有摘除p)
		}
	}

	eti /= num;
	ewi /= num;
	printf("\n该组作业平均周转时间为:%0.2f", eti);
	printf("\n该组作业平均带权周转时间为:%0.2f", ewi);
	return 0;

}

        在销毁最后一个单元节点时,由于多个指针出现,需要同事进行释放,否则会出现野指针警告导致程序无法运行。        

        

  • 1
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值