操作系统面经

本文深入探讨了操作系统中的内存管理,包括最佳置换算法(OPT)、FIFO、LRU和时钟算法,并重点介绍了LRU的实现方式。此外,还讲解了内存分区如DMA、Normal和HighMemory区域。同时,对比了段和页的区别,以及进程和线程在资源管理和执行效率上的差异。最后,详细阐述了LRU缓存的Java实现,包括使用HashMap和双向链表的数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

操作系统

https://zhuanlan.zhihu.com/p/143273007
常见置换算法有以下四种:

最佳置换算法(OPT)(不可能实现)
淘汰以后永不使用或最长时间内不再被访问的页面;保证获得最低的缺页率。 但操作系统无法知道各个页面下一次将在什么时候被访问,因此该算法是无法被实现的;

先进先出(FIFO)置换算法
优先淘汰最早进入内存的页面;实现简单,但性能差;

Belady异常:FIFO算法会产生当所分配的物理块数增大而页故障数不减反增的异常现象;

最近最少使用(LRU)置换算法
置换未使用时间最长的页面; LRU是堆栈类算法,性能较好,但需要寄存器和栈的硬件支持;实现起来困难,且开销大;

时钟(CLOCK)置换算法
环形链表,也叫NRU算法

内存分区

内存分区(ZONE)
Linux 对内存节点进行分区;将节点分为DMA、Normal、High Memory 内存区;
在这里插入图片描述DMA内存区:直接内存访问区,通常为物理内存的起始16M;主要供I/O外设使用,无需CPU参与的外设和内存DMA;
Normal内存区:从16M到896M内存区;内核可以直接使用
Hight Memory内存区:896M以后的内存区;高端内存,内核不能直接使用
在这里插入图片描述
段和页的区别:
段是信息的逻辑单位,根据用户的需要划分,段对用户是可见的; 页时信息的物理单位,为管理内存方便和划分的,对用户透明的。
段的大小不固定,根据功能觉得;页的大小固定,由系统觉得;
段向用户提供二维地址空间;页向用户提供一维地址空间;
段便于存储保护和信息共享;页的保护和共享受到限制;
分段和分页:分页的粒度更小;
分段:将程序分为代码段、数据段、堆栈段等; 分页:将段分成均匀的小块,通过页表映射物理内存;
https://www.cnblogs.com/Leophen/p/11397699.html

进程线程区别

根本区别:进程是操作系统资源调度的基本单位,线程是任务的调度执行的基本单位
开销方面:进程都有自己的独立数据空间,程序之间的切换开销大;线程也有自己的运行栈和程序计数器,线程间的切换开销较小。 而线程共享所属进程的资源,因此共享简单,但是同步复杂,需要用加锁等措施

线程在进程下行进(单纯的车厢无法运行)一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

LRU算法

https://leetcode-cn.com/problems/lru-cache/solution/san-chong-fang-fa-dai-ni-shou-si-lrusuan-fa-javaba/

public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {

    private int capacity;

    LRULinkedHashMap(int capacity) {
        // 初始大小,0.75是装载因子,true是表示按照访问时间排序
        super(capacity, 0.75f, true);
        //传入指定的缓存最大容量
        this.capacity = capacity;
    }

    /**
     * 实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}

也可以使用LinkedList和HashMap实现,但时间复杂度较高。使用HashMap可以通过O(1)时间拿到元素,但是无法在O(1)时间定位它在链表中的位置,在LinkedList里访问元素仍然是顺序遍历,所以删除元素的时间复杂度仍然是O(n)。并不是高效的Lru算法。

因为从HashMap中删除元素需要Key,所以这里在链表中存放Key而不是Value。
`public class LRUCacheBeta<K, V> {
int capacity;
Map<K, V> map;
LinkedList list;

public LRUCacheBeta(int capacity) {
    this.capacity = capacity;
    this.map = new HashMap<>();
    this.list = new LinkedList<>();
}

/**
 * 添加元素
 * 1.元素存在,放到队尾
 * 2.不存在,判断链表是否满。
 * 如果满,则删除队首元素,放入队尾元素,删除更新哈希表
 * 如果不满,放入队尾元素,更新哈希表
 */
public void put(K key, V value) {
    V v = map.get(key);
    if (v != null) {
        list.remove(key);
        list.addLast(key);
        map.put(key, value);
        return;
    }

    //队列未满,添加到尾部
    if (list.size() < capacity) {
        list.addLast(key);
        map.put(key, value);
    } else {
        //队列已满,移除队首
        K firstKey = list.removeFirst();
        map.remove(firstKey);
        list.addLast(key);
        map.put(key, value);
    }
}

/**
 * 访问元素
 * 元素存在,放到队尾
 */
public V get(K key) {
    V v = map.get(key);
    if (v != null) {
        list.remove(key);
        list.addLast(key);
        return v;
    }
    return null;
}
}

三. 使用双向链表结构+HashMap实现
在方法二中,删除操作的时间复杂度仍是O(n),那么如何使其复杂度降为O(1)?我们可以自定义双向链表的结构,这里定义了内部类Node,存放KV以及前后指针。这样我们通过hashmap找到对应Node,然后根据其前驱节点进行指针的操作,就可以实现复杂度O(1)的删除操作。

同样因为访问HashMap需要key,所以定义Node节点存放了K和V,而不是只存放V。保存队列的头节点和尾节点。

在代码中,我们通过调整指针,定义了三个方法,分别是添加元素到队尾,将队列中元素移动到队尾,删除队列头节点并返回,因为是双向链表,特别注意指针变换的顺序以及不要遗漏前驱和后继指针。

public class LRUCache<K, V> {
    private int size;
    private HashMap<K, Node> map;
    private Node head;
    private Node tail;

    LRUCache(int size) {
        this.size = size;
        map = new HashMap<>();
    }

    /**
     * 添加元素
     * 1.元素存在,将元素移动到队尾
     * 2.不存在,判断链表是否满。
     * 如果满,则删除队首元素,放入队尾元素,删除更新哈希表
     * 如果不满,放入队尾元素,更新哈希表
     */
    public void put(K key, V value) {
        Node node = map.get(key);
        if (node != null) {
            //更新值
            node.v = value;
            moveNodeToTail(node);
        } else {
            Node newNode = new Node(key, value);
            //链表满,需要删除首节点
            if (map.size() == size) {
                Node delHead = removeHead();
                map.remove(delHead.k);
            }
            addLast(newNode);
            map.put(key, newNode);
        }
    }

    public V get(K key) {
        Node node = map.get(key);
        if (node != null) {
            moveNodeToTail(node);
            return node.v;
        }
        return null;
    }

    public void addLast(Node newNode) {
        if (newNode == null) {
            return;
        }
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            //连接新节点
            tail.next = newNode;
            newNode.pre = tail;
            //更新尾节点指针为新节点
            tail = newNode;
        }
    }

    public void moveNodeToTail(Node node) {
        if (tail == node) {
            return;
        }
        if (head == node) {
            head = node.next;
            head.pre = null;
        } else {
            //调整双向链表指针
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
        node.pre = tail;
        node.next = null;
        tail.next = node;
        tail = node;
    }

    public Node removeHead() {
        if (head == null) {
            return null;
        }
        Node res = head;
        if (head == tail) {
            head = null;
            tail = null;
        } else {
            head = res.next;
            head.pre = null;
            res.next = null;
        }
        return res;
    }

    class Node {
        K k;
        V v;
        Node pre;
        Node next;

        Node(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值