模拟进程状态转换
一、实验目的
通过实验理解进程基本概念、状态转换及其控制。
二、实验内容
利用高级语言编写程序,模拟进程状态转换的过程。
进程的主要状态:就绪、运行、阻塞、终止,状态之间的转换如下图所示:
实现提示:
(1)采用进程控制块(PCB)描述一个进程的所有信息包括:进程标识符、处理机状态、进程调度信息以及控制信息,它是系统感知进程存在的唯一标志,进程与 PCB是一一对应的。
(2)对PCB 中的内容在此可做一些简化处理,只包括:进程标识符(内部标识符、外部标识符)、当前状态(“就绪”,“阻塞”,“执行”和“终止”)、要求运行的时间片数、优先级等。
(3)模拟过程
按优先级高低对进程进行调度,每个进程可有四个状态,即:“就绪”,“阻塞”,“执行”和“终止”(假设优先数越大优先级越高,以及进程的初始状态为就绪状态)。
为便于处理,应该随机地赋予所有进程各自的“优先级”和“要求运行的时间片数”,由用户给定。
程序中共设 3 个队列:就绪队列,阻塞队列和终止队列。
按优先级调度时,进程每运行一次,优先级降低一位,要求的时间片数减 1,如果要求的时间片数为零了,则将其加入终止队列。否则,将其按照优先级的次序重新加入就绪队列。
要求的时间片数为零了,则将其加入终止队列。否则,将其按照优先级的次序重新加入就绪队列。
进程运行过程中,遇到阻塞时,将该进程加入阻塞队列。(通过随机函数得到随机数,满足某种情况则使之加入到阻塞队列,或人为设置是否阻塞)。
重复调度,直到就绪队列中所有进程全部运行结束。
(4)主要功能模块 (参考)
初始化进程:初始化进程的基本数据
新进程插入就绪队列:向就绪队列插入进程(按优先级排序)
调度:从就绪队列中选取一进程运行一个时间片
阻塞:将被阻塞的执行进程放入阻塞队列
唤醒:唤醒阻塞队列中的进程
终止:完成的进程放入消亡队列
显示:输出各队列中进程的所有信息,包括正在执行的进程
(5)输入/输出
输入要求:进程数以及每个进程的标识符(按顺序数字编号)、当前状态(默认为“就绪”)、要求运行的时间片数、初始优先级。(要能随时添加新进程)
输出要求:每次进程状态发生转换时,输出各队列进程的信息。
三、代码:
/*
* 作者:
功能:模拟进程状态转换(进程数范围:1-10)
4个PCB状态标识常量:1.就绪Ready、2.运行Running、3.阻塞Block、4.终止Terminate
用Run()函数去模拟的CPU的run:所以Run函数应包含以下情况的处理(即,4种队列对应的处理情况):
(1)就绪队列(为了方便找最大优先级,当然用priority_queue更方便,但我采用更熟悉的链表结构):
就绪_1->就绪_1
就绪_1->运行_2
(2)运行队列
运行_2->就绪_1
运行_2->运行_2
运行_2->阻塞_3
运行_2->终止_4
(3)阻塞队列(唤醒概率:1/8、被阻塞概率:1/4)
阻塞_3->唤醒->就绪_1
阻塞_3->唤醒->运行_1,(当就绪、运行队列为空时的情况,跳过进入就绪,直接唤醒到运行)
阻塞_3->阻塞_3//唤醒概率没有满足的时候
(4)终止队列:
对终止队列里的进程不必要进行处理
*/
#include <iostream>
#include <cstdlib>
#include<queue>
#include <windows.h>
using namespace std;
enum State {
Ready,Running,BlockForIO,Terminate
};
struct PCB {
int id = 0;
int priority = 0;//初始化进程时,随机分配,抢占式优先级分配
int timeslice = 0;//初始化进程时,随机分配
State state = Ready;//假设各进程满足除CPU外一切资源,初始状态为就绪
PCB* next=NULL;
};
PCB* head=NULL;
queue<PCB*> runningQueue, blockQueueForIO, terminateQueue;
int blockedProbability;
void InitPCB();
PCB* FindMaxPriority();
void DeletePCB(int id);
void Run();
void PrintPCB();
void Display_TerminateQueue();
void Display_BlockQueueForIO();
void Display_RunningQueue();
void Display_Ready();
void InsertHead(PCB* p);
void InitPCB() {
srand((unsigned)time(NULL));
int processNumber=rand()%10+1;//随机进程个数范围:1-10
PCB* p=NULL;
PCB* q=NULL;
for (int i = 0; i < processNumber; i++) {
q=new PCB;
q->id = i+1;
q->priority = rand() % 30 + 1;//优先级范围,为了显示效果更好,使优先级范围小一点
q->state = Ready;
q->timeslice = rand() % 10+1;//同理
if (i == 0) {
head = q;
p= q;
}
else {
p->next=q;
p = p->next;
}
}
}
PCB* FindMaxPriority() {
PCB* p = head;
PCB* q =NULL;
int max_priority = 0;
if (head == NULL)return NULL;
while (p) {
if (p->priority > max_priority) {
q = p;
max_priority = p->priority;
}
p = p->next;
}
return q;
}
void DeletePCB( int id) {
PCB* p=head;
PCB* q=p->next;
if (p->id == id) {
p->next = NULL;
head = q;
}
else {
while (q->id!=id) {
p = p->next;
q = p->next;
}
p->next = q->next;
q->next = NULL;
}
}
void Run() {
srand((unsigned)time(NULL));
PCB* p=NULL;
PCB* q= FindMaxPriority();
/*假设:当上一次运行完成时时,cpu才会有空去(找可运行性的就绪状态进程)
或者(检查唤醒条件是否满足也可以说去检查是否有唤醒信号,所以应放在此位置)
注意:之所以先去检查唤醒而不是去找可运行性的就绪状态进程(即以下代码的顺序问题),
是为了避免当所有可执行进程执行完了才去唤醒操作,不符合现实
*/
if (!blockQueueForIO.empty()) {
//理论上我认为,因为在多次频繁检查IO占用情况时,IO一段时间都是被占用的,被唤醒的概率会比阻塞概率小
int awakenedProbability = rand() % 8 + 1;//唤醒概率:1/8
if (awakenedProbability == 4) {
p = blockQueueForIO.front();
blockQueueForIO.pop();
p->state = Ready;
InsertHead(p);
}
}
if (runningQueue.empty()) {
if (head!=NULL) {
q->state = Running;
DeletePCB(q->id);
runningQueue.push(q);
}else {
//当运行队列、就绪队列为空时,此时资源一定不会占用,这也是唤醒概率的弊端,直接进入运行队列
if (!blockQueueForIO.empty()) {
p = blockQueueForIO.front();
blockQueueForIO.pop();
p->state = Running;
runningQueue.push(p);
//此时阻塞也不可能发生
blockedProbability = 0;
}
else {
return;
}
}
}else {
/*运行队列里的进程一定是可以执行的,而且在函数开头,此进程一定打印过,
不会出现刚调入到运行队列还没有打印就被放入阻塞队列的的情况,
由于当运行队列里的进程下个时间段可以执行时,再考虑它会不会阻塞
*/
blockedProbability = rand() % 4 + 1;//被阻塞概率:1/4
//如果运行队列为空,即使达到阻塞概率也不可能会阻塞
if (blockedProbability == 2&&head!=NULL) {
p=runningQueue.front();
runningQueue.pop();
p->state = BlockForIO;
blockQueueForIO.push(p);
q->state = Running;
DeletePCB(q->id);
runningQueue.push(q);
}
p=runningQueue.front();
p->timeslice -= 1;
p->priority -= 1;
if (p->timeslice < 1) {
runningQueue.pop();
p->state = Terminate;
terminateQueue.push(p);
if (q) {
DeletePCB(q->id);
q->state = Running;
runningQueue.push(q);
}
}
else if (q) {//若就绪队列没进程,一定不会阻塞并且当前进程也不会进入就绪队列,所以当就绪队列有进程是时,凭概率设置阻塞
//当优先级不是最高时,当前进程一定会下台,所以下次也一定没有它运行的机会,阻塞的意义:当下次还有运行的机会时,给他阻塞,剥夺他运行的机会
if (p->priority < q->priority) {
runningQueue.pop();
p->state = Ready;
InsertHead(p);
q->state = Running;
DeletePCB(q->id);
runningQueue.push(q);
}
}
}
PrintPCB();
cout << endl << endl<<endl<<endl;
Run();
}
void InsertHead(PCB* p) {
p->next = head->next;
head->next = p;
}
void Display_Ready() {
PCB* p = head;
while (p) {
cout << "ID: " << p->id << " Priority: " << p->priority << " Timeslice: " << p->timeslice << endl;
p = p->next;
}
}
void Display_RunningQueue() {
if (!runningQueue.empty()) {
PCB* t = runningQueue.front();
cout << "ID: " << t->id << " Priority: " << t->priority << " Timeslice: " << t->timeslice << endl;
}
}
void Display_BlockQueueForIO() {
PCB* tempPCBArray[10];
int n = 0;
PCB* t;
if (!blockQueueForIO.empty()) {
while (!blockQueueForIO.empty()) {
t = blockQueueForIO.front();
tempPCBArray[n] = t;
n++;
cout << "ID: " << t->id << " Priority: " << t->priority << " Timeslice: " << t->timeslice << endl;
blockQueueForIO.pop();
}
for (int i = 0; i < n; i++) {
blockQueueForIO.push(tempPCBArray[i]);
}
}
}
void Display_TerminateQueue() {
PCB* tempPCBArray[10];
int n = 0;
PCB* t;
if (!terminateQueue.empty()) {
while (!terminateQueue.empty()) {
t = terminateQueue.front();
tempPCBArray[n] = t;
n++;
cout << "ID: " << t->id << " Priority: " << t->priority << " Timeslice: " << t->timeslice << endl;
terminateQueue.pop();
}
for (int i = 0; i < n; i++) {
terminateQueue.push(tempPCBArray[i]);
}
}
}
void PrintPCB() {
cout << "就绪队列:" << endl; Display_Ready(); cout << endl;
cout << "运行队列:"<<endl; Display_RunningQueue(); cout << endl;
cout << "IO阻塞队列:" << endl; Display_BlockQueueForIO(); cout << endl;
cout << "终止队列:" << endl; Display_TerminateQueue(); cout << endl;
}
int main() {
InitPCB();
Run();
return 0;
}
总结
以上代码仍然有好多改进之处(代码冗余部分我就懒得改了),出于老师的要求程度和时间考虑,就这么写吧。转载请注明出处。