时间片轮转调度算法的模拟实现

时间片轮转调度算法的模拟实现

任务和要求

要求在充分理解时间片轮转调度算法原理的基础上,编写一个可视化的算法模拟程序。

具体任务如下:

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,则弹出警告对话框提示用户时间片不能为零。
  • 如果时间片大小有效,则禁用时间片输入控件,并设置开始调度标识符 startFtrue

3. 初始化进程队列

  • 创建一个临时队列 temp,并将所有进程队列 allPCBQu 中的元素复制到 temp 中。
  • 循环遍历临时队列 temp,根据 PCB 对象的当前指令类型,将其放入对应的等待队列中,即计算指令放入就绪等待队列,输入指令放入输入等待队列,输出指令放入输出等待队列,等待指令放入其他等待队列。获取就绪等待队列的第一个PCB对象作为当前运行进程,若就绪等待队列为空,则当前运行进程为空(None)。

4. 启动调度

  • 将日志文件中的时间片大小信息更新为用户输入的值。
  • 将日志文件中的调度开始信息更新为当前时间。
  • 设置开始调度标识符 startFtrue,开始执行调度算法。

5. 更新界面和日志

  • 将当前运行进程的进程名更新到界面控件 IDC_EDIT_this 中。
  • 将当前进程数量和总进程数量更新到界面控件 IDC_EDIT_currentIDC_EDIT_total 中。
  • 将就绪队列、输入等待队列、输出等待队列和其他等待队列中的 PCB 对象的进程名输出到日志文件中。

3. 界面设计

本程序的用户界面使用 MFC 库进行设计,采用经典的对话框布局,将界面元素分为三个主要区域:

1. 顶部工具栏

  • 该区域位于界面顶部,包含以下功能按钮:
    • 打开文件: 允许用户选择并打开一个文本文件,文件中包含模拟进程的描述信息。
    • 开始调度: 启动轮转调度算法,开始模拟进程调度过程。
    • 暂停调度: 暂停当前调度过程,用户可以随时继续调度。
    • 重置: 重置调度器状态,清空所有队列,准备下一次调度。
    • 时间片大小: 允许用户设置时间片的大小,控制进程执行的时间长度。

2. 中间进程信息显示区域

  • 该区域位于界面中间,用于实时展示当前调度进程的信息,包括:
    • 当前运行进程: 显示当前正在运行的进程名。
    • 当前剩余进程数: 显示当前剩余未完成的进程数量。
    • 总进程数: 显示所有进程的总数量。

3. 底部队列状态展示区域

  • 该区域位于界面底部,分为四个部分,分别展示不同状态下的进程列表:
    • 就绪队列: 显示当前处于就绪状态的进程列表。
    • 输入等待队列: 显示当前处于输入等待状态的进程列表。
    • 输出等待队列: 显示当前处于输出等待状态的进程列表。
    • 其他等待队列: 显示当前处于其他等待状态的进程列表。

界面交互

  • 用户可以通过点击工具栏上的按钮来控制进程调度过程。
  • 用户可以在“时间片大小”编辑框中输入时间片的大小,并点击“开始调度”按钮启动调度。
  • 用户可以通过“暂停调度”按钮暂停当前调度过程,并随时点击“开始调度”按钮继续调度。(暂停后再开始,会出现调度错误)
  • 用户可以通过“重置”按钮重置调度器状态,清空所有队列,准备下一次调度。

4. 功能实现代码

时间片轮转调度算法

  1. 使用定时器模拟调度执行
  2. 调度当前运行进程
    • 检查当前运行进程是否为空(None)。
    • 若存在,则分配一个时间片给该进程执行,即该进程执行的指令操作时间减去时间片。若为空(None)则执行调度等待队列。
    • 如果该进程在时间片内完成,即执行的指令操作时间小于或等于0,则指令指针指向下一条指令。
    • 检查该进程的第一条指令类型,放入相应的等待队列。
    • 获取就绪等待队列的第一个进程作为当前运行进程,并将其从就绪等待队列中移除。
  3. 调度等待队列
    • 循环开始时的等待队列(避免后面放入的进程影响调度)。
    • 获取输入、输出、其他等待队列的第一个对象。
    • 给该进程对象分配一个时间片给该进程执行,即该进程执行的指令操作时间减去时间片。
    • 如果该进程在时间片内完成,即执行的指令操作时间小于或等于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 类打开文件,并逐行读取文件内容。

    • 使用 CStringstring 类型转换函数将文件内容转换为字符串。

    • 解析字符串,并根据字符串的第一个字符判断是进程信息还是指令信息。

    • 如果是进程信息,则创建一个新的 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

posted @ 2024-12-30 12:45  末雨摸鱼  阅读(147)  评论(0)    收藏  举报