模拟请求分页式存储管理 ---4种置换算法
请求调页+页面置换
1.虚拟存储系统
操作系统中,为了提高内存利用率,提供了内外存进程对换机制;内存空间的分配和回收均以页为单位进行;一个进程只需将其一部分(段或页)调入内存便可运行;还支持请求调页的存储管理方式。
当进程在运行中需要访问某部分程序和数据时,发现其所在页面不在内存,就立即提出请求(向CPU发出缺中断),由系统将其所需页面调入内存。这种页面调入方式叫请求调页。
2. 页面置换过程
当CPU接收到缺页中断信号,中断处理程序先保存现场,分析中断原因,转入缺页中断处理程序。该程序通过查找页表,得到该页所在外存的物理块号。如果此时内存未满,能容纳新页,则启动磁盘I/O将所缺之页调入内存,然后修改页表。如果内存已满,则须按某种置换算法从内存中选出一页准备换出,是否重新写盘由页表的修改位决定,然后将缺页调入,修改页表。利用修改后的页表,去形成所要访问数据的物理地址,再去访问内存数据。整个页面的调入过程对用户是透明的。
(1) 页表:放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。
(2) 页框:RAM块,来描述物理内存空间,由操作系统实现从逻辑页到物理页框的页面映射,同时负责对所有页的管理和进程运行的控制。模拟时对于页框的分配数量自主设定。
(3) 访问位:不论是读还是写(get or set),系统都会设置该页的访问位,记录本页在一段时间内被访问的次数,或最近已有多长时间未被访问,它的值用来帮助操作系统在发生缺页中断时选择要被淘汰的页,即用于页面置换。
(4) 修改位(脏位):用于页面的换出,如果某个页面被修改过(即为脏),在淘汰该页时,必须将其写回磁盘,反之,可以直接丢弃该页。
(5) 有效位(驻留位、中断位):表示该页是内存还是磁盘。
(6) 保护位:存取控制位,用于指出该页允许什么类型的访问,如果用一位来标识的话:1表示只读,0表示读写。
1.LRU
least recently used 最近时间内 最久未被使用的页面被置换
如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小,拿“最近的过去”预测“最近的未来”。
LRU算法的硬件支持
LRU算法虽然是一种比较好的算法,但是要求系统有较多的支持硬件。为了了解一个进程在内存中的各个页面各有多少时间未被进程访问,以及如何快速得知道哪一页是最近最久未使用的页面,须有寄存器和栈两类硬件之一的支持。
1)寄存器
为了记录某进程在内存中各页的使用情况,须为每个内存中的页面配置一个移位寄存器,可表示为
当进程访问某物理块时,要将相应的寄存器的Rn−1位置成1。此时,定时信号将每隔一定时间将寄存器右移一位。如果我们把n位寄存器的数看作是一个整数,那么,具有最小数值的寄存器所对应的页面,就是最近最久未使用的页面。 (e.g. 每100ms右移一位,最高位补0还是补1的问题)
2)栈
可利用一个特殊的栈保持当前使用的各个页面的页面号。每当进程访问某页面是,便将该页面的页面号从栈中移出,将它压入栈顶。因此,栈顶始终是最近最久未使用页面的页面号。
模拟实现2:
采用双向链表+heapmap配合实现,靠近链表头部的越是最近访问过得,链表尾部是最久未被访问的,从而体现使用的时间顺序,heapmap记录表项地址。
过程:
要操作页面K
未在内存 1.内存未满->直接插入链表头部,记录地址
2.内存已满->先删除链表尾部节点,再插入新节点到链表头部,并且更新map中增加该节点
已在内存:更新节点的值,把当前访问的节点移到链表头部,并且更新map中该节点的地址。
class LRUBlock{ public: LRUBlock(int capacity) { size = capacity; } /** 操作页面k */ void set(int blockId,int k) { ///未在内存 if(blockMap.find(k) == blockMap.end()) { if(blockList.size() == size) {///删除链表尾部节点(最少访问的节点) blockMap.erase(blockList.back().pageId); blockList.pop_back(); } ///插入新节点到链表头部,并且更新map中增加该节点 blockList.push_front(BlockNode(blockId,k)); blockMap[k] = blockList.begin(); } ///已在内存 else { ///更新节点的值,把当前访问的节点移到链表头部,并且更新map中该节点的地址 blockList.splice(blockList.begin(), blockList, blockMap[k]); blockMap[k] = blockList.begin(); } } list<BlockNode> getList() { return this->blockList; } private: list<BlockNode> blockList; unordered_map<int, list<BlockNode>::iterator> blockMap;///记录结点地址 int size; };
void LRU(LRUBlock&lru,int address,map<int,TableEntry>&pageTable,map<int,int>&memoryBlock) { int pageNum=address/unitBlockSize; if(pageTable[pageNum].entryStatus==1){ cout<<"页面已在内存中\n"; lru.set(pageTable[pageNum].blockId,pageNum); } else { int s=memoryBlock.size(); if(memoryBlock[s-1]==1){///直接分 cout<<"所分配内存块还未用完\n"; int i=s-1; for(;i>=0;i--){ if(memoryBlock[i]==0)break; } memoryBlock[i+1]=0; pageTable[pageNum].entryStatus=1; pageTable[pageNum].blockId=i+1; lru.set(i+1,pageNum); } else///先swap再分 { cout<<"所分配内存块全被占用,需要置换操作\n"; BlockNode blockNode=lru.getList().back(); cout<<"置换出"<<blockNode.pageId<<"\t置换入"<<pageNum<<endl; ///抢 pageTable[blockNode.pageId].entryStatus=0; pageTable[blockNode.pageId].blockId=-1; pageTable[pageNum].entryStatus=1; pageTable[pageNum].blockId=blockNode.blockId; lru.set(blockNode.blockId,pageNum); } pageBreak++; } total++; }
2.LFU:
least frequently used 最少使用置换
如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小,LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。
注意:
一般情况下,LFU效率要优于LRU,且能够避免周期性或者偶发性的操作导致缓存命中率下降的问题。但LFU需要记录数据的历史访问记录,一旦数据访问模式改变,LFU需要更长时间来适用新的访问模式,即:LFU存在历史数据影响将来数据的“缓存污染”效用。
实现:
用数组存储内存中表项情况,通过遍历查找最小值。
过程:
要操作页面K
未在内存 1.内存未满:更新页表,表项放入数组。
2.内存已满:更新页表,数组删除要淘汰的表项,加入新表项。
已在内存:更新页表,表项重新放入数组。
3.FIFO
first in first out 最早出现置换
队列实现
过程:
已在内存:none
不在内存:
内存未满:表项加入队尾
内存已满:pop队首,新表项加入队尾
4.CLOCK
NRU---Not Recently Used
用数组存储内存中表项情况,修改数组中表项访问情况选择置出页面。
置换策略:依次访问数组中的表项,遇到status=1的置为0,再给一次驻留内存的机会,遇到status=0的换出。
过程:
已在内存:none
不在内存:
内存未满:表项加入数组
内存已满:拿新来表项替换置出表项