前言
操作系统课程设计——设计一个按照时间片轮转法实现处理机调度的程序
一、课程设计题目及内容
题目:设计一个按照时间片轮转法实现处理机调度的程序
时间片轮转法实现处理机调度的程序设计提示如下:
-
假设系统有n个进程,每个进程用一个进程控制块(PCB)来代表。进程控制块的格式如下表所示,且参数意义也相同。
| 进程名 |
| 链接指针 |
| 到达时间 |
| 估计运行时间 |
| 进程状态 | -
按照进程到达的先后顺序排成一个循环队列,设一个队首指针指向第一个到达进程的首址。另外再设一个当前运行进程指针,指向当前正运行的进程。
-
执行处理机调度时,首先选择队首的第一个进程运行。
-
由于本题目是模拟实验,所以对被选中的进程并不实际启动运行,而只是执行如下操作:
1)估计运行时间减1;
2)输出当前运行进程的名字 -
进程运行一次后,以后的调度则将当前指针依次下移一个位置,指向下一个进程,即调整当前运行指针指向该进程的链接指针所指进程,以指示应运行进程,同时还应判断该进程的剩余运行时间是否为0,若不为0,则等待下一轮的运行,若该进程的剩余运行时间为0,则将该进程的状态置为完成状态“C”,并退出循环队列。
-
若就绪队列不为空,则重复上述的步骤 4 和 5 直到所有进程都运行完为止。
-
在所设计的调度程序中,应包含显示或打印语句,以便显示或打印每次选中进程的名称及运行一次后队列的变化情况。
二、程序中使用的数据结构及主要符号说明
数据结构:
// 进程控制块:
typedef struct PCB {
char name; //进程名(这里用A、B、C…作为进程名)
struct PCB *next; //连接指针,指向下一个到达的进程
int ArriveTime; //进程到达时间
int RunTime; //进程估计运行时间
char state; //进程状态,完成状态为‘C’(Complete),未完成为‘N’(Not Complete)
} PCB;
全局变量:
PCB *head; //头指针,指向就绪队列的首进程
int processNum; //进程数量
int latestTime; //进程最迟到达时间
int longestTime; //进程最长运行时间
各函数说明:
- ①void init(int n) :
初始化函数,为每个进程随机设置到达时间和估计运行时间,并用连接指针将各个进程连接,参数n记录传过来的进程数实参processNum。
实现思路:用指针cur和pre分别指向当前进程和前一个进程,初始时pre的next指向head,然后循环n次:给cur分配新的地址空间,为该进程根据最迟到达时间和最长运行时间随机设置到达时间和估计运行时间,进程名为A、B、C…(对应的一个),初始状态设为’N’,若是首次循环,则head指向当前进程cur,然后pre的next指向cur,实现各进程的连接,接着更新前驱指针:pre= cur。循环结束后,此时cur指向最后一个进程,让cur的next指向head,实现循环队列,最后将pre和cur指向NULL,然后释放给他们分配的内存。
- ②void showQueue() : 打印队列进程情况函数
实现思路:建立cur指针指向head,循环processNum次,输出对应进程的进程名、到达时间、估计运行时间和进程状态,然后cur指向下一个进程,继续打印。
- ③void showArrive(int time) : 打印当前时刻到达就绪队列的进程情况,参数time记录当前时刻。
实现思路:建立cur指针指向head,循环processNum次,如果遍历到的进程的到达时间大于此时的time,则return,函数结束,输出对应进程的进程名、到达时间、估计运行时间和进程状态,然后cur指向下一个进程,继续打印。
- ④void BubbleSort(PCB **a,int n) : 冒泡排序,参数PCB **a为待排序的进程块数组,int n为进程数。
- ⑤process_sort() : 排序函数,由于各个进程的到达时间是用随机数产生的,各个进程的到达时间并不是按照进程名排列的,对各个进程按照到达时间进行排序(采用冒泡排序),相同到达时间则按照进程名的顺序排。
实现思路:进程就绪队列是单向链表构成的一个循环队列,这里我是先用一个临时数组temp存放进程队列中的进程,然后对这个数组按照进程到达时间进行冒泡排序,然后重新将排完序后的temp数组里的进程排成循环队列。
- ⑥void exitQueue() : 检查是否有处于完成状态的进程,若有,将处于完成状态的进程退出进程队列。
实现思路:建立局部指针变量cur和pre,用于指向当前进程和前一个进程,初始pre的next指向head,cur指向head。遍历一遍队列中的进程,若cur->stata == ‘C’,则pre->next = cur->next,如果cur指向的是首进程,还需修改head的指向,同时processNum–,并退出循环;若cur->stata == ‘N’,则队列间的指针连接不变,执行下列操作:pre->next = cur; pre = cur; cur = cur->next。循环结束后,考虑到可能进程删除后循环队列会被破坏,因此cur先指向head,然后循环一遍将cur指向队尾,再让cur的next指向head,形成新的循环队列,最后将pre指向NULL,然后清除给pre动态分配的内存。
- ⑦bool existArrive(int time) : 判断当前时刻是否有进程到达就绪队列,为了在没有进程到达的情况下不打印队列进程情况
实现思路:判断首进程head的ArriveTime是否小于等于当前时刻time,若小于则return true,否则return false。
- ⑧void newArriveShow(int time) : 若当前时刻有新进程到达就绪队列,则打印到达信息:“进程XX已到达,加入就绪队列!”
实现思路:用指针cur遍历各个进程,如果cur指向的进程的ArriveTime刚好等于time,则打印该到达信息,当遍历到ArriveTime大于time的进程时,结束函数。
- ⑨void RR() : 时间片轮转函调度函数
实现思路:变量time记录当前时刻,如果当前时刻head->ArrivrTime >
time(首进程的到达时间大于当前时刻,即当前时刻还未有进程到达)时,循环输出“当前无进程到达”的提示,同时time++,直到有进程到达时,开始调度工作。cur指针先指向head,当进程数processNum大于0时循环执行以下操作:调用newArriveShow函数,若有新进程到达,输出到达的提示信息;判断cur遍历到的进程到达时间是否大于当前时刻time,若大于则cur重新指回head;调用existArrive函数判断当前时刻是否有进程到达,若有进程到达,则打印当前进程正在运行的提示并打印此时到达的进程情况,同时cur->RunTime—(当前进程的估计运行时间减一),若没有进程到达,则打印等待进程到达的提示;运行一次后判断cur->RunTime(当前进程的估计运行时间)是否为0,若为0则将该进程的状态置为“C”(cur->state = ‘C’)并给出进程完运行完成的提示,然后cur指向下一个进程;调用exitQueue函数,将状态为‘C’的进程从队列中移除,同时processNum减一,最后time++,进入下一次循环,当processNum = 0时退出循环,函数结束。
- ⑩main函数
先由用户输入进程最迟到达时间、最长运行时间和进程数量,然后调用init初始化函数进行初始化,然后打印排序前后的进程队列,接着调用RR函数开始时间片轮转调度,最后清除给head动态分配的内存。
三、源代码
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
typedef int bool;
#define true 1
#define false 0
typedef struct PCB {
char name; //进程名(这里用A、B、C…作为进程名)
struct PCB *next; //连接指针,指向下一个到达的进程
int ArriveTime; //进程到达时间
int RunTime; //进程估计运行时间
char state; //进程状态,完成状态为'C'(Complete),未完成为'N'(Not Complete)
}PCB;
PCB *head; //头指针,指向就绪队列的首进程
int processNum; //进程数量
int latestTime; //进程最迟到达时间
int longestTime; //进程最长运行时间
//初始化,为每个进程随机设置到达时间和估计运行时间,并且用连接指针将各个进程连接起来
void init(int n) {
srand((unsigned)time(NULL)); //生成时间种子
head = (PCB*)malloc(sizeof(PCB));
PCB *cur, *pre; //cur指向当前进程,pre指向前一个进程,用于将各个进程连接起来
pre = (PCB*)malloc(sizeof(PCB));
pre->next = head;
for(int i = 0; i < n; i++) {
cur = (PCB*)malloc(sizeof(PCB));
//设置随机的到达时间和估计运行时间
//到达时间区间是[0, latestTime],估计运行时间区间是[1,longestTime]
cur->ArriveTime = rand() % (latestTime + 1);
cur->RunTime = rand() % longestTime + 1;
cur->name = (char)(i + 65); //进程名称为A、B、C…
cur->state = 'N'; //初始时处于未完成状态
if(i == 0) {
head = cur; //头指针指向首个进程
}
pre->next = cur; //进程间的指针链接
pre = cur; //更新前驱指针
}
cur->next = head; //最后一个进程的下一个进程指向首进程
//将要清除内存的指针指向NULL
pre = NULL; cur = NULL;
//清除动态分配的内存,防止内存泄漏
free(pre); free(cur);
}
//打印队列中各个进程的情况
void showQueue() {
PCB *cur = head;
printf("进程名\t\t到达时间\t估计运行时间\t进程状态\n");
for(int i = 0; i < processNum; i++) {
printf("%c\t\t%d\t\t%d\t\t%c\n", cur->name, cur->ArriveTime, cur->RunTime, cur->state);
cur = cur->next; //遍历下一个进程
}printf("\n");
}
//打印当前时刻到达就绪队列的进程情况
void showArrive(int time) {
PCB *cur = head;
printf("进程名\t\t到达时间\t估计运行时间\t进程状态\n");
for(int i = 0; i < processNum; i++) {
//只打印显示当前时刻到达就绪队列中的进程
if(cur->ArriveTime > time){
return;
}
printf("%c\t\t%d\t\t%d\t\t%c\n", cur->name, cur->ArriveTime, cur->RunTime, cur->state);
cur = cur->next;
}
//printf("\n");
}
//由于各个进程的到达时间是用随机数产生的,各个进程的到达时间并不是按照进程名排列的
//对各个进程按照到达时间进行排序(采用冒泡排序),相同到达时间则按照进程名的顺序排
//冒泡排序
void BubbleSort(PCB **a,int n)
{
for(int i = 1; i <= n; i++) //进行n-1趟
{
int flag = 1; //flag = 1表示数组已排好序
//每一趟将到达时间最晚的进程排在数组待排序序列中的末端
for(int j = 0; j < n - i; j++)
{
if(a[j]->ArriveTime > a[j+1]->ArriveTime)
{
PCB *t = a[j];
a[j] = a[j+1];
a[j+1] = t;
flag = 0; //排序未完成
}
}
if(flag) //数组已排好序
break;
}
}
/*
进程就绪队列是单向链表构成的一个循环队列,这里我是先用一个临时数组temp存放进程队列中的进程,然后对这个数组
按照进程到达时间进行冒泡排序,然后重新将排完序后的temp数组里的进程排成循环队列。
*/
void process_sort() {
PCB **temp = (PCB**)malloc(processNum * sizeof(PCB*)); //建立临时数组,存放各个进程,然后对此数组排序
for(int i = 0; i < processNum; i++) {
temp[i] = (PCB*)malloc(sizeof(PCB)); //动态分配大小
}
PCB *cur = (PCB*)malloc(sizeof(PCB)); //创建指针用于指向当前进程
cur = head;
//将各个进程放入temp数组中
for(int i = 0; i < processNum; i++) {
temp[i] = cur;
cur = cur->next;
}
BubbleSort(temp, processNum); //按到达时间排序
//将排序后的temp数组中的元素重新链接为进程就绪队列
head = temp[0];
cur = head;
for(int i = 1; i < processNum; i++) {
cur->next = temp[i];
cur = cur->next;
}
cur->next = head; //最后一个进程的next指向队首进程
//清除动态分配的内存,防止内存泄漏
cur = NULL;
free(temp); free(cur);
}
//检查是否有处于完成状态的进程,若有,将处于完成状态的进程退出进程队列
void exitQueue() {
PCB *pre = (PCB*)malloc(sizeof(PCB)); //前驱指针
pre->next = head;
PCB *cur = head;
int num = processNum; //记录当前剩余的进程数
for(int i = 0; i < num; i++) {
//如果当前进程处于完成状态
if(cur->state == 'C') {
//如果是首进程退出就绪队列,需要修改head的指向
if(i == 0) {
head = head->next;
}
pre->next = cur->next; //将cur指向的进程删除
processNum--; //剩余进程数减一
//因为每次一个进程运行一个时间片后,都会调用此函数检查是否有进程运行完成了并将其删除,每次最多只有一个进程处于完成状态,因此找到这个完成的进程将其删除后,即可退出循环
break;
}
//当前进程未完成,则指针正常连接
else {
pre->next = cur;
pre = cur;
cur = cur->next;
}
}
//如果是队列首元素被删掉,需要重新更新队尾元素的链接指针
//cur先指向head,然后遍历到cur指向队尾,再让此时的cur->next指向head
cur = head;
for(int i = 0; i < processNum - 1; i++) {
cur = cur->next;
}
cur->next = head;
//清除动态分配的内存,防止内存泄漏
pre = NULL; free(pre);
}
//判断当前时刻是否有进程到达就绪队列,为了在没有进程到达的情况下不打印队列进程情况
bool existArrive(int time) {
int flag = 0; //标记是否有进程到达
//只需要检查队列首进程是否到达即可
if(head->ArriveTime <= time)
flag = 1;
if(flag) return true;
else return false;
}
//若当前时刻有新进程到达就绪队列,则打印到达信息
void newArriveShow(int time) {
PCB *cur = head;
for(int i = 0; i < processNum; i++) {
if(cur->ArriveTime == time) {
printf("---->进程%c已到达,加入就绪队列!\n", cur->name);
}
if(cur->ArriveTime > time)
return;
cur = cur->next;
}
}
//时间片轮转调度
void RR() {
int time = 0; //记录当前时刻
//等到有进程到达时处理机才开始进程调度工作
printf("=====================开始时间片轮转调度=====================\n\n");
while(head->ArriveTime > time) {
printf("当前时刻time = %d, 剩余待完成进程总数processNum = %d\n", time, processNum);
printf("%d时刻:无进程到达就绪队列,等待中…\n\n\n", time);
time++;
}
PCB *cur = head;
printf("当前时刻time = %d, 剩余待完成进程总数processNum = %d\n", time, processNum);
//进程队列中有剩余进程时,循环执行
while(processNum > 0) {
newArriveShow(time);
//如果进程还没到达的话,应该重新从队列首进程开始运行,和后面的cur = cur->next;对应起来
if(cur->ArriveTime > time) cur = head;
if(existArrive(time)) {
printf("%d时刻:%c进程正在运行,此时进程队列为:\n", time, cur->name);
cur->RunTime--; //该进程的估计运行时间减一
}
else
printf("%d时刻:当前进程就绪队列中无进程,等待中…\n", time);
if(cur->RunTime == 0) {
cur->state = 'C'; //如果进程估计运行时间为0,则将进程的状态置为完成状态
}
if(existArrive(time)) {
showArrive(time); //打印此时进程就绪队列中的进程情况
}
if(cur->state == 'C') {
printf("---->%c进程已完成,退出就绪队列!\n", cur->name);
}
cur = cur->next;
//检查是否有处于完成状态的进程,若有,则该进程退出队列
exitQueue();
time++; //当前时刻加一
//打印当前时刻以及剩余进程数(这里的剩余进程数是总共还剩多少个进程没运行完,并不是当前就绪队列中的进程数)
if(processNum > 0)
printf("\n\n当前时刻time = %d, 剩余待完成进程总数processNum = %d\n", time, processNum);
else
printf("\n===============所有进程运行完毕,调度任务结束===============\n");
}
}
int main()
{
printf("请输入进程最迟到达时间:");
scanf("%d", &latestTime);
printf("请输入进程最长运行时间:");
scanf("%d", &longestTime);
printf("请输入需要调度运行的进程数量(系统将为这些进程随机设置到达时间和估计运行时间):");
scanf("%d", &processNum);
init(processNum); //初始化
printf("\n------------------------进程队列------------------------\n");
showQueue(); //打印初始化后的进程队列
process_sort(); //按照进程到达时间排序
printf("\n---------------------排序后进程队列---------------------\n");
showQueue(); //打印排序后的进程队列
RR(); //开始时间片轮转调度
//清除动态分配的内存,防止内存泄漏
free(head);
return 0;
}
四、执行程序,并打印程序运行时的初值和运算结果
①运行结果1(初始数据随机生成):
运行结果2(初始数据随机生成):
③运行结果3(初始数据随机生成):
五、实验结果分析,实验收获和体会
本次课程设计选择的题目是《设计一个按照时间片轮转法实现处理机调度的程序》,根据题目中的要求,可以知道本次实验每个时间片为1,主要的思路在前面已经给出。根据上面多组运行结果,可以看到该程序达成了这个课程设计题目的的要求,当有新进程到达时,会输出该进程到达的提示;当有进程运行时,能输出当前运行进程的名字,且该进程的估计运行时间减1,每一次运行后打印队列中各个进程的变化情况,若进程已完成,则打印出的进程状态就是C,然后将其从队列中删除,下一次打印进程情况就不会再显示这个进程了;当没有进程执行,则输出“当前进程就绪队列中无进程,等待中…”。而且无论有无进程运行,都会打印当前时刻和剩余待完成的进程总数,当所有进程的完成时,此时processNum为0,进程队列为空,结束循环,至此,时间片调度任务结束。