设计一个按照时间片轮转法实现处理机调度的程序。
(1) 假设系统有n个进程,每个进程用一个进程控制块(PCB)来代表。进程控制块的格式如下表所示,且参数意义也相同。
进程名 |
---|
链接指针 |
到达时间 |
估计运行时间 |
进程状态 |
(2) 按照进程到达的先后顺序排成一个循环队列,设一个队首指针指向第一个到达进程的首址。另外再设一个当前运行进程指针,指向当前正运行的进程。
(3) 执行处理机调度时,首先选择队首的第一个进程运行。
(4) 由于本题目是模拟实验,所以对被选中的进程并不实际启动运行,而只是执行如下操作:
1)估计运行时间减1;
2)输出当前运行进程的名字。
用这两个操作来模拟进程的一次运行。
(5) 进程运行一次后,以后的调度则将当前指针依次下移一个位置,指向下一个进程,即调整当前运行指针指向该进程的链接指针所指进程,以指示应运行进程,同时还应判断该进程的剩余运行时间是否为0,若不为0,则等待下一轮的运行,若该进程的剩余运行时间为0,则将该进程的状态置为完成状态“C”,并退出循环队列。
(6) 若就绪队列不为空,则重复上述的步骤(4)和(5)直到所有进程都运行完为止。
(7) 在所设计的调度程序中,应包含显示或打印语句,以便显示或打印每次选中进程的名称及运行一次后队列的变化情况。
本实验使用了一个结构表示进程控制块(PCB),其所有的属性以及含义如下:
typedef struct PCB //PCB结构
{
int name; //进程名字
int arrivalTime; //进程到达时间
int needTime; //进程需要运行的时间
int serverTime; //进程已经运行的时间
int leftSysTime; //进程结束时系统时间
int flag; //进程状态,(1-已完成,0-未完成)
struct PCB* next; //链接指针
}PCB;
为了方便整个流程的操作,本实验使用了链式队列对进程的操作。下面的initQueue队列是保存初始化得到的进程,每个进程按照“进程到达时间”升序排列initQueue队列中。而readyQueue队列是进程的就绪队列,系统在readyQueue队列的队头取一个进程运行,根据系统时间从initQueue队列中取出到达时间合适的进程插到readyQueue队列的队尾,然后再将刚刚运行完的进程插入到队尾。endQueue队列是用来存放已经运行结束的进程,此队列用于计算平均周转时间和平均带权周转时间。
typedef struct PCBQueue //队列
{
struct PCB* front; //指向队头
struct PCB* rear; //指向队尾
}PCBQueue;
PCBQueue* initQueue = (PCBQueue*)malloc(sizeof(PCBQueue)); //初始化队列
PCBQueue* readyQueue = (PCBQueue*)malloc(sizeof(PCBQueue)); //就绪队列
PCBQueue* endQueue = (PCBQueue*)malloc(sizeof(PCBQueue)); //已经结束的进程队列
//RoudRobin.c
#include
#include
#include
typedef struct PCB //PCB结构
{
int name; //进程名字
int arrivalTime; //进程到达时间
int needTime; //进程需要运行的时间
int serverTime; //进程已经运行的时间
int leftSysTime; //进程结束时系统时间
int flag; //进程状态,(1-已完成,0-未完成)
struct PCB* next; //链接指针
}PCB;
typedef struct PCBQueue //队列
{
struct PCB* front; //指向队头
struct PCB* rear; //指向队尾
}PCBQueue;
int systime; //系统运行时间
int processes; //进程个数
//初始化数据
PCBQueue* Init()
{
srand(time(NULL));
processes = rand() % 15 + 5; //随机初始化processes个进程个数
systime = 0;
PCBQueue* initQueue = (PCBQueue*)malloc(sizeof(PCBQueue)); //初始化队列
if (!initQueue)
exit(-1);
initQueue->front = initQueue->rear = NULL;
PCB* p = (PCB*)malloc(sizeof(PCB)); //初始化队列添加一个头节点
if (!p)
exit(-1);
p->arrivalTime = -1;
p->next = NULL;
initQueue->front = initQueue->rear = p;
for (int i = 1; i <= processes; i++)
{
p = (PCB*)malloc(sizeof(PCB)); //初始化队列添加进程
p->name = i; //进程的名字
if (i <= 2) //进程到达的时间
p->arrivalTime = rand() % i;
else
p->arrivalTime = rand() % (i + 7);
p->needTime = rand() % 5 + 2; //进程需要运行的时间
p->flag = 0;
p->serverTime = 0;
PCB* temp = initQueue->front;
while (temp) //根据新生成的PCB在初始化队列initQueue中找到合适的位置
{
if (p->arrivalTime >= temp->arrivalTime)
{
initQueue->rear = temp;
temp = temp->next;
}
else
break;
}
//将新生成的PCB插入到初始化队列initQueue中合适的位置
p->next = temp;
initQueue->rear->next = p;
initQueue->rear = initQueue->front;
}
//打印
printf("申请CPU的进程名、到达时间、运行时间如下:\n");
printf("进程 到达时间 运行时间\n");
for (int i = 0; i < processes; i++)
{
initQueue->rear = initQueue->rear->next;
if (initQueue->rear)
printf("p%-3d %6d %10d\n", initQueue->rear->name, initQueue->rear->arrivalTime, initQueue->rear->needTime);
}
printf("\n");
//删除多余的头节点
p = initQueue->front;
initQueue->rear = initQueue->front = initQueue->front->next;
free(p);
return initQueue;
}
//在初始化队列中找出某个系统时间刚好到达的进程放到就绪队列中
void Fine(PCBQueue* initQueue, PCBQueue* readyQueue)
{
while (initQueue->front != NULL) //遍历就绪队列
{
if (initQueue->front->arrivalTime == systime) //将就绪队列中‘当前系统时间’的进程添加到就绪队列
{
if (readyQueue->front == NULL)
{
readyQueue->front = readyQueue->rear = initQueue->front;
initQueue->front = initQueue->front->next;
readyQueue->rear->next = NULL;
}
else
{
readyQueue->rear->next = initQueue->front;
readyQueue->rear = initQueue->front;
initQueue->front = initQueue->front->next;
readyQueue->rear->next = NULL;
}
}
else
break;
}
}
void CalTime(PCBQueue* e)
{
PCB* p;
int turnoverTime = 0, temp1; //平均周转时间
float turnoverTimeAvg = 0, temp2; //平均带权周转时间
printf("所有进程运行结束!\n\n");
printf("------------------时间片轮转法性能指标------------------\n");
printf("进程名 到达时间 运行时间 结束时间 周转时间 带权周转时间\n");
while (e->front)
{
temp1 = e->front->leftSysTime - e->front->arrivalTime;
temp2 = (float)temp1 / e->front->needTime;
turnoverTime += temp1; //平均周转时间
turnoverTimeAvg += temp2; //平均带权周转时间
printf(" p%-3d %5d %7d %9d %9d %15f\n", e->front->name, e->front->arrivalTime, e->front->needTime, e->front->leftSysTime, temp1, temp2);
p = e->front;
e->front = e->front->next;
free(p);
}
printf("平均周转时间:%f,平均带权周转时间:%f\n", (float)turnoverTime / processes, turnoverTimeAvg / processes);
}
//模拟执行
void SimRun(PCBQueue* initQueue)
{
PCBQueue* endQueue = (PCBQueue*)malloc(sizeof(PCBQueue)); //已经结束的进程队列
if (!endQueue)
exit(-1);
endQueue->rear = endQueue->front = NULL;
PCBQueue* readyQueue = (PCBQueue*)malloc(sizeof(PCBQueue)); //就绪队列
if (!readyQueue)
exit(-1);
readyQueue->rear = readyQueue->front = NULL;
Fine(initQueue, readyQueue); //从初始化队列找出现在系统时间到达的进程放到就绪队列
printf("以上所有进程运行过程和每时刻进程就绪队列情况如下:\n");
while (initQueue->front || readyQueue->front)
{
if (readyQueue->front) //就绪队列不为空
{
PCB* p = readyQueue->front;
printf("系统时间:%d\t正在运行的进程:p%-3d 就绪队列:", systime, p->name); //打印正在执行的进程、就绪队列
p = readyQueue->front->next;
while (p)
{
printf("p%-2d ", p->name);
p = p->next;
}
printf("\n");
p = readyQueue->front;
p->serverTime += 1;
systime += 1;
Fine(initQueue, readyQueue); //进程运行完后理论上要进行“保护现场”,所以应该先让这个时刻到达的进程插入就绪队列最后
if (p->needTime == p->serverTime) //该进程已经运行完了
{
p->leftSysTime = systime;
p->flag = 1; //进程状态改变
readyQueue->front = readyQueue->front->next;
p->next = NULL;
if (endQueue->front == NULL)
{
endQueue->front = endQueue->rear = p;
}
else
{
endQueue->rear->next = p;
endQueue->rear = p;
}
}
else //进程还没执行完,插入到就绪队列最后
{
if (readyQueue->front != readyQueue->rear)
{
readyQueue->front = readyQueue->front->next;
readyQueue->rear->next = p;
readyQueue->rear = p;
readyQueue->rear->next = NULL;
}
else
readyQueue->front = readyQueue->rear;
}
}
else if (initQueue->front) //就绪队列为空,初始化队列不为空
{
printf("系统时间:%d\t正在运行的进程:无 等待队列:无\n", systime);
systime += 1;
Fine(initQueue, readyQueue);
}
else //就绪队列、初始化队列均为空
break;
}
CalTime(endQueue); //计算周转时间
free(readyQueue); //释放内存
free(initQueue);
free(endQueue);
}
void main()
{
PCBQueue* initQueue = NULL;
int number = 1;
while (number != 3)
{
printf("----------时间片轮转法----------\n");
printf("1.运行时间片轮转法算法\n");
printf("2.清空屏幕\n");
printf("3.退出程序\n");
scanf_s("%d", &number);
switch (number)
{
case 1:
initQueue = Init(); //返回按照进程到达的先后顺序排成一个队列
SimRun(initQueue); //模拟运行
printf("\n");
break;
case 2:
system("clear");
break;
case 3:
break;
default:
printf("输入错误\n");
}
}
}
输出结果说明:系统时间:是用来记录模拟记录进程到达时间和进程运行时间,时间片轮转开始的时候设时间为0,每个进程运行一个时间片时系统时间加1;正在运行的进程:是指从就绪队列中取出合适的进程运行;就绪队列:是指这个时间(系统时间)去掉刚取出的运行的进程,剩余在等待运行的进程的队列。
根据最后一个实验结果,进程P1、是在系统时间为0时刻就到达了,这个时候先将进程P1从初始化队列initQueue取出并且插到就绪队列readyQueue中,接着从就绪队列readyQueue队头取出进程P1,P1运行一个时间片,系统时间+1,P1结束后查看P1的运行时间是否已经满足,如果满足则改变进程状态并且移出就绪队列;否则查看初始化队列有没有进程到达时间与系统时间相等,有则取出插入到就绪队列后面,没有就直接将刚刚运行的还没结束的进程插入到就绪队列后面。如此重复。例如最后一个实验结果中系统时间为第1秒时,进程P1运行结束了,这个时候进程P2到达了,而P2应该先插入到就绪队列后面,然后如果P1还没满足运行时间就插在就绪队列最后面,即P2后面。
本实验通过初始化得到一个进程等待队列,模拟进程在不同时间申请CPU资源,通过一个就绪队列模拟进程的调度、执行过程。通过大量的实验结果验证得到该程序正确地实现了时间片轮转算法模拟。
通过这次课程设计,我对时间片轮转调度算法进行了大量的资料查询,从而对时间片轮转调度算法思想有了更深的理解,对该算法的操作也有了熟练的操作;另外通过大量的链表操作,我对链表的知识有了更深的理解,而且锻炼了我的思维能力,我能更全面地思考问题。
时间片轮转算法是系统将所有的就绪进程按先来先服务算法的原则,排成一个队列,每次调度时,系统把处理机分配给队列首进程,并让其执行一个时间片。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序根据这个请求停止该进程的运行,将它送到就绪队列的末尾,再把处理机分给就绪队列中新的队列首进程,同时让它也执行一个时间片。
该实验的提示中做给的进程控制块(PCB)的格式中“进程状态”这一属性其实在本次模拟实验中用处不大,因为如果一个进程运行完后,可以直接从就绪队列中删除掉,而不需要更改这个“进程状态”,更改后又不起作用;而且删除后就绪队列就会减短,遍历的时候可以省略掉对每个进程是否已经结束的判断,从而减少运行时间。