目录
实验要求
以下为引用内容——摘抄与上机实验报告要求(可以忽略)或者看看和你们的实验报告是否一样,有类似的地方可以阅读本篇文章。
实验二:进程的调度
一、实验项目类型:设计型
二、实验目的和要求
加深对进程概念的理解,掌握用队列组织进程的方法,掌握进程调度算法。
三、实验内容
编程模拟实现进程的调度。
具体内容包括:
1、用链表组织进程(进程控制块);
2、进程调度原语;
3、要求采用时间片轮转调度算法;
4、编写主函数对所做工作进程测试。
四、实验主要仪器设备
个人计算机、C程序编译器
五、实验具体内容和步骤的说明
这个实验主要考虑如何实现处理器调度。
1、现场信息
现场信息记录进程执行过程中的各种信息。当进程由于某种原因让出处理器时,需要将现场信息记录在进程控制块中,当进行进程调度时,从选中进程的进程控制块中读取现场信息进行现场恢复。现场信息包括处理器的相关寄存器内容,包括通用寄存器、程序计数器和程序状态字寄存器等。在实验中,可选取几个寄存器作为代表。用大写的全局变量AX、BX、CX、DX模拟通用寄存器、大写的全局变量PC模拟程序计数器、大写的全局变量PSW模拟程序状态字寄存器。本实验要求读取一个寄存器的值,予以输出。另外还需要在调度时修改进程的状态信息,也一并输出。
2、进程调度
多道程序设计的系统中,处于就绪态的进程往往是多个,它们都要求占用处理器,单处理器系统的处理器只有一个,进程调度就是解决这个处理器竞争问题的。进程调度的任务就是按照某种算法从就绪进程队列中选择一个进程,让它占有处理器。因此进程调度程序就应该包括两部分,一部分是在进程就绪队列中选择一个进程,并将其进程控制块从进程就绪队列中摘下来,另一部分工作就是分配处理器给选中的进程,也就是将指向正在运行进程的进程控制块指针指向该进程的进程控制块,并将该进程的进程控制块信息写入处理器的各个寄存器中
实验中采用时间片轮转调度算法。时间片轮转调度算法让就绪进程按就绪的先后次序排成队列,每次总是选择就绪队列中的第一个进程占有处理器,但是规定只能使用一个“时间片”。时间片就是规定进程一次使用处理器的最长时间。实验中采用每个进程都使用相同的不变的时间片。
3、调度原语的实现
(1)调度时对就绪队列的操作就是从队头摘下一个进程;(时间片轮转);
(2)让出CPU的进程现场信息保存至CPU;
(3)就绪队列摘下的进程状态信息改为执行;
(4)被调度进程前一次运行的中间结果恢复到CPU;
(4)进程执行(用一个延时表示);
(5)根据执行进程的实际情况决定执行进程的去向,如果进程执行结束,则进入结束队列;如果进程未结束,则重新回到就绪队列,此时需要恢复CPU现场信息,并修改状态为就绪;
(6)转下一次调度;
因为要从队尾挂入进程控制块。因此为就绪队列定义两个指针,一个头指针,指向就绪队列的第一个进程控制块;一个尾指针,指向就绪队列的最后一个进程控制块。
4、主程序
完成上述功能后,编写主函数进行测试:与第一次上机内容合并,首先建立一个就绪队列,手工输入信息建立几个进程;然后进行进程调度;最后将指向正在运行进程的指针指向的进程控制块的内容输出,察看结果。
注意:
这篇文章中小光会带你们将老师布置的实验内容这部分,详细的讲解一下,大家可以直接复制代码,也可以学习一下怎么写,在这个基础上加以拓展,这样就不会查重了。
正文开始
让我们先来看一下实验要求中的代码(代码如下)——直接用,谢谢
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 定义进程状态
enum ProcessStatus {
READY,
RUNNING,
FINISHED
};
// 进程控制块结构体
struct ProcessControlBlock {
int processID;
enum ProcessStatus status;
int registers[4]; // 模拟通用寄存器
int PC; // 程序计数器
int programStatusWord; // 程序状态字寄存器
int timeSlice; //剩余时间
struct ProcessControlBlock* next;
};
// 定义结束队列的头指针和尾指针
struct ProcessControlBlock* finishedQueueHead = NULL;
struct ProcessControlBlock* finishedQueueTail = NULL;
// 将进程加入结束队列
void addToFinishedQueue(struct ProcessControlBlock** finishedQueueHead, struct ProcessControlBlock** finishedQueueTail, struct ProcessControlBlock* pcb) {
if (*finishedQueueHead == NULL) {
*finishedQueueHead = pcb;
*finishedQueueTail = pcb;
} else {
(*finishedQueueTail)->next = pcb;
*finishedQueueTail = pcb;
}
pcb->next = NULL;
}
// 创建进程控制块
struct ProcessControlBlock* createProcess(int processID, int timeSlice) {
struct ProcessControlBlock* newProcess = (struct ProcessControlBlock*)malloc(sizeof(struct ProcessControlBlock));
newProcess->processID = processID;
newProcess->status = READY;
newProcess->timeSlice = timeSlice; // 设置时间片
newProcess->next = NULL;
// 初始化寄存器
newProcess->registers[0] = 0;
newProcess->registers[1] = 0;
newProcess->registers[2] = 0;
newProcess->registers[3] = 0;
newProcess->PC = 0;
newProcess->programStatusWord = 0;
return newProcess;
}
// 将进程加入就绪队列
void addToReadyQueue(struct ProcessControlBlock** readyQueueHead, struct ProcessControlBlock** readyQueueTail, struct ProcessControlBlock* pcb) {
if (*readyQueueHead == NULL) {
*readyQueueHead = pcb;
*readyQueueTail = pcb;
}
else {
(*readyQueueTail)->next = pcb;
*readyQueueTail = pcb;
}
pcb->next = NULL;
}
// 从就绪队列中移除进程
struct ProcessControlBlock* removeFromReadyQueue(struct ProcessControlBlock** readyQueueHead, struct ProcessControlBlock** readyQueueTail) {
if (*readyQueueHead == NULL) {
return NULL;
}
struct ProcessControlBlock* removedProcess = *readyQueueHead;
*readyQueueHead = (*readyQueueHead)->next;
if (*readyQueueHead == NULL) {
*readyQueueTail = NULL;
}
return removedProcess;
}
// 时间片轮转调度
void schedule(struct ProcessControlBlock** readyQueueHead, struct ProcessControlBlock** readyQueueTail) {
struct ProcessControlBlock* runningProcess = removeFromReadyQueue(readyQueueHead, readyQueueTail);
if (runningProcess != NULL) {
printf("进程 %d 正在运行...\n", runningProcess->processID);
// 保存当前寄存器信息到进程控制块
// (这里用局部变量代替了全局变量)
int AX = runningProcess->registers[0];
int BX = runningProcess->registers[1];
int CX = runningProcess->registers[2];
int DX = runningProcess->registers[3];
int PC = runningProcess->PC;
int PSW = runningProcess->programStatusWord;
// 修改状态为运行中
runningProcess->status = RUNNING;
// 模拟进程执行
sleep(1); // 模拟执行时间
// 根据时间片和实际情况决定进程的去向
runningProcess->timeSlice--;
if (runningProcess->timeSlice > 0) {
runningProcess->status = READY;
addToReadyQueue(readyQueueHead, readyQueueTail, runningProcess);
printf("进程 %d 返回就绪队列。\n", runningProcess->processID);
}
else {
printf("进程 %d 时间片剩余: %d\n", runningProcess->processID, runningProcess->timeSlice);
// 将进程加入结束队列
addToFinishedQueue(&finishedQueueHead, &finishedQueueTail, runningProcess);
}
}
}
int main() {
// 定义就绪队列的头指针和尾指针
struct ProcessControlBlock* readyQueueHead = NULL;
struct ProcessControlBlock* readyQueueTail = NULL;
// 创建进程并加入就绪队列 (第三个参数表示时间片)
addToReadyQueue(&readyQueueHead, &readyQueueTail, createProcess(1, 3));
addToReadyQueue(&readyQueueHead, &readyQueueTail, createProcess(2, 2));
addToReadyQueue(&readyQueueHead, &readyQueueTail, createProcess(3, 1));
// 多次进行进程调度
for (int i = 0; i < 3; ++i) {
schedule(&readyQueueHead, &readyQueueTail);
}
//schedule(&readyQueueHead, &readyQueueTail);
return 0;
}
对以上代码进行一下解释防止有人看不到创建过程:
这段代码是一个简单的进程调度模拟程序,用于演示操作系统中的进程调度过程。让我逐步解释这段代码的主要部分:
首先定义了一个枚举类型
ProcessStatus
用来表示进程的状态,包括就绪(READY)、运行(RUNNING)和完成(FINISHED)三种状态。然后定义了一个结构体
ProcessControlBlock
,用来表示进程控制块,其中包括进程ID、状态、寄存器、程序计数器、程序状态字寄存器和剩余时间等信息。接着定义了结束队列的头指针和尾指针,并实现了将进程加入结束队列的函数
addToFinishedQueue
。创建进程控制块的函数
createProcess
用来初始化一个新的进程控制块,并将其加入就绪队列的函数addToReadyQueue
。从就绪队列中移除进程的函数
removeFromReadyQueue
,以及时间片轮转调度函数schedule
。最后在
main
函数中创建了几个进程,并进行了多次进程调度。在
main
函数中,首先创建了三个进程,并将它们加入就绪队列。然后进行了三次进程调度,每次调度会从就绪队列中取出一个进程来执行,模拟进程的运行,然后根据剩余时间片和实际情况将进程放回就绪队列或者移到结束队列。整体上,这段代码演示了一个简单的进程调度模拟过程,利用了就绪队列和结束队列来管理进程,以及时间片轮转调度的基本思想。
展示一下运行效果图(如果大家的运行效果是这样就说明完成了)
我使用是编译环境是DevC++大家也可以使用其他的编译环境。
难点讲解(记小本本,boos可能会问)
代码中的pcb在哪?
在这段代码中,“pcb”是指进程控制块(Process Control Block)的缩写。进程控制块是操作系统用来存储有关进程的信息的数据结构,包括进程状态、程序计数器、CPU寄存器、CPU调度信息等。
在这个代码示例中,ProcessControlBlock
结构体被定义为表示一个进程控制块。然后,该代码通过调用 createProcess
函数来创建新的进程控制块,并将它们添加到就绪队列中,这个队列由 readyQueueHead
和 readyQueueTail
指针管理。进程控制块的实例化是在 createProcess
函数中完成的,并且每个进程控制块都被分配了一个唯一的 processID
以及初始的时间片(timeSlice)。
在 main
函数中,通过以下代码创建了三个进程控制块,并将它们加入到就绪队列中:
addToReadyQueue(&readyQueueHead, &readyQueueTail, createProcess(1, 3));
addToReadyQueue(&readyQueueHead, &readyQueueTail, createProcess(2, 2));
addToReadyQueue(&readyQueueHead, &readyQueueTail, createProcess(3, 1));
每个 createProcess
调用都会返回一个指向新创建的 ProcessControlBlock
结构体的指针,这些指针被传递给 addToReadyQueue
函数来将进程加入就绪队列。
在 schedule
函数中,从就绪队列中移除进程(现在运行的进程),并根据时间片执行进程调度。如果进程的时间片耗尽,则它会被加入到结束队列中,这个队列由 finishedQueueHead
和 finishedQueueTail
指针管理。如果进程还有剩余的时间片,则它会被重新加入到就绪队列中,以便在下一次调度时再次被选中运行。
总的来说,“pcb”在这段代码里代表的是通过 createProcess
创建的各个 ProcessControlBlock
实例,它们分别代表不同的进程控制块,并且在就绪队列和结束队列之间移动。
这里面是如何创建链表的?
在这段代码中,链表的创建和操作主要涉及到两个队列:就绪队列(ready queue)和结束队列(finished queue)。每个队列都是通过单链表实现的,链表的节点由 ProcessControlBlock
结构体实例构成。
以下是链表相关的关键操作:
1.初始化队列头尾指针: 代码开始时,就绪队列和结束队列的头尾指针都被初始化为 NULL
,表示队列为空。
struct ProcessControlBlock* readyQueueHead = NULL;
struct ProcessControlBlock* readyQueueTail = NULL;
struct ProcessControlBlock* finishedQueueHead = NULL;
struct ProcessControlBlock* finishedQueueTail = NULL;
2.创建节点(进程控制块): createProcess
函数用于创建一个新的 ProcessControlBlock
节点,并返回指向该节点的指针。
struct ProcessControlBlock* createProcess(int processID, int timeSlice) {
// 分配内存并初始化进程控制块...
}
3.添加节点到就绪队列: addToReadyQueue
函数将新创建的进程控制块添加到就绪队列的尾部。
void addToReadyQueue(struct ProcessControlBlock** readyQueueHead, struct ProcessControlBlock** readyQueueTail, struct ProcessControlBlock* pcb) {
// 如果队列为空,则新节点即为头节点也是尾节点...
// 否则,将新节点添加到尾节点后面,并更新尾指针...
}
4.从就绪队列移除节点: removeFromReadyQueue
函数从就绪队列的头部移除一个节点并返回它,模拟了队列的出队操作。
struct ProcessControlBlock* removeFromReadyQueue(struct ProcessControlBlock** readyQueueHead, struct ProcessControlBlock** readyQueueTail) {
// 移除头部节点,并更新头指针...
}
5.添加节点到结束队列: addToFinishedQueue
函数将运行结束的进程控制块添加到结束队列的尾部。
void addToFinishedQueue(struct ProcessControlBlock** finishedQueueHead, struct ProcessControlBlock** finishedQueueTail, struct ProcessControlBlock* pcb) {
// 如果队列为空,则新节点即为头节点也是尾节点...
// 否则,将新节点添加到尾节点后面,并更新尾指针...
}
在 main
函数中,通过调用 addToReadyQueue
函数三次,创建了三个进程控制块并将它们加入到就绪队列中,这样就形成了一个包含三个节点的链表。
链表的每个节点都有一个 next
指针,用来指向下一个节点。当添加新节点到队列时,如果队列为空,则新节点既是头节点也是尾节点;如果队列不为空,则将新节点链接到当前尾节点的 next
指针上,并更新尾节点指针为新节点。这些操作确保了队列按照先进先出(FIFO)的顺序管理节点。
详细解释一下上面代码中的"时间片轮转调度"
在操作系统中,时间片轮转调度(Round-Robin Scheduling)是一种常用的CPU调度算法,它将固定的时间片(time slice)分配给每个就绪队列中的进程。当一个进程的时间片用完后,如果它还没有完成,它会被放回就绪队列的末尾等待下一次调度;如果完成了,则它会被移动到结束队列。
在上面的代码中,schedule
函数实现了时间片轮转调度的逻辑:
void schedule(struct ProcessControlBlock** readyQueueHead, struct ProcessControlBlock** readyQueueTail) {
// 从就绪队列头部移除一个进程用于运行
struct ProcessControlBlock* runningProcess = removeFromReadyQueue(readyQueueHead, readyQueueTail);
if (runningProcess != NULL) {
printf("进程 %d 正在运行...\n", runningProcess->processID);
// 修改状态为运行中
runningProcess->status = RUNNING;
// 模拟进程执行
sleep(1); // 模拟执行时间
// 根据时间片和实际情况决定进程的去向
runningProcess->timeSlice--;
if (runningProcess->timeSlice > 0) {
// 如果时间片没有用完,进程返回就绪队列
runningProcess->status = READY;
addToReadyQueue(readyQueueHead, readyQueueTail, runningProcess);
printf("进程 %d 返回就绪队列。\n", runningProcess->processID);
}
else {
// 如果时间片用完,进程加入结束队列
printf("进程 %d 时间片剩余: %d\n", runningProcess->processID, runningProcess->timeSlice);
addToFinishedQueue(&finishedQueueHead, &finishedQueueTail, runningProcess);
}
}
}
让我们逐步分析这段代码:
-
移除就绪队列中的进程:
removeFromReadyQueue
函数从就绪队列的头部移除一个进程控制块,模拟出队操作。被移除的进程控制块赋值给runningProcess
变量,表示这个进程将被调度执行。 -
检查进程是否存在: 如果
runningProcess
不为NULL
,则表示就绪队列中有可运行的进程。 -
打印运行信息: 输出当前正在运行的进程信息。
-
修改进程状态: 将
runningProcess
的状态设置为RUNNING
,表示进程正在执行。 -
模拟进程执行: 使用
sleep(1)
来模拟进程运行了一段时间。实际系统中,进程会执行实际的代码,但在这个简化的模拟中,只是等待一秒钟来表示进程的执行。 -
时间片递减: 进程的时间片
timeSlice
减一,模拟消耗了一个时间单位。 -
判断时间片是否用完: 如果
timeSlice
仍然大于零,说明进程还有剩余的时间片,可以在将来的某个时刻继续运行。此时,进程的状态重新设置为READY
,并通过addToReadyQueue
函数将其重新加入到就绪队列的末尾。 -
处理时间片用完的情况: 如果
timeSlice
不大于零,说明进程的时间片已经用完。在这个模拟中,并没有考虑进程是否已经完成其工作,而是直接将其加入到结束队列中,通过addToFinishedQueue
函数实现。
这个 schedule
函数在 main
函数中被循环调用,每次循环都会对一个进程进行调度。当所有进程都运行完毕后,它们都会被加入到结束队列中,这时就绪队列将为空。
这样就结束了,感谢大家的观看,百分百成功,不会出现运行不了的问题。但一定要按照小光的代码复制,全部复制粘贴就行了。
结语
关注小光,小光帮你写实验报告(不是真的帮你写,就是我写好,你直接复制拿走的那种)也可以看看小光的其他文章。
🌌点击下方个人名片,交流会更方便哦~(欢迎到博主主页加入我们的 CodeCrafters联盟一起交流学习)↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓