进程的id,到达时间,运行时间都是随机生成的
需要注意的是:在时间片轮转法中,假设n时刻就绪队列为 头->:‘C’ ‘N’ <-尾,n时刻就绪队列首部的进程C运行一个时间片且C未运行完,下一时刻进程A到达就绪队列,则就绪队列的排序应该是:N A C。也就是说,当前时刻到达的进程应该排在上一时刻没有运行完重新回到就绪队列排队的进程的前面
没有使用标准模板库queue容器(老师不给),sum一开始设置为9,后面改成了5
一、课程设计题目及内容
设计一个按照时间片轮转法实现处理机调度的程序
时间片轮转法实现处理机调度的程序设计提示如下:
(1)假设系统有n个进程,每个进程用一个进程控制块(PCB)来代表。进程控制块的格式如下表所示,且参数意义也相同。
进程控制块格式 |
---|
进程名 |
链接指针 |
到达时间 |
估计运行时间 |
进程状态 |
(2)按照进程到达的先后顺序排成一个循环队列,设一个队首指针指向第一个到达进程的首址。另外再设一个当前运行进程指针,指向当前正运行的进程。
(3)执行处理机调度时,首先选择队首的第一个进程运行。
(4)由于本题目是模拟实验,所以对被选中的进程并不实际启动运行,而只是执行如下操作:
1)估计运行时间减1;
2)输出当前运行进程的名字。
用这两个操作来模拟进程的一次运行。
(5)进程运行一次后,以后的调度则将当前指针依次下移一个位置,指向下一个进程,即调整当前运行指针指向该进程的链接指针所指进程,以指示应运行进程,同时还应判断该进程的剩余运行时间是否为0,若不为0,则等待下一轮的运行,若该进程的剩余运行时间为0,则将该进程的状态置为完成状态“C”,并退出循环队列。
(6)若就绪队列不为空,则重复上述的步骤(4)和(5)直到所有进程都运行完为止。
(7)在所设计的调度程序中,应包含显示或打印语句,以便显示或打印每次选中进程的名称及运行一次后队列的变化情况。
二、实验源代码
代码如下:
#include<iostream>
#include<time.h>
#include<iomanip>
using namespace std;
typedef struct PCB
{
int name;//进程名(id)
struct PCB* next;//指针
int ta;//到达时间
int tw;//工作时间
int state;//完成状态(完成为1,未完成为0)
}PCB, * PCBptr;
typedef struct
{
PCBptr front;
PCBptr rear;
}LinkQueue;
void print(LinkQueue& Q)
{
auto p = Q.front->next;
cout << "name" << setw(6) << "ta" << setw(6) << "tw" << endl;
while (p != NULL)
{
cout << p->name << setw(8) << p->ta << setw(8) << p->tw << endl;
p = p->next;
}
}
void InitQueue(LinkQueue& Q)
{
Q.front = Q.rear = new PCB;
Q.front->next = NULL;
}
int DeQueue(LinkQueue& Q)
{
if (Q.front == Q.rear)return 0;
auto p = Q.front->next;
Q.front->next = p->next;
if (Q.rear == p)Q.rear = Q.front;
delete p;
return 1;
}
void EnQueue(LinkQueue& Q, PCB e)
{
PCBptr p;
p = new PCB;
p->name = e.name; p->next = e.next; p->state = e.state; p->ta = e.ta; p->tw = e.tw;
p->next = NULL; Q.rear->next = p;
Q.rear = p;
}
void EnQueue2(LinkQueue& Q)//对头元素插入队尾
{
PCBptr p;
p = new PCB;
p->name = Q.front->next->name; p->next = Q.front->next->next; p->state = Q.front->next->state;
p->ta = Q.front->next->ta; p->tw = Q.front->next->tw;
p->next = NULL; Q.rear->next = p;
Q.rear = p;
}
int NOW = 0;
void solve(LinkQueue& Q)
{
int sum = 5;//一共9个进程
bool A[9]{ false };//用于随机生成
PCB P[9];
//初始化
int now = 0;
for (int i = 0; i < sum; i++)
while (1)
{
P[i].name = rand() % sum;
if (A[P[i].name] == false)
{
A[P[i].name] = true;
if (i == 0)P[i].ta = 0;
else
{
int temp = rand() % 5 + 0;
P[i].ta = now + temp;
now = P[i].ta;
}
P[i].tw = rand() % 5 + 1;
P[i].state = 0;//完成状态初始化
break;
}
}
cout << "进程名" << " " << "到达时间" << " " << "运行时间" << endl;
for (int i = 0; i < sum; i++)
cout << P[i].name << setw(10) << P[i].ta << setw(10)<<P[i].tw<<endl;
int pi = 0, finish = 0,flag=0;
while (finish < sum)
{
while (pi<sum)
{
if (P[pi].ta <= NOW)
EnQueue(Q, P[pi++]);
else break;
}
if (flag == 1)//上一秒有结点未处理
{
EnQueue2(Q);
DeQueue(Q);
flag = 0;
}
cout << "-------------时间片为:" << NOW << "--------------\n当前的就绪队列为:\n";
print(Q);
if (Q.front->next!=NULL)
{
auto p = Q.front->next;
cout << ">>>>当前运行的进程为:"<<p->name << " //时间片:" <<NOW<< endl;
p->tw -= 1;
if (p->tw == 0)
{
cout << "进程:" << p->name << " 已完成" << endl;
finish++; DeQueue(Q);
}
else//否则把未完成的结点放到队尾,头指针后移
{
if (P[pi].ta == NOW + 1)//pi指到的进程会在下一时刻到达
flag = 1;
else
{
EnQueue2(Q);
DeQueue(Q);
}
}
cout << "进程运行完毕" << endl;
}
else { cout << "当前无进程运行\n"; }
NOW++;
}
}
int main()
{
srand((int)time(0));
LinkQueue Q;
InitQueue(Q);
solve(Q);
}
三、程序运行时的效果
四、程序中使用的数据及主要符号说明
typedef struct PCB
{
int name;//进程名(id)
struct PCB* next;//指针
int ta;//到达时间
int tw;//工作时间
int state;//完成状态(完成为1,未完成为0)
}PCB, * PCBptr;
typedef struct
{
PCBptr front;
PCBptr rear;
}LinkQueue;
int NOW = 0;//记录(调度过程中的)当前的时间
int pi = 0, finish = 0,flag=0;//pi为数组的下标,finish为完成进程的个数,flag作为判断标志
五、功能模块的设计分析及算法描述
1.所需要的数据结构
①一个PCB结构的结构数组(用于存放随机初始化的进程数据)
构造过程:
int sum = 5;//一共9个进程
bool A[9]{ false };//用于随机生成
PCB P[9];
//初始化
int now = 0;
for (int i = 0; i < sum; i++)
while (1)
{
P[i].name = rand() % sum;
if (A[P[i].name] == false)
{
A[P[i].name] = true;
if (i == 0)P[i].ta = 0;
else
{
int temp = rand() % 5 + 0;
P[i].ta = now + temp;
now = P[i].ta;
}
P[i].tw = rand() % 5 + 1;
P[i].state = 0;//完成状态初始化
break;
}
}
②一个就绪队列:
队列的初始化:
void InitQueue(LinkQueue& Q)
{
Q.front = Q.rear = new PCB;
Q.front->next = NULL;
}
队列结构如图:
该数组是先随机生成一个没有生成过的进程id,然后再随机生成它的信息,到达时间的生成为当前时刻+0~5,所以这个数组其实是按到达时间排序的,前面的先到后面的后到。
2.具体的实现过程
①首先有个指向数组PCB P[9]的指针pi,pi指向还没可以进入队列中的进程(到时间才能进入),这个只负责(在第一次时)把进程放入就绪队列,后续的循环操作就是队列负责了。
然后就是要设定一个全局变量int NOW来记录当前的时间(片),初始化为0
①首先判断是否有进程的到达时间刚好等于当前时间(要进入就绪队列了),有就将pi指向的进程放入就绪队列,pi往后移,没有就进行下一步
②就绪队列中头指针指向的结点的运行时间-1,然后判断运行时间是否为0,若为0,调用(队头)出队函数DeQueue,若没有,进行第③步
③此时判断下一个时间片是否有进程要进来,因为pi指的是还没放进去的进程,所以可以根据P[pi].tw?=NOW+1来判断。如果没有,调用Enqueue2函数(将头指针指向的结点放在就绪队列之后,然后更改头指针),如果有,置判断数flag=1,进行下步骤④
④if(flag==1){…}在while循环中的步骤①的后面进行判断。如果flag=1,说明上一步的头结点还没出队(因为要保留信息所以没出),且当前时刻到达的进程已经入队了,所以这个时候可以把头结点移到队伍的后面,然后让这个队头出队。
如果队为空就等于没有进程运行
每一次大循环时间片就+1,所以是以每一时刻的变化来设计的。然后有个int finish来记录完成的进程数,当finish=sum时大循环结束
3.函数使用
队头出队:
int DeQueue(LinkQueue& Q)
{
if (Q.front == Q.rear)return 0;
auto p = Q.front->next;
Q.front->next = p->next;
if (Q.rear == p)Q.rear = Q.front;
delete p;
return 1;
}
两个插入函数都是操作同一个队列
插入函数1(首部加入达到时间=当前时间的进程):
void EnQueue(LinkQueue& Q, PCB e)
{
PCBptr p;
p = new PCB;
p->name = e.name; p->next = e.next; p->state = e.state; p->ta = e.ta; p->tw = e.tw;
p->next = NULL; Q.rear->next = p;
Q.rear = p;
}
插入函数2(将还没有运行完但当前时间片用完了的进程重新插入就绪队列的后面):
void EnQueue2(LinkQueue& Q)//对头元素插入队尾
{
PCBptr p;
p = new PCB;
p->name = Q.front->next->name; p->next = Q.front->next->next; p->state = Q.front->next->state;
p->ta = Q.front->next->ta; p->tw = Q.front->next->tw;
p->next = NULL; Q.rear->next = p;
Q.rear = p;
}