页面置换算法

如果进程运行时需要的内存很大时,物理内存不足以载入所有的内存,那么久需要将进程的数据保存到磁盘中,然后在需要的时候,把其加载到内存,但是物理内存已经满了,所以需要把一些内存保存到磁盘中,腾出内存空间。这样的过程叫做页面置换

硬件支持

为了支持页面置换,页表需要得到一些硬件支持,比如存在位,页面缺失错误中断。

存在位是为了告诉页面是否保存在内存中,如果不在内存中,则会产生一个页面错误,操作系统陷入内核调用页面置换算法,处理完后,再把控制权交给进程

页面置换算法

页面置换算法主要有最优置换、随机置换、最近未使用置换、FIFO置换、第二次机会置换、时钟页面置换、LRU等

最优置换

最优置换的策略是置换最远将来才会被使用的页面。这个是最优的置换策略,但是该方法无法实现,基本的原因是进程永远无法知道最远的使用的页面是哪个,所以这个策略只能用作与其他策略作为对比

随机置换

随机置换就是每次随机置换一个页面,这种方法对结果不可控,极端情况下会出现一个经常访问的页面永远缺失,所以这类方案也比较少使用

最近未使用置换

主要思想是把页面的访问和修改分成四类情况,每次缺页发生时,挑选最近一个没有访问页面置换出去,也叫做NRU。

其实这种方法跟LRU有点类似了,但是不完全一样。这个算法隐含的意思是,在最近一个时钟滴答中(典型的时间是大约20ms)淘汰一个没有被访问的已修改页面要比淘汰一个被频繁使用的“干净”页面好。NRU主要优点是易于理解和能够有效地被实现,虽然它的性能不是最好的,但是已经够用了。

FIFO置换

FIFO即先进先出,每次都置换最先进入到内存的页面,但是可能会存在一个经常使用的页面,会被置换出去

第二次机会置换

第二次机会置换是FIFO的改进版本。主要原理是检查最老页面的R位。如果R位是0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是1,就将R位清0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续搜索”

第二次机会算法就是寻找一个最近的时钟间隔以来没有被访问过的页面。如果所有的页面都被访问过了,该算法就简化为纯粹的FIFO算法

时钟页面置换

尽管第二次机会算法是一个比较合理的算法,但它经常要在链表中移动页面,既降低了效率又不是很有必要。一个更好的办法是把所有的页面都保存在一个类似钟面的环形链表中,一个表针指向最老的页面

当发生缺页中断时,算法首先检查表针指向的页面,如果它的R位是0就淘汰该页面,并把新的页面插入这个位置,然后把表针前移一个位置;如果R位是1就清除R位并把表针前移一个位置,重复这个过程直到找到了一个R位为0的页面为止

LRU

LRU又称最近最少使用,这种算法是最常见的,一般要求会实现,就是字面意思,非常好理解。这里主要讲其实现

一般实现主要是链表+map,链表保存淘汰的顺序,map用于保存内容以便于查找,为了减少链表的查找,链表节点应该包含key和value。下面是一个粗略的实现,这种实现是非线程安全的,所以如果要保证线程安全,则需要添加锁,在读写的时候加锁,操作完解锁

class LRUCache {
public:
    unordered_map<int, list<pair<int, int>>::iterator> m;
    list<pair<int, int>> l;
    int cap;
    LRUCache(int capacity) {
        this->cap = capacity;
    }
    
    int get(int key) {
        if(!m.count(key)) return -1;
        auto p = *(m[key]);
        l.erase(m[key]);
        l.push_front(p);
        m[key] = l.begin();
        return p.second;
    }
    
    void put(int key, int value) {
        if(m.count(key)) {
            l.erase(m[key]);
        } else {
           if(l.size() == this->cap) {
               m.erase(l.back().first);
               l.pop_back();
           } 
        }
        auto p = make_pair(key, value);
        l.push_front(p);
        m[key] = l.begin();
    }
};

小结

本文讲述了常用的页面置换策略,其都是现代操作系统虚拟内存中的一部分,同时在平时的业务代码中,也是常见的策略,尤其是LRU算法。

最后考虑一个问题,操作系统在执行进程的时候,不停的进行页面置换,即内存抖动。如果出现这种情况,现代操作系统一般采取的做法是选择一个密集型进程,然后将其kill掉

上一篇:nuget离线


下一篇:【毕业季】毕业随笔