1.设计和实现最佳置换算法、先进先出置换算法、最近最久未使用置换算法、改进型Clock淘汰算法和页面缓冲算法PBA;
2.通过页面访问序列随机发生器实现对上述算法的测试及性能比较。
1)工作集
多数程序都显示出高度的局部性,也就是说,在一个时间段内,一组页面被反复引用。这组被反复引用的页面随着时间的推移,其成员也会发生变化。有时这种变化是剧烈的,有时这种变化则是渐进的。我们把这组页面的集合称为工作集。
2)缺页率
缺页率 = 缺页中断次数/页面访问次数
1)基本思想
选择永不使用或是在最长时间内不再被访问(即距现在最长时间才会被访问)的页面淘汰出内存。
2)评价
理想化算法,具有最好性能(对于固定分配页面方式,本法可保证获得最低的缺页率),但实际上却难于实现,故主要用于算法评价参照。
1)基本思想
选择最先进入内存即在内存驻留时间最久的页面换出到外存;
进程已调入内存的页面按进入先后次序链接成一个队列,并设置替换指针以指向最老页面。
2)评价
简单直观,但不符合进程实际运行规律,性能较差,故实际应用极少。
1)基本思想
以“最近的过去”作为“最近的将来”的近似,选择最近一段时间最长时间未被访问的页面淘汰出内存。
2)评价
适用于各种类型的程序,性能较好,但需要较多的硬件支持。
1)基本思想
①
从查寻指针当前位置起扫描内存分页循环队列,选择A=0且M=0的第一个页面淘汰;若未找到,转②
②
开始第二轮扫描,选择A=0且M=1的第一个页面淘汰,同时将经过的所有页面访问位置0;若不能找到,转①
2)评价
与简单Clock算法相比,可减少磁盘的I/O操作次数,但淘汰页的选择可能经历多次扫描,故实现算法自身的开销增大。
1)基本思想
页面缓冲算法是目前Linux系统较为常见的一种页面置换算法。
当需要置换页面时,采用FIFO从所有以分配页面中选择最先进入的页面淘汰。该算法规定将一个被淘汰的页放入两个链表中的一个,即如果页面未被修改,就将它直接放入空闲链表中;否则,便放入已经修改页面的链表中的一个。须注意的是,这时页面在内存中并不做物理上的移动,而只是将页表中的表项移到上述两个链表之一中。
2)评价
缓冲算法计算上FIFO是完全一致的(工作物理块的变化情况)。如果在把空闲物理块调用至工作物理块的步骤也算作缺页的前提下(这一点非常重要),连缺页率的计算结果也会和同情况下的FIFO一致。
1. 课题假设前提说明
1)虚拟内存(即[程序/进程]逻辑地址空间)页面总数为N,页号从0到N-1
2)进程分配获得由W个物理块组成的物理内存
3)页表用整数数组或结构数组来表示
4)页面访问序列串是一个整数序列,整数的取值范围为0到N -
1。页面访问序列串中的每个元素p表示对页面p的一次访问
2. 页面访问序列随机生成说明
符合局部访问特性的随机生成算法
1)确定虚拟内存的尺寸N,工作集的起始位置p,工作集中包含的页数e,工作集移动率m(每处理m个页面访问则将起始位置p
+1),以及一个范围在0和1之间的值t;
2)生成m个取值范围在p和p + e间的随机数,并记录到页面访问序列串中;
3)生成一个随机数r,0 ≤ r ≤ 1;
4)如果r < t,则为p生成一个新值,否则p = (p + 1) mod N;
5)如果想继续加大页面访问序列串的长度,请返回第2步,否则结束。
3. 性能测评及问题说明
测试不同的页面访问序列及不同的虚拟内存尺寸,并从缺页率、算法开销等方面对各个算法进行比较。(同时请给出在给定页面访问序列的情况下,发生页面置换次数的平均值)
虚拟内存页面总数为P,标号从0到P-1;
引用串RS(reference
string)是一个整数序列,整数的取值范围为0到P-1。RS的每个元素p表示对页面p的一次引用;
物理内存由F帧组成,标号从0到F-1。引入一个数组M[F],数组元素M[f]中包含数字p,它表示帧f中包含页面p;
在上述变量设定基础上,页面替换算法顺次读取RS中的每个元素。对于RS中的元素值p,算法搜索数组M[F],判断是否存在某个f,使得M[f] == p。如果未发现,则表示页面缺失。这时,算法必须根据其特定的替换规则,选择一帧M[i],用页面p替换其中的内容,即令M[i] = p。
1. 最佳替换算法
最佳替换算法所选择的被淘汰页面是以后永不使用的,或许是在最长(未来)时间内不在被访问的页面。
显而易见,采用最佳置换算法可以保证获得最低的缺页率。但是未来的情况是无法预知的,在实验中,由于引用串是事先知道的,所以我们可以得到最佳替换算法结果,用来评估其它算法。
2. FIFO算法
FIFO算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰。该算法的实现只需把一个进程已调入内存的页面按先后次序链接成一个队列,并设置一个替换指针,使它总是指向最老的页面即可。
3. LRU算法
LRU算法根据页面调入内存后的使用情况做出决策,它选择最近最久未使用的页面予以淘汰,即假设每个页面有一个访问字段记录这个页面自上次被访问以来所经历的时间,需要淘汰一个页面时,选择现有页面中时间值最大的进行替换。
4. 改进Clock算法
在将一个页面换出时,如果该页已被修改过,便须将它重新写到磁盘上;但如果该页未被修改过,则不必将它拷回磁盘。同时满足这两条件的页面作为首先淘汰的页。由访问位A和修改位M可以组合成下面四种类型的页面:
1 类(A=0,M=0):表示该页最近既未被访问、又未被修改,是最佳淘汰页。
2 类(A=0,M=1):表示该页最近未被访问,但已被修改,并不是很好的淘汰页。
3 类(A=1,M=0):最近已被访问,但未被修改,该页有可能再被访问。
4 类(A=1,M=1):最近已被访问且被修改,该页有可能再被访问。 在内存中的每个页必定是这四类页面之一,在进行页面置换时,其执行过程可分成以下三步:
(1)从指针所指示的当前位置开始,扫描循环队列,寻找A=0且M=0的第一类页面,将所遇到的第一个页面作为所选中的淘汰页。在第一次扫描期间不改变访问位A。
(2)如果第一步失败,即查找一周后未遇到第一类页面,则开始第二轮扫描,寻找A=0且M=1的第二类页面,将所遇到的第一个这类页面作为淘汰页。在第二轮扫描期间,将所有经过的页面的访问位置0。
(3)如果第二步也失败,即未找到第二类页面,则将指针返回到开始的位置,并将所有的访问位复0。然后,重复第一步,如果仍失败,必要时再重复第二步,此时就一定能够找到被淘汰的页。
5. 页面缓冲算法PBA
思路与FIFO是完全一致的(工作物理块的变化情况)。如果在把空闲物理块调用至工作物理块的步骤也算作缺页的前提下(这一点非常重要),连缺页率的计算结果也会和同情况下的FIFO一致。
(1)宏定义
#define P 32 //虚拟内存页面总数
#define F 8 //物理内存页面总数
#define RS_MAX_SIZE 32 //引用串最大长度
(2)全局变量
int rs[RS_MAX_SIZE]; //引用串及大小
int rs_size;
int M[F]; //内存页面及已经使用的大小
int m_size = 0;
int oldest_FIFO = 0;//used in FIFO,指向最老页面的指针
int queue_LRU[F];//used in LRU,实现排队功能的数组及大小
int queue_size = 0;
int replace_clock = 0;//use in Clock,Clock指针和访问位数组
int access_bit[F] = { 0 };
int modify_bit[F] = { 0 };
假定当前工作面由虚拟内存中连续的页面组成以及引用在当前工作面内的分布是均匀的。另外,多数程序都显示出高度的局部性,即,在一个时间段内,一组页面被反复引用。这组被反复引用的页面随着时间的推移,其成员也会发生变化。有时这种变化是剧烈的,有时这种变化则是渐进的。我们把这组页面的集合称为当前工作面。
为了给渐进移动建立模型,我们做如下假设:
工作面在固定的方向匀速前进(如,在处理了m个引用后,把工作面的起始页面加1)。对于剧烈移动,我们则重新随机选择工作面的起始页面即可。剧烈移动在整个移动中的比率,我们用t表示。
根据以上假设,引用串可用如下算法产生:
(1)确定虚拟内存的尺寸P,工作面的起始位置p,工作面中包含的页数e,工作面移动率m,以及一个范围在0和1之间的值t;
(2)生成m个取值范围在p和p + e间的随机数,并记录到引用串中;
(3)生成一个随机数r,0 <= r <= 1;
(4)如果r < t,则为p生成一个新值,否则p = (p + 1) mod P;
(5)如果想继续加大引用串的长度,请返回第(2)步,否则结束。
代码设计如下:
宏定义:
#define P 32 //虚拟内存页面总数
全局变量:
int rs[RS_MAX_SIZE]; //引用串及大小
int rs_size;
创建RS代码:
void CreateRS() {
rs_size = 0;
srand((unsigned)time(NULL));
int p = rand() % P;
int e = 8;
int m = 4;
double t = rand() / (RAND_MAX + 1.0);
int rs_index = 0;
while (rs_size + m <= RS_MAX_SIZE) {
for (int i = 0; i < m; i++) {
int cur = rand() % e + p;
rs[rs_index++] = cur;
}
double r = rand() / (RAND_MAX + 1.0);
if (r < t)
p = rand() % P;
else
p = (p + 1) % P;
rs_size += m;
}
}
实现最佳置换算法不需要附加的数据结构,算法的实现是通过对RS串后续未访问部分的遍历得到未来不需要访问的页面或最晚被访问的页面进行替换。
代码实现如下(其中OPT_Rep()是替换时采取的操作,为了逻辑清晰设计的函数):
void OPT_Rep(int i) {//i is the current index of rs
int visited[F] = { 0 };
for (int j = 0; j < F; j++) {
for (int m = i + 1; m < rs_size; m++) {
if (M[j] == rs[m]) {
visited[j] = m - i;
break;
}
}
}
int replace_index;
int temp = 0;
for (int j = 0; j < F; j++) {
if (visited[j] == 0) {
M[j] = rs[i];
return;
}
if (visited[j] > temp) {
temp = visited[j];
replace_index = j;
}
}
M[replace_index] = rs[i];
}
/*
Name:OPT()
Achieve:最佳置换算法
*/
void OPT() {
int replace_num = 0;
for (int i = 0; i < rs_size; i++) {
if (Contains(rs[i]))
continue;
if (m_size < F) {
M[m_size++] = rs[i];
}
else {
OPT_Rep(i);
replace_num++;
}
}
printf("OPTReplace: \n the page fault rate of replacement is %6.3f%%", 100 * ((float)replace_num / P));
printf("\n\n");
}
FIFO需要一个指向最老页面的指针,每当该页面被替换的时候,把该指针加1(模F)。算法代码如下:
/*
Name:void FIFO()
Achieve:先进先出法(Fisrt In First Out)
如果一个数据最先进入缓存中,则应该最早被淘汰
每次替换最先进入内存的页面
*/
void FIFO() {
int replace_num = 0;
for (int i = 0; i < rs_size; i++) {
if (Contains(rs[i])) {
continue;
}
if (m_size < F) {
M[m_size++] = rs[i];
}
else {
M[oldest_FIFO] = rs[i];
oldest_FIFO = (oldest_FIFO + 1) % F;
replace_num++;
}
}
printf("FIFOReplace: \n the page fault rate of replacement is %6.3f%%", 100 * ((float)replace_num / P));
printf("\n\n");
}
LRU算法需要一个尺寸为F的数组,该数组用来实现排队功能,每次处理一个新的页面引用时,则把该页放置在队列的末尾。这样,当需要淘汰一个页面时,从队首取到的即最长时间未被用到的页面。代码实现如下:
/*
Name: void LRU ()
Achieve: 最近最久未使用(Least Recently Used)
如果一个数据在最近一段时间没有被访问到,那么在将来被访问的可能性也很小
淘汰最长时间未被使用的页面
*/
void LRU() {
int replace_num = 0;
for (int i = 0; i < rs_size; i++) {
if (Contains(rs[i])) {
for (int j = 0; j < queue_size; j++) {
if (queue_LRU[j] == rs[i]) {
for (int m = j; m < queue_size; m++)
queue_LRU[m] = queue_LRU[m + 1];
queue_LRU[queue_size - 1] = rs[i];
}
break;
}
continue;
}
if (m_size < F) {
queue_LRU[queue_size++] = rs[i];
M[m_size++] = rs[i];
}
else {
for (int j = 0; j < F; j++) {
if (M[j] == queue_LRU[0]) {
for (int m = 0; m < queue_size; m++) {
queue_LRU[m] = queue_LRU[m + 1];
}
queue_LRU[queue_size - 1] = M[j];
M[j] = rs[i];
break;
}
}
replace_num++;
}
}
printf("LRUReplace: \n the page fault rate of replacement is %6.3f%%", 100 * ((float)replace_num / P));
printf("\n\n");
}
改进Clock算法比简单Clock算法需要多一个数组记录每一帧的置换情况,另外也需要一个指针实现Clock功能。代码实现如下:
/*
Name: void MoreClock ()
Achieve: 改进Clock算法
访问位access,修改位modify,通过至多四轮扫描找出最适合淘汰的页面
*/
void MoreClock() {
int replace_num = 0;
int flag = 0;
int temp = 1;
for (int i = 0; i < rs_size; i++) {
if (Contains(rs[i])) {
for (int j = 0; j < m_size; j++) {
if (M[j] == rs[i]) {
access_bit[j] = 1;
}
}
//continue;
}
else {
for (int j = 0; j < m_size; j++) {
if (M[j] == rs[i]) {
modify_bit[j] = 1;
}
}
if (m_size < F) {
access_bit[m_size] = 1;
M[m_size++] = rs[i];
}
else {
while (temp == 1) {
oncemore: if (access_bit[replace_clock] == 0 && modify_bit[replace_clock] == 0) {//扫描循环队列,寻找A=0且M=0的第一类页面
M[replace_clock] = rs[i];
access_bit[replace_clock] = 0;
temp = 0;
}
else {
if (flag) {
if (access_bit[replace_clock] == 0 && modify_bit[replace_clock] == 1) {//扫描循环队列,寻找A=0且M=0的第一类页面
M[replace_clock] = rs[i];
access_bit[replace_clock] = 0;
temp = 0;
}
else {
access_bit[replace_clock] = 0;
if (replace_clock + 1 == F) {//如果第二步也失败,即未找到第二类页面,则将指针返回到开始的位置,并将所有的访问位复0
replace_clock = (replace_clock + 1) % F;
access_bit[F] = { 0 };
flag = 0;
goto oncemore;
}
replace_clock = (replace_clock + 1) % F;
}
//flag = 0;
}
else {
if (replace_clock + 1 == F)//如果还没有扫描一周
flag = 1;
//access_bit[replace_clock] = 0;
replace_clock = (replace_clock + 1) % F;
}
}
}
replace_num++;
}
}
}
printf("MoreClockReplace: \n the page fault rate of replacement is %6.3f%%", 100 * ((float)replace_num / P));
printf("\n\n");
}
代码实现如下:
/*
Name: void PBA ()
Achieve: 页面缓冲算法PBA
*/
void PBA(){
//缺页数量初始化
replace_num=0;
//页面序列指针初始化
pPage=0;
//缺页中断记录初始化
for(int i=0;i<P;i++){
interrupt[i]='N';
}
//将物理块填充满
fillFront();
//设置替换块指针:此处的更替指针的原理与FIFO是相同的
int pReplaceBlock=0;
//使用位初始化(此处用作缺页数列的填充)
for(int i=0;i<blockNum;i++){
useBit[i]=false;
}
//将物理块填充过程中的缺页补全,替换块指针不动(填充完后又回到第一个位置)
//从第1页到第pPage页共产生blockNum个缺页中断
for(int i=0;i<pPage;i++){
for(int j=0;j<blockNum;j++){
if(page[i]==blockSta[j][pPage-1]){
if(!useBit[j]){
useBit[j]=true;
interrupt[i]='Y';
replace_num+=1;
}
break;
}
}
}
//从能填充满物理块的那一个页面的下一个页面起开始遍历
for(int i=pPage;i<P;i++){
//
for(int j=0;j<freeBlock;j++){
freeBlockSta[j][i]=freeBlockSta[j][i-1];
}
//寻找所有的物理块内是否存储了当前所查找的页面i
bool findPage = false;
for(int j=0; j<blockNum; j++){
if(page[i]==blockSta[j][i-1]){
findPage=true;
break;
}
}
//将前一个页面所对应的物理块状态复制到当前页面所对应的物理块状态
for(int j=0;j<blockNum;j++){
blockSta[j][i]=blockSta[j][i-1];
}
//物理块内已存在相同页面
if(findPage){
//上一页面的物理块状态就是当前页面的物理块状态
//上一页面的物理块状态已复制,直接进行下一页面即可
continue;
}
//物理块内不存在相同页面
else{
//是否在缓冲物理块内
int inFreeBLockLocation=-1;
for(int j=0;j<freeBlock;j++){
if(page[i]==freeBlockSta[j][i]){
inFreeBLockLocation=j;
break;
}
}
//在缓冲物理块内
if(inFreeBLockLocation!=-1){
//产生缺页
replace_num+=1;
interrupt[i]='Y';
//将两块的内容交换
int temp=blockSta[pReplaceBlock][i];
blockSta[pReplaceBlock][i]=freeBlockSta[inFreeBLockLocation][i];
freeBlockSta[inFreeBLockLocation][i]=temp;
//将替换指针后移
pReplaceBlock=(pReplaceBlock+1)%blockNum;
//将这一块的内容放到最后
for(int j=inFreeBLockLocation;j<freeBlock-1;j++){
freeBlockSta[j][i]=freeBlockSta[j+1][i];
}
freeBlockSta[freeBlock-1][i]=temp;
}
else{
int spaceFreeBlock=-1;
for(int j=0;j<freeBlock;j++){
if(freeBlockSta[j][i]==0){
spaceFreeBlock=j;
break;
}
}
//有空白的缓冲物理块:
//当前物理块填入空白物理块
//当前页面填入当前物理块
if(spaceFreeBlock!=-1){
//产生缺页
replace_num+=1;
interrupt[i]='Y';
//将替换指针所指向的物理块进行替换
freeBlockSta[spaceFreeBlock][i]=blockSta[pReplaceBlock][i];
blockSta[pReplaceBlock][i]=page[i];
//将替换指针后移
pReplaceBlock=(pReplaceBlock+1)%blockNum;
}
//不存在空白的缓冲物理块
//将空闲物理块的最后一个位置的内容替换成当前的页面,缺页操作同上
else{
//产生缺页
replace_num+=1;
interrupt[i]='Y';
//temp保留当前物理块内容
//当前工作物理块填入当前页面作为新内容
int temp=blockSta[pReplaceBlock][i];
blockSta[pReplaceBlock][i]=page[i];
//抛弃空闲物理块第一块的内容,temp放入最后一块的内容
for(int j=0;j<freeBlock-1;j++){
freeBlockSta[j][i]=freeBlockSta[j+1][i];
}
freeBlockSta[freeBlock-1][i]=temp;
//将替换指针后移
pReplaceBlock=(pReplaceBlock+1)%blockNum;
}
}
}
}
}
通过改变代码中宏定义的P值和F值来改变模拟的页面数和物理块数,以此得到不同的页面数和物理块数的情况下各种页面置换算法的缺页率如何。
实验输出样例:
本个样例中,页面总数为32,物理块数为8。(e = 8, m = 4)
1. FIFO算法是否比随机算法优越?
本人对不同的页面总数和物理块数进行了实验(未在报告中标出),从得到的页面置换算法缺页率的许多样例中来看,FIFO算法并不一定每次都优于随机算法,有时因为“随机”的关系,随机算法的缺页率会低于FIFO算法缺页率。但整体情况来看,FIFO算法在多数情况下替换次数小于Random算法。
2. LRU算法比FIFO算法优越多少?
对于LRU算法和FIFO算法的比较,通过所有实验结果来看,两种算法缺页率比较接近,但是总体来说FIFO算法会稍好一点。
3. LRU算法和Optimal算法有何差距?
Optimal算法是将未来最久需要使用的页面进行替换,LRU是将过去最近最久未使用的页面进行替换。Optimal算法优于其算法本身的特定性质决定了一定能够实现最小的缺页率,但是Optimal算法实际是不可实现的,因此用LRU算法实现对Optimal算法的近似,将最近最久未使用近似为未来最久未使用,但这样的猜测正确率在实验中是不可保证的。
4. Clock算法和LRU算法有何差距?
Clock算法算是对LRU算法的近似,主要差别在于两种算法实现时的数据结构有一些不同。LRU算法通过一个链表实现排队功能,而Clock算法的链表还需要实现循环的功能,还需要能实现Clock功能的指针。
在实现最佳置换算法、先进先出置换算法、最近最久未使用置换算法、改进型Clock淘汰算法时通过之前老师的讲解和同学在实验课上的经验分享,能够较好的完成这些算法。另外通过与同学之间对于算法的讨论更快的了解和实现了算法。但是在实现改进型Clock淘汰算法时,为了实现在第二轮扫描中,找不到选择A=0且M=1的页面淘汰的情况下可以转入第一轮扫描重新扫描的功能,使用了“goto”语句,不过goto语句一般情况下不建议使用,但是目前还没想到替代的方法,应该在之后继续改进最好可以取消“goto”语句的使用。
另外,实现改进Clock算法时,深刻体会到虽然性能较于简单Clock算法有所提升,但是实现过程却变得十分复杂而且需要硬件的支持,因此在这两种算法中选择使用时,一定要仔细斟酌。
在本次试验中,虽然十分明确最佳置换算法由于算法特性决定了其名副其实确实是“最佳置换”的算法,但是也因为其算法特性导致实际中不可实现,不过我们可以使用最近最久未使用算法将最近最久未使用近似为未来最久未使用,这种虽然实际无法实现最佳算法但是可以通过修正来近似“最佳”的思想可以值得学习。
本次试验中,在思考如何实现虚拟页式存储管理时遇到了问题,突然不知从何处做起比较好,后来在网页上学习页面置换算法的相关内容时,发现了一个比较好的能模拟实际进程与页面的关系来给出随机序列的方法,因此将其借用,实验结束后将自己继续思考自己如何产生较好的随机序列的方法进行替换。
源代码:GitHub:https://github.com/Topdu/os/tree/master/homework/页面置换