Interview_操作系统_day22
死锁发生的条件以及如何解决死锁
死锁是指两个或者两个以上进程在执行过程中,因为争夺资源而造成相互等待的现象。
四个发生条件分别为:
- 互斥条件。进程分配到的资源不允许其他进程同时访问。若其他进程想要访问该资源,只能等待到占用资源的进程使用完。
- 请求和保持条件。进程获得一定资源后,又对其他资源发出请求。如果其他资源被占用,此时请求阻塞,但该进程不会释放以有的资源。
- 不可剥夺条件。进程获得的资源在使用完成之前不可被剥夺,只能自己使用完后释放。
- 循环等待条件。发生死锁时,一定存在一个 进程——资源 的循环链。
解决死锁的方法:
- 破坏请求和保持条件。在进程开始运行之前,必须一次性分配该进程需要的所有资源。
- 破坏不可剥夺条件。当一个进程对新资源的请求又不能被满足时,必须释放已经获得的所有资源。
- 破坏循环等待条件。系统对所有资源类型进行编号,每个进程必须按序号递增的顺序请求资源。如果想要请求序号较低的资源,必须释放已经获得的高序号的资源。
虚拟内存技术
传统存储器存在问题:当有的作业很大或同一时间有大量作业要求运行时,其需要的内存空间超过了内存总容量,作业无法全部装入,导致作业无法运行。这都是由于传统存储器要求一次性装入作业导致的,所以采用了虚拟内存。
虚拟内存技术使进程在运行过程中,内存中只装入了当前要运行的少数页面,其余部分暂存在外存上。如果程序访问的页尚未调入内存中,便发出缺页中断,\(OS\) 将需要的页调入内存。如果内存满了,无法装入新的页时,便会使用页面置换方式将暂时不用的页调至外存,再将要访问的页调入内存。
虚拟内存的优点:
- 可以更加高效的使用物理内存。
- 使内存的管理更加便捷。在编译程序的时候使用虚拟地址,就不会因为物理地址有时被占用而需要重新编译了。
- 更加安全。每个进程运行在各自的虚拟内存地址空间中,互相不干扰对方。
虚拟内存的缺点:
- 虚拟内存需要建立额外的数据结构,需要占用额外的内存。
- 虚拟地址到物理地址的转换增大的运行时间。
- 页面的换入换出需要磁盘 \(I/O\),需要耗费很大的时间。
虚拟内存和物理内存怎么对应
请求分页存储管理中一般使用二级页表。
- 从 \(cr3\) 中取出页表地址。
- 根据地址前十位,找到对应的索引项。此时获得页目录的地址,而不是页的地址。
- 根据地址中间十位,从页表中获得该页的起始地址。
- 将获得的页的起始地址和最后 \(12\) 位地址相加,获得想要的物理地址。
操作系统中的缺页中断
通过 \(malloc\) 分配内存时,只是分配了虚拟内存而不是实际的物理地址,进程访问时也是访问的虚拟地址而不是物理地址。
在请求分页系统中,可以查询页表的状态来确定要访问的页表是否在内存中。每当要访问的页面不存在内存中时,就会发生一个缺页中断,然后操作系统会将缺失的页调入到内存中。
缺页中断的处理一般分为 \(4\) 个步骤:
- 保护 \(CPU\) 现场
- 分析中断原因
- 转入缺页中断处理程序进行处理
- 恢复 \(CPU\) 现场并继续处理
虚拟内存置换的方式
当访问一个不存在内存中的页时,需要从外存调入。如果此时内存已满,就需要调出一个页到外存,在将需要的页调入。这个过程叫做缺页置换。
- 最佳置换算法:调出的页面是未来不访问或最久不访问的页面,但由于实际过程中无法预知未来,这是一种理论的算法。
- 先进先出 \((FIFO)\) 页面置换算法:置换掉最早调入内存的页面,也就是说在内存中按队列的形式管理页,从队尾插入,从队首删除。
- 最近最久未使用 \((LRU)\) 置换算法:置换掉最近一段时间内最久未访问的页面。根据局部性原理,刚刚被访问过的页面可能马上又要被访问,而较长时间未访问的页面可能最近不会访问。
- 最少使用 \((LFU)\) 置换算法。置换掉最近一段时间访问次数最少的页面。
- \(CLOCK\) 置换算法。为每一页设置一个访问位,再将页面设置成循环队列。在选择一个页面时,如果访问位是 \(0\),就把它置换掉,如果是 \(1\),就把访问位置为 \(0\) 并开始检查下一个页面。
为什么要有 \(cache\)
\(cache\) 是 \(CPU\) 的高速缓存,可以加快读取数据的速度。
在页式存储结构中,需要先访问内存获得数据物理地址,然后再去物理地址中读出数据。但访问内存的速度比较慢,所有引入了 \(cache\),访问一个地址时,先去 \(cache\) 中查找,如果命中了就可以直接获得其物理地址,然后访问数据,这样可以大大增加访问速度。
\(5\) 种 \(IO\) 模型
- 阻塞 \(IO\)。一直检查 \(IO\) 事件是否就绪,没有就继续等待,期间什么事也不做。
- 非阻塞 \(IO\)。每隔一段时间检查一下 \(IO\) 事件是否就绪,没有就绪就做其他事。
- 信号驱动 \(IO\)。安装一个信号处理函数,进程继续运行。当 \(IO\) 事件就绪时,进程会收到 \(SIGIO\) 信号,然后处理 \(IO\) 事件。
- \(IO\) 多路复用。\(IO\) 多路复用多了个 \(select\) 函数,\(select\) 监听程序的多个文件描述符,当某个文件描述符就绪时,就返回所有描述符,然后应用系统检查哪个文件描述符上有事件发生。
- 异步 \(IO\)。应用程序把 \(IO\) 请求给内核后,由内核去完成相关操作。当内核完成相关操作后,会发信号告诉应用进程本次 \(IO\) 已经完成。
水平触发和边缘触发
- 水平触发(状态达到):当被监控的文件描述符上有可读写事件发生时,会通知用户程序去读写。如果用户一次读写没取完数据,他会一直通知用户。如果这个描述符是用户不关心的,它每次都返回通知用户,则会导致用户对于关心的描述符的处理效率降低。
- 边缘触发(状态变化):当被监控的文件描述符上有可读写事件发生时,会通知用户程序去读写,它只会通知用户进程一次,这需要用户一次把内容读取完,相对于水平触发,效率更高。如果用户一次没有读完数据,再次请求时,不会立即返回,需要等待下一次的新的数据到来时才会返回,这次返回的内容包括上次未取完的数据。
\(select\) 和 \(poll\) 是水平触发的。\(epoll\) 支持水平触发也支持边缘触发,但默认是水平触发的。
\(windows\) 消息机制
当用户使用键盘或者鼠标时,系统会把这些操作转化成消息。系统会将这些消息放入消息队列中,然后对应的进程会循环从消息队列中取出消息,完成对应的操作。