模拟请求分页式存储管理 ---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)寄存器 
为了记录某进程在内存中各页的使用情况,须为每个内存中的页面配置一个移位寄存器,可表示为 

R=Rn1Rn2R2R1R0


当进程访问某物理块时,要将相应的寄存器的Rn1位置成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<<"页面已在内存中
";
        lru.set(pageTable[pageNum].blockId,pageNum);
    }
    else
    {
        int s=memoryBlock.size();
        if(memoryBlock[s-1]==1){///直接分
            cout<<"所分配内存块还未用完
";
            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<<"所分配内存块全被占用,需要置换操作
";
            BlockNode blockNode=lru.getList().back();
            cout<<"置换出"<<blockNode.pageId<<"	置换入"<<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

不在内存:

  内存未满:表项加入数组

  内存已满:拿新来表项替换置出表项

 

 

 

原文地址:https://www.cnblogs.com/kimsimple/p/7065929.html