实验八* 虚拟内存系统的页面置换算法模拟
1、实验目的
通过对页面、页表、地址转换和页面置换过程的模拟,加深对虚拟页式内存管理系统的页面置 换原理和实现过程的理解。
2、实验内容
(1)假设每个页面中可存放 10 条指令,分配给一作业的内存块(页框)数为 4。
(2)用 C 语言模拟一作业的执行过程,该作业共有 320 条指令,即它的地址空间为 32 页,目前 它的所有页都还未调入内存。在模拟过程中,如果所访问的指令已在内存,则显示其物理地址,并 转下一条指令。如果所访问的指令还未装入内存,则发生缺页,此时须记录缺页的次数,并将相应 页调入内存;如果 4 个内存块中均已装入该作业,则需进行页面置换;最后显示其物理地址,并转下 一条指令。在所有 320 条指令执行完毕后,请计算并显示作业运行过程中发生的缺页率。
(3)置换算法请分别考虑 OPT、FIFO 和 LRU 算法。
(4) 测试用例(作业中指令序列)按下述原则生成:
通过随机数产生一个指令序列,共 320 条指令。
① 50%的指令是顺序执行的;
② 25%的指令是均匀分布在前地址部分;
③ 25%的指令是均匀分布在后地址部分;
具体的实施方法是:
① 在[0, 319]的指令地址之间随机选取一起点 m;
② 顺序执行一条指令,即执行地址为 m+1 的指令;
③ 在前地址[0, m+1]中随机选取一条指令并执行,该指令的地址为 m1;
④ 顺序执行一条指令,其地址为 m1+1;
⑤ 在后地址[m1+2, 319]中随机选取一条指令并执行; 32
33 ⑥ 重复上述步骤①~⑤,直到执行 320 条指令。
将指令序列变换为页地址流 假设:
① 页面大小为 1K;
② 用户内存容量为 4 页;
③用户虚存容量为 32K; 在用户虚存中,按每 K 存放 10 条指令排列虚存地址,即 320 条指令在虚存中的存放方式为: 第 0 条~第 9 条指令为第 0 页(对应虚存地址为[0,9]);
第 10 条~第 19 条指令为第 1 页(对应虚存地址为[10,19]);
……第 310 条~第 319 条指令为第 31 页(对应虚存地址为[310,319]);
按以上方式,用户指令可组成 32 页。
3、关键代码
#include<stdio.h>
#include<stdlib.h>
int order[320]; //指令序列,存有320条指令
int leaf[320]; //注意题目要求,每页存储10条指令,内存块存的是页不是指令。如果存的是指令的话,缺页次数会大大增加
//没有数组leaf,存指令缺页率大于0.9(不太正常)
void get(int order[320]){ //作业中的指令序列(测试用例)
int i;
for(i=0;i<320;i=i+4){
int m=320*rand()/(RAND_MAX+1); //rand()/(RAND_MAX+1)产生一个[0,1)之间的数,乘以320为[0,320)
order[i]=m+1;
int m1=(m+2)*rand()/(RAND_MAX+1);
order[i+1]=m1;
order[i+2]=m1+1;
int m2=(320-m1-2)*rand()/(RAND_MAX+1)+m1+2;
order[i+3]=m2;
}
}
int* OPT(int leaf[320],int a,int p[4]){
int i,j,k,c=0; //因为最佳置换是淘汰“未来不再使用的”或“在离当前最远位置上出现的”页面,从当前页面往后,如果一旦出现未来还要使用已在内存的页面,c就自加1。
//当c=3时,说明数组p中已经有三个未来还要使用,剩下的一个或者是未使用,或者是离当前最远位置
int flag[4]={0}; //标志数组,看当前内存中的页面未来是否还会被使用
for(i=a+1;i<320;i++){
for(j=0;j<4;j++){
if(p[j]==leaf[i]){
c++;
flag[j]=1;
}
}
/*找到距离当前位置最远的内存块*/
if(c==3){
for(k=0;k<4;k++){
if(flag[k]==0){
p[k]=leaf[a]; //找到内存中要被置换的内存块
break;
}
}
break; //已经置换,跳出循环,节约时间
}
}
/*可能内存中会有至少一个块未来不再使用,这样就不能光凭c=3置换,而应该根据标志数组随机置换*/
if(c!=3){
for(k=0;k<4;k++){
if(flag[k]==0){
p[k]=leaf[a]; //找到内存中要被置换的内存块
break;
}
}
}
return p;
}
int* FIFO(int leaf[320],int a,int p[4],int flag){
int i,j,k;
printf("flag为:%d",flag);//flag传入正确
if(flag==1){
p[0]=leaf[a];
}
else if(flag==2){
p[1]=leaf[a];
}
else if(flag==3){
p[2]=leaf[a];
}
else{
p[3]=leaf[a];
}
return p;
}
int* LRU(int leaf[320],int a,int p[4]){
int i,j,k,c=0; //因为最近最久未用置换算法置换时淘汰最近一段时间最久没有使用的页面,即选择上次 使用距当前最远的页面淘汰,与OPT置换实现大致相同
int flag[4]={0}; //标志数组,看当前内存中的页面未来是否还会被使用
for(i=a-1;i>=0;i--){
for(j=0;j<4;j++){
if(p[j]==leaf[i]){
c++;
flag[j]=1;
}
}
/*找到距离当前位置最远的内存块*/
if(c==3){
for(k=0;k<4;k++){
if(flag[k]==0){
p[k]=leaf[a]; //找到内存中要被置换的内存块
break;
}
}
break; //已经置换,跳出循环,节约时间
}
}
/*可能内存中会有至少一个块未来不再使用,这样就不能光凭c=3置换,而应该根据标志数组随机置换*/
if(c!=3){
for(k=0;k<4;k++){
if(flag[k]==0){
p[k]=leaf[a]; //找到内存中要被置换的内存块
break;
}
}
}
return p;
}
void OFL(int order[320],int c,int leaf[320]){
int i,j,flag=0,flag1=1; //flag标志当前所访问的指令是否在内存里,不在flag=0,在flag=1
//count1为缺页次数,flag1标志当前内存块是否已满,-1表示没有装入指令,内存块也没有满
int p[4]={-1,-1,-1,-1}; //分配给一作业的内存块为4,先初始化为-1方便下面计算缺页次数,指令范围[0,319]
int FIFOcount=1; //后面FIFO算法计数,置换时第几次调用FIFO()影响结果。观察发现,因为存入指令是按下标顺序,所以先进先出置换应该也按下标顺序,4换为一轮
double count1=0;
for(i=0;i<320;i++){
printf("当前存入指令的物理地址是:");
printf("%3d,",order[i]);
for(j=0;j<4;j++){
if(leaf[i]==p[j]){
flag=1;
break;
}
if(p[j]==-1)
flag1=0;
}
if(flag==0){ //缺页
count1++; //缺页次数自加1
printf("不在内存中,");
if(flag1==0){ //指令不在内存块中,且内存未满,直接调入
for(j=0;j<4;j++)
if(p[j]==-1){
p[j]=leaf[i];
break;
}
}
else //当前内存块已满,开始置换,置换后再调入
{
switch(c)
{
case 1: OPT(leaf,i,p);break;
case 2:FIFO(leaf,i,p,FIFOcount%4); FIFOcount++;break;
case 3:LRU(leaf,i,p);break;
default:break;
}
}
}
else printf("在内存中,");
if(flag==0)
printf("存入之后的内存状态为(-1代表该内存块没有存入页):%d %d %d %d\n",p[0],p[1],p[2],p[3]);
if(flag==1)
printf(" 内存状态为(-1代表该内存块没有存入页):%d %d %d %d\n",p[0],p[1],p[2],p[3]);
flag=0; //flag重新置为0,观察下一条指令是否在内存块中
flag1=1; //观察内存块是否已满
}
switch(c)
{
case 1:printf("OPT置换算法缺页率为:%.0f/320",count1);break;
case 2:printf("FIFO置换算法缺页率为:%.0f/320",count1);break;
case 3:printf("LRU置换算法缺页率为:%.0f/320",count1);break;
default: break;
}
printf("=%.6f\n",count1/320);
}
int main(void){
int choose,i;
get(order);
for(i=0;i<320;i++){
leaf[i]=order[i]/10;
}
while(1){
printf("\t\t\t*****虚拟内存置换*****\t\t\t\n");
printf("\t\t\t*****1、OPT算法*****\t\t\t\n");
printf("\t\t\t*****2、FIFO算法*****\t\t\t\n");
printf("\t\t\t*****3、LRU算法*****\t\t\t\n");
printf("\t\t\t*****4、获得一个新的指令序列*****\t\t\t\n");
printf("\t\t\t*****5、退出*****\t\t\t\n");
printf("请输入要进行的操作(1/2/3/4):");
scanf("%d",&choose);
if(choose>=4) {
if(choose==4) {
get(order);
for(i=0;i<320;i++){
leaf[i]=order[i]/10;
}
printf("指令序列重置成功!\n");
}
else if(choose==5) break;
else {
printf("请求错误,请重新输入!\n");
//break;不能跳出循环,否则非正常停止
}
}
else OFL(order,choose,leaf);
}
return 0;
}
4、模块介绍
(1)题目要求实现最佳置换算法(OPT)、先进先出置换算法(FIFO)、最近最久未用置换算法(LRU),这三个方法置换策略不同,但都是内存块存满之后再置换。所以可以写一个公共的OFL模块,用来实现这三种算法不需要置换时的操作!
(2)主模块main(): 先调用order()方法让程序随机产生一个符合题目要求的指令序列,并得到320条指令所在的页号。然后在while循环中输出一个菜单页面,用户可以通过键盘输入选择不同的算法(\t是输出空格,让菜单更加美观)。只有用户输入5主函数才会退出while循环,return 0;结束运行,否则可以一直输入。用户输入选择后,调用OFL(int order[320],int choose,int leaf[320])
(3)OFL模块:此模块是三种算法未需要具体置换的模块,先定义内存块p[4],初始化为-1,-1代表内存块没有存入页。for循环遍历指令数组order[]并将每一条指令地址输出。内层for循环遍历内存块;定义两个标志,flag和flag1。flag初始化为0,表示内存块中是否已经存入当前要执行的指令的页,1代表已经存入,则不需要置换和调入,0代表没有存入。flag1初始化为1,代表内存块是否已满,0表示未满,1表示满了后面就要置换。flag=0缺页,缺页次数自加,在此基础上若flag1=0则调入所需页即可,flag1=1则根据用户输入调入指定置换算法。flag=1不需置换和调入。
(4)OPT模块:实现OPT算法的置换,置换时淘汰“未来不再使用的”或“在离当前最远位置上出现的”页面。在OFL中传入当前执行的指令下标a和每条指令所在的页leaf[320]与当前内存块p[4]。定义一个标志数组flag[4],初始化0,用来标记内存块中的页未来是否被使用,1表示会被使用,0表示不会。定义c计数,从当前页面往后,如果一旦出现未来还要使用已在内存的页面,c就自加1,flag[]变为1。当c=3时说明最后flag[]=0的内存块就是距离当前位置最远的内存块,也是要被置换的内存块;置换并跳出外层for循环。当然也会有特殊情况,可能内存中会有至少一个块未来不再使用,这样就不能光凭c=3置换,而应该遍历完指令序列后根据标志数组随机置换。
(5)LRU模块:实现方法大体跟OPT模块相同,只不过它不是遍历当前指令以后的序列,而是遍历当前指令以前的序列,找到上次使用距当前最远的页面。
(6)FIFO先进先出模块:看似难办,但实现方法非常简单。存入页的时候是0-4按顺序存入内存块,所以不难知道p[0]中的页应该是最先存入的,p[1]第二,p[3]第三,p[4]最后。那么每次选用FIFO置换时,可以用置换的总次数模4,计算出这次要置换的是哪个内存块,置换即可!
(7)get模块:得到随机320条指令序列,rand()/(RAND_MAX+1)产生一个[0,1)之间的数。按指令生成原则设计此模块。如int m1=(m+2)*rand()/(RAND_MAX+1);
order[i+1]=m1;order[i+2]=m1+1;产生[0,m+2)的一个随机数m1,执行这条指令,就是将这条指令存进指令序列,跟其它一起按顺序执行。
5、思考题
(1)(1)通过具体运行,当分配给作业的内存块数增加时,缺页率将会降低。
(2)为什么一般情况下,LRU具有比FIFO更好的性能?
先进先出置换算法(FIFO):是最简单的页面置换算法。这种算法的基本思想是:当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。其理由是:最早调入主存的页面不再被使用的可能性最大。
最近最久未使用(Least Recently Used):该算法以最近的过去作为不久将来的近似,将过去最长一段时间里不曾被使用的页面置换掉。在该算法的模拟过程中,每当物理块中的页面被访问时(包括原先存在的和后来置换进入的页面),便将其物理块访问标记置为-1。以后每执行一条指令,便将物理块中各页面的访问标记加1,需置换时访问标记最大的便是将要被置换的。
FIFO 算法将页面进入内存后的时间长短作为淘汰依据,而LRU则是以使用频率作为淘汰依据,一般情况下,LRU之所以比FIFO具有更好的性能是因为长时间驻留并不一定代表不使用,FiFO的淘汰机制有可能增加了缺页率。