实验1 实验环境的使用
- 实验目的
- 熟悉操作系统集成实验环境 OS Lab 的基本使用方法。
- 练习编译、调试 EOS 操作系统内核以及 EOS 应用程序。
二、实验正文
- 启动 OS Lab
- 学习 OS Lab 的基本使用方法
- 新建 Windows 控制台应用程序项目
- 生成项目
- 执行项目
- 调试项目
- 使用断点中断执行
- 单步调试
- 查看变量的值
- 调用堆栈
- EOS 内核项目的生成和调试
- 新建 EOS 内核项目
- 生成项目
- 调试项目
- 查看软盘镜像文件中的内容
- 查看 EOS SDK(Software Development Kit)文件夹
- EOS 应用程序项目的生成和调试
- 新建 EOS 内核项目
- 生成项目
- 调试项目
- 查看软盘镜像文件中的内容
- 修改 EOS 应用程序项目名称
- 退出 OS Lab
- 保存 EOS 内核项目
三、综合实验总结或结论
第一次上机进行EOS实验,熟悉了实验环境的基本使用方法,并初步掌握了调试程序的方法
四、思考题
- 练习使用单步调试功能(逐过程、逐语句),体会在哪些情况下应该使用“逐过程”调试,在哪些情况下应该使用“逐语句”调试。练习使用各种调试工具(包括“监视”窗口、“调用堆栈”窗口等)。
逐语句,就是每次执行一行语句,如果碰到函数调用,它就会进入到函数里面。 而逐过程,碰到函数时,不进入函数,而是把函数调用当成一条语句执行。 所以,在需要进入函数体时用逐语句调试,不需要进入函数体时用逐过程调试。
- 思考生成 EOS SDK 文件夹的目的和作用。查看 EOS SDK 文件夹中的内容,明白文件夹的组织结构和各个文件的来源和作用。查看 EOS 应用程序包含了 SDK 文件夹中的哪些头文件,是如何包含的?
- (1)EOS SDK为应⽤程序调⽤系统API提供服务,可作为⽤户编程中使⽤的⼯具包集合。
(2)其主要包括INC头⽂件LIB⽂件夹、导⼊库⽂件和BIN⽂件夹、动态链接库、可执⾏程序、⼆进制⽂件。
(3)包含的头⽂件有:eos.h负责导出API函数,eosdef.h声明负责导出函数类型的定义,error.h负责导出错误码。
(4)EOS应⽤程序在项⽬的头⽂件中只是包含了eos.h⽂件,在eos.h⽂件中⼜包含了eosdef.h和error.h⽂件。
实验2 操作系统的启动
- 实验目的
- 跟踪调试 EOS 在 PC 机上从加电复位到成功启动的全过程,了解操作系统的启动过程。
- 查看 EOS 启动后的状态和行为,理解操作系统启动后的工作方式。
二、实验正文
- 准备实验
- 调试 EOS 操作系统的启动过程
2.1使用 Bochs 做为远程目标机
2.2使用 Bochs 做为远程目标机
2.3 调试软盘引导扇区程序
2.4 调试加载程序
2.5 调试内核
2.6 EOS 启动后的状态和行为
三、综合实验总结或结论
通过此次实验,我了解了操作系统的启动过程和操作系统启动后的工作方式。
四、思考题
- 为什么 EOS 操作系统从软盘启动时要使用 boot.bin 和 loader.bin 两个程序?使用一个可以吗?它们各自的主要功能是什么?如果将 loader.bin 的功能移动到 boot.bin 文件中,则 boot.bin 文件的大小是否仍然能保持小于 512 字节?
在IDE环境启动执⾏EOS操作系统时,会将boot.bin,loader.bin,kernal.dll三个
⼆进制写⼊软件镜像⽂件中,然后让虚拟机来执⾏软盘中的EOS操作系统,使⽤其中⼀个是不能运⾏的。
- 软盘引导扇区加载完毕后内存中有两个用户可用的区域,为什么软盘引导扇区程序选择将 loader.bin加载到第一个可用区域的 0x1000 处呢?这样做有什么好处?这样做会对 loader.bin 文件的大小有哪些限制。
- ⽤户只⽤两个可⽤区域,加载位置⾮此即彼。第⼀个可⽤⽤户区是低地址区,且空间⼤⼩⽐较⼩,适合容纳⼩⽂件,所以我们选择将占⽤空loder.bin加载到第⼀⽤户区。优点:低地址开始,便于搜索查找⼩⽂件占⽤⼩空间,节约资源。限制:loder.bin⽂件必须⼩于1c00k。
实验3 进程的创建
- 实验目的
- 练习使用 EOS API 函数 CreateProcess 创建一个进程,掌握创建进程的方法,理解进程和程序的区别。
- 调试跟踪 CreateProcess 函数的执行过程,了解进程的创建过程,理解进程是资源分配的单位。
二、实验正文
- 准备实验
- 练习使用控制台命令创建 EOS 应用程序的进程
- 练习通过编程的方式让应用程序创建另一个应用程序的进程
- 调试 CreateProcess 函数
- 调试 PsCreateProcess 函数
- 练习通过编程的方式创建应用程序的多个进程
三、综合实验总结或结论
通过此次实验,我掌握了创建进程的方法,加深了对进程程序区别的理解。
四、思考题
- 在源代码文件 NewTwoProc.c 提供的源代码基础上进行修改,要求使用 hello.exe 同时创建 10 个进程。提示:可以使用 PROCESS_INFORMATION 类型定义一个有 10 个元素的数组,每一个元素对应一个进程。使用一个循环创建 10 个子进程,然后再使用一个循环等待 10 个子进程结束,得到退出码后关闭句柄。
部分代码为:
STAETUPINFO StartupInfo:
PROCESS_INFORMATION p[10];
INT nResult=0;
ULONG ulExitCode;
BOOL n[10];
Int I;
printf(“Create 10 processes and wait for the process exit… \n\n”);
StartupInfo.StdInput=GetStdHandle(STD_INPUT_HANDLE);
StartupInfo.StdOutput=GetStdHandle(STD_OUTPUT_HANDLE);
StartupInfo.StdError=GetStdHandle(STD_ERROR_HANDLE);
for(i=0;i<10;i++)
{
n[i]=CreateProcess(“A:\\Hello.exe”,NULL,0,&StartupInfo,&p[i]);
}
for(i=0;i<10;i++)
{
if(n[i])
{
WaitForSingleObject(p[i].ProcessHandle,INFINITE);
}
}
for(i=0;i<10;i++)
{
if(n[i])
{
GetExitCodeProcess(p[i].ProcessHandle,&ulExitCode);
printf(“\nThe process %d exit with %d.\n”,i+1, ulExitCode);
}
}
for(i=0;i<10;i++)
{
if(n[i])
{
CloseHandle(p[i].ProcessHandle);
CloseHandle(p[i].ThreadHandle);
}
}
for(i=0;i<10;i++)
{
if(!n[i])
{
printf(“create process failed.error code: 0x%X.\n”,GetLastError());
nResult=1;
}
}
Return nResult;
}
执行结果:
- 在 PsCreateProcess 函数中调用了 PspCreateProcessEnvironment 函数后又先后调用了PspLoadProcessImage 和 PspCreateThread 函数,学习这些函数的主要功能。能够交换这些函数被调用的顺序吗?思考其中的原因。
- PspCreateProcessEnvironment 创建了进程控制块,地址空间和分配了句柄表,PspLoadProcessImage将进程的可执⾏映象加载的到了进程的地址空间中,PspCreateThread 创建了进程的主线程。这三个函数知道⾃⼰从哪⾥开始执⾏,执⾏哪些指令,因此不能交换它们的顺序。
实验4 线程的状态和转换
- 实验目的
- 调试线程在各种状态间的转换过程,熟悉线程的状态和转换。
- 通过为线程增加挂起状态,加深对线程状态的理解。
二、实验正文
- 准备实验
- 调试线程状态的线程由阻塞状态进入就绪状态转换过程
2.1 线程由阻塞状态进入就绪状态
2.2 线程由运行状态进入就绪状态
2.3 线程由就绪状态进入运行状态
2.4 线程由运行状态进入阻塞状态
- 为线程增加挂起状态
三、综合实验总结或结论
通过此次实验,我理解了线程和状态的转换并调试了转换过程,明⽩了loop指令的⽤法以及resume原语的结构。
四、思考题
- 思考一下,在本实验中,当 loop 线程处于运行状态时,EOS 中还有哪些线程,它们分别处于什么状态。可以使用控制台命令 pt 查看线程的状态。
有⼀个优先级为0的空闲线程处于就绪状态,8个优先级为24的控制台线程处于阻塞状态,1个优先级的24的控制台派遣线程处于阻塞状态。
- 当 loop 线程在控制台 1 中执行,并且在控制台 2 中执行 suspend 命令时,为什么控制台 1 中的 loop线程处于就绪状态而不是运行状态?
- 在控制台2 中执⾏suspend 命令时,优先级为24的控制台2线程抢占处理器,即控制台2线程处于运⾏状态,因此此时loop处于就绪状态。
- 总结一下在图 5-3 中显示的转换过程,哪些需要使用线程控制块中的上下文(将线程控制块中的上下文恢复到处理器中,或者将处理器的状态复制到线程控制块的上下文中),哪些不需要使用,并说明原因。
就绪→运⾏,运⾏→就绪,运⾏→阻塞需要使⽤TCB因为这些过程有线程调进或调出处理机的过程,新建→就绪,阻塞→就绪不需要使⽤TCB上下⽂,因为没有占⽤处理机资源。
- 在本实验 3.2 节中总结的所有转换过程都是分步骤进行的,为了确保完整性,显然这些转换过程是不应该被打断的,也就是说这些转换过程都是原语操作(参见本书第 2.6 节)。请读者找出这些转换过程的原语操作(关中断和开中断)是在哪些代码中完成的。(提示,重新调试这些转换过程,可以在调用堆栈窗口列出的各个函数中逐级查找关中断和开中断的代码。)
IntState=KeEnableInterrupts(FALSE);//关中断
KeEnableInterrupts(IntState);//开中断
实验5 进程的同步
- 实验目的
- 使用 EOS 的信号量,编程解决生产者—消费者问题,理解进程同步的意义。
- 调试跟踪 EOS 信号量的工作过程,理解进程同步的原理。
- 修改 EOS 的信号量算法,使之支持等待超时唤醒功能(有限等待),加深理解进程同步的原理。
二、实验正文
- 准备实验
- 使用 EOS 的信号量解决生产者-消费者问题
- 调试 EOS 信号量的工作过程
3.1 创建信号量
3.2 等待、释放信号量
3.2.1 等待信号量(不阻塞)
3.2.2 释放信号量(不唤醒)
3.2.3 等待信号量(阻塞)
3.2.4 释放信号量(唤醒)
- 修改 EOS 的信号量算法
修改后程序如图:
运行结果如图:
三、综合实验总结或结论
通过此次实验,我对生产者消费者问题的理解得到了加深,通过实验直观的看到了生产者消费者问题的过程。
四、思考题
- 思考在 ps/semaphore.c 文件内的 PsWaitForSemaphore 和 PsReleaseSemaphore 函数中,为什么要使 用原子操作?可以参考本书第 2 章中的第 2.6 节。
EOS 内核中维护了⼤量内核数据,正是这些数据描述了EOS操作系统的状态如果有⼀组相互关联的内核数据共同描述了这个操作系统的某个状态,那么在修改这样⼀组内核数据时就必须保证⼀致性。这就要求修改这部分数据的代码在执⾏过程中不能被打断,这种操作叫做“原语操作”。
2、绘制 ps/semaphore.c 文件内 PsWaitForSemaphore 和 PsReleaseSemaphore 函数的流程图。
3、根据本实验 3.3.2 节中设置断点和调试的方法,自己设计一个类似的调试方案来验证消费者线程在消费 24 号产品时会被阻塞,直到生产者线程生产了 24 号产品后,消费者线程才被唤醒并继续执行的过程。
运行结果如图:
实验12 读文件和写文件
- 实验目的
- 了解在 EOS 应用程序中读文件和写文件的基本方法。
- 通过为 FAT12 文件系统添加写文件功能,加深对 FAT12 文件系统和磁盘存储器管理原理的理解。
二、实验正文
- 准备实验
- 编写代码调用 EOS API 函数读取文件中的数据
- 调试 FAT12 文件系统的读文件功能
- 为 FAT12 文件系统添加写文件功能
4.1 完成一个最简单的情况
4.2 使 FatWriteFile 函数写入文件的数据可以跨越一个扇区的边界
4.3 使 FatWriteFile 函数写入文件的数据可以跨越多个扇区的边界
三、综合实验总结或结论
通过此次实验,我了解了EOS程序中读写文件的基本方法,加深了对存储器管理的理解
四、思考题
- 结合 FAT12 文件系统,说明“文件大小”和“文件占用磁盘空间大小”的区别,并举例说明文件的这 两个属性值变化的方式有什么不同。
为了更好地管理磁盘空间和更高效地从硬盘读取数据,操作系统规定一个簇中只能放置一个文件的内容,因此文件所占用的空间,只能是簇的整数倍;而如果文件实际大小小于一簇,它也要占一簇的空间。所以,一般情况下文件所占空间要略大于文件的实际大小,只有在少数情况下,即文件的实际大小恰好是簇的整数倍时,文件的实际大小才会与所占空间完全一致。
- 分析使用链式分配方式管理磁盘空间的优缺点。
优点
1、提高了磁盘空间利用率,不需要为每个文件预留物理块。
2、有利于文件插入和删除。
3、有利于文件动态扩充。
缺点
1、存取速度慢,不适于随机存取。
2、当物理块间的连接指针出错时,数据丢失。
3、更多的寻道次数和寻道时间。
4、链接指针占用一定的空间,降低了空间利用率。
综合实验1 处理机调度
一、 综合实验的目的与要求
该实践环节是对学生掌握操作系统知识的综合检验,同时考察学生系统程序设计与编码能力。要求学生理解用户操作的流程,合理设计操作系统结构,明确操作系统各功能模块的联系,能够编码实现操作系统常用功能模块。
二、实验正文
- 系统总体结构设计
1.1 设计PCB模块,包括进程信息(名称,指向下一进程指针,到达时间,要求运行时间,已经运行实践,运行状态)
1.2 设计checkin()方法,来检查后备队列是否有准备好的进程入队。设计checkif()方法,来判断当前进行的进程是否能用完一个时间片。设计run()方法,来进行时间片轮转调度进程。设计putin()方法,来进行数据输入。
- 功能模块实现
- 数据结构模块
struct PCB {
char name; //名称
char Pointer; //指向下一进程指针
int arrivetime; //到达时间
int questiontime; //要求运行的时间
int havetime; //已经运行的时间
char state; //R就绪,E结束
};
//通过设计PCB结构,来存储每个进程的信息,并进行之后的进程调度。
-
- 检查后备队列是否有准备好的进程入队
void checkin(queue<PCB>& backlist, deque<PCB>& readylist, int nowtime) //检查后备队列是否有准备好的进程入队
{
if ((backlist.front().arrivetime <= nowtime))
{
PCB p = backlist.front();
readylist.push_front(p);
backlist.pop();
}
}
//通过判断后备队列中每个进程的到达时间是否小于当前时间,如果小于就进入就绪队列。
-
- 判断当前进行的进程是否能用完一个时间片
//如果当前进程剩余时间小于时间片长度,则运行完当前进程后开启一个新的时间片运行就绪队列中下一进程。在拿出下一进程前要先判断就绪队列是否有进程入队。当前时间加上当前进程的剩余时间。
如果当前进程剩余时间大于时间片长度,对当前进程执行一个时间片的长度,然后检查后备队列后,将未执行完进程放入就绪队列等待下一次执行。
-
- 进行时间片轮转调度
void run(deque<PCB>& readylist, queue<PCB>& backlist, int& nowtime, int timeslice) //进行时间片轮转调度进程
{
while ((!readylist.empty()) || (!backlist.empty()))
{
if (!backlist.empty())
{
checkin(backlist, readylist, nowtime); //检查后备队列是否有调入 ?
}
if (readylist.empty() && (!backlist.empty())) //准备队列为空,但后备队列不为空
{
//进行操作
cout << "当前时间 " << nowtime << " 无进程正在进行" << endl;
nowtime = nowtime + 1;
}
else
{
//PCB p = readylist.front();
checkif(readylist, backlist, nowtime, timeslice); //对准备队列第一个进程进行时间片分配
}
}
}
//如果后备队列和就绪队列都不为空时,先检查是否有调入,如果就绪队列为空,后被队列不为空,进行入队。否则通过调用checkif()方法进行调度。
-
- 输入方法,输入进程数,时间片长度,进程信息等
void putin(queue<PCB>& backlist, int& n, int& timeslice)
{
cout << "请输入进程数:";
cin >> n;
cout << "请输入时间片长度:";
cin >> timeslice;
for (int i = 1; i <= n; i++)
{
PCB p;
cout << "请输入第" << i << "个进程的名称:";
cin >> p.name;
cout << "请输入第" << i << "个进程的到达时间:";
cin >> p.arrivetime;
cout << "请输入第" << i << "个进程的服务时间:";
cin >> p.questiontime;
p.havetime = 0;
p.state = 'R';
backlist.push(p);
}
}
-
- 最后在main()函数中调用以上方法来实现时间片轮转调度
int main()
{
queue<PCB> backlist;
deque<PCB> readylist;
int n = 0, nowtime = 0, timeslice = 0;
putin(backlist, n, timeslice);
cout << endl << "————————以下为输出————————" << endl;
run(readylist, backlist, nowtime, timeslice);
}
- 模块集成实现
- 在 checkif()方法中调用checkin()方法,来进行是否有进程到达的判断及入队
- 在run()方法中进行模块的集成,实现时间片轮转调度。
三、综合实验总结或结论
- 在此次实验中,通过c++编程实现了时间片轮转方法,我对时间片轮转方法的理解更深刻。在实验中,第一次采用的方法是将新到达的进程插入就绪队列尾端,得到了与书上不同的结果,经过查阅资料后发现书上采用将新到达的进程插入就绪队列对头,修改后得到与书上相同结果。
附录(设计流程图、程序、表格、数据等)
完整程序:
#include<iostream>
#include<queue>
using namespace std;
struct PCB {
char name; //名称
char Pointer; //指向下一进程指针
int arrivetime; //到达时间
int questiontime; //要求运行的时间
int havetime; //已经运行的时间
char state; //R就绪,E结束
};
void checkin(queue<PCB>& backlist, deque<PCB>& readylist, int nowtime) //检查后备队列是否有准备好的进程入队
{
if ((backlist.front().arrivetime <= nowtime))
{
PCB p = backlist.front();
readylist.push_front(p);
backlist.pop();
}
}
void checkif(deque<PCB>& readylist, queue<PCB>& backlist, int& nowtime, int timeslice) //判断当前进行的进程是否能用完一个时间片
{
if (!readylist.empty())
{
PCB p = readylist.front();
if ((p.questiontime - p.havetime) > timeslice) //剩余时间比时间片长
{
p.havetime += timeslice;
readylist.pop_front();
cout << "时间 " << nowtime << " 正在进行的进程为:" << p.name << endl;
nowtime += timeslice;
if (!backlist.empty())
{
checkin(backlist, readylist, nowtime);
}
readylist.push_back(p);
if (!readylist.empty()) //找到下一个进程
{
PCB w = readylist.front();
p.Pointer = w.name;
}
else
{
if (!backlist.empty())
{
PCB w = backlist.front();
p.Pointer = w.name;
}
else
p.Pointer = 'N';
}
cout << " " << p.name << "的初始状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << 0 << " " << 'R' << " " << 'N' << endl;
cout << " " << p.name << "的当前状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << p.havetime << " " << p.state << " " << p.Pointer << endl;
}
else
{
nowtime = nowtime + (p.questiontime - p.havetime);
p.havetime = p.questiontime;
p.state = 'E';
readylist.pop_front();
if (!readylist.empty()) //找到下一个进程
{
PCB w = readylist.front();
p.Pointer = w.name;
}
else
{
if (!backlist.empty())
{
PCB w = backlist.front();
p.Pointer = w.name;
}
else
p.Pointer = 'N';
}
cout << "时间 " << nowtime << " 正在进行的进程为:" << p.name << endl;
cout << " " << p.name << "的初始状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << 0 << " " << 'R' << " " << 'N' << endl;
cout << " " << p.name << "的当前状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << p.havetime << " " << p.state << " " << p.Pointer << endl;
// cout << " " << p.name << "的要求运行时间为:" << p.questiontime << " 已运行时间为:" << p.havetime << endl;
// cout << "进程 " << p.name << " 已经运行完" << endl;
}
}
}
void run(deque<PCB>& readylist, queue<PCB>& backlist, int& nowtime, int timeslice) //进行时间片轮转调度进程
{
while ((!readylist.empty()) || (!backlist.empty()))
{
if (!backlist.empty())
{
checkin(backlist, readylist, nowtime); //检查后备队列是否有调入 ?
}
if (readylist.empty() && (!backlist.empty())) //准备队列为空,但后备队列不为空
{
//进行操作
cout << "当前时间 " << nowtime << " 无进程正在进行" << endl;
nowtime = nowtime + 1;
}
else
{
//PCB p = readylist.front();
checkif(readylist, backlist, nowtime, timeslice); //对准备队列第一个进程进行时间片分配
}
}
}
void putin(queue<PCB>& backlist, int& n, int& timeslice)
{
cout << "请输入进程数:";
cin >> n;
cout << "请输入时间片长度:";
cin >> timeslice;
for (int i = 1; i <= n; i++)
{
PCB p;
cout << "请输入第" << i << "个进程的名称:";
cin >> p.name;
cout << "请输入第" << i << "个进程的到达时间:";
cin >> p.arrivetime;
cout << "请输入第" << i << "个进程的服务时间:";
cin >> p.questiontime;
p.havetime = 0;
p.state = 'R';
backlist.push(p);
}
}
int main()
{
queue<PCB> backlist;
deque<PCB> readylist;
int n = 0, nowtime = 0, timeslice = 0;
putin(backlist, n, timeslice);
cout << endl << "————————以下为输出————————" << endl;
run(readylist, backlist, nowtime, timeslice);
}
运行结果:
综合实验2 主存储器空间的分配和回收
一、 综合实验的目的与要求
该实践环节是对学生掌握操作系统知识的综合检验,同时考察学生系统程序设计与编码能力。要求学生理解用户操作的流程,合理设计操作系统结构,明确操作系统各功能模块的联系,能够编码实现操作系统常用功能模块。
二、实验正文
- 系统总体结构设计
设计randomout()方法,来 按要求生成320个随机数。设计change()方法,来将指令序列改为页地址流。设计checkifin()方法,来判断当前指令页是否在内存中。设计print()方法,来打印内存中页号。设计FIFO()方法,来用队列实现先进先出算法。
- 功能模块实现
2.1按要求生成320个随机数
void randomout(queue<int> &bbacklist)
{
int k = 0;
srand(GetTickCount64());
while (k < 320)
{
int m = rand() % 319;
bbacklist.push(m + 1);
k++;
int m1 = rand() % (m + 1);
bbacklist.push(m1);
k++;
bbacklist.push(m1 + 1);
k++;
//randomValue = (rand() % (max - min + 1)) + min;//范围[min,max]
int m2 = (rand() % (319 - (m1 + 2) + 1)) + (m1 + 2);
bbacklist.push(m2);
k++;
}
}//通过设计PCB结构,来存储每个进程的信息,并进行之后的进程调度。
2.2 将指令序列改为页地址流
void change(queue<int> bbacklist, queue<int>& backlist)
{
int k = 0;
while (k < 320)
{
int p = bbacklist.front();
bbacklist.pop();
// cout << p << " ";
backlist.push(p / 10);
k++;
cout << p / 10 << " ";
}
}
//题目中要求每页10个进程,将随机生成的进程对10求余数,来找到在哪页中。
2.3 判断当前指令页是否在内存中
bool checkifin(queue<int> remlist, int p)
{
int len = remlist.size();
int q;
for (int i = 0; i < len; i++)
{
q = remlist.front();
if (p == q)
return true;
remlist.pop();
if (!remlist.empty())
q = remlist.front();
else
return false;
}
return false;
}
//在内存中查找每一页,如果找到与当前页相同页号,返回true,否则返回false。
2.4 用队列实现先进先出算法
void FIFO(queue<int> backlist, queue<int> &remlist, int n, int& less)
{
int count = 1;
while (!backlist.empty())
{
int p = backlist.front();
cout << "当前轮次:" << count << " 内存内容:";
if (checkifin(remlist, p)) //如果当前指令页在内存中
{
backlist.pop(); //指令队列弹出当前指令
print(remlist, n);
cout << endl;
}
else //如果不在
{
int l = remlist.size();
if (l < n) //如果内存还没满,直接进内存
{
remlist.push(p);
backlist.pop();
less++;
print(remlist, n);
}
else //如果内存已经满了,则置换内存中最先进的
{
remlist.pop();
remlist.push(p);
backlist.pop();
less++;
print(remlist, n);
}
cout << " 未命中";
cout << endl;
}
count++;
}
}
//如果当前指令页存在内存中,从指令队列中取出当前指令,执行下一条。如果不存在于内存中,检查内存是否已满,如果未满直接进入内存中。如果已满,则置换出内存中最先进入的指令,然后执行下一条指令。
- 模块集成实现
- 在 FIFO()方法中调用checkinfin()方法,来判断当前指令是否在内存中。
2.在main()函数中及进行模块集成实现,先输入内存大小,然后随机产生指令,并转换为页地址流。然后调用FIFO()方法,进行先进先出置换。
int main()
{
int n = 0, less = 0;
cout << "请输入内存大小:";
cin >> n;
queue<int> bbacklist;
queue<int> backlist;
queue<int> remlist;
randomout(bbacklist);
cout << endl << "——————以下为随机产生的指令所在的页数————————" << endl;
change(bbacklist, backlist);
cout << endl << "——————————以下为置换——————————" << endl;
FIFO(backlist, remlist, n, less);
cout << "缺页数:" << less << endl;
cout << "缺页率:";
float s = (less / 320.0 * 1.0) * 100;
float ss;
cout << s << "%";
cout << endl << "命中率:";
ss = 100 - s;
cout << ss << "%";
}
三、综合实验总结或结论
在此次实验中,通过c++编程实现了先进先出指令置换方法,加深了我对置换算法的理解。在设计中,将整个的功能分解为小的部分,分别实现各小块功能,最后集合起来实现最终功能。
附录(设计流程图、程序、表格、数据等)
设计流程图:
完整代码:
#include<iostream>
#include<queue>
#include<windows.h>
using namespace std;
void randomout(queue<int> &bbacklist) //按要求生成320个随机数
{
int k = 0;
srand(GetTickCount64());
while (k < 320)
{
int m = rand() % 319;
bbacklist.push(m + 1);
k++;
int m1 = rand() % (m + 1);
bbacklist.push(m1);
k++;
bbacklist.push(m1 + 1);
k++;
//randomValue = (rand() % (max - min + 1)) + min;//范围[min,max]
int m2 = (rand() % (319 - (m1 + 2) + 1)) + (m1 + 2);
bbacklist.push(m2);
k++;
}
}
void change(queue<int> bbacklist, queue<int>& backlist) //将指令序列改为页地址流
{
int k = 0;
while (k < 320)
{
int p = bbacklist.front();
bbacklist.pop();
// cout << p << " ";
backlist.push(p / 10);
k++;
cout << p / 10 << " ";
}
}
bool checkifin(queue<int> remlist, int p) //判断当前指令页是否在内存中
{
int len = remlist.size();
int q;
for (int i = 0; i < len; i++)
{
q = remlist.front();
if (p == q)
return true;
remlist.pop();
if (!remlist.empty())
q = remlist.front();
else
return false;
}
return false;
}
void print(queue<int> remlist, int n) //打印内存中页号
{
int len = remlist.size();
for (int i = 1; i <= len; i++)
{
int p = remlist.front();
remlist.pop();
cout << p << " ";
}
if (len < n)
{
for (int i = 1; i <= (n - len); i++)
{
cout << 0 << " ";
}
}
}
void FIFO(queue<int> backlist, queue<int> &remlist, int n, int& less) //用队列实现先进先出算法
{
int count = 1;
while (!backlist.empty())
{
int p = backlist.front();
cout << "当前轮次:" << count << " 内存内容:";
if (checkifin(remlist, p)) //如果当前指令页在内存中
{
backlist.pop(); //指令队列弹出当前指令
print(remlist, n);
cout << endl;
}
else //如果不在
{
int l = remlist.size();
if (l < n) //如果内存还没满,直接进内存
{
remlist.push(p);
backlist.pop();
less++;
print(remlist, n);
}
else //如果内存已经满了,则置换内存中最先进的
{
remlist.pop();
remlist.push(p);
backlist.pop();
less++;
print(remlist, n);
}
cout << " 未命中";
cout << endl;
}
count++;
}
}
int main()
{
int n = 0, less = 0;
cout << "请输入内存大小:";
cin >> n;
queue<int> bbacklist;
queue<int> backlist;
queue<int> remlist;
randomout(bbacklist);
cout << endl << "——————以下为随机产生的指令所在的页数————————" << endl;
change(bbacklist, backlist);
cout << endl << "——————————以下为置换——————————" << endl;
FIFO(backlist, remlist, n, less);
cout << "缺页数:" << less << endl;
cout << "缺页率:";
float s = (less / 320.0 * 1.0) * 100;
float ss;
cout << s << "%";
cout << endl << "命中率:";
ss = 100 - s;
cout << ss << "%";
}
运行结果:
综合实验3 模块集成
一、 综合实验的目的与要求
该实践环节是对学生掌握操作系统知识的综合检验,同时考察学生系统程序设计与编码能力。要求学生理解用户操作的流程,合理设计操作系统结构,明确操作系统各功能模块的联系,能够编码实现操作系统常用功能模块。
二、实验正文
- 系统总体结构设计
1.1设计PCB结构,用来记录进程信息,与实验一不同的是,PCB模块增加了对每个进程指令信息的记录。设计neicun结构,来记录内存信息,包括容量和内存内容
1.2 包含实验一实验二中的全部方法,具体是在实验一的基础上给每个进程增加了指令信息,在每次进程调度时进行指令进出内存。
2. 功能模块实现
主要是修改了实验一中的checkif()方法,在里面加上了实验二中的指令置换方法。
- 模块集成实现
在checkif()方法中在每次执行进程时,进行进程各自指令的置换。
三、综合实验总结或结论
在进行综合集成的过程中,刚开始不知道从哪里下手,通过对操作系统运行过程进行分析后明白了每个进程的指令执行工过程。设计时我令每个时间片每个进程进行6条指令,但没有考虑每条指令执行时间不同,程序存在一定缺陷,但基本完成了主要功能。
附录(设计流程图、程序、表格、数据等)
完整程序:
#include<iostream>
#include<queue>
#include<windows.h>
using namespace std;
struct PCB {
char name; //名称
char Pointer; //指向下一进程指针
int arrivetime; //到达时间
int questiontime; //要求运行的时间
int havetime; //已经运行的时间
char state; //R就绪,E结束
int zhi;
queue<int> zhis;
queue<int> zhiye;
int less;
};
struct neicun {
int big; //内存大小
queue<int> remlist;
};
void checkin(queue<PCB>& backlist, deque<PCB>& readylist, int nowtime) //检查后备队列是否有准备好的进程入队
{
if ((backlist.front().arrivetime <= nowtime))
{
PCB p = backlist.front();
readylist.push_front(p);
backlist.pop();
}
}
bool checkifin(queue<int> remlist, int p) //判断当前指令页是否在内存中
{
int len = remlist.size();
int q;
for (int i = 0; i < len; i++)
{
q = remlist.front();
if (p == q)
return true;
remlist.pop();
if (!remlist.empty())
q = remlist.front();
else
return false;
}
return false;
}
void print(queue<int> remlist, int n) //打印内存中页号
{
int len = remlist.size();
for (int i = 1; i <= len; i++)
{
int p = remlist.front();
remlist.pop();
cout << p << " ";
}
if (len < n)
{
for (int i = 1; i <= (n - len); i++)
{
cout << 0 << " ";
}
}
}
void FIFO(queue<int> backlist, queue<int>& remlist, int n, int& less) //用队列实现先进先出算法
{
int count = 1;
while (!backlist.empty())
{
int p = backlist.front();
cout << "当前轮次:" << count << " 内存内容:";
if (checkifin(remlist, p)) //如果当前指令页在内存中
{
backlist.pop(); //指令队列弹出当前指令
print(remlist, n);
cout << endl;
}
else //如果不在
{
int l = remlist.size();
if (l < n) //如果内存还没满,直接进内存
{
remlist.push(p);
backlist.pop();
less++;
print(remlist, n);
}
else //如果内存已经满了,则置换内存中最先进的
{
remlist.pop();
remlist.push(p);
backlist.pop();
less++;
print(remlist, n);
}
cout << " 未命中";
cout << endl;
}
count++;
}
}
void checkif(deque<PCB>& readylist, queue<PCB>& backlist, int& nowtime, int timeslice,neicun &nei) //判断当前进行的进程是否能用完一个时间片
{
if (!readylist.empty())
{
PCB p = readylist.front();
if ((p.questiontime - p.havetime) > timeslice) //剩余时间比时间片长,一个时间片6条指令
{
p.havetime += timeslice;
readylist.pop_front();
cout << "时间 " << nowtime << " 正在进行的进程为:" << p.name << endl;
nowtime += timeslice;
//指令
int kk = 6;
while (!p.zhiye.empty() && kk > 0)
{
//FIFO(p.zhiye, nei.remlist, nei.big, p.less);
int pp = p.zhiye.front();
// cout << "当前轮次:" << count << " 内存内容:";
if (checkifin(nei.remlist, pp)) //如果当前指令页在内存中
{
p.zhiye.pop(); //指令队列弹出当前指令
print(nei.remlist, nei.big);
cout << endl;
}
else //如果不在
{
int l = nei.remlist.size();
if (l < nei.big) //如果内存还没满,直接进内存
{
nei.remlist.push(pp);
p.zhiye.pop();
p.less++;
print(nei.remlist, nei.big);
}
else //如果内存已经满了,则置换内存中最先进的
{
nei.remlist.pop();
nei.remlist.push(pp);
p.zhiye.pop();
p.less++;
print(nei.remlist, nei.big);
}
cout << " 未命中";
cout << endl;
}
kk--;
}
if (!backlist.empty())
{
checkin(backlist, readylist, nowtime);
}
readylist.push_back(p);
if (!readylist.empty()) //找到下一个进程
{
PCB w = readylist.front();
p.Pointer = w.name;
}
else
{
if (!backlist.empty())
{
PCB w = backlist.front();
p.Pointer = w.name;
}
else
p.Pointer = 'N';
}
cout << " " << p.name << "的初始状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << 0 << " " << 'R' << " " << 'N' << endl;
cout << " " << p.name << "的当前状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << p.havetime << " " << p.state << " " << p.Pointer << endl;
}
else
{
nowtime = nowtime + (p.questiontime - p.havetime);
p.havetime = p.questiontime;
p.state = 'E';
readylist.pop_front();
if (!readylist.empty()) //找到下一个进程
{
PCB w = readylist.front();
p.Pointer = w.name;
}
else
{
if (!backlist.empty())
{
PCB w = backlist.front();
p.Pointer = w.name;
}
else
p.Pointer = 'N';
}
cout << "时间 " << nowtime << " 正在进行的进程为:" << p.name << endl;
cout << " " << p.name << "的初始状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << 0 << " " << 'R' << " " << 'N' << endl;
cout << " " << p.name << "的当前状态为:" << p.name << " " << p.arrivetime << " " << p.questiontime << " " << p.havetime << " " << p.state << " " << p.Pointer << endl;
// cout << " " << p.name << "的要求运行时间为:" << p.questiontime << " 已运行时间为:" << p.havetime << endl;
// cout << "进程 " << p.name << " 已经运行完" << endl;
int kk = 6;
while (!p.zhiye.empty() && kk > 0)
{
//FIFO(p.zhiye, nei.remlist, nei.big, p.less);
int pp = p.zhiye.front();
// cout << "当前轮次:" << count << " 内存内容:";
if (checkifin(nei.remlist, pp)) //如果当前指令页在内存中
{
p.zhiye.pop(); //指令队列弹出当前指令
print(nei.remlist, nei.big);
cout << endl;
}
else //如果不在
{
int l = nei.remlist.size();
if (l < nei.big) //如果内存还没满,直接进内存
{
nei.remlist.push(pp);
p.zhiye.pop();
p.less++;
print(nei.remlist, nei.big);
}
else //如果内存已经满了,则置换内存中最先进的
{
nei.remlist.pop();
nei.remlist.push(pp);
p.zhiye.pop();
p.less++;
print(nei.remlist, nei.big);
}
cout << " 未命中";
cout << endl;
}
kk--;
}
}
}
}
void run(deque<PCB>& readylist, queue<PCB>& backlist, int& nowtime, int timeslice,neicun &nei) //进行时间片轮转调度进程
{
while ((!readylist.empty()) || (!backlist.empty()))
{
if (!backlist.empty())
{
checkin(backlist, readylist, nowtime); //检查后备队列是否有调入 ?
}
if (readylist.empty() && (!backlist.empty())) //准备队列为空,但后备队列不为空
{
//进行操作
cout << "当前时间 " << nowtime << " 无进程正在进行" << endl;
nowtime = nowtime + 1;
}
else
{
//PCB p = readylist.front();
checkif(readylist, backlist, nowtime, timeslice,nei); //对准备队列第一个进程进行时间片分配
}
}
}
void randomout(queue<int>& bbacklist,int mm) //按要求生成mm个随机数
{
int k = 0;
srand(GetTickCount64());
while (k < mm)
{
int m = rand() % (mm-1);
bbacklist.push(m + 1);
k++;
int m1 = rand() % (m + 1);
bbacklist.push(m1);
k++;
bbacklist.push(m1 + 1);
k++;
//randomValue = (rand() % (max - min + 1)) + min;//范围[min,max]
int m2 = (rand() % ((mm-1) - (m1 + 2) + 1)) + (m1 + 2);
bbacklist.push(m2);
k++;
}
}
void change(queue<int> bbacklist, queue<int>& backlist,int mm,int big) //将指令序列改为页地址流
{
int k = 0;
while (k < mm)
{
int p = bbacklist.front();
bbacklist.pop();
// cout << p << " ";
backlist.push(p / big);
k++;
cout << p / big << " ";
}
}
void putin(queue<PCB>& backlist, int& n, int& timeslice,neicun &nei)
{
cout << "请输入进程数:";
cin >> n;
cout << "请输入时间片长度:";
cin >> timeslice;
for (int i = 1; i <= n; i++)
{
PCB p;
cout << "请输入第" << i << "个进程的名称:";
cin >> p.name;
cout << "请输入第" << i << "个进程的到达时间:";
cin >> p.arrivetime;
cout << "请输入第" << i << "个进程的服务时间:";
cin >> p.questiontime;
cout << "请输入第" << i << "个进程的指令条数:";
cin >> p.zhi;
//随机生成zhi条指令
randomout(p.zhis, p.zhi);
change(p.zhis, p.zhiye,p.zhi,nei.big);
cout << endl;
p.havetime = 0;
p.state = 'R';
backlist.push(p);
}
}
int main()
{
queue<PCB> backlist;
deque<PCB> readylist;
neicun nei;
cout << "请输入内存大小:";
cin >> nei.big;
int n = 0, nowtime = 0, timeslice = 0;
putin(backlist, n, timeslice,nei);
cout << endl << "————————以下为输出————————" << endl;
run(readylist, backlist, nowtime, timeslice,nei);
}
运行结果: