目录

操作系统之页面置换算法(三)

发生缺页中断时 OS需要在内存选择一个页面将其换出内存 用来为调入的页面腾出空间 如果要换出的页面 修改过 则需要回写磁盘 否则直接覆盖该页面就行 若一个频繁使用的页面被换出 则很有可能又在不久被调入内存 造成不必要开销 因此需要合适的页面置换算法来决定调入到哪个页面

缺页次数 = 总数 - 空白列数

缺页率 = 缺页次数 / 总列数

最佳置换算法(OPT)

每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。

它的前提是要预先知道所有要访问的页面, 但只有在进程执行的时候才能知道接下来会访问到哪个页面 因此是无法实现的

先进先出算法 (FIFO)

每次选择淘汰的页面是最早进入内存的页面

实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可。

可以看为 淘汰页面号最长的 到0的时候 3页面号最长 于是淘汰3号 上面总共缺页9次

FIFO 是唯一一个会产生Belady异常的算法 即当当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。

实现简单 但考虑到实际运行情况的规律与该算法不一定适应 先进入的页面也有可能被经常访问 算法性能差

最近最久未使用置换算法 (LRU)

每次淘汰的页面是最近最久未使用的页面

实现方法:赋予每个页面对应的页表项中,用访问字段(下图)记录该页面自上次被访问以来所经历的时间t 当需要淘汰一个页面时,选择现有页面中t值最大的,即最近最久未使用的页面

要访问3页面的时候 从右往左推 发现7离3最远 于是覆盖7

需要专门的硬件支持,虽然算法性能好但是实现围难,开销大


public class LRU {

    public static class Node<V> {
        public V value;
        public Node<V> last;
        public Node<V> next;

        public Node(V value) {
            this.value = value;
        }
    }

    public static class NodeDoubleLinkedList<V> {
        private Node<V> head;
        private Node<V> tail;

        public NodeDoubleLinkedList() {
            this.head = null;
            this.tail = null;
        }

        public void addNode(Node<V> newNode) {
            if (newNode == null) {
                return;
            }
            if (this.head == null) {
                this.head = newNode;
                this.tail = newNode;
            } else {
                this.tail.next = newNode;
                newNode.last = this.tail;
                this.tail = newNode;
            }
        }

        public void moveNodeToTail(Node<V> node) {
            // 本来就在最后面 不动
            if (this.tail == node) {
                return;
            }
            // 如果是在第一个 直接
            if (this.head == node) {
                this.head = node.next;
                this.head.last = null;
            } else {
                // 否则让该node断开
                node.last.next = node.next;
                node.next.last = node.last;
            }
            // 放入最后一个
            node.last = this.tail;
            node.next = null;
            this.tail.next = node;
            this.tail = node;
        }

        public Node<V> removeHead() {
            if (this.head == null) {
                return null;
            }
            Node<V> res = this.head;
            if (this.head == this.tail) {
                this.head = null;
                this.tail = null;
            } else {
                this.head = res.next;
                res.next = null;
                this.head.last = null;
            }
            return res;
        }

    }

    public static class MyCache<K, V> {
        private HashMap<K,Node<V>> keyNodeMap;
        private HashMap<Node<V>,K> nodeKeyMap;
        private NodeDoubleLinkedList<V> nodeList;
        private int capacity;

        public MyCache(int capacity) {
            if (capacity < 1) {
                throw new RuntimeException("should be more than 0.");
            }
            this.keyNodeMap = new HashMap<K, Node<V>>();
            this.nodeKeyMap = new HashMap<Node<V>, K>();
            this.nodeList = new NodeDoubleLinkedList<V>();
            this.capacity = capacity;
        }

        public V get(K key){
            if(this.keyNodeMap.containsKey(key)){
                Node<V> res = this.keyNodeMap.get(key);
                this.nodeList.moveNodeToTail(res);
                return res.value;
            }
            return null;
        }

        public void set(K key, V value){
            if(this.keyNodeMap.containsKey(key)){
                Node<V> node = this.keyNodeMap.get(key);
                node.value = value;
                this.nodeList.moveNodeToTail(node);
            }else{
                Node<V> newNode = new Node<>(value);
                this.keyNodeMap.put(key,newNode);
                this.nodeKeyMap.put(newNode, key);
                this.nodeList.addNode(newNode);
                // 超过了限制 则将最近醉久未使用的Node删除
                if(this.keyNodeMap.size() == this.capacity + 1){
                    this.removeMostUnusedCache();
                }
            }
        }
        private void removeMostUnusedCache(){
            Node<V> removeNode = this.nodeList.removeHead();
            K removeKey = this.nodeKeyMap.get(removeNode);
            this.nodeKeyMap.remove(removeNode);
            this.keyNodeMap.remove(removeKey);
        }
    }

    public static void main(String[] args) {
        MyCache<String, Integer> testCache = new MyCache<String, Integer>(3);
        testCache.set("A", 1);
        testCache.set("B", 2);
        testCache.set("C", 3);
        System.out.println(testCache.get("B"));
        System.out.println(testCache.get("A"));
        testCache.set("D", 4);
        System.out.println(testCache.get("D"));
        System.out.println(testCache.get("C"));
    }
}

时钟置换算法(CLOCK)

为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成个循环队列。形似时钟 当某页被访问时,其访问位置为1.当需要淘汰一个页面时,只需检查页的访问位如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检査下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的 CLOCK算法选择一个淘汰页面最多会经过两轮扫描

如果访问位为0 淘汰 为1 则置为0 访问下一个

改进型的时钟置换算法

简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。 因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作。这就是改进型的时钟置换算法的思想。 修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。 为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过

设置为2个位 优先淘汰没有被修改的页面

算法规则

  1. 第一轮:从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
  2. 第二轮:若第一轮扫描失败,则重新扫描,査找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
  3. 第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
  4. 第四轮:若第三轮扫描失败,则重新扫描,査找第一个(0,1)的帧用于替换

由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描定会有一个帧被选中,因此改进型 CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描

一轮扫00 二轮扫01 三轮扫00 四轮扫01

总结