一. 页面置换三大算法简介
1. FIFO(先进先出置换算法)
选择最先进入内存的页面进行置换,即在内存中驻留时间最长的页面要被淘汰。
该算法的核心思想是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大。
2. LRU(最近最久未使用置换算法)
选择最近一段时间内最长时间没有被访问过的页面进行置换。
该算法根据数据的历史访问记录来进行淘汰数据,其核心思想是:如果数据最近被访问过,那么将来被访问的几率也更高。
3. OPT(最佳置换算法)
选择永不使用或者长时间内不再被访问的页面进行置换。这是一种理想化的算法,具有最好的性能,但是实际上却难以实现。原因是程序在实际执行过程中无法预测到随后要被访问到的页面,它们都是随机到达的。
我们的模拟过程是基于一开始的页面访问顺序就已经确定好了,模拟过程才得以实现。
二. 实现具体流程
设该程序对应的指令的总条数为320
1. 基于随机数产生该程序依次执行的指令的地址序列
指令地址范围为[0, 319],指令的地址按下述原则生成:
A:50%的指令是顺序执行的
B:25%的指令是均匀分布在前地址部分
C:25%的指令是均匀分布在后地址部分
具体的实施方法是:
A:在[0,319]的指令地址之间随机选取一起点m
B:顺序执行一条指令,即执行地址为m+1 的指令
C:在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m’
D:顺序执行一条指令,其地址为m’+1
E:在后地址[m’+2,319]中随机选取一条指令并执行
F:重复步骤A-E,直到320 次指令
2. 将指令地址序列根据页面大小转换为页号序列
页面大小的取值范围为 1K,2K,4K,8K,16K。
设页面大小为1K,用户内存容量4页到32页,用户虚存容量为32K。
在用户虚存中,按每K(即每页)存放10条指令排列虚存地址,即320条指令在虚存中的存放方式为:第 0 条-第 9 条指令为第 0 页(对应虚存地址为[0,9]);
第 10 条-第 19 条指令为第 1 页(对应虚存地址为[10,19]);
………………………………
第 310 条-第 319 条指令为第 31 页(对应虚存地址为[310,319]);
按以上方式,用户指令可组成 32 页。
3. 合并相邻页号
在生成的页号序列中,对于相邻相同的页号,合并为一个页号。
4. 指定分配给该程序的内存块数
分配给该程序的内存块数取值范围为1块,2块,直到程序使用的页面数。
5. 执行页面置换算法的模拟过程
分别采用 FIFO、LRU和OPT 算法对页号序列进行调度,计算出对应的缺页中断率。
并打印出页面大小、分配给程序的内存块数、算法名、对应的缺页中断率。
三. 实现关键思路
1. FIFO
设置一个指针始终指向最早进入内存块序列的内存块,并保持动态更新。
2. LRU
可以使用双向链表加上哈希表来实现:
- 当访问到新的(不位于当前内存块序列中)内存块时(即缺页),将其插入到链表首部;
- 如果当前内存块序列中某个内存块被命中(被使用到),则将其移到链表首部;
- 为链表设置一个容量(设为分配给该程序的内存块数),当超出这个容量时,丢弃链表尾部的内存块。
- 剩下的哈希表用来查找内存块。
实际上,Java已经为我们提供这种结构了!——-LinkedHashMap
我们主要将其中的accessOrder设置为true,保证了LinkedHashMap底层实现的双向链表是按照访问的先后顺序排序。
3. OPT
主要工作是:在当前访问位置,记录当前的内存块序列中的所有内存块在后续未访问的页号序列中的位置(即下标)。
可以设置一个指针记录上述结果中的最大下标,该下标对应的内存块即为缺页时要被置换的内存块。
四. 实现代码
- 考虑到程序的指令序列会比较庞大,采用GUI界面显示不太友好,故不采用GUI界面实现模拟过程,而采用控制台方式实现。
- 考虑到分配的内存块数可能会比较大,将内存块序列采用横向显示,且一开始按从左到右的顺序分配内存块。
Code:
pageReplacementSimulation.java
package com.wuchangi;
import java.util.*;
public class PageReplacement
{
//页面大小,每个页面可包含instructionsNumPerPage条指令
public static int instructionsNumPerPage;
//存放该程序依次执行的指令的有序地址序列
public static int[] instructionsSequence = null;
//存放将有序指令地址序列转换成(经过合并相邻页号)的有序页号序列
public static int[] pagesSequence = null;
//指定分配给该程序的内存块数
public static int memoryBlocksNum;
public static void main(String[] args)
{
int count = 1;
Scanner scan = new Scanner(System.in);
System.out.println("\t\t**********欢迎使用页面置换模拟系统!**********\n");
while (true)
{
System.out.println("*****第 " + count + " 个程序的页面置换模拟过程*****\n");
System.out.println("请输入程序包含的指令条数:(只支持5的倍数, 退出系统请输入-1)");
int inputValue = scan.nextInt();
if(inputValue == -1) break;
int instructionsNum = inputValue;
instructionsSequence = generateInstructionsSequence(instructionsNum);
System.out.println("系统随机生成的指令地址序列如下:");
showInstructionsSequence(instructionsSequence);
System.out.println();
System.out.println("请输入页面大小(1,2,4,8,16 分别表示 1k,2k,4k,8k,16k):");
//每1k存放10条指令
instructionsNumPerPage = scan.nextInt() * 10;
pagesSequence = convertToPagesSequence(instructionsSequence, instructionsNumPerPage);
System.out.println("该指令地址序列对应的页号序列(已经过相邻页号合并)如下:");
showPagesSequence(pagesSequence);
System.out.println();
System.out.println("实际总共使用到的页号个数为:" + pagesSequence.length);
System.out.println();
System.out.println("请输入分配给该程序的内存块数:(1~" + pagesSequence.length + ")");
memoryBlocksNum = scan.nextInt();
while(true)
{
System.out.println("请输入需要模拟的页面置换算法标号:(1:FIFO, 2:LRU, 3:OPT, 退出该程序的页面置换模拟过程请输入-1)");
int flag = scan.nextInt();
if(flag == -1) break;
switch (flag)
{
case 1:
FIFO(pagesSequence, memoryBlocksNum);
break;
case 2:
LRU(pagesSequence, memoryBlocksNum);
break;
case 3:
OPT(pagesSequence, memoryBlocksNum);
break;
default:
System.out.println("您的输入有误!");
}
System.out.println();
}
System.out.println("\n\n");
count++;
}
System.out.println("\n~~~~~~~~~~您已成功退出系统!~~~~~~~~~~");
}
//instructionsNum为5的倍数
public static int[] generateInstructionsSequence(int instructionsNum)
{
int[] instructionsSequence = new int[instructionsNum];
int count = 0;
while (count < instructionsNum)
{
int randomAddress1 = 0 + (int) (Math.random() * (((instructionsNum - 1) - 0) + 1));
instructionsSequence[count] = randomAddress1;
randomAddress1++;
instructionsSequence[++count] = randomAddress1;
int randomAddress2 = 0 + (int) (Math.random() * ((randomAddress1 - 0) + 1));
instructionsSequence[++count] = randomAddress2;
randomAddress2++;
instructionsSequence[++count] = randomAddress2;
int randomAddress3 = (randomAddress2 + 1) + (int) (Math.random() * (((instructionsNum - 1) - (randomAddress2 + 1)) + 1));
instructionsSequence[++count] = randomAddress3;
count++;
}
return instructionsSequence;
}
public static void showInstructionsSequence(int[] instructionsSequence)
{
for (int i = 0; i < instructionsSequence.length; i++)
{
System.out.printf("%5s", instructionsSequence[i]);
if ((i + 1) % 20 == 0)
{
System.out.println();
}
}
System.out.println();
}
public static int[] convertToPagesSequence(int[] instructionsSequence, int instructionsNumPerPage)
{
ArrayList<Integer> pagesList = new ArrayList<Integer>();
int temp = -1;
//页号
int pageIndex;
for (int i = 0; i < instructionsSequence.length; i++)
{
pageIndex = instructionsSequence[i] / instructionsNumPerPage;
//将相邻的页号合并
if (pageIndex != temp)
{
pagesList.add(pageIndex);
temp = pageIndex;
}
}
//有序页号序列经合并之后长度最长不超过指令的有序地址序列长度
int[] pagesSequence = new int[pagesList.size()];
for (int i = 0; i < pagesList.size(); i++)
{
pagesSequence[i] = pagesList.get(i);
}
return pagesSequence;
}
public static void showPagesSequence(int[] pagesSequence)
{
for (int i = 0; i < pagesSequence.length; i++)
{
System.out.printf("%5s", pagesSequence[i]);
if ((i + 1) % 20 == 0)
{
System.out.println();
}
}
System.out.println();
}
public static void FIFO(int[] pagesSequence, int memoryBlocksNum)
{
//执行页号序列期间内存块的状态
int[][] memoryBlocksState = new int[pagesSequence.length][memoryBlocksNum];
//该指针指向将要被置换的内存块的位置(下标位置)
int curPosition = 0;
//执行每个页号时内存块序列的状态
int[] tempState = new int[memoryBlocksNum];
//记录缺页情况, 1表示缺页,0表示不缺页
int[] isLackOfPage = new int[pagesSequence.length];
Arrays.fill(isLackOfPage, 0, pagesSequence.length, 0);
//缺页次数
int lackTimes = 0;
//开始时,内存块状态都为空闲(-1表示)
Arrays.fill(tempState, 0, memoryBlocksNum, -1);
for (int i = 0; i < pagesSequence.length; i++)
{
//如果缺页
if (findKey(tempState, 0, memoryBlocksNum - 1, pagesSequence[i]) == -1)
{
isLackOfPage[i] = 1;
lackTimes++;
tempState[curPosition] = pagesSequence[i];
//指针向右移动超过memoryBlocksNum时,重置其指向开始的内存块位置0
if (curPosition + 1 > memoryBlocksNum - 1)
{
curPosition = 0;
}
else
{
curPosition++;
}
}
//保存当前内存块序列的状态
System.arraycopy(tempState, 0, memoryBlocksState[i], 0, memoryBlocksNum);
}
showMemoryBlocksState(memoryBlocksState, pagesSequence, memoryBlocksNum, isLackOfPage, lackTimes);
}
public static void LRU(int[] pagesSequence, int memoryBlocksNum)
{
//维护一个最近使用的内存块集合
LRULinkedHashMap<String, Integer> recentVisitedBlocks = new LRULinkedHashMap<String, Integer>(memoryBlocksNum);
//执行页号序列期间内存块的状态
int[][] memoryBlocksState = new int[pagesSequence.length][memoryBlocksNum];
//该指针指向将要被置换的内存块的位置(下标位置)
int curPosition = 0;
//执行每个页号时内存块序列的状态
int[] tempState = new int[memoryBlocksNum];
//记录缺页情况, 1表示缺页,0表示不缺页
int[] isLackOfPage = new int[pagesSequence.length];
Arrays.fill(isLackOfPage, 0, pagesSequence.length, 0);
//缺页次数
int lackTimes = 0;
//开始时,内存块状态都为空闲(-1表示)
Arrays.fill(tempState, 0, memoryBlocksNum, -1);
for (int i = 0; i < pagesSequence.length; i++)
{
//如果缺页
if(findKey(tempState, 0, memoryBlocksNum - 1, pagesSequence[i]) == -1)
{
isLackOfPage[i] = 1;
lackTimes++;
//如果内存块还有剩余
if(tempState[memoryBlocksNum - 1] == -1)
{
tempState[curPosition] = pagesSequence[i];
recentVisitedBlocks.put(String.valueOf(pagesSequence[i]), pagesSequence[i]);
curPosition++;
}
//如果内存块都已被使用
else
{
//找到当前内存块序列中最近最少使用的内存块,并将其置换
curPosition = findKey(tempState, 0, memoryBlocksNum - 1, recentVisitedBlocks.getHead());
tempState[curPosition] = pagesSequence[i];
recentVisitedBlocks.put(String.valueOf(pagesSequence[i]), pagesSequence[i]);
}
}
//如果不缺页
else
{
//将这里被使用的pageSequence[i]在最近使用的内存块集合中的原先位置调整到最近被访问的位置
recentVisitedBlocks.get(String.valueOf(pagesSequence[i]));
}
//保存当前内存块序列的状态
System.arraycopy(tempState, 0, memoryBlocksState[i], 0, memoryBlocksNum);
}
showMemoryBlocksState(memoryBlocksState, pagesSequence, memoryBlocksNum, isLackOfPage, lackTimes);
}
public static void OPT(int[] pagesSequence, int memoryBlocksNum)
{
//执行页号序列期间内存块的状态
int[][] memoryBlocksState = new int[pagesSequence.length][memoryBlocksNum];
//该指针指向将要被置换的内存块的位置(下标位置)
int curPosition = 0;
//执行每个页号时内存块序列的状态
int[] tempState = new int[memoryBlocksNum];
//记录缺页情况, 1表示缺页,0表示不缺页
int[] isLackOfPage = new int[pagesSequence.length];
Arrays.fill(isLackOfPage, 0, pagesSequence.length, 0);
//缺页次数
int lackTimes = 0;
//开始时,内存块状态都为空闲(-1表示)
Arrays.fill(tempState, 0, memoryBlocksNum, -1);
for (int i = 0; i < pagesSequence.length; i++)
{
//如果缺页
if(findKey(tempState, 0, memoryBlocksNum - 1, pagesSequence[i]) == -1)
{
isLackOfPage[i] = 1;
lackTimes++;
//如果内存块还有剩余
if(tempState[memoryBlocksNum - 1] == -1)
{
tempState[curPosition] = pagesSequence[i];
curPosition++;
}
//如果内存块都已被使用
else
{
int maxLoc = 0;
for(int j = 0; j < memoryBlocksNum; j++)
{
//找出当前内存块序列中的内存块tempState[j]在将来会被访问到的(第一个)位置
int loc = findKey(pagesSequence, i + 1, pagesSequence.length - 1, tempState[j]);
//如果将来该内存块都不再被使用了
if (loc == -1)
{
curPosition = j;
break;
}
//找出当前内存块序列中的所有内存块在将来会被访问到的最远位置,设为maxLoc
else
{
if(maxLoc < loc)
{
maxLoc = loc;
curPosition = j;
}
}
}
tempState[curPosition] = pagesSequence[i];
}
}
//保存当前内存块序列的状态
System.arraycopy(tempState, 0, memoryBlocksState[i], 0, memoryBlocksNum);
}
showMemoryBlocksState(memoryBlocksState, pagesSequence, memoryBlocksNum, isLackOfPage, lackTimes);
}
//返回key在arr中第一次出现的位置,start和end为数组下标, 找不到则返回-1
public static int findKey(int[] arr, int start, int end, int key)
{
for (int i = start; i <= end; i++)
{
if (arr[i] == key)
{
return i;
}
}
return -1;
}
public static void showMemoryBlocksState(int[][] memoryBlocksState, int[] pagesSequence, int memoryBlocksNum, int[] isLackofPage, int lackTimes)
{
String[] pagesDescription = {"不缺页", "缺页"};
int pagesSequenceLength = pagesSequence.length;
for (int i = 0; i < pagesSequenceLength; i++)
{
System.out.println("当前访问页号:" + pagesSequence[i]);
System.out.print("\t");
for (int j = 0; j < memoryBlocksNum * 6 + 1; j++)
{
System.out.print("-");
}
System.out.print("\n\t");
for (int k = 0; k < memoryBlocksNum; k++)
{
if (k == 0)
{
System.out.print("|");
}
//如果当前内存块还没被使用,置为空
if (memoryBlocksState[i][k] == -1)
{
System.out.printf("%5s|", " ");
}
else
{
System.out.printf("%5s|", memoryBlocksState[i][k]);
}
}
System.out.print(" 缺页情况:" + pagesDescription[isLackofPage[i]]);
System.out.print("\n\t");
for (int j = 0; j < memoryBlocksNum * 6 + 1; j++)
{
System.out.print("-");
}
System.out.println();
}
//缺页率
double lackOfPagesRate = lackTimes * 1.0 / pagesSequence.length;
System.out.println("\n该程序的页号序列长度为:" + pagesSequence.length + ", 执行该算法后,缺页次数为:" + lackTimes + ", 缺页率为:" + lackOfPagesRate * 100 + "%");
}
}
//LRU算法的辅助存储类
class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V>
{
//最大内存块数(容量)
private int maxMemoryBlocksNum;
//设置默认负载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
public LRULinkedHashMap(int maxCapacity)
{
//设置accessOrder为true,保证了LinkedHashMap底层实现的双向链表是按照访问的先后顺序排序
super(maxCapacity, DEFAULT_LOAD_FACTOR, true);
this.maxMemoryBlocksNum = maxCapacity;
}
//得到最近最少被访问的元素
public V getHead()
{
return (V) this.values().toArray()[0];
}
//移除多余的最近最少被访问的元素
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest)
{
return size() > maxMemoryBlocksNum;
}
}
五. 测试情况
顺便附上项目源码,支持开源精神,欢迎star、fork:
https://github.com/Yuziquan/PageReplacementSimulation
(希望可以帮到有需要的人~~)