实验准备
进程控制块
设计思路
采用动态优先级时间片轮转法。
其中进程个数、各个进程的名称为控制台输入。
进程优先级为随机生成,进程需要运行的时间为其优先级数的2倍。
随机性可能对这种算法产生以下影响:
-
优先级调整:随机性可能导致进程的优先级在不同时间点发生变化,使得算法在不同时间点对进程的调度产生不同的结果。
-
时间片分配:随机性可能导致时间片的分配不确定,使得不同进程在不同时间点获得的时间片大小不一致,从而影响进程的调度顺序。
-
调度结果不确定:由于随机性的影响,算法在不同运行中可能产生不同的调度结果,使得调度的稳定性降低。
-
性能波动:随机性可能导致算法的性能在不同时间点产生波动,使得算法的性能难以预测和优化。
‘F’表示完成态,‘R’表示运行态,‘r’表示就绪态
PCB* createProcess(const char* name, int needtime, int prio);//创建进程
PCB* removeProcess(PCB*& queue);//移出队列
void insertProcess(PCB*& queue, PCB* process);//插入队列
void adjustPriority(PCB* queue);//调整优先级
PCB* comparePriority(PCB*& readyQueue, PCB*& runningQueue);//比较就绪队列和运行队列的优先级
其中,优先级的调整规则:运行队列中的进程时间片用完,优先级数=优先级数-还需要运行的时间;就绪队列中待运行队列运行完,每个进程优先级数+10
主要思想:初始,将就绪队列中优先级最高的进程放入运行队列,待到时间片用完,调整运行队列、就绪队列的优先级数,选择进程进入运行队列(就绪队列最高优先级大于运行队列优先级)或保持原有进程运行(调整后,运行队列优先级大于就绪队列最高优先级)。
同时进行可视化绘图,采用opengl库进行绘制。
程序流程图
主要函数流程图如下:
核心代码
//main函数和dynamicPriorityScheduling核心函数。
int main(int argc, char** argv) {
glutInit(&argc, argv); // 初始化 GLUT
// 初始化OpenGL窗口
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(800, 600);
glutCreateWindow("Process Scheduling Visualization");
int numProcesses;
cout << "请输入进程数量: " << endl;
cin >> numProcesses;
cout << "请输入时间片时间:" << endl;
cin >> T;
// 设置随机种子
srand(time(0));
// 生成随机进程
for (int i = 0; i < numProcesses; ++i) {
char name[10];
int needtime, prio;
cout << "请输入第" << i + 1 << "个进程的名字:" << endl;
cin >> name;
needtime = rand() % 20 + 1; // 生成1到20之间的随机执行时间
prio = needtime * 2;
cout << "进程 " << name << " 优先级:" << prio << " 需要运行时间:" << needtime << endl;
PCB* newProcess = createProcess(name, needtime, prio);
insertProcess(readyQueue, newProcess);
bubbleSort(readyQueue);
}
auto displayCallback = [](void) {
dynamicPriorityScheduling(readyQueue, runningQueue, finishedQueue, T);
};
// 注册OpenGL回调函数
glutDisplayFunc(displayCallback);
glutIdleFunc(displayCallback);
// 设置OpenGL背景颜色为黑色
glClearColor(0.0, 0.0, 0.0, 0.0);
// 进入OpenGL主循环
glutMainLoop();
return 0;
}
///
void dynamicPriorityScheduling(PCB*& readyQueue, PCB*& runningQueue, PCB*& finishedQueue, int T) {
int temptime = 0;
int time = T;
PCB* currentProcess = removeProcess(readyQueue);//获取优先级最高的进程
insertProcess(runningQueue, currentProcess);//将当前进程插入到运行队列
while (runningQueue != nullptr) {
// 可视化部分
draw: {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glOrtho(-1.5, 1.5, -1.5, 1.5, -1, 1);
//glEnable(GL_DEPTH_TEST);
// 绘制readyQueue的结点
PCB* readyTemp = readyQueue;
for (int i = 0; readyTemp != nullptr; i++) {
glColor3f(0.0, 1.0, 0.0); // 绿色表示readyQueue
glRectf(0.0 + 0.35 * i, -0.5, 0.3 + 0.35 * i, -0.2);
glColor3f(0.0, 0.0, 0.0); // 黑色表示文字
glRasterPos2f(0.13 + 0.35 * i, -0.35);
for (const char& c : readyTemp->name) {
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, c);
}
readyTemp = readyTemp->next;
}
// 绘制runningQueue的结点
PCB* runningTemp = runningQueue;
for (int i = 0; runningTemp != nullptr; i++) {
glColor3f(1.0, 0.0, 0.0); // 红色表示runningQueue
glRectf(0.0 + 0.35 * i, 0.0, 0.3 + 0.35 * i, 0.3);
glColor3f(0.0, 0.0, 0.0); // 黑色表示文字
glRasterPos2f(0.13 + 0.35 * i, 0.15);
for (const char& c : runningTemp->name) {
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, c);
}
runningTemp = runningTemp->next;
}
// 绘制finishedQueue的结点
PCB* finishedTemp = finishedQueue;
for (int i = 0; finishedTemp != nullptr; i++) {
glColor3f(0.0, 0.0, 1.0); // 蓝色表示finishedQueue
glRectf(0.0 + 0.35 * i, 0.5, 0.3 + 0.35 * i, 0.8);
glColor3f(1.0, 1.0, 1.0); // 黑色表示文字
glRasterPos2f(0.13 + 0.35 * i, 0.65);
for (const char& c : finishedTemp->name) {
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, c);
}
finishedTemp = finishedTemp->next;
}
//glDisable(GL_DEPTH_TEST);
glLoadIdentity();
glColor3f(1.0, 1.0, 1.0);
renderBitmapString(-0.5, -0.25, GLUT_BITMAP_9_BY_15, "readyQueue");
renderBitmapString(-0.5, 0.1, GLUT_BITMAP_9_BY_15, "runningQueue");
renderBitmapString(-0.5, 0.45, GLUT_BITMAP_9_BY_15, "finishQueue");
glutSwapBuffers();
glFlush();
}
if (runningQueue == nullptr) break;//绘制完最后一个状态,跳出循环
currentProcess->state = 'R';//修改当前进程状态为运行态
for (currentProcess->count = 0; currentProcess->count != T; currentProcess->count++) {//时间片消耗
;
cout << "当前进程:" << currentProcess->name << endl;
currentProcess->needtime--;
cout << "还需要运行时间:" << currentProcess->needtime << endl;
currentProcess->cputime++;
//cout << "已经占用CPU运行时间:" << currentProcess->cputime << endl;
cout << "优先级:" << currentProcess->prio << endl;
cout << endl;
Sleep(500);
if (currentProcess->needtime == 0) {//判断进程是否运行完
currentProcess->state = 'F';
cout << ".............已完成" << currentProcess->name << endl;
insertProcess(finishedQueue, currentProcess);
removeProcess(runningQueue);
break;
}
}
if (readyQueue == nullptr && runningQueue == nullptr) {//绘制最后一个状态
goto draw;
}
//调整优先级
adjustPriority(readyQueue);
adjustPriority(runningQueue);
Sleep(500);
cout << endl;
currentProcess = comparePriority(readyQueue, runningQueue);//比较运行队列和就绪队列以一个元素的优先级大小
if (currentProcess != runningQueue) {//是否发生轮转
PCB* p1 = removeProcess(runningQueue);
p1->state = 'r';
insertProcess(readyQueue, p1);
}
}
}
实验展示
第一组实验数据:
控制台输入进程数量3,时间片为5,进程名分别为A、B、C。
初始状态绘图:
完成状态绘图:
结果分析:初始状态,就绪队列中进程按照优先级从大到小排序的顺序为,C\A\B(C进程优先级30,A优先级8,B优先级4),C进程(优先级30最大)最先进入运行队列,后经过动态优先级调整(C进程优先级40,A优先级18,B优先级14),以及时间片轮转,以此类推,各个进程按照规则选择进入运行队列运行,最后完成的顺序为A\B\C。
第二组实验数据:
控制台输入进程数量4,时间片为5,进程名分别为A、B、C、D。
初始状态绘图:
进程运行中绘图:
完成状态绘图:
结果分析:初始状态,就绪队列中进程按照优先级从大到小排序的顺序为,B\D\A\C(B进程优先级40,D优先级8,A优先级6,C优先级6),B进程(优先级40最大)最先进入运行队列,后经过动态优先级调整(B进程优先级25,D优先级18,A优先级16,C优先级16),以及时间片轮转,以此类推,各个进程按照规则选择进入运行队列运行,最后完成的顺序为D\A\C\B。
此处仅展示两组实验测试,如需更多测试请运行源代码。
动态优先级时间片轮转法算法是一种多任务调度算法,具有以下特性:
- 动态优先级:该算法根据进程的状态和行为动态调整进程的优先级,以便更好地响应系统的需求。这意味着具有更高优先级的进程将获得更多的CPU时间。
- 时间片轮转:算法使用时间片轮转的方式来轮流为每个就绪队列中的进程分配时间片,以确保每个进程都有机会执行,并避免某个进程长时间占用CPU。
- 公平性:时间片轮转法保证了就绪队列中的每个进程都有机会执行,从而确保了对所有进程的公平调度。
- 响应时间短:由于时间片轮转法的特性,使得进程在就绪队列中等待执行的时间相对较短,因此可以实现较短的响应时间。
- 实时性能:动态优先级时间片轮转法算法可以用于实时系统,因为它可以根据进程的优先级调整时间片,以满足实时任务的要求。
- 简单性:相对于其他调度算法,动态优先级时间片轮转法算法相对简单,易于实现和管理。
动态优先级时间片轮转调度算法的优缺点如下:
优点:
- 灵活性:动态优先级时间片轮转调度算法可以根据进程的状态和优先级动态地调整进程的执行顺序,从而更好地适应不同类型的工作负载。
- 公平性:通过时间片轮转,保证了每个进程都有机会获得CPU时间片,避免了某些进程长时间占用CPU而导致其他进程无法得到执行的情况。
- 响应时间:对于优先级较高的进程,能够及时地获得CPU时间片,从而提高了系统的响应速度。
缺点:
- 饥饿问题:如果系统中存在优先级较高的进程不断产生,可能会导致优先级较低的进程长时间得不到执行,造成饥饿现象。
- 上下文切换开销:由于需要频繁地根据进程的优先级进行调度,可能导致较大的上下文切换开销,影响系统的性能。
- 难以确定参数:动态优先级时间片轮转调度算法需要根据系统负载和进程特性动态调整优先级和时间片大小,这需要较为复杂的算法和参数调优。