时间片轮转调度算法的模拟实现
时间片轮转调度算法的模拟实现
任务和要求
要求在充分理解时间片轮转调度算法原理的基础上,编写一个可视化的算法模拟程序。
具体任务如下:
1、根据需要,合理设计PCB结构,以适用于时间片轮转调度算法;
2、设计模拟指令格式,并以文件形式存储,程序能够读取文件并自动生成指令序列。
3、根据文件内容,建立模拟进程队列,并能采用时间片轮转调度算法对模拟进程进行调度。
任务要求:
1、进程的个数,进程的内容(即进程的功能序列)来源于一个进程序列描述文件。
2、需将调度过程输出到一个运行日志文件。
3、开发平台及语言不限(尽量避免使用python)。
4、要求设计一个Windows可视化应用程序。
模拟进程文件
模拟指令的格式:操作命令+操作时间
- C : 表示在CPU上计算
- I : 表示输入
- O : 表示输出
- W : 表示等待
- H : 表示进程结束
操作时间代表该操作命令要执行多长时间(时间片个数)。这里假设 I/O 设备的数量没有限制,I 和 O 设备都只有一类。
I,O,W三条指令实际上是不占有CPU的,执行这三条指令就应该将进程放入对应的等待队列(输入等待队列,输出等待队列 ,其他等待队列)。
1. 程序架构
模块划分:
- 文件读取模块:负责读取进程序列描述文件。
- 进程管理模块:负责创建进程队列。
- 调度模块:负责执行调度算法。
- 日志记录模块:负责记录调度过程。
- 用户界面模块:负责显示控件和调度过程。
模块关系:
- 文件读取模块将读取的进程信息传递给进程调度模块。
- 进程调度模块将进程队列信息传递给调度模块。
- 调度模块将调度结果传递给日志记录模块和用户界面模块。
2. 程序执行逻辑描述
1. 检查是否打开文件
- 检查所有进程队列
allPCBQu是否为空,如果为空,则表示用户尚未打开模拟进程文件。 - 弹出警告对话框提示用户打开文件,并调用
OnBnClickedButtonopen()函数打开文件对话框。
2. 获取时间片大小
- 从界面控件
IDC_EDIT_slice中获取用户输入的时间片大小,并将其转换为整数类型。 - 如果时间片大小为 0,则弹出警告对话框提示用户时间片不能为零。
- 如果时间片大小有效,则禁用时间片输入控件,并设置开始调度标识符
startF为true。
3. 初始化进程队列
- 创建一个临时队列
temp,并将所有进程队列allPCBQu中的元素复制到temp中。 - 循环遍历临时队列
temp,根据PCB对象的当前指令类型,将其放入对应的等待队列中,即计算指令放入就绪等待队列,输入指令放入输入等待队列,输出指令放入输出等待队列,等待指令放入其他等待队列。获取就绪等待队列的第一个PCB对象作为当前运行进程,若就绪等待队列为空,则当前运行进程为空(None)。
4. 启动调度
- 将日志文件中的时间片大小信息更新为用户输入的值。
- 将日志文件中的调度开始信息更新为当前时间。
- 设置开始调度标识符
startF为true,开始执行调度算法。
5. 更新界面和日志
- 将当前运行进程的进程名更新到界面控件
IDC_EDIT_this中。 - 将当前进程数量和总进程数量更新到界面控件
IDC_EDIT_current和IDC_EDIT_total中。 - 将就绪队列、输入等待队列、输出等待队列和其他等待队列中的
PCB对象的进程名输出到日志文件中。
3. 界面设计
本程序的用户界面使用 MFC 库进行设计,采用经典的对话框布局,将界面元素分为三个主要区域:
1. 顶部工具栏
- 该区域位于界面顶部,包含以下功能按钮:
- 打开文件: 允许用户选择并打开一个文本文件,文件中包含模拟进程的描述信息。
- 开始调度: 启动轮转调度算法,开始模拟进程调度过程。
- 暂停调度: 暂停当前调度过程,用户可以随时继续调度。
- 重置: 重置调度器状态,清空所有队列,准备下一次调度。
- 时间片大小: 允许用户设置时间片的大小,控制进程执行的时间长度。
2. 中间进程信息显示区域
- 该区域位于界面中间,用于实时展示当前调度进程的信息,包括:
- 当前运行进程: 显示当前正在运行的进程名。
- 当前剩余进程数: 显示当前剩余未完成的进程数量。
- 总进程数: 显示所有进程的总数量。
3. 底部队列状态展示区域
- 该区域位于界面底部,分为四个部分,分别展示不同状态下的进程列表:
- 就绪队列: 显示当前处于就绪状态的进程列表。
- 输入等待队列: 显示当前处于输入等待状态的进程列表。
- 输出等待队列: 显示当前处于输出等待状态的进程列表。
- 其他等待队列: 显示当前处于其他等待状态的进程列表。
界面交互
- 用户可以通过点击工具栏上的按钮来控制进程调度过程。
- 用户可以在“时间片大小”编辑框中输入时间片的大小,并点击“开始调度”按钮启动调度。
- 用户可以通过“暂停调度”按钮暂停当前调度过程,并随时点击“开始调度”按钮继续调度。(暂停后再开始,会出现调度错误)
- 用户可以通过“重置”按钮重置调度器状态,清空所有队列,准备下一次调度。
4. 功能实现代码
时间片轮转调度算法:
- 使用定时器模拟调度执行
- 调度当前运行进程
- 检查当前运行进程是否为空(None)。
- 若存在,则分配一个时间片给该进程执行,即该进程执行的指令操作时间减去时间片。若为空(None)则执行调度等待队列。
- 如果该进程在时间片内完成,即执行的指令操作时间小于或等于0,则指令指针指向下一条指令。
- 检查该进程的第一条指令类型,放入相应的等待队列。
- 获取就绪等待队列的第一个进程作为当前运行进程,并将其从就绪等待队列中移除。
- 调度等待队列
- 循环开始时的等待队列(避免后面放入的进程影响调度)。
- 获取输入、输出、其他等待队列的第一个对象。
- 给该进程对象分配一个时间片给该进程执行,即该进程执行的指令操作时间减去时间片。
- 如果该进程在时间片内完成,即执行的指令操作时间小于或等于0,则指令指针指向下一条指令。
- 检查该进程的第一条指令类型,放入相应的等待队列,并将其从原来的等待队列中移除。
具体代码如下:
BOOL CMFCQSRRDlg::OnInitDialog()
{
//……框架生成代码……
// TODO: 在此添加额外的初始化代码
SetTimer(TIMERID, 100, 0); // 设置定时器,时间片大小为定时器调用时间间隔,1000为1秒,
//……框架生成代码……
}
//开始定时器调度
void CMFCQSRRDlg::OnTimer(UINT_PTR nIDEvent)
{
if (startF && currentPCBnum > 0) {
//处理三个等待队列的PCB
runInput();
runOutput();
runOther();
if (!readyQu.empty())
{
currentPCB = readyQu.front();
readyQu.pop();
}
else {
currentPCB = NULL;
}
if (currentPCB == NULL) {
thisPCB = _T("NONE");
}
else {
thisPCB = currentPCB->getPName().c_str();
}
// 输出至控件
q2e(readyQu, IDC_EDIT_CONTENT);
q2e(inputQu, IDC_EDIT_CONTENT2);
q2e(outputQu, IDC_EDIT_CONTENT3);
q2e(otherQu, IDC_EDIT_CONTENT4);
GetDlgItem(IDC_EDIT_this)->SetWindowText(thisPCB);
CString str;
str.Format(_T("%d"), currentPCBnum);
GetDlgItem(IDC_EDIT_current)->SetWindowText(str);
// 输出至日志
outfile << "============================ " << schedulingTimes++ << " ============================" << endl;
outfile << "当前进程数量:" << currentPCBnum << endl;
outfile << "当前运行进程:" << c2s(thisPCB) << endl;
outLog(readyQu, "就绪队列");
outLog(inputQu, "输入等待队列");
outLog(outputQu, "输出等待队列");
outLog(otherQu, "其他等待队列");
if (currentPCBnum <= 0) {
outfile << endl << "============================ 调度完成 ============================" << endl;
AfxMessageBox(_T("调度完成!"), MB_OK | MB_ICONINFORMATION);
}
// 处理当前运行进程
if (currentPCB != NULL) {
currentPCB->runInstruction(timeSlice);
InstructionType type = currentPCB->getNextI()->getType(); //获取当前pcb的第一条指令的类型
switch (type)
{
default:
break;
case COMPUTE:
readyQu.push(currentPCB);
break;
case INPUT1:
inputQu.push(currentPCB);
break;
case OUTPUT:
outputQu.push(currentPCB);
break;
case WAIT:
otherQu.push(currentPCB);
break;
case HALT:
currentPCB = NULL;
currentPCBnum--;
break;
}
}
}
}
文件读取功能在 OnBnClickedButtonopen () 函数中实现,具体如下:
-
使用
CFileDialog类创建一个文件对话框 -
调用
DoModal()函数显示文件对话框,并等待用户选择文件。 -
读取文件内容
-
如果用户选择了文件,则获取文件的路径并创建日志文件。
-
使用
CStdioFile类打开文件,并逐行读取文件内容。 -
使用
CString和string类型转换函数将文件内容转换为字符串。 -
解析字符串,并根据字符串的第一个字符判断是进程信息还是指令信息。
-
如果是进程信息,则创建一个新的
PCB对象,并设置进程名和进程标识符。 -
如果是指令信息,则创建一个新的
instruction对象,并设置指令名、指令类型和运行时间。 -
将指令对象添加到
PCB对象的指令序列中。
-
-
更新界面和日志
- 将所有进程队列的信息输出到日志文件中。
- 更新进程总数和初始化完成的信息到日志文件中。
具体代码如下:
// 打开文件按钮
void CMFCQSRRDlg::OnBnClickedButtonopen()
{
// TODO: 在此添加控件通知处理程序代码
// 创建文件对话框
CFileDialog fileDlg(TRUE, _T("txt"), NULL, OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, _T("文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*||"), this);
// 显示文件对话框
if (fileDlg.DoModal() == IDOK){
// 获取用户选择的文件路径
CString FilePath = fileDlg.GetPathName();
// 创建日志
createLogFile();
outfile << "开始读取模拟进程文件:" << c2s(FilePath) << endl;
// 打开文件并读取内容
CStdioFile file(FilePath, CFile::modeRead);
CString cstrContent;
string data;
PCB* currentPCB = nullptr;// 当前进程
instruction* currentInstruction = nullptr;// 当前指令
instruction* lastInstruction = nullptr;// 最后一条指令
// 处理文件内容
while (file.ReadString(cstrContent))
{
data = c2s(cstrContent);// CString => string
if (data[0] == 'P') {
// 创建新的PCB
PCB* newPCB = new PCB();
newPCB->setPName(data);
newPCB->setPID(stoi(data.substr(1)));
currentPCB = newPCB;
totalPCBnum++;// 计算PCB总数
currentPCBnum++;// 计算当前PCB数
allPCBQu.push(currentPCB);// 将pcb装入队列中
currentInstruction = nullptr;
lastInstruction = nullptr;
}
else {
// 创建新的instruction
instruction* newInstruction = new instruction();
newInstruction->setCName(data);
newInstruction->setType(data[0]);
newInstruction->setRuntime(stoi(data.substr(1)));
// 当前指令指针为空,说明进程指针未指向指令
if (currentInstruction == nullptr) {
currentPCB->setNextI(newInstruction);
currentInstruction = newInstruction;
}
else {
lastInstruction->setNext(newInstruction);
}
lastInstruction = newInstruction;
currentPCB->setEndI(lastInstruction);
}
}
file.Close();
outLog(allPCBQu, "所有进程队列");
outfile << "进程总数:" << totalPCBnum << endl
<< "初始化完成!" << endl
<< "===========================================================" << endl;
}
}
日志创建功能在createLogFile()函数中实现,具体如下:
- 设置日志文件路径和名称
- 创建日志文件
- 如果文件打开成功,则将当前时间戳和日志文件创建成功的提示信息写入文件。
- 如果文件打开失败,则使用
AfxMessageBox()函数弹出错误消息对话框。
具体代码如下:
// CPRC.h
// 创建日志文件
void createLogFile() {
CString logFilePath = _T("log\\");
CString logFileName = _T("log ");
// 使用CTime获取当前时间
CTime time = CTime::GetCurrentTime();
CString timeT = time.Format(_T("%Y-%m-%d %H-%M-%S"));
logFileName += timeT;
// 添加文件扩展名
logFileName += _T(".txt");
// 创建文件
outfile.open((LPCTSTR)logFilePath + logFileName, ios::out | ios::app);
if (outfile.is_open()) {
// 文件创建成功,可以写入内容
outfile << c2s(timeT) << "\t日志文件创建成功!" << endl;
}
else {
// 文件创建失败,弹出错误消息
AfxMessageBox(_T("无法创建日志文件!"), MB_ICONERROR);
return;
}
}
将队列中 PCB 对象的进程名输出到日志文件的功能在outLog()函数中实现,具体如下:
- 复制队列避免在遍历队列时修改原始队列,导致逻辑错误。
- 输出队列信息
- 使用
outfile文件流对象将队列名称quName写入日志文件。 - 使用循环遍历临时队列
temp,获取每个PCB对象的进程名,并将其写入日志文件。 - 使用空格分隔不同的进程名。
- 使用
具体代码如下:
// CPRC.h
// 输出至日志文件
void outLog(queue<PCB*>qu, string quName) {
queue<PCB*> temp = qu;
string strText;
outfile << quName << ":";
while (!temp.empty()) {
outfile << temp.front()->getPName()/* <<"("<<temp.front()->getNextI()->getCName() << ")"*/<<" ";
temp.pop(); // 移除队列头部的元素
}
outfile << endl;
}
处理不同队列中 PCB 对象的功能在各自执行函数中实现,具体如下:
1. 就绪队列执行 (runReady)
- 循环遍历就绪队列
readyQu,获取每个PCB对象。 - 获取
PCB对象的当前指令类型。 - 如果指令类型为
COMPUTE,则将该PCB对象设置为当前运行进程currentPCB,并将其从就绪队列中移除。 - 如果指令类型为其他类型,则将该
PCB对象放入对应的等待队列中,并将其从就绪队列中移除。
2. 输入队列执行 (runInput)
- 循环遍历输入等待队列
inputQu,获取每个 PCB 对象。 - 执行
PCB对象的当前指令,并更新运行时间。 - 获取
PCB对象的当前指令类型。 - 根据指令类型,将该
PCB对象放入对应的队列中,并将其从输入等待队列中移除。
3. 输出队列执行 (runOutput)
- 循环遍历输出等待队列
outputQu,获取每个PCB对象。 - 执行
PCB对象的当前指令,并更新运行时间。 - 获取
PCB对象的当前指令类型。 - 根据指令类型,将该
PCB对象放入对应的队列中,并将其从输出等待队列中移除。
4. 其他队列执行 (runOther)
- 循环遍历其他等待队列
otherQu,获取每个PCB对象。 - 执行
PCB对象的当前指令,并更新运行时间。 - 获取
PCB对象的当前指令类型。 - 根据指令类型,将该
PCB对象放入对应的队列中,并将其从其他等待队列中移除。
具体代码如下:
// 就绪队列运行在调度时实现
// 输入队列执行
void CMFCQSRRDlg::runInput() {
if (!inputQu.empty()) {
int n = inputQu.size();
for (int i = 0; i < n; i++) {
PCB* pcb = inputQu.front();
pcb->runInstruction(timeSlice);
InstructionType type = pcb->getNextI()->getType(); //获取当前pcb的第一条指令的类型
switch (type)
{
default:
break;
case COMPUTE:
readyQu.push(pcb);
break;
case INPUT1:
inputQu.push(pcb);
break;
case OUTPUT:
outputQu.push(pcb);
break;
case WAIT:
otherQu.push(pcb);
break;
case HALT:
delete pcb;
currentPCBnum--;
break;
}
//outfile << endl << "===========test==============" << endl;
//outfile << pcb->getPName() << " " << pcb->getRunTime() << endl;
//outLog(readyQu, "就绪队列");
//outLog(inputQu, "输入等待队列");
//outLog(outputQu, "输出等待队列");
//outLog(otherQu, "其他等待队列");
//outfile << endl << "============test=============" << endl;
inputQu.pop();
}
//exchange(inputQu);
}
}
// 输出队列执行
void CMFCQSRRDlg::runOutput() {
if (!outputQu.empty()) {
int n = outputQu.size();
for (int i = 0; i < n; i++) {
PCB* pcb = outputQu.front();
pcb->runInstruction(timeSlice);
InstructionType type = pcb->getNextI()->getType(); //获取当前pcb的第一条指令的类型
switch (type)
{
default:
break;
case COMPUTE:
readyQu.push(pcb);
break;
case INPUT1:
inputQu.push(pcb);
break;
case OUTPUT:
outputQu.push(pcb);
break;
case WAIT:
otherQu.push(pcb);
break;
case HALT:
delete pcb;
currentPCBnum--;
break;
}
outputQu.pop();
}
}
}
// 其他队列执行
void CMFCQSRRDlg::runOther() {
if (!otherQu.empty()) {
int n = otherQu.size();
for (int i = 0; i < n; i++) {
PCB* pcb = otherQu.front();
pcb->runInstruction(timeSlice);
InstructionType type = pcb->getNextI()->getType(); //获取当前pcb的第一条指令的类型
switch (type)
{
default:
break;
case COMPUTE:
readyQu.push(pcb);
break;
case INPUT1:
inputQu.push(pcb);
break;
case OUTPUT:
outputQu.push(pcb);
break;
case WAIT:
otherQu.push(pcb);
break;
case HALT:
delete pcb;
currentPCBnum--;
break;
}
otherQu.pop();
}
}
}
详细代码如下:
GitHub:MFC-OS-RR

浙公网安备 33010602011771号