实验三 虚拟存储器
一、实验目的与要求
模拟分页式虚拟存储管理中硬件的地址转换和缺页中断,以及选择页面调度算法处理缺页中断。
在计算机系统中,为了提高主存利用率,往往把辅助存储器(如磁盘)作为主存储器的扩充,使多道运行的作业的全部逻辑地址空间总和可以超出主存的绝对地址空间。用这种办法扩充的主存储器称为虚拟存储器。通过本实验帮助同学理解在分页式存储管理中怎样实现虚拟存储器。
第一题:模拟分页式存储管理中硬件的地址转换和产生缺页中断。
(1)分页式虚拟存储系统是把作业信息的副本存放在磁盘上,当作业被选中时,可把作业的开始几页先装入主存且启动执行。为此,在为作业建立页表时,应说明哪些页已在主存,哪些页尚未装入主存,页表的格式为:
其中,标志----用来表示对应页是否已经装入主存,标志位=1,则表示该页已经在主存,标志位=0,则表示该页尚未装入主存。
主存块号----用来表示已经装入主存的页所占的块号。
在磁盘上的位置----用来指出作业副本的每一页被存放在磁盘上的位置。
(1) 作业执行时,指令中的逻辑地址指出了参加运算的操作存放的页号和单元号,硬件的地址转换机构按页号查页表,若该页对应标志为“1”,则表示该页已在主存,这时根据关系式:
绝对地址=块号×块长+单元号
计算出欲访问的主存单元地址。如果块长为2的幂次,则可把块号作为高地址部分,
把单元号作为低地址部分,两者拼接而成绝对地址。若访问的页对应标志为“0”,
则表示该页不在主存,这时硬件发“缺页中断”信号,有操作系统按该页在磁盘上
的位置,把该页信息从磁盘读出装入主存后再重新执行这条指令。
(2) 设计一个“地址转换”程序来模拟硬件的地址转换工作。当访问的页在主存时,则形成绝对地址,但不去模拟指令的执行,而用输出转换后的地址来代替一条指令的执行。当访问的页不在主存时,则输出“* 该页页号”,表示产生了一次缺页中断。
(3) 假定主存的每块长度为128个字节;现有一个共七页的作业,其中第0页至第3页已经装入主存,其余三页尚未装入主存;该作业的页表为:
如果作业依次执行的指令序列为:
(4) 运行设计的地址转换程序,显示或打印运行结果。因仅模拟地址转换,并不模拟指令的执行,故可不考虑上述指令序列中的操作。
第二题:用先进先出(FIFO)页面调度算法处理缺页中断。
(1) 在分页式虚拟存储系统中,当硬件发出“缺页中断”后,引出操作系统来处理这个中断事件。如果主存中已经没有空闲块,则可用FIFO页面调度算法把该作业中最先进入主存的一页调出,存放到磁盘上,然后再把当前要访问的页装入该块。调出和装入后都要修改页表页表中对应页的标志。
(2) FIFO页面调度算法总是淘汰该作业中最先进入主存的那一页,因此可以用一个数组来表示该作业已在主存的页面。假定作业被选中时,把开始的m个页面装入主存,则数组的元素可定为m个。例如:
P[0],P[1],….,P[m-1]
其中每一个P[i](i=0,1,….,m-1)表示一个在主存中的页面号。它们的初值为:
P[0]:=0,P[1]:=1,….,P[m-1]:=m-1
用一指针k指示当要装入新页时,应淘汰的页在数组中的位置,k的初值为“0”。
当产生缺页中断后,操作系统选择P[k]所指出的页面调出,然后执行:
P[k]:=要装入页的页号
k:=(k+1) mod m
再由装入程序把要访问的一页信息装入到主存中。重新启动刚才那条指令执行。
(3) 编制一个FIFO页面调度程序,为了提高系统效率,如果应淘汰的页在执行中没有修改过,则可不必把该页调出(因在磁盘上已有副本)而直接装入一个新页将其覆盖。因此在页表中增加是否修改过的标志,为“1”表示修改过,为“0”表示未修改过,格式为:
由于是模拟调度算法,所以,不实际启动输出一页和装入一页的程序,而用输出调
出的页号和装入的页号来代替一次调出和装入的过程。
(4) 磁盘上,在磁盘上的存放地址以及已装入主存的页和作业依次执行的指令序列都同第一题中(4)所示。于是增加了“修改标志”后的初始页表为:
按依次执行的指令序列,运行你所设计的程序,显示或打印每次调出和装入的页号,
以及执行了最后一条指令后的数组P的值。
(5) 为了检查程序的正确性,可再任意确定一组指令序列,运行设计的程序,核对执行的结果。
二、程序中使用的数据结构及符号说明
数据结构:
主存中的页面的逻辑结构:顺序表。物理结构:数组。因为是模拟运行,为了节省内存空间,主存中只存了页号,用int类型存储,主存中的页面数M为4。
符号说明:
结构体page的一项:有4个属性:flag标志位,blockNum主存块号,diskPos在磁盘上位置,editFlag修改标志。
struct page{
int flag; //标志
int blockNum; //主存块号
int diskPos; //在磁盘上的位置
int editFlag; //修改标志
page()
{
flag = blockNum = diskPos = editFlag = 0;
}
};
int P[M];//主存
主存中的页面数M为4;FIFO算法中,指针为k;结构体instruct表示操作,有3个属性:op操作,pageNum页号,unitNum单元号。
struct instruct {
string op;//操作
int pageNum;//页号
int unitNum;//单元号
};
instruct inst[105];
三、流程图
四、实验测试结果及结果分析
打印初始页表,每次调出(要调出一页时)和装入的页号,执行最后一条指令后在主存中的页面号(即数组的值)。
下图4-1为初始页表,与实验指导书的内容是完全一致的。
执行到第5条指令,调出第0页时,装入了第6页。因为修改标志位为0,所以不必将“0”调出并输出。如图4-2所示。
执行到第6条指令,调出第1页时,装入了第4页。因为修改标志位为0,所以同样不必将“1”调出并输出。如图4-3所示。
执行到第7条指令,调出第2页时,装入了第5页。因为修改标志位为0,所以同样不必将“2”调出并输出。如图4-4所示。
执行到第8条指令,调出第3页时,装入了第1页。因为修改标志位为1,所以需要将“3”输出。如图4-5所示。
执行到第9条指令,调出第6页时,装入了第2页。因为修改标志位为0,所以同样不必将“6”调出并输出。如图4-6所示。
执行到最后一条指令,调出第4页时,装入了第6页。因为修改标志位为0,所以同样不必将“4”调出并输出。如图4-7所示。
五、实验小结
通过本次关于模拟分页式存储管理中硬件的地址转换和缺页中断,以及选择页面调度算法处理缺页中断的实验,我了解并掌握了在分页式存储管理系统中的通过页号以及存储标志查找其在页表中的绝对地址的过程,以及页面中发生缺页中断时,如何调用置换算法来页面中存储的内容来避免中断。在FIFO先进先出算法中是要替换率先存入主存块的内容。LRU页面调度算法则是把该作业中最先进入主存的一页调出,存放到磁盘上,然后再把当前要访问的页装入该块。
附件:含比较详细注释说明的源程序清单
#include<iostream>
using namespace std;
const int M= 4;//主存页面数
const int blocklen = 128; //主存每块长度
const int N = 12;//指令数
int m = 4;
int n = 12;
int k = 0;//FIFO的指针
const int PageSize = 7;//作业需要的页面数
int pageSize = 7;
struct page{
int flag; //标志
int blockNum; //主存块号
int diskPos; //在磁盘上的位置
int editFlag; //修改标志
page()
{
flag = blockNum = diskPos = editFlag = 0;
}
};
int P[M];//主存
struct instruct {
string op; //操作
int pageNum; //页号
int unitNum; //单元号
void print()
{
cout << "此操作为:" << op << " 页号为:" << pageNum << " 单元号为:" << unitNum << endl;
}
instruct(string o = "", int pn = 0, int un = 0)
{
op = o;
pageNum = pn;
unitNum = un;
}
};
instruct inst[105];
page p[105];
void input() //手动输入
{
cout << "请输入页块的个数:";
cin >> m;
cout << "请输入页面个数:";
cin >> pageSize;
cout << "请输入指令的个数:";
cin >> n;
for (int i = 0; i < m; i++)
{
cout << "请输入作业的标志、主存块号、在磁盘中的位置:";
int flag, blocknum, diskpos;
cin >> flag >> blocknum >> diskpos;
p[i].flag = flag;
if (flag == 1)
{
P[k] = i;
k = (k + 1) % m;
}
p[i].blockNum = blocknum;
p[i].diskPos = diskpos;
}
for (int i = 0; i < n; i++)
{
string op;
int pagenum, unitnum;
cin >> op;
cin >> pagenum >> unitnum;
inst[i] = { op,pagenum,unitnum };
}
k = 0; //回到起点
return;
}
void init()
{
p[0].flag = 1;
p[0].blockNum = 5;
p[0].diskPos = 011;
p[1].flag = 1;
p[1].blockNum = 8;
p[1].diskPos = 012;
p[2].flag = 1;
p[2].blockNum = 9;
p[2].diskPos = 013;
p[3].flag = 1;
p[3].blockNum = 1;
p[3].diskPos = 021;
p[4].diskPos = 022;
p[5].diskPos = 023;
p[6].diskPos = 121;
inst[0] = { "+",0,70 };
inst[1] = { "+",1,50 };
inst[2] = { "*",2,15 };
inst[3] = { "存",3,21 };
inst[4] = { "取",0,56 };
inst[5] = { "-",6,40 };
inst[6] = { "移位",4,53 };
inst[7] = { "+",5,23 };
inst[8] = { "存",1,37 };
inst[9] = { "取",2,78 };
inst[10] = { "+",4,1 };
inst[11] = { "存",6,84 };
P[0] = 0;
P[1] = 1;
P[2] = 2;
P[3] = 3;
}
void interrupt(int L)
{//中断服务
int j = P[m - 1];//栈底页面调出
if (p[j].editFlag == 1)
cout << "调出页面:" << j << endl;//调出该页面
p[j].flag = 0;
cout << "存入页面:" << L << endl;
P[3] = P[2];
P[2] = P[1];
P[1] = P[0];
P[0] = L;//调整主存中的页面
p[L].flag = 1;
}
void cpuwork()
{
for (int i = 0; i < n; i++)
{
int L = inst[i].pageNum;//取指令访问的页号
inst[i].print(); //打印初始信息
if (p[L].flag == 1)
{
int addr = p[L].blockNum * blocklen + inst[i].unitNum;//计算绝对地址
if (inst->op == "存")
{
p[L].editFlag = 1;
}
cout << "绝对地址为:" << addr << endl;
int pos = 0;//调整主存的页面
for (int i = 1; i < m; i++)
{
if (P[i] == L) {
pos = i;
break;
}
}
if (pos != 0)
for (int i = pos; i >= 1; i--)
P[i] = P[i - 1];
P[0] = L;
}
else {
cout << "*******中断*******\n";
interrupt(L);
i--;//重新查页表
}
cout << "**************\n该页在主存中\n";
for (int i = 0; i < m; i++)
{
cout << "页号:" << P[i] << " 主存块号:" << p[P[i]].blockNum << " 修改标志:" << p[P[i]].editFlag
<< " 磁盘位置:" << p[P[i]].diskPos << endl;
}
cout << "********************************\n\n";
cout << "********************************\n该作业的页面\n";
for (int i = 0; i < pageSize; i++)
cout << "页号:" << P[i] << " 主存块号:" << p[P[i]].blockNum << " 修改标志:" << p[P[i]].editFlag
<< " 磁盘位置:" << p[P[i]].diskPos << endl;
cout << "********************************\n\n";
}
}
int main()
{
int choose;
cout << "选择输入方式:1. 自动输入 2.手动输入" << endl;
cin >> choose;
if (choose == 1)
init();
else
input();
cpuwork();
}