侧边栏壁纸
博主头像
一定会去到彩虹海的麦当

说什么呢?约定好的事就一定要做到啊!

  • 累计撰写 63 篇文章
  • 累计创建 16 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

[面试题]-操作系统

一定会去到彩虹海的麦当
2022-05-18 / 0 评论 / 0 点赞 / 107 阅读 / 15,615 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

一 操作系统基础

面试官顶着蓬松的假发向我走来,只见他一手拿着厚重的 Thinkpad ,一手提着他那淡黄的长裙。

1.1 什么是操作系统?

操作系统是管理硬件和软件的一种应用程序。操作系统是运行在计算机上最重要的一种软件,它管理计算机的资源和进程以及所有的硬件和软件。它为计算机硬件和软件提供了一种中间层,使应用软件和硬件进行分离,让我们无需关注硬件的实现,把关注点更多放在软件应用上。

通常情况下,计算机上会运行着许多应用程序,它们都需要对内存和 CPU 进行交互,操作系统的目的就是为了保证这些访问和交互能够准确无误的进行。

什么是用户态和内核态

用户态和内核态是操作系统的两种运行状态。

  1. 用户态 (user mode) : 用户态运行的进程可以直接读取用户程序的数据。
  2. 系统态 (kernel mode): 可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。

那么为什么要有用户态和内核态呢?

这个主要是访问能力的限制的考量,计算机中有一些比较危险的操作,比如设置时钟、内存清理,这些都需要在内核态下完成,如果随意进行这些操作,那你的系统得崩溃多少次。

用户态和内核态是如何切换的?

所有的用户进程都是运行在用户态的,但是我们上面也说了,用户程序的访问能力有限,一些比较重要的比如从硬盘读取数据,从键盘获取数据的操作则是内核态才能做的事情,而这些数据却又对用户程序来说非常重要。所以就涉及到两种模式下的转换,即用户态 -> 内核态 -> 用户态,而唯一能够做这些操作的只有 系统调用,而能够执行系统调用的就只有 操作系统

一般用户态 -> 内核态的转换我们都称之为 trap 进内核,也被称之为 陷阱指令 (trap instruction)

他们的工作流程如下:

二 进程和线程

2.1 进程和线程的区别

👨‍💻面试官: 好的!我明白了!那你再说一下: 进程和线程的区别

🙋 我:  好的! 下图是 Java 内存区域,我们从 JVM 的角度来说一下线程和进程之间的关系吧!

一个进程中可以有多个线程,多个线程共享进程的方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器虚拟机栈本地方法栈

总结:  线程是进程划分成的更小的运行单位, 一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。

进程的控制结构

PCB 是进程存在的唯⼀标识,包含有进程描述信息、进程控制和管理信息、资源分配清单、CPU相关信息(例如寄存器的值)等
PCB通常是通过链表的⽅式进⾏组织,把具有相同状态的进程链在⼀起,组成各种队列。

  • 将所有处于就绪状态的进程链在⼀起,称为就绪队列;
  • 把所有因等待某事件⽽处于等待状态的进程链在⼀起就组成各种阻塞队列

2.2 进程有哪几种状态?

👨‍💻面试官 : 那你再说说进程有哪几种状态?

🙋 :我们一般把进程大致分为 5 种状态,这一点和线程 open in new window很像!

  • 创建状态 (new) :进程正在被创建,尚未到就绪状态。
  • 就绪状态 (ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源 (处理器分配的时间片) 即可运行。
  • 运行状态 (running) :进程正在处理器上上运行 (单核 CPU 下任意时刻只有一个进程处于运行状态)。
  • 阻塞状态 (waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
  • 结束状态 (terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。

在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次 运⾏的时候,再从硬盘换⼊到物理内存。


挂起状态:描述进程没有占⽤实际的物理内存空间的情况

  • 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
  • 就绪挂起状态:进程在外存(硬盘),但只要进⼊内存,即刻⽴刻运⾏

2.3 进程间的通信方式

👨‍💻面试官进程间的通信常见的的有哪几种方式呢?

🙋 :大概有 7 种常见的进程间的通信方式。

管道

1、匿名管道

  • 匿名管道顾名思义,它没有名字标识,匿名管道是特殊⽂件只存在于内存,没有存在于⽂件系统中,shell命令中的「 | 」竖线就是匿名管道
  • 通信的数据是⽆格式的流并且⼤⼩受限,通信的⽅式是单向的,数据 只能在⼀个⽅向上流动,如果要双向通信,需要创建两个管道
  • 匿名管道是只能⽤于存在⽗⼦关系的 进程间通信,匿名管道的⽣命周期随着进程创建⽽建⽴,随着进程终⽌⽽消失

以使⽤ fork 创建⼦进程,创建的⼦进程会复制⽗进程的⽂件描述符,这样就做到了两个进程各有相同写入端描述符、读取端描述符,两个进程就可以通过各自的文件描述符写入和读取同一个管道文件实现跨进程通信。

2、命名管道

  • 命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使⽤命名管道的前提,需要在⽂件系统 创建⼀个类型为 p 的设备⽂件,那么毫⽆关系的进程就可以通过这个设备⽂件进⾏通信

不管是匿 名管道还是命名管道,进程写⼊的数据都是缓存在内核中,另⼀个进程读取数据时候⾃然也是从内核中获 取,同时通信数据都遵循先进先出原则,不⽀持 lseek 之类的⽂件定位操作

消息队列

  • 消息队列克服了管道通信的数据是⽆格式的字节流的问题,消息队列的消息体是可以⽤户⾃定义的数据类型
  • 消息队列通信的速度不是最及时的,毕竟每次数据的写⼊和读取都需要经过⽤户态与内核态之间的拷⻉过程。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除;
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

共享内存

  • 共享内存可以解决消息队列通信中⽤户态与内核态之间数据拷⻉过程带来的开销,它直接分配⼀个共享空 间,每个进程都可以直接访问,就像访问进程⾃⼰的空间⼀样快捷⽅便,不需要陷⼊内核态或者系统调⽤
  • 但是便捷⾼效的共享内存通信,带来新的 问题,多进程竞争同个共享资源会造成数据的错乱

信号量

  • 信号量用来保护共享资源,以确保任何时刻只能有⼀个进程访问共享资源,这种⽅式就是互斥 访问
  • 信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是⼀个计数器,表示的 是资源个数,其值可以通过两个原⼦操作来控制,分别是 P 操作和 V 操作。

信号

  • 信号是进程间通信机制中唯 ⼀的异步通信机制,信号可以在应⽤进程和内核之间直接交互,内核也可以利⽤信号来通知⽤户空间的进 程发⽣了哪些系统事件
  • 信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)
  • ⼀旦有信号发⽣,进程有三种⽅式响应信号 1. 执⾏默认操作、2. 捕捉信号、3. 忽略信号

Socket通信

  • 为了解决与不同主机的进程间通信问题
  • 也可以⽤于本地主机进程间通信
  • 可根据创建 Socket 的类型不同,分为三种常⻅的通信⽅式,⼀个是基于 TCP 协议的通信⽅式,⼀个是基于 UDP 协议 的通信⽅式,⼀个是本地进程间通信⽅式

2.4 线程间的同步的方式

👨‍💻面试官那线程间的同步的方式有哪些呢?

1、锁

使⽤加锁操作和解锁操作可以解决并发线程/进程的互斥问题。

任何想进⼊临界区的线程,必须先执⾏加锁操作。若加锁操作顺利通过,则线程可进⼊临界区;在完成对 临界资源的访问后再执⾏解锁操作,以释放该临界资源

2、信号量

通常信号量表示资源的数量,对应的变量是⼀个整型( sem )变量。

通过两个原⼦操作的系统调⽤函数来控制信号量:

  • P 操作,这个操作会把信号量减去 1,相减后如果信号量 < 0,则表明资源已被占⽤,进程需 阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使⽤,进程可正常继续执⾏。
  • V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进 程,于是会将该进程唤醒运⾏;相加后如果信号量 > 0,则表明当前没有阻塞中的进程

信号量初始化为1:使得两个进程互斥访问共享内存
信号量初始化为0:实现多进程同步的方式

其他类型进程

守护进程

指在后台运行的,没有控制终端与之相连的进程。它独立于控制终端,周期性地执行某种任务。
Linux的大多数服务器就是用守护进程的方式实现的,如web服务器进程http等

孤儿进程

如果父进程先退出,子进程还没退出,那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)
一个父进程退出,而它的一个或多个了进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程

如果了进程先退出,父进程还没退出,那么了进程必须等到父进程捕获到了了进程的退出状态才真正结束,否则这个时候了进程就成为僵尸进程。
设置僵尸进程的目的是维护了进程的信息,以便父进程在以后某个时候获取。

2.5 进程的调度算法

👨‍💻面试官你知道操作系统中进程的调度算法有哪些吗?

🙋 :嗯嗯!这个我们大学的时候学过,是一个很重要的知识点!

为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法,它们是:

  • 先到先服务 (FCFS) 调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 短作业优先 (SJF) 的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 时间片轮转调度算法 : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin) 调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
  • 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
  • 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。

2.6 什么是死锁

👨‍💻面试官你知道什么是死锁吗?

🙋 :死锁描述的是这样一种情况:多个进程 / 线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程 / 线程被无限期地阻塞,因此程序不可能正常终止。

2.7 死锁的四个条件

👨‍💻面试官产生死锁的四个必要条件是什么?

🙋 :如果系统中以下四个条件同时成立,那么就能引起死锁:

  • 互斥:资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
  • 占有并等待:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
  • 非抢占:资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
  • 循环等待:有一组等待进程 {P0, P1,..., Pn}P0 等待的资源被 P1 占有,P1 等待的资源被 P2 占有,…,Pn-1 等待的资源被 Pn 占有,Pn 等待的资源被 P0 占有。

注意,只有四个条件同时成立时,死锁才会出现。

2.8 解决死锁的方法

解决死锁的方法可以从多个角度去分析,一般的情况下,有预防,避免,检测和解除四种

  • 预防 是采用某种策略,限制并发进程对资源的请求,从而使得死锁的必要条件在系统执行的任何时间上都不满足。
  • 避免则是系统在分配资源时,根据资源的使用情况提前做出预测,从而避免死锁的发生
  • 检测是指系统设有专门的机构,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
  • 解除 是与检测相配套的一种措施,用于将进程从死锁状态下解脱出来

死锁的预防

死锁四大必要条件上面都已经列出来了,很显然,只要破坏四个必要条件中的任何一个就能够预防死锁的发生。

破坏第一个条件 互斥条件:使得资源是可以同时访问的,这是种简单的方法,磁盘就可以用这种方法管理,但是我们要知道,有很多资源 往往是不能同时访问的 ,所以这种做法在大多数的场合是行不通的。

破坏第三个条件 非抢占 :也就是说可以采用 剥夺式调度算法,但剥夺式调度方法目前一般仅适用于 主存资源处理器资源 的分配,并不适用于所以的资源,会导致 资源利用率下降

所以一般比较实用的 预防死锁的方法,是通过考虑破坏第二个条件和第四个条件。

1、静态分配策略

静态分配策略可以破坏死锁产生的第二个条件(占有并等待)。所谓静态分配策略,就是指一个进程必须在执行前就申请到它所需要的全部资源,并且知道它所要的资源都得到满足之后才开始执行。进程要么占有所有的资源然后开始执行,要么不占有资源,不会出现占有一些资源等待一些资源的情况。

静态分配策略逻辑简单,实现也很容易,但这种策略 严重地降低了资源利用率,因为在每个进程所占有的资源中,有些资源是在比较靠后的执行时间里采用的,甚至有些资源是在额外的情况下才是用的,这样就可能造成了一个进程占有了一些 几乎不用的资源而使其他需要该资源的进程产生等待 的情况。

2、层次分配策略

层次分配策略破坏了产生死锁的第四个条件 (循环等待)。在层次分配策略下,所有的资源被分成了多个层次,一个进程得到某一次的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源,按这种策略,是不可能出现循环等待链的,因为那样的话,就出现了已经申请了较高层的资源,反而去申请了较低层的资源,不符合层次分配策略,证明略。

死锁的避免

上面提到的 破坏 死锁产生的四个必要条件之一就可以成功 预防系统发生死锁 ,但是会导致 低效的进程运行资源使用率 。而死锁的避免相反,它的角度是允许系统中同时存在四个必要条件 ,只要掌握并发进程中与每个进程有关的资源动态申请情况,做出 明智和合理的选择 ,仍然可以避免死锁,因为四大条件仅仅是产生死锁的必要条件。

我们将系统的状态分为 安全状态不安全状态 ,每当在未申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。

如果操作系统能够保证所有的进程在有限的时间内得到需要的全部资源,则称系统处于安全状态,否则说系统是不安全的。很显然,系统处于安全状态则不会发生死锁,系统若处于不安全状态则可能发生死锁。

那么如何保证系统保持在安全状态呢?通过算法,其中最具有代表性的 避免死锁算法 就是 Dijkstra 的银行家算法,银行家算法用一句话表达就是:当一个进程申请使用资源的时候,银行家算法 通过先 试探 分配给该进程资源,然后通过 安全性算法 判断分配后系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待,若能够进入到安全的状态,则就 真的分配资源给该进程

银行家算法详情可见:《一句话 + 一张图说清楚——银行家算法》open in new window

操作系统教程树中讲述的银行家算法也比较清晰,可以一看.

死锁的避免 (银行家算法) 改善解决了 资源使用率低的问题 ,但是它要不断地检测每个进程对各类资源的占用和申请情况,以及做 安全性检查 ,需要花费较多的时间。

死锁的检测

对资源的分配加以限制可以 预防和避免 死锁的发生,但是都不利于各进程对系统资源的充分共享。解决死锁问题的另一条途径是 死锁检测和解除 (这里突然联想到了乐观锁和悲观锁,感觉死锁的检测和解除就像是 乐观锁 ,分配资源时不去提前管会不会发生死锁了,等到真的死锁出现了再来解决嘛,而 死锁的预防和避免 更像是悲观锁,总是觉得死锁会出现,所以在分配资源的时候就很谨慎)。

这种方法对资源的分配不加以任何限制,也不采取死锁避免措施,但系统 定时地运行一个 “死锁检测” 的程序,判断系统内是否出现死锁,如果检测到系统发生了死锁,再采取措施去解除它。

进程 - 资源分配图

操作系统中的每一刻时刻的系统状态都可以用进程 - 资源分配图来表示,进程 - 资源分配图是描述进程和资源申请及分配关系的一种有向图,可用于检测系统是否处于死锁状态

用一个方框表示每一个资源类,方框中的黑点表示该资源类中的各个资源,每个键进程用一个圆圈表示,用 有向边 来表示进程申请资源和资源被分配的情况

图中 2-21 是进程 - 资源分配图的一个例子,其中共有三个资源类,每个进程的资源占有和申请情况已清楚地表示在图中。在这个例子中,由于存在 占有和等待资源的环路 ,导致一组进程永远处于等待资源的状态,发生了 死锁

进程 - 资源分配图中存在环路并不一定是发生了死锁。因为循环等待资源仅仅是死锁发生的必要条件,而不是充分条件。图 2-22 便是一个有环路而无死锁的例子。虽然进程 P1 和进程 P3 分别占用了一个资源 R1 和一个资源 R2,并且因为等待另一个资源 R2 和另一个资源 R1 形成了环路,但进程 P2 和进程 P4 分别占有了一个资源 R1 和一个资源 R2,它们申请的资源得到了满足,在有限的时间里会归还资源,于是进程 P1 或 P3 都能获得另一个所需的资源,环路自动解除,系统也就不存在死锁状态了。

死锁检测步骤

知道了死锁检测的原理,我们可以利用下列步骤编写一个 死锁检测 程序,检测系统是否产生了死锁。

  1. 如果进程 - 资源分配图中无环路,则此时系统没有发生死锁
  2. 如果进程 - 资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
  3. 如果进程 - 资源分配图中有环路,且涉及到的资源类有多个资源,此时系统未必会发生死锁。如果能在进程 - 资源分配图中找出一个 既不阻塞又非独立的进程 ,该进程能够在有限的时间内归还占有的资源,也就是把边给消除掉了,重复此过程,直到能在有限的时间内 消除所有的边 ,则不会发生死锁,否则会发生死锁。(消除边的过程类似于 拓扑排序)

死锁的解除

当死锁检测程序检测到存在死锁发生时,应设法让其解除,让系统从死锁状态中恢复过来,常用的解除死锁的方法有以下四种:

  1. 立即结束所有进程的执行,重新启动操作系统 :这种方法简单,但以前所在的工作全部作废,损失很大。
  2. 撤销涉及死锁的所有进程,解除死锁后继续运行 :这种方法能彻底打破死锁的循环等待条件,但将付出很大代价,例如有些进程可能已经计算了很长时间,由于被撤销而使产生的部分结果也被消除了,再重新执行时还要再次进行计算。
  3. 逐个撤销涉及死锁的进程,回收其资源直至死锁解除。
  4. 抢占资源 :从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除。

两阶段加锁

虽然很多情况下死锁的避免和预防都能处理,但是效果并不好。随着时间的推移,提出了很多优秀的算法用来处理死锁。例如在数据库系统中,一个经常发生的操作是请求锁住一些记录,然后更新所有锁定的记录。当同时有多个进程运行时,就会有死锁的风险。

一种解决方式是使用 两阶段提交 (two-phase locking)。顾名思义分为两个阶段,一阶段是进程尝试一次锁定它需要的所有记录。如果成功后,才会开始第二阶段,第二阶段是执行更新并释放锁。第一阶段并不做真正有意义的工作。

如果在第一阶段某个进程所需要的记录已经被加锁,那么该进程会释放所有锁定的记录并重新开始第一阶段。从某种意义上来说,这种方法类似于预先请求所有必需的资源或者是在进行一些不可逆的操作之前请求所有的资源。

不过在一般的应用场景中,两阶段加锁的策略并不通用。如果一个进程缺少资源就会半途中断并重新开始的方式是不可接受的。

三 操作系统内存管理基础

3.1 内存管理介绍

👨‍💻 面试官: 操作系统的内存管理主要是做什么?

🙋 我:  操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。

3.2 常见的几种内存管理机制

👨‍💻 面试官: 操作系统的内存管理机制了解吗?内存管理有哪几种方式?

🙋 我:  这个在学习操作系统的时候有了解过。

简单分为连续分配管理方式非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 块式管理 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理段式管理

  1. 块式管理 : 远古时代的计算机操作系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片。
  2. 页式管理 :把主存分为大小相等且固定的一页一页的形式,页较小,相比于块式管理的划分粒度更小,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
  3. 段式管理 : 页式管理虽然提高了内存利用率,但是页式管理其中的页并无任何实际意义。 段式管理把主存分为一段段的,段是有实际意义的,每个段定义了一组逻辑信息,例如, 有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。

简单来说:页是物理单位,段是逻辑单位。分页可以有效提高内存利用率,分段可以更好满足用户需求。

👨‍💻面试官 : 回答的还不错!不过漏掉了一个很重要的 段页式管理机制 。段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 段页式管理机制 中段与段之间以及段的内部的都是离散的。

🙋 :谢谢面试官!刚刚把这个给忘记了~

3.3 快表和多级页表

👨‍💻面试官 : 页表管理机制中有两个很重要的概念:快表和多级页表,这两个东西分别解决了页表管理中很重要的两个问题。你给我简单介绍一下吧!

🙋 :在分页内存管理中,很重要的两点是:

  1. 虚拟地址到物理地址的转换要快。
  2. 解决虚拟地址空间大,页表也会很大的问题。

快表

为了提高虚拟地址到物理地址的转换速度,操作系统在 页表方案 基础之上引入了 快表 来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。

使用快表之后的地址转换流程是这样的:

  1. 根据虚拟地址中的页号查快表;
  2. 如果该页在快表中,直接从快表中读取相应的物理地址;
  3. 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
  4. 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。

看完了之后你会发现快表和我们平时经常在我们开发的系统使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。

多级页表

引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景,具体可以查看下面这篇文章

总结

为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表(即 TLB)的概念。 不论是快表还是多级页表实际上都利用到了程序的局部性原理,局部性原理在后面的虚拟内存这部分会介绍到。

3.4 分页机制和分段机制的共同点和区别

👨‍💻面试官分页机制和分段机制有哪些共同点和区别呢?

🙋

  1. 共同点
    • 分页机制和分段机制都是为了提高内存利用率,减少内存碎片。
    • 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
  2. 区别
    • 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
    • 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。

3.5 逻辑 (虚拟) 地址和物理地址

👨‍💻面试官 :你刚刚还提到了逻辑地址和物理地址这两个概念,我不太清楚,你能为我解释一下不?

🙋 我:  em… 好的嘛!我们编程一般只有可能和逻辑地址打交道,比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。

3.6 CPU 寻址了解吗? 为什么需要虚拟地址空间?

👨‍💻面试官CPU 寻址了解吗? 为什么需要虚拟地址空间?

🙋 :这部分我真不清楚!

于是面试完之后我默默去查阅了相关文档!留下了没有技术的泪水。。。

这部分内容参考了 Microsoft 官网的介绍,地址:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/virtual-address-spaces?redirectedfrom=MSDNopen in new window

现代处理器使用的是一种称为 虚拟寻址 (Virtual Addressing) 的寻址方式。使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。  实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 内存管理单元(Memory Management Unit, MMU) 的硬件。如下图所示:

为什么要有虚拟地址空间呢?

先从没有虚拟地址空间的时候说起吧!没有虚拟地址空间的时候,程序直接访问和操作的都是物理内存 。但是这样有什么问题呢?

  1. 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
  2. 想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。

总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。

通过虚拟地址访问内存有以下优势:

  • 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
  • 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
  • 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。

四 虚拟内存

4.1 什么是虚拟内存 (Virtual Memory)?

👨‍💻面试官 :再问你一个常识性的问题!什么是虚拟内存 (Virtual Memory)?

🙋 :这个在我们平时使用电脑特别是 Windows 系统的时候太常见了。很多时候我们使用了很多占内存的软件,这些软件占用的内存可能已经远远超出了我们电脑本身具有的物理内存。为什么可以这样呢? 正是因为 虚拟内存 的存在,通过 虚拟内存 可以让程序可以拥有超过系统物理内存大小的可用内存空间。另外,虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)。这样会更加有效地管理内存并减少出错。

虚拟内存是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是 “使用硬盘空间来扩展内存 “的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间,并且 把内存扩展到硬盘空间。推荐阅读:《虚拟内存的那点事儿》open in new window

维基百科中有几句话是这样介绍虚拟内存的。

虚拟内存 使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如 RAM)的使用也更有效率。目前,大多数操作系统都使用了虚拟内存,如 Windows 家族的 “虚拟内存”;Linux 的“交换空间” 等。From:https://zh.wikipedia.org/wiki / 虚拟内存 open in new window

4.2 局部性原理

👨‍💻面试官 :要想更好地理解虚拟内存技术,必须要知道计算机中著名的局部性原理。另外,局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。

🙋 :局部性原理是虚拟内存技术的基础,正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。

以下内容摘自《计算机操作系统教程》 第 4 章存储器管理。

早在 1968 年的时候,就有人指出我们的程序在执行的时候往往呈现局部性规律,也就是说在某个较短的时间段内,程序执行局限于某一小部分,程序访问的存储空间也局限于某个区域。

局部性原理表现在以下两个方面:

  1. 时间局部性 :如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
  2. 空间局部性 :一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。

时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存” 的两级存储器的结构,利用局部性原理实现髙速缓存。

4.3 虚拟存储器

勘误:虚拟存储器又叫做虚拟内存,都是 Virtual Memory 的翻译,属于同一个概念。

👨‍💻面试官 :~ 都说了虚拟内存了。你再讲讲虚拟存储器把!~

🙋

这部分内容来自:王道考研操作系统知识点整理 open in new window

基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其他部分留在外存,就可以启动程序执行。由于外存往往比内存大很多,所以我们运行的软件的内存大小实际上是可以比计算机系统实际的内存大小大的。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。这样,计算机好像为用户提供了一个比实际内存大得多的存储器——虚拟存储器

实际上,我觉得虚拟内存同样是一种时间换空间的策略,你用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的空间来支持程序的运行。不得不感叹,程序世界几乎不是时间换空间就是空间换时间。

4.4 虚拟内存的技术实现

👨‍💻面试官虚拟内存技术的实现呢?

🙋 虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。  虚拟内存的实现有以下三种方式:

  1. 请求分页存储管理 :建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
  2. 请求分段存储管理 :建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
  3. 请求段页式存储管理

这里多说一下?很多人容易搞混请求分页与分页存储管理,两者有何不同呢?

请求分页存储管理建立在分页管理之上。他们的根本区别是是否将程序全部所需的全部地址空间都装入主存,这也是请求分页存储管理可以提供虚拟内存的原因,我们在上面已经分析过了。

它们之间的根本区别在于是否将一作业的全部地址空间同时装入主存。请求分页存储管理不要求将作业全部地址空间同时装入主存。基于这一点,请求分页存储管理可以提供虚存,而分页存储管理却不能提供虚存。

不管是上面那种实现方式,我们一般都需要:

  1. 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
  2. 缺页中断:如果需执行的指令或访问的数据尚未在内存(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段调入到内存,然后继续执行程序;
  3. 虚拟地址空间 :逻辑地址到物理地址的变换。

4.5 页面置换算法

👨‍💻面试官 :虚拟内存管理很重要的一个概念就是页面置换算法。那你说一下 页面置换算法的作用? 常见的页面置换算法有哪些?

🙋

这个题目经常作为笔试题出现,网上已经给出了很不错的回答,我这里只是总结整理了一下。

地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断 。

缺页中断 就是要访问的不在主存,需要操作系统将其调入主存后再进行访问。 在这个时候,被内存映射的文件实际上成了一个分页交换文件。

当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,我们可以把页面置换算法看成是淘汰页面的规则。

  • OPT 页面置换算法(最佳页面置换算法) :最佳 (Optimal, OPT) 置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面, 这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
  • FIFO(First In First Out) 页面置换算法(先进先出页面置换算法) : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
  • LRU (Least Recently Used)页面置换算法(最近最久未使用页面置换算法) :LRU 算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
  • LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法) : 该置换算法选择在之前时期使用最少的页面作为淘汰页。

IO 篇

操作系统中的时钟是什么

时钟 (Clocks) 也被称为定时器 (timers),时钟 / 定时器对任何程序系统来说都是必不可少的。时钟负责维护时间、防止一个进程长期占用 CPU 时间等其他功能。时钟软件 (clock software) 也是一种设备驱动的方式。下面我们就来对时钟进行介绍,一般都是先讨论硬件再介绍软件,采用由下到上的方式,也是告诉你,底层是最重要的。

时钟硬件

在计算机中有两种类型的时钟,这些时钟与现实生活中使用的时钟完全不一样。

  • 比较简单的一种时钟被连接到 110 V 或 220 V 的电源线上,这样每个电压周期会产生一个中断,大概是 50 - 60 HZ。这些时钟过去一直占据支配地位。
  • 另外的一种时钟由晶体振荡器、计数器和寄存器组成,示意图如下所示

这种时钟称为可编程时钟 ,可编程时钟有两种模式,一种是 一键式 (one-shot mode),当时钟启动时,会把存储器中的值复制到计数器中,然后,每次晶体的振荡器的脉冲都会使计数器 -1。当计数器变为 0 时,会产生一个中断,并停止工作,直到软件再一次显示启动。还有一种模式是方波 (square-wave mode) 模式,在这种模式下,当计数器变为 0 并产生中断后,存储寄存器的值会自动复制到计数器中,这种周期性的中断称为一个时钟周期。

什么是 DMA

DMA 的中文名称是直接内存访问,它意味着 CPU 授予 I/O 模块权限在不涉及 CPU 的情况下读取或写入内存。也就是 DMA 可以不需要 CPU 的参与。这个过程由称为 DMA 控制器(DMAC)的芯片管理。由于 DMA 设备可以直接在内存之间传输数据,而不是使用 CPU 作为中介,因此可以缓解总线上的拥塞。DMA 通过允许 CPU 执行任务,同时 DMA 系统通过系统和内存总线传输数据来提高系统并发性。

直接内存访问的特点

DMA 方式有如下特点:

  • 数据传送以数据块为基本单位
  • 所传送的数据从设备直接送入主存,或者从主存直接输出到设备上
  • 仅在传送一个或多个数据块的开始和结束时才需 CPU 的干预,而整块数据的传送则是在控制器的控制下完成。

DMA 方式和中断驱动控制方式相比,减少了 CPU 对 I/O 操作的干预,进一步提高了 CPU 与 I/O 设备的并行操作程度。

DMA 方式的线路简单、价格低廉,适合高速设备与主存之间的成批数据传送,小型、微型机中的快速设备均采用这种方式,但其功能较差,不能满足复杂的 I/O 要求。

讲一讲IO多路复用?

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合

  • 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
  • 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
  • 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
  • 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
  • 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
  • 与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
0

评论区