本系统裸机上模拟硬件开始,进行了对计算机虚拟页式存储管理系统的仿真,通过对内存、外存、存储管理部件、缺页中断机构等硬件的模拟,以及对进程的PCB,页表等软件结构的模拟,以请求分页的方式,实现了先来先服务、非剥夺动态优先级两种进程调度算法和LRU、FIFO、SCR、CLOCK四种页面置换算法,模拟了操作系统中分配内存外存、地址映射转换、缺页中断处理、进程调度、页面置换等过程,并将整个运行过程可视化地展现了出来。
效果 :
运行的一些情况均会保存下来于文档中,具体自己看源码。
源码地址:
备注:因为源码里有一些隐私信息,之前是想找个时间好好改的,但是有些人一直在私信里要,加上自己犯懒,
我就简单的把隐私信息抹掉了放上来了,因为新电脑没有QT我也没有调试,所以可能直接打开QT project是打不开的,
核心代码全部在dialog.cpp里,注释非常详细,只要认真看,百分百能看懂,再说,核心就是这些C++代码,你不做界面的话,直接copy也能控制台给你输出了- -。exe我也没有放,想要直接下载一份源码,改个名字就交作业的抱歉了,但是,因为这个项目简单,你直接重头开始建项目,建文件把我代码复制进去,重新构建,稍微调试下应该也是很简单的。总之还是希望好好学吧,本身是不难的。
随便截图举个例子就知道注释很详细了,因为老师当时的要求我几乎是逐行注释了,看一下就知道怎么写了。
另外,这个代码,是我初学操作系统这门课写的,瑕疵很多,另老师要求按书上的算法,举个例子如果是LRU,时间上可以有更好的做法,详情请看Leetcode上的LRU哈。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 目的及意义
2 课程设计思路及完成任务与功能说明
2.1 课程设计思路
此次课程设计,从裸机上的硬件仿真开始,由下而上,先对内存、外存、存储管理部件、缺页中断机构等硬件部件进行仿真,再对上层的软件进行仿真,设计了先来先服务、非剥夺动态优先级调度两种进程调度算法和LRU、SCR、FIFO、CLCOK四种页面置换算法,整个系统从随机创建产生进程开始,到进程运行,再到进程运行过程中指令的执行,再到指令执行过程中内存的分配与页面的置换,一层一层,直到最后系统中进程全部运行完毕结束。模拟了进程的并发环境与请求分页虚拟存储管理系统,最后再进行界面制作。
2.2 完成任务
完成了对请求分页虚拟存储管理系统的仿真,其中,实现了先来先服务、非剥夺动态优先级调度两种进程调度算法下的进程调度;LRU、SCR、FIFO、CLCOK四种页面置换算法下的页面置换。
2.3 功能说明
本系统可以根据用户输入的进程个数随机产生进程,展现进程从产生,到全部运行完毕的过程。系统中的时间变化,某个时刻运行的进程,某个时刻运行的指令,以及每个时刻内存占用页面的变化等均可以在界面上直观地查看。运行的总体信息以及外存、内存的分配的情况也可以方便地通过界面上的按钮打开相关文件查看。
3 裸机硬件部件仿真设计
3.1 外存
外存大小2000KB,分为500块,每块4KB,每块的属性有三个:
- 地址 (2)装入的页面 (3)占用标志位
外存块:
class diskentry
{
public:
diskentry()
{
dflag=0;
}
int diskaddress;
int dpage;//装入的页面即装入的内容
int dflag;//外存是否页面装入标志位=1为占用
};
外存:vector<diskentry> disk(500);
3.2 内存
内存大小64KB,分为16页,每页4KB,每个物理页框的属性有三个:
(1)装入的页面 (2)占用标志位 (3)LRU算法中用到的计数器
物理页框:
class pageframe
{
public:
pageframe()//初始化
{
counter=0;
flag0=0;
}
int flag0;//该页框是否有页装入的标志
int page;//装入的页面
int counter;//LRU算法中用到的计数器
};
内存:vector<pageframe> memorytable(16);
3.3 页表基址寄存器
由于页表基址寄存器里只保存了当前活动页表的基址,所有用一个整型数据替代:
int ptr;
3.4 存储管理部件mmu
mmu的功能设计如下:1 根据页表基址寄存器里的活动页表的基址,找到当前运行进程的页表;2 根据当前指令的逻辑地址,获得页号,偏移地址;3 对比页号与页表长度,如发生越界中断,返回错误;4 根据页号查找快表,如果命中拼接获得并送出物理地址;5 快表未命中,查找页表,如果命中拼接获得送出物理地址,并写入快表;6 页表未命中,发出缺页中断。获得缺页中断返回的物理页框号,拼接获得并送出物理地址。
用函数模拟了mmu的功能:参数为:逻辑地址,当前执行的指令,一个页面可视化需要的指针,返回值为物理地址。
int mmu(int logicaladdress,int instructnum,Ui::Dialog *dis)//mmu
{
int i,f;
QString tempStr;
//根据页表基址获得页表 逻辑地址17位 页号5位 偏移地址12位
int pagenumber=logicaladdress>>11;//获得页号
int offsetaddress=logicaladdress&0x07ff;//获得偏移地址
int physicaladdress;//转化后的物理地址
int framenumber;//页框号
if(pagenumber>runningprocess.pagetablelength-1)//如果页号大于页表长度-1
{
return error;//发生越界中断
}
else{
//int position=pagetableaddress+pagenumber*pagetableentrylen;//将页表始址与页号和页表项长度的乘积相加,得到该页表项在页表中的位置
for( i=0;i<tlb.size();i++)//搜索快表 如果快表里没有记录 那肯定不在快表里( tlb.size==0)
{
if(pagenumber==tlb[i].pagenumber)//命中
{
framenumber=tlb[i].framenumber;
physicaladdress=(framenumber<<12)+offsetaddress;//拼接物理地址
memorytable[framenumber].counter=0;//LRU 算法中用到的计数器
for(int p=0;p<3;p++)
{
if(runningprocess.pf[p]!=-1&&runningprocess.pf[p]!=framenumber)
{
memorytable[p].counter++;//其他装有页面的页框对应的计数器加1
}
}
pgtlist[ptr][pagenumber].r=1;//命中,引用位修改为1
memorytable[framenumber].page=pagenumber;//装入的页面的页号
return physicaladdress;
}
}
//快表未命中
for(i=0;i<pgtlist[ptr].size();i++)//搜索进程页表 如果进程页表size为0 一定不命中
{
if(pagenumber==pgtlist[ptr][i].pagenumber)//命中 {
if(pgtlist[ptr][i].flag==1)//如果该页面在主存
{
framenumber=pgtlist[ptr][i].framenumber;
physicaladdress=(framenumber<<12)+offsetaddress;//拼接物理地址
memorytable[framenumber].counter=0;
for(int p=0;p<3;p++)
{
if(runningprocess.pf[p]!=-1&&runningprocess.pf[p]!=framenumber)
{
memorytable[p].counter++;//其他装有页面的页框对应的计数器加1
}
}
memorytable[framenumber].page=pagenumber;
pgtlist[ptr][pagenumber].r=1;//命中,引用位修改为1
//memorytable[framenumber].flag0=1;本来就是已占用
//写入快表
if(tlb.size()==3)
{
tlb[0].pagenumber=pagenumber;
tlb[0].framenumber=framenumber;
}
else
{//创建新的快表项
tlbentry newentry;
newentry.pagenumber=pagenumber;
newentry.framenumber=framenumber;
tlb.push_back(newentry);
}
return physicaladdress;
}
}
else{}
}
//进程页表未命中
//缺页
runningprocess.instructnum=instructnum;
framenumber=pagehandling(pagenumber,dis);//缺页异常处理
system_time=system_time+1;
for(int g=0;g<n;g++)
{
if(pcblist[g].intime==system_time)
ReadyQueue.push_back(pcblist[g]);
}
physicaladdress=(framenumber<<12)+offsetaddress;//拼接物理地址
WaitQueue.erase(WaitQueue.begin());
return physicaladdress;
}//else{}
}
3.5 缺页中断机构
缺页中断机构功能:1 为发生缺页中断的页面分配物理内存;2 如果该进程获得的物理内存已经达到固定局部分配策略给的的物理页框数,则选择页面置换算法进行页面置换;3 修改快表和页表信息。
用一个函数模拟,参数为缺页的页号,一个页面可视需要的指针,返回值为分配的物理块号。
int pagehandling(int pagenumber,Ui::Dialog *dis)//
{
//挂起请求调页的进程
QString tempStr;
runningprocess.state=block;
WaitQueue.push_back(runningprocess);//进程等待 假设等待==挂起
DISKFLAG=1;
int framenumber;
//根据调度策略调度进程
//暂时不放cpu
//根据页号搜索对应的外存地址 这里应该设个时间- -假设从磁盘调入某页内容到内存为t(ns)
for(int i=0;i<pgtlist[ptr].size();i++)
{
if(pagenumber==pgtlist[ptr][i].pagenumber)
{
int diskaddress=pgtlist[ptr][i].diskaddress;//获取辅存地址
//查看内存中有无空闲页框,有则分配一个,根据固定分配局部置换策略分配(这个页框指从分配的固定物理块数中取,如果固定物理块数满,不可能再拿一个空的出来)
ofile.close();
if(runningprocess.framecount<3)
{
int l=fenpei(3,pagenumber);//分配物理内存
if(l!=error)
{
it=memorytable.begin();
framenumber=l;
runningprocess.framecount=runningprocess.framecount+1;//a占用的页框数增加
for(int k=0;k<3;k++)
{
if(runningprocess.pf[k]==-1)
{
runningprocess.pf[k]=framenumber;//修改进程中信息
break;
}
}
}
//写入页表
pgtlist[ptr][i].framenumber=l;
//写入快表
if(tlb.size()==3)
{//按先入先出淘汰 其实 如果快表的size为3的话肯定有页面置换,不会执行到这里
tlb[0].pagenumber=pagenumber;
tlb[0].framenumber=framenumber;
}
else
{//创建新的快表项
tlbentry newentry;
newentry.pagenumber=pagenumber;
newentry.framenumber=framenumber;
tlb.push_back(newentry);
}
return framenumber;
}
else
{
//根据置换算法选择页面替换
tempStr=dis->comboBox_2->currentText();
if(tempStr=="SCR")
{
framenumber=Scr(pagenumber);//SCR调换算法
}
else if(tempStr=="LRU")
{
framenumber=Lru(pagenumber);
}
else if(tempStr=="FIFO")
{
framenumber=Fifo(pagenumber);
}
else if(tempStr=="CLOCK")
{
framenumber=Clock(pagenumber);
}
//调度完毕后返回到原进程发生中断的地方执行
return framenumber;
running(n);
return framenumber;
}
}
}
}
3.6 快表
快表是一块小容量的相联存储器,由高速缓存器组成,速度快,设计快表可以存储3个页表项的页号和对应的物理框号。
快表项:
class tlbentry//快表表项
{
public:
int pagenumber;//页号
int framenumber;//页框号
};
快表:vector<tlbentry> tlb(0);
3.7 CPU与时钟
CPU只需要提供一个现在正在CPU运行的进程信息,时钟只需要一个可以自动增加的时间单位,所有都只用一个整型值替代
int runningprocess;//当前正占用CPU的进程
int system_time;//系统当前时间,初始化为最早到达的进程的到达时间,执行一条指令需要1个时间单位,发生缺页中断到调页后恢复运行页需要1个时间单位
4 通用数据结构设计
4.1 进程的PCB结构设计
PCB类的属性有:进程状态、进程id、进程优先级、进程页表基址、页表长度、指令数目、到达时间等。其中pcb0()函数对各属性进行初始化,并输出打印一些信息。
class pcb
{
public:
int i,j;
int state;//进程状态
int pid;//进程标识符
int priority;//进程优先级 数值越小优先级越高
int pagetableaddress;//页表起始地址
int pagetablelength;//页表长度 (页表项个数)
int pagetableentrylen;//页表项长度 16位
int framecount;//已占用的物理块数
int intime;//进程到达时间
int pf[3];//记录占用了哪些物理页框的数组
int instructcount;//指令的数目
//假设一条指令占1页
int instructaddress;//第一条指令的首地址(逻辑地址)
int instructnum;//当前执行到哪条指令
int inss;//当前执行到哪条指令(这里的位置指的是在指令序列容器里的下标)
vector<int> instructorder;//如果这里写vector<int> instructorder(0);报错syntax error:'constant' 因为这样等于初始化了大小 得在构造函数里初始化 或者不初始化
void pcb0()//初始化{
ofile.open("d:\\xxx.txt",ios::app);//打开记录文件,以追加的方式写入 入口调用了Writedown()函数,该文件一定存在了
instructorder.resize(0);
state=runnable;
priority=(rand()%10)+1;
pid=(rand()%100)+1;//rand()%(b-a+1)+a 产生[a,b]之间的随机数
pagetableaddress=(rand()%10);//这里的页表基址是在装有页表基址中的相对位置 0-9
pagetablelength=32;//其实应该是页表项个数,这样判断越界时候可以通过页号判断。。但是初始化为instructcount会出错。。。
pagetableentrylen=16;//单位是位
framecount=0;//占用的页框刚开始为0
intime=(rand()%10)+1;
this->pf[0]=-1;//初始化为-1,值为-1就是没有占用物理页框,大于等于0的话,值就代表占用的物理块号
this->pf[1]=-1;
this->pf[2]=-1;
instructcount=(rand()%10)+1;//产生[1,10]条指令
instructaddress=0;//假设一条指令占0800h,第一条指令首地址是虚拟地址 逻辑空间地址连续从0地址开始
instructorder.push_back(0);
for(i=0;i<instructcount-1;i++)//产生指令的随机执行序列 但是序列第一个一定是0号指令
{
j=rand()%instructcount;
instructorder.push_back(j);
}
pgtlist[pagetableaddress].resize(instructcount);
vector<int> exs=externstorage(instructcount,pid);
instructnum=0;
for(i=0;i<instructcount;i++)//页表项的页号
{
pgtlist[pagetableaddress][i].pagenumber=i;
pgtlist[pagetableaddress][i].flag=0;//刚开始都不在内存中
pgtlist[pagetableaddress][i].changeflag=0;
}
for( j=0;j<instructcount;j++)
{
pgtlist[pagetableaddress][j].diskaddress=exs[j];
}
}
};
4.2 与进程有关的队列的设计
与进程有关的队列有:进程队列、就绪队列、等待队列、结束队列;都使用vector容器保存,这样操作比较方便。
vector<pcb> ReadyQueue(0);//就绪队列
vector<pcb> EndQueue(0);//结束队列
vector<pcb> WaitQueue(0);//等待队列
vector<pcb> pcblist(0);//进程 队列
4.3 页表
所有进程的页表,都放在了内存的最后一块,装在一个vector容器中,页表的基址即在这个容器的下标,进程逻辑地址空间最大128KB,页表最大32页,每页4KB,页表项的属性:
(1)页号 (2)页框号 (3)驻留标志位 (4)外存地址 (5)修改位 (6)SCR、CLOCK算法中用到的引用位
页表项:
class pagetableentry//进程页表页表项(请求式的页表项)
{
public:
int pagenumber;//页号 5位
int framenumber;//页框号 4位
int flag;//驻留标志位 1位
int diskaddress;//外存地址 4位 //不弄一个内页表外页表了个
int changeflag;//修改位 1位 等于1为修改了
int r;//引用位 SCR CLOCK算法中用到
pagetableentry()
{
r=1;
}
};//一个页表项共16位,一个页表最多32个页表项,页表最多占64B
页表:
vector<pagetableentry> pagetable;
5 软件系统设计
5.1 系统中的类及类图
系统中一共有五个类,分别是页表项类(pagetableentry)、快表项类(tlbentry)、物理页框类(pageframe)、外存块类(diskentry)、进程控制块类(pcb),各类之间没有联系。类图如下:
图5-1