模拟实现的算法:FIFO,Optimal(最佳置换),LRU,Clock,改进的Clock算法
一、先入先出(FIFO):
最简单的页面置换算法是先入先出(FIFO)法。这种算法的实质是,总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。理由是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大。建立一个FIFO队列,收容所有在内存中的页。被置换页面总是在队列头上进行。当一个页面被放入内存时,就把它插在队尾上。
这种算法只是在按线性顺序访问地址空间时才是理想的,否则效率不高。因为那些常被访问的页,往往在主存中也停留得最久,结果它们因变“老”而不得不被置换出去。
FIFO的另一个缺点是,它会产生Belady现象,即在增加存储块的情况下,反而使缺页中断率增加了。
package paging; import java.util.LinkedList; /** * FIFO(先进先出)页面置换算法 * * @author wz * @date 15/11/30. */ public class FIFO { private LinkedList<Integer> memoryBlock; void pageReplacement(int[] pageString, int memBlockNum) { memoryBlock = new LinkedList<>(); int pageFaultCount = 0, pageReplaceCount = 0; for (int i = 0; i < pageString.length; i++) { if (memoryBlock.contains(pageString[i])) continue; if (memoryBlock.size() >= memBlockNum) { memoryBlock.pollFirst(); // memoryBlock.set(0, pageString[i]); pageReplaceCount++; } memoryBlock.add(pageString[i]); pageFaultCount++; } System.out.println("缺页中断率: "+pageFaultCount/(double)pageString.length); System.out.println("页面置换次数: "+pageReplaceCount); } }
二、Optimal(最佳置换)
这是一种理想情况下的页面置换算法,但实际上是不可能实现的。该算法的基本思想是:发生缺页时,有些页面在内存中,其中有一页将很快被访问(也包含紧接着的下一条指令的那页),而其他页面则可能要到10、100或者1000条指令后才会被访问,每个页面都可以用在该页面首次被访问前所要执行的指令数进行标记。最佳页面置换算法只是简单地规定:标记最大的页应该被置换。这个算法唯一的一个问题就是它无法实现。当缺页发生时,操作系统无法知道各个页面下一次是在什么时候被访问。虽然这个算法不可能实现,但是最佳页面置换算法可以用于对可实现算法的性能进行衡量比较。
当请求页面不在内存中时,选择已在内存中的永不使用的或者是在最长时间内不再被访问的页面置换出去,将请求的页面换入。
模拟算法如下:
package paging; import java.util.LinkedList; /** * Optimal(最佳)置换算法 * * @author wz * @date 15/11/30. */ public class Optimal { private LinkedList<Integer> memoryBlock; void pageReplacement(int[] pageString, int memBlockNum) { memoryBlock = new LinkedList<>(); int maxDistIndex,willVisit,replaceIndex = -1; int pageFaultCount = 0, pageReplaceCount = 0; for (int i = 0; i < pageString.length; i++) { if (memoryBlock.contains(pageString[i])) continue; if (memoryBlock.size() >= memBlockNum) { // 查找最长时间内不被访问的页 maxDistIndex = -1; for (int j = 0; j < memBlockNum; j++) { willVisit = 0; for (int k = i+1; k < pageString.length; k++) { if (memoryBlock.get(j) == pageString[k]) { if (k > maxDistIndex){ maxDistIndex = k; replaceIndex = j; } willVisit = 1; break; } } if (willVisit == 0){ replaceIndex = j; break; } } memoryBlock.set(replaceIndex, pageString[i]); pageReplaceCount++; } else memoryBlock.add(pageString[i]); pageFaultCount++; } System.out.println("缺页中断率: "+pageFaultCount/(double)pageString.length); System.out.println("页面置换次数: "+pageReplaceCount); } }
当请求页面不在内存中时,将最近最久未用的页面置换出去。用栈来存储内存中的页面,将栈底页面换出,将请求页面换入压入栈顶。
LRU算法是与每个页面最后使用的时间有关的。当必须置换一个页面时,LRU算法选择过去一段时间里最久未被使用的页面。
LRU算法是经常采用的页面置换算法,并被认为是相当好的,但是存在如何实现它的问题。LRU算法需要实际硬件的支持。其问题是怎么确定最后使用时间的顺序,对此有两种可行的办法:
1.计数器。最简单的情况是使每个页表项对应一个使用时间字段,并给CPU增加一个逻辑时钟或计数器。每次存储访问,该时钟都加1。每当访问一个页面时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可以始终保留着每个页面最后访问的“时间”。在置换页面时,选择该时间值最小的页面。这样做,[1] 不仅要查页表,而且当页表改变时(因CPU调度)要 维护这个页表中的时间,还要考虑到时钟值溢出的问题。
2.栈。用一个栈保留页号。每当访问一个页面时,就把它从栈中取出放在栈顶上。这样一来,栈顶总是放有目前使用最多的页,而栈底放着目前最少使用的页。由于要从栈的中间移走一项,所以要用具有头尾指针的双向链连起来。在最坏的情况下,移走一页并把它放在栈顶上需要改动6个指针。每次修改都要有开销,但需要置换哪个页面却可直接得到,用不着查找,因为尾指针指向栈底,其中有被置换页。
此处使用栈,模拟算法如下:
package paging; import java.util.LinkedList; /** * LRU(最近最久未使用)页面置换算法 * * @author wz * @date 15/11/30. */ public class LRU { private LinkedList<Integer> memoryBlock; void pageReplacement(int[] pageString, int memBlockNum) { memoryBlock = new LinkedList<>(); int pageFaultCount = 0, pageReplaceCount = 0; for (int i = 0; i < pageString.length; i++) { if (memoryBlock.contains(pageString[i])){ memoryBlock.addLast(memoryBlock.remove(memoryBlock.indexOf(pageString[i]))); continue; }else if (memoryBlock.size() >= memBlockNum) { memoryBlock.pollFirst(); pageReplaceCount++; } memoryBlock.addLast(pageString[i]); pageFaultCount++; } System.out.println("缺页中断率: "+pageFaultCount/(double)pageString.length); System.out.println("页面置换次数: "+pageReplaceCount); } }
四、Clock算法
当某一页首次装入内存中时,则将该页框的使用位设置为1;当该页随后被访问到时(在访问产生缺页中断之后),它的使用位也会被设置为1。
当请求页面不在内存中时,查找内存中的页面,每当遇到一个使用位为1的页框时,就将该位重新置为0;如果在这个过程开始时,缓冲区中所有页框的使用位均为0时,则选择遇到的第一个页框置换;如果所有页框的使用位均为1时,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,再次循环遍历,置换第一个遇到的使用位为0的页面。
模拟算法如下:
package paging; import java.util.LinkedList; /** * 简单Clock置换算法 * * @author wz * @date 15/11/30. */ public class Clock { private LinkedList<Integer> memoryBlock; private int[] accessed; void pageReplacement(int[] pageString, int memBlockNum) { memoryBlock = new LinkedList<>(); accessed = new int[memBlockNum]; int pageFaultCount = 0, pageReplaceCount = 0; for (int i = 0; i < pageString.length; i++) { if (memoryBlock.contains(pageString[i])){ accessed[memoryBlock.indexOf(pageString[i])] = 1; continue; }else if (memoryBlock.size() >= memBlockNum) { for (int j = 0; j < accessed.length;j++) { accessed[j] ^= 1; //取反 if(accessed[j]==1){ memoryBlock.set(j,pageString[i]); break; } if (j == accessed.length-1) j = -1; } pageReplaceCount++; } else{ memoryBlock.addLast(pageString[i]); accessed[memoryBlock.size()-1] = 1; } pageFaultCount++; } System.out.println("缺页中断率: "+pageFaultCount/(double)pageString.length); System.out.println("页面置换次数: "+pageReplaceCount); } }
五、改进的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。然后,重复第一步,如果仍失败,必要时再重复第二步,此时就一定能够找到被淘汰的页。
模拟算法如下:
package paging; import java.util.LinkedList; /** * 改进的Clock置换算法 * * @author wz * @date 15/11/30. */ public class ClockImprove { private LinkedList<Integer> memoryBlock; private int[] accessed; private int[] modified; void pageReplacement(int[] pageString, int[] modifyStatus, int memBlockNum) { int index; memoryBlock = new LinkedList<>(); accessed = new int[memBlockNum]; modified = new int[memBlockNum]; int pageFaultCount = 0, pageReplaceCount = 0; for (int i = 0; i < pageString.length; i++) { if (memoryBlock.contains(pageString[i])){ index = memoryBlock.indexOf(pageString[i]); accessed[index] = 1; if (modified[index]==0) modified[index]=modifyStatus[i]; continue; } else if (memoryBlock.size() >= memBlockNum) { index=-1; while (true){ for (int j = 0; j < accessed.length; j++) { if (accessed[j] == 0 && modified[j]==0){ index=j; break; } } if (index >= 0) break; for (int j = 0; j < accessed.length; j++) { if (accessed[j] == 0 && modified[j]==1){ index = j; break; } accessed[j]=0; } if (index >= 0) break; } memoryBlock.set(index,pageString[i]); pageReplaceCount++; } else{ memoryBlock.addLast(pageString[i]); index = memoryBlock.size()-1; } accessed[index] = 1; modified[index]=modifyStatus[i]; pageFaultCount++; } System.out.println("缺页中断率: "+pageFaultCount/(double)pageString.length); System.out.println("页面置换次数: "+pageReplaceCount); } }