操作系统实验四-页面置换算法
16281141
实验目的及基本要求
设计和实现最佳置换算法、先进先出置换算法、最近最久未使用置换算法、页面缓冲置换算法;通过页面访问序列随机发生器实现对上述算法的测试及性能比较
实验内容:
工作集与缺页率
工作集
多数程序都显示出高度的局部性,也就是说,在一个时间段内,一组页面被反复引用。这组被反复引用的页面随着时间的推移,其成员也会发生变化。有时这种变化是剧烈的,有时这种变化则是渐进的。我们把这组页面的集合称为工作集。
缺页率
缺页率 = 缺页中断次数/页面访问次数
课题假设前提说明
虚拟内存页面总数为N,页号从0到N-1
物理内存由M个物理块组成
页面访问序列串是一个整数序列,整数的取值范围为0到N - 1。页面访问序列串中的每个元素p表示对页面p的一次访问
页表用整数数组或结构数组来表示
符合局部访问特性的随机生成算法
- 确定虚拟内存的尺寸N,工作集的起始位置p,工作集中包含的页数e,工作集移动率m(每处理m个页面访问则将起始位置p +1),以及一个范围在0和1之间的值t;
- 生成m个取值范围在p和p + e间的随机数,并记录到页面访问序列串中;
- 生成一个随机数r,0 ≤ r ≤ 1;
- 如果r < t,则为p生成一个新值,否则p = (p + 1) mod N;
- 如果想继续加大页面访问序列串的长度,请返回第2步,否则结束。
最佳置换算法
基本思想
选择永不使用或是在最长时间内不再被访问(即距现在最长时间才会被访问)的页面淘汰出内存
评价
理想化算法,具有最好性能(对于固定分配页面方式,本法可保证获得最低的缺页率),但实际上却难于实现,故主要用于算法评价参照
最佳置换算法是一种理论上的方法,其所选择的被淘汰页面将是以后永久不使用的,或许是在最长(未来)时间内不再被访问的页面。由于人们目前无法预知,一个进程在内存的若干个页面中,哪个页面是未来最长时间内不在访问的,因而该算法是无法实现的,但可以利用该算法去评价其他算法。
void ORA() {
int i,j;
int num_0,num_1,num_2,num_max;
int interrupt_num=0;
//num_0=num_1=num_2=0;
for(i=0;i < phy_size;i++) //前三个数进内存
phy[i]=ref[i];
for(i=0;i<phy_size;i++) //输出最初的三个数
cout<<phy[i]<<"\t";
cout<<endl;
for(j=phy_size;j<ref_size;j++)
{
if(!(ref[j]==phy[0] || ref[j]==phy[1] || ref[j]==phy[2]))
{
num_0=getnum(phy[0],j+1);
num_1=getnum(phy[1],j+1);
num_2=getnum(phy[2],j+1);
num_max=max1(num_0,num_1,num_2);
if(num_0==num_max)
phy[0]=ref[j];
else
if(num_1==num_max)
phy[1]=ref[j];
else
if(num_2==num_max)
phy[2]=ref[j];
interrupt_num++;cout<<"进入页:"<<ref[j]<<endl;
for(i=0;i<phy_size;i++) //输出内存状态
cout<<phy[i]<<"\t";
cout<<endl<<endl;
}
cout<<"最佳置换算法缺页中断次数:"<<interrupt_num<<endl; interrupt[0]=((float)interrupt_num/20.0)*100.0;
}
`}
先进先出置换算法
基本思想
选择最先进入内存即在内存驻留时间最久的页面换出到外存
进程已调入内存的页面按进入先后次序链接成一个队列,并设置替换指针以指向最老页面
评价
简单直观,但不符合进程实际运行规律,性能较差,故实际应用极少
该策略把分配给进程的页框看成一个循环缓冲区,按循环移动页,它所需要的只是一个指针,该指针在进程的页框中循环,因此这是实现起来最简单的页面置换策略。该策略置换出那些在页框中驻留时间最久的页,认为驻留时间最久了,到现在可能不再用了。这个推断是错误的,因为会经常出现一部分程序或数据在整个程序的生命周期中使用频率都很高的情况,如果使用该算法,则这些页需要反复的调入调出
void FIFO()
{
LinkQueue L;
QueuePtr p;
int i,j,e,m;
int interrupt_num=0;
InitQueue(L);
for(i=0;i<phy_size;i++)
{
EnQueue(L,ref[i]);
}
p=(QueuePtr)malloc(sizeof(QNode));
p=L.front->next;
for(j=0;p!=NULL && j<phy_size;j++)
{
cout<<p->data<<"\t";
p=p->next;
}
cout<<endl;
for(i=phy_size;i<ref_size;i++)
{
if(!SearchQueue(L,ref[i],m))
//产生缺页中断,选择最先进入的页被替换
{
DeQueue(L,e);
//cout<<e<<endl;
EnQueue(L,ref[i]);
interrupt_num++;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY | FOREGROUND_BLUE);
}
cout<<"进入页:"<<ref[i]<<endl;
p=L.front->next;
for(j=0;p!=NULL && j<phy_size;j++)
{
cout<<p->data<<"\t";
p=p->next;
}
cout<<endl<<endl;
}
cout<<"先进先出置换算法缺页中断次数:"<<interrupt_num<<endl;
interrupt[2]=((float)interrupt_num/20.0)*100.0;
free(p);
DestroyQueue(L);
}
最近最久未使用置换算法LRU
基本思想
以“最近的过去”作为“最近的将来”的近似,选择最近一段时间最长时间未被访问的页面淘汰出内存
评价
适用于各种类型的程序,性能较好,但需要较多的硬件支持
置换内存中上次使用距当前最远的页。根据局部性原理,这也是最近最不可能访问的页,实际上,LRU策略的性能接近于OPT,该方法的问题是难于实现。一种方法是给每一页添加一个最后访问的时间标签,并且每次访问存储器时都要更新这个标签。
void LRU()
{
int QNode_num=0;
LinkQueue L;
QueuePtr p;
int i,j,e;
int interrupt_num=0;
InitQueue(L);
for(i=0;i<phy_size;i++)
{
EnQueue(L,ref[i]);
}
p=(QueuePtr)malloc(sizeof(QNode));
p=L.front->next;
for(j=0;p!=NULL && j<phy_size;j++)
{
cout<<p->data<<"\t";
p=p->next;
}
cout<<endl;
for(i=phy_size;i<ref_size;i++)
{
if(!SearchQueue(L,ref[i],QNode_num))
//产生缺页中断,选择最“老”的页面被替换
{
DeQueue(L,e);
//cout<<e<<endl;
EnQueue(L,ref[i]);
interrupt_num++;
}
else if(QNode_num==1)
{
EnQueue(L,ref[i]);
DeQueue(L,e);
}
else if(QNode_num==2)
{
DelMid_Queue(L,e);
EnQueue(L,e);
}
cout<<"进入页:"<<ref[i]<<endl;
p=L.front->next;
for(j=0;p!=NULL && j<phy_size;j++)
{
cout<<p->data<<"\t";
p=p->next;
}
cout<<endl<<endl;
}
cout<<"最近最久未使用置换算法缺页中断次数:"<<interrupt_num<<endl;
interrupt[3]=((float)interrupt_num/20.0)*100.0;
DestroyQueue(L);
free(p);
}}
改进型Clock置换算法
基本思想
① 从查寻指针当前位置起扫描内存分页循环队列,选择A=0且M=0的第一个页面淘汰;若未找到,转②
② 开始第二轮扫描,选择A=0且M=1的第一个页面淘汰,同时将经过的所有页面访问位置0;若不能找到,转①
评价
与简单Clock算法相比,可减少磁盘的I/O操作次数,但淘汰页的选择可能经历多次扫描,故实现算法自身的开销增大
最简单的时钟策略需要给每一页框关联一个附加位,称为使用位。当某一页首次装入内存中时,则将该页页框的使用位设置为1;当该页随后被访问到时(在访问产生缺页中断之后),它的使用位也会被设置为1。对于页面置换算法,用于置换算法,用于置换的候选页框集合(当前进程:局部范围;整个内存;全局范围)被看做是一个循环缓冲区,并且有一个指针针与之相关联。当一页被置换时,该指针针被设置成指向缓冲区中的下一页框。当需要置换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一页框框。每当遇到一个使用位为1的页框框时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有页框的使用位均为0时,则选择遇到的第一个页框置换;如果所有页框的使用位均为1时,则指针针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,置换该页框中的页。可见该策略类似于FIFO(先进先出),唯一不同的是,在时钟策略中使用位为1的页框框被跳过,该策略之所以称为时钟策略,是因为可以把页框形象地想象成在一个环中。
void CLOCK()
{
int interrupt_num=0;
int i;
int LNode_hit_num=0; //标记带内存中与带进入页面相同的页面的位置
int LNode_flag_num=0; //标记访问位为0的页面在内存中的位置
LinkList L;
CreatList(L);
LinkList p;
p=(LinkList)malloc(sizeof(LNode));
for(i=0;i<phy_size;i++)
{
Insert_LNode(L,ref[i]);
}
if(L->next==L) exit(-1);
p=L->next;
for(;p!=L;p=p->next)
{
cout<<p->data<<"\t";
//p->flag=1;
}
cout<<endl;
p=L->next;
while(p!=L)
{
cout<<"A:"<<p->flag<<"\t";
p=p->next;
}
cout<<endl<<endl;
for(i=phy_size;i<ref_size;i++)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY | FOREGROUND_INTENSITY);
if(!Search_LinkList(L,ref[i],LNode_hit_num))
{
Search_LL_Flag(L,LNode_flag_num);
//找到第一个flag标志为0的结点,其序号记录LNode_flag_num中
LNode_flag_num--;
Exchange_LNode(L,ref[i],LNode_flag_num);
//将链表L中序号为LNode_flag_num的结点替换为内容为ref[i]的结点
interrupt_num++;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY | FOREGROUND_BLUE);
}
else
Set_LL_Flag(L,LNode_hit_num);
cout<<"进入页:"<<ref[i]<<endl;
p=L->next;
for(;p!=L;p=p->next)
{
cout<<p->data<<"\t";
//p->flag=1;
}
cout<<endl;
p=L->next;
while(p!=L)
{
cout<<"A:"<<p->flag<<"\t";
p=p->next;
}
cout<<endl<<endl;
}
cout<<"CLOCK置换算法缺页中断次数:"<<interrupt_num<<endl; //
interrupt[4]=((float)interrupt_num/20.0)*100.0;
DestroyLinkList(L);
//free(L);
}
void Modified_Clock()
{;
int interrupt_num=0;
int i,temp;
int LNode_hit_num=0;
int LNode_flag_num=0;
int LNode_modify_num=0;
LinkList L;
CreatList(L);
LinkList p;
p=(LinkList)malloc(sizeof(LNode));
for(i=0;i<phy_size;i++)
{
Insert_LNode(L,ref[i]);
}
if(L->next==L) exit(-1);
p=L->next;
for(;p!=L;p=p->next)
{
cout<<p->data<<"\t\t";
//p->flag=1;
}
cout<<endl;
Sleep(1000);
srand(time(NULL));
temp=rand()%3;
cout<<"修改页(内存中序号):"<<temp<<endl;
Set_LL_modify(L,temp);
p=L->next;
while(p!=L)
{
cout<<"A:"<<p->flag<<"\tM:"<<p->modify<<"\t";
p=p->next;
}
cout<<endl<<endl;
for(i=phy_size;i<ref_size;i++)
{
if(!Search_LinkList(L,ref[i],LNode_hit_num))
{
Search_LL_ModifyClock(L,LNode_modify_num);
//Search_LL_Flag(L,LNode_flag_num);
LNode_modify_num--;
Exchange_LNode(L,ref[i],LNode_modify_num);
interrupt_num++;
}
else
Set_LL_Flag(L,LNode_hit_num);
cout<<"进入页:"<<ref[i]<<endl;
p=L->next;
for(;p!=L;p=p->next)
{
cout<<p->data<<"\t\t";
//p->flag=1;
}
cout<<endl;
Sleep(1000);
srand(time(NULL)); //设置时间种子
temp=rand()%3;
cout<<"修改页(内存中序号):"<<temp<<endl;
Set_LL_modify(L,temp);
p=L->next;
while(p!=L)
{
cout<<"A:"<<p->flag<<"\tM:"<<p->modify<<"\t";
p=p->next;
}
cout<<endl<<endl;
}
cout<<"改进的CLOCK置换算法缺页中断次数:"<<interrupt_num<<endl; //
interrupt[5]=((float)interrupt_num/20.0)*100.0;
DestroyLinkList(L);
//free(L);
}
页面缓冲算法PBA
基本思想
设立空闲页面链表和已修改页面链表
采用可变分配和基于先进先出的局部置换策略,并规定被淘汰页先不做物理移动,而是依据是否修改分别挂到空闲页面链表或已修改页面链表的末尾
空闲页面链表同时用于物理块分配
当已修改页面链表达到一定长度如Z个页面时,一起将所有已修改页面写回磁盘,故可显著减少磁盘I/O操作次数
需要设置一个缓冲区。该算法将一个被淘汰的页面放入两个链表中的一个,即如果页面未被修改,就将它放入空闲链表中;否则便放入到已修改的链表中。页面在内存中并不做物理上的移动,而是将页表中的表项移到上述两个链表之一。空闲链表和修改页面链表。
void PBA(){
for (int j = 0; j < 2; ++j) {
pushBackToFreePagePBA(-1);
}
// 初始化进程缓冲区和状态标志,空闲内容标志为-1
memset(free_M,-1, sizeof(free_M));
memset(free_State,-1, sizeof(free_State));
// 当前执行的指令标志 int curAlign = 0;
// 循环遍历测试序列 for(int i=0;i < testCount; i++,curAlign++)
{
short p = hasEmptyPlacePBA();
if(p)
{
short t = isInPoolPBA(testAlignment[curAlign]);
if(!t)
{
// 未命中则向空闲位置写入当前指令块号
free_M[p-1]=testAlignment[curAlign];
// 更新内存读写状态
free_State[p-1]=test_M[curAlign];
printPBA(curAlign,0,p-1);
continue;
}
else
{// 更新内存读写状态
free_State[t-1] = (free_State[t-1]==0)?test_M[curAlign]:1;
printPBA(curAlign,3,t-1);
}
} else {
// 当前指令是否命中缓冲区内容 if(short t = isInPoolPBA(testAlignment[curAlign]))
{
// 更新内存读写状态 free_State[t-1] = (free_State[t-1]==0)?test_M[curAlign]:1;
printPBA(curAlign,3,t-1);
continue;
}
else{
int waitInsert = free_M[freeMPointer];
if(free_State[freeMPointer] == 0)
if(freePageListLength < FREELISTLENGTH){
pushBackToFreePagePBA(waitInsert);
} else{
getElemOfFreePagePBA(0);
pushBackToFreePagePBA(waitInsert);
}
// 对内容做了修改插入到修改链表表尾
else {
pushBackToChangePagePBA(waitInsert);
}
short find;
// 访问的页号是否在空闲链表中
find = isInFreePagePBA(testAlignment[curAlign]);
if(find != -1) {
free_M[freeMPointer] = getElemOfFreePagePBA(find);
free_State[freeMPointer] = 0;
freeMPointer = (freeMPointer+1)%FREEMLENGTH;
printPBA(curAlign,1,find+1);
} else{
// 访问的页号是否在修改链表中 find = isInChangePagePBA(testAlignment[curAlign]);
if(find != -1){
free_M[freeMPointer] = getElemOfChangePagePBA(find);
freeMPointer = (freeMPointer+1)%FREEMLENGTH;
free_State[freeMPointer] = 1;
printPBA(curAlign,2,find+1);
} else{
// 若两个链表都没有找到,则引发缺页中断 if(freePageListLength > 0)
getElemOfFreePagePBA(0);
free_M[freeMPointer] = testAlignment[curAlign];
free_State[freeMPointer] = test_M[curAlign];
lackInterrupt++;
freeMPointer = (freeMPointer+1)%FREEMLENGTH;
printPBA(curAlign,0,freeMPointer,1);
}
}
// 判断修改链表是否已满
if(changePageListLength == CHANGELISTLENGTH)
{
list* temp = changePageList;
list *tt = temp;
for (int ii = 0; ii < changePageListLength; ++ii) {
temp = temp->next;
if(tt!=NULL) free(tt);
tt = temp;
}
changePageList = NULL // 初始化修改链表,长度赋值为0 ;
changePageListLength = 0;
}}} }}
实验结果
页面访问序列:
12 15 13 15 18 17 16 18 18 17 16 18 18 21 20 18 22 21 23 21
缺页率
最佳置换 35%
先进先出置换 45%
最近最久未使用置换 40%
CLOCK置换 45%
改进CLOCK置换 45%
页面访问序列:
13 12 15 13 16 15 18 15 16 19 17 19 20 19 18 19 22 21 20 22
缺页率
最佳置换 35%
先进先出置换 45%
最近最久未使用置换 40%
CLOCK置换 45%
改进CLOCK置换 45%
最佳置换算法
13 12 15
进入页:13
13 12 15
进入页:16
16 12 15
进入页:15
16 12 15
进入页:18
16 18 15
进入页:15
16 18 15
进入页:16
16 18 15
进入页:19
19 18 15
进入页:17
19 18 17
进入页:19
19 18 17
进入页:20
19 18 20
进入页:19
19 18 20
进入页:18
19 18 20
进入页:19
19 18 20
进入页:22
22 18 20
进入页:21
22 21 20
进入页:20
22 21 20
进入页:22
22 21 20
最佳置换算法缺页中断次数:7
先进先出置换算法
13 12 15
进入页:13
13 12 15
进入页:16
12 15 16
进入页:15
12 15 16
进入页:18
15 16 18
进入页:15
15 16 18
进入页:16
15 16 18
进入页:19
16 18 19
进入页:17
18 19 17
进入页:19
18 19 17
进入页:20
19 17 20
进入页:19
19 17 18
进入页:18
17 20 18
进入页:19
20 18 19
进入页:22
18 19 22
进入页:21
19 22 21
进入页:20
22 21 20
进入页:22
22 21 20
先进先出置换算法缺页中断次数:10
最近最久置换算法
13 12 15
进入页:13
13 12 15
进入页:16
15 13 16
进入页:15
13 16 15
进入页:18
16 15 18
进入页:15
16 18 15
进入页:16
18 15 16
进入页:19
15 16 19
进入页:17
16 19 17
进入页:19
16 17 19
进入页:20
17 19 20
进入页:19
17 20 19
进入页:18
20 19 18
进入页:19
20 18 19
进入页:22
18 19 22
进入页:21
19 22 21
进入页:20
22 21 20
进入页:22
21 21 22
最近最久置换算法缺页中断次数:9
改进的CLOCK置换算法
13 12 15
修改页:1
A:1 M:0 A:1 M:1 A:1 M:0
进入页:13
13 12 15
修改页:1
A:1 M:0 A:1 M:1 A:1 M:0
进入页:16
16 12 15
修改页:1
A:1 M:0 A:0 M:1 A:0 M:0
进入页:15
16 12 15
修改页:2
A:1 M:0 A:0 M:1 A:1 M:1
进入页:18
16 18 15
修改页:2
A:0 M:0 A:1 M:0 A:1 M:1
进入页:15
16 18 15
修改页:2
A:0 M:0 A:1 M:0 A:1 M:1
进入页:16
16 18 15
修改页:2
A:1 M:0 A:1 M:0 A:1 M:1
进入页:19
19 18 15
修改页:0
A:1 M:1 A:0 M:0 A:0 M:1
进入页:17
19 17 15
修改页:0
A:1 M:1 A:1 M:0 A:0 M:1
进入页:19
19 17 15
修改页:0
A:1 M:1 A:1 M:0 A:0 M:1
进入页:20
19 17 20
修改页:0
A:0 M:1 A:0 M:0 A:1 M:0
进入页:19
19 17 20
修改页:1
A:1 M:1 A:0 M:1 A:1 M:0
进入页:18
19 18 20
修改页:1
A:0 M:1 A:1 M:1 A:1 M:0
进入页:19
19 18 20
修改页:1
A:1 M:1 A:1 M:1 A:1 M:0
进入页:22
19 18 20
修改页:1
A:0 M:1 A:0 M:1 A:1 M:0
进入页:21
21 18 22
修改页:2
A:1 M:0 A:0 M:1 A:1 M:1
进入页:20
21 20 22
修改页:2
A:0 M:0 A:0 M:1 A:1 M:1
进入页:22
21 21 22
修改页:2
A:0 M:0 A:1 M:0 A:1 M:0
代码函数说明
1、 void set_rand_num() //产生具有局部特性的随机数列;
2、 int Exchange_LNode(LinkList &L,int e,int i)//将链表L中序号为i的结点替换为内容为e的结点;
3、 bool Search_LinkList(LinkList &L,int e,int &i)//找到链表L中内容为e的结点,并用i返回其位置,i=1表示第一个非头结点,依次类推;
4、 void Search_LL_Flag(LinkList &L,int &i)//用i返回第一个flag为0的结点的位置,i=1表示第一个非头结点,以此类推;
5、 void Set_LL_Flag(LinkList &L,int i) //设置链表L中的序号为i的结点的flag标志为1;
6、 int Search_LL_ModifyClock(LinkList &L,int &modify_num)//找到改进的CLOCK算法所需要淘汰的页,用modify_num返回其位置;
此函数根据书上给的思路,第一遍扫描A=0且M=0的页面予以淘汰,若失败,则进行第二轮扫描A=0且M=1的页面,第二轮扫描时将所有访问过的页面的访问位A置0;若失败则重复上述两部;
7、void Set_LL_modify(LinkList &L,int i) //设置链表L中的序号为i的结点的modify标志为1;
8、bool SearchQueue(LinkQueue &Q,int e,int &i) //寻找队列Q中结点data域等于e的结点,并用i返回其在Q中的位置;