操作系统重要知识点考点总结
操作系统概论笼统的描述了操作系统的目的:更加高效地利用计算机资源
在最早的时候没有操作系统,程序员通过纸带和计算机交流。加载程序,取回结果都要靠纸带,在装带的时候 cpu 处于空闲状态
然后出现了脱机的概念,即通过外围设备进行输入和输出,节省了人工装纸带的时间,提高了输入输出速度。但是此时一个用户还是独占一台计算机
然后出现了单通道批处理的概念,把一堆程序塞到机器里面,按照 顺序 执行,就能实现一台机器多个任务,但是因为 【输入,计算,输出】的顺序不变,同一时间还是仅有一个部件在工作。在输入的时候无法同时进行计算,导致硬件资源利用不充分
然后就加入了 流水线 的概念,出现了多通道批处理。在程序 1 计算的时候 cpu 忙,但是输入设备空闲,可以利用这个空闲,同时获取程序 2 需要的输入:
这样下来硬件利用充分了,但是还有一个缺点,那就是无法进行人机 交互 ,然后就引入了分时操作系统,按照时间片切换任务进行调度,能够在较短的时间内响应用户的输入
分时系统响应速度有限,于是为了进一步进行精确的控制,引入了中断的概念和实时系统,在一定的时间内产生时钟中断,保证任务定时交付
不论什么操作系统,均要满足以下特性:
关于信号量更加详细的笔记:信号量机制与共享资源的并发访问问题
多道程序一并推进,如果访问同一资源,势必造成同步问题。信号量用来处理并发访问问题。
信号量由两个部分组成:
通过一对 不可中断 的 PV 操作对信号量进行控制:
总结:要申请什么就 P 一下,要归还什么就 V 一下
此外还有 AND 型信号量,一次分配所有资源,要么就不分配(解决死锁问题)
管程方便程序员管理互斥资源。一个管程同一时间只能被一个进程占有以实现互斥,程序员无需自己实现互斥
管程封装了条件变量作为阻塞的原因(阻塞可以有很多原因,等待 io 或者等待网络),同时提供两个操作:
注:
考研 408 的书上面好像才有 wait,signal 和 PV 操作的具体区分,老师的 ppt 上面并未具体区分二者
多个进程因竞争资源而陷入僵局,没有外力干涉无法解除
死锁的四个必要条件:
注:满足必要条件不一定会造成死锁,造成死锁的根本原因是 互斥资源的竞争和进程推进顺序非法
通过破坏死锁的四个必要条件进行死锁预防:
在分配资源之前进行检测,避免系统进入不安全的状态,从而避免死锁。
已知进程的最大需求,现有资源个数,已分配资源个数,如果能够找到一个分配序列,使得所有任务顺利完成,就称此时系统进入安全状态
以 单种类 资源为例:
首先 3 满足 P2 的需求,首先分配给 P2,P2 完成后释放占有的 2 资源,现在手头上资源数目为 3+2 = 5
然后 5 满足 P1 的需求,首先分配给 P1,P1 完成后释放占有的 5 资源,现在手头上资源数目为 5+5 = 10
然后分配给 P3,顺利完成,系统处于安全状态
注:
处于安全状态 一定不会 进死锁
处于危险状态 有可能 进死锁
银行家算法管理了多种,多个资源的分配问题。在每次分配视作一次 ”试探“,分配后进行安全性检查,如果危险,那么撤回分配
例:
首先根据资源总数,减去已经分配的资源数目,算出目前可用资源量:R1,R2,R3 分别为 2,3,3
然后计算 need 表,也就是每个进程还需要多少资源才能推进
最后遍历 need 表,找一个可以满足的进程,分配资源,推进进程,回收已经分配的资源(Allocated 表,下图未画出,在上面的题图上)重复:
死锁检测机制在分配的时候不做任何处理,但是会检测系统状态,提供解除死锁的手段。
资源分配图规定的图元如下:
例:
在图中找到一个可以满足全部资源的进程(圆圈),比如 P1 拿到了两个 R1,申请一个 R2,但是 R2 还有剩余一个,于是 P1 可以获得全部资源,这个时候 P1 可以完成并且释放所有资源,就可以撤销 P1 的所有边,得到新的资源图:
然后重复上述过程,消掉 P2,如图:
如果最后能够消除所有的进程,说明系统不存在死锁
关于调度更加详细的笔记:处理机调度算法
首先是周转时间描述进程调度的好坏:
周 转 时 间 = 完 成 时 刻 − 进 入 时 刻 周转时间 = 完成时刻 - 进入时刻 周转时间=完成时刻−进入时刻
带权周转时间是在周转时间的基础上,除以作业的实际运行时间,以反应等待时间和实际执行实际的比率:
带 权 周 转 时 间 = 周 转 时 间 作 业 实 际 运 行 时 间 {}\\ 带权周转时间 = \frac{周转时间}{作业实际运行时间} \\ {}\\ 带权周转时间=作业实际运行时间周转时间
然后是一些调度算法:
注意这里响应比的计算公式:
响 应 比 = 已 经 等 待 的 时 间 + 需 要 运 行 的 时 间 需 要 运 行 的 时 间 响应比 = \frac{已经等待的时间+需要运行的时间}{需要运行的时间} 响应比=需要运行的时间已经等待的时间+需要运行的时间
等的越久,响应比越高,越 “急”
然后就是多级反馈队列,有一个小细节:如果队列二在运行进程 p,此时队列一来了一个新进程,那么新进程将抢占处理机。于是将 p 插入队列二的队尾,然后调度新进程
题目:时间片轮转调度
有 5 个进程从 0 时刻按照 P1,P2,P3,P4,P5 的顺序到达
他们分别需要的执行时间为:20,15,35,25,40 毫秒
以 5 毫秒作为时间片进行调度,求平均周转时间
解:
做题的时候写出调度顺序就不容易出错
题目:高响应比优先
有五个进程,到达时间为0,1,3,5,9,需要执行时间分别为7,10,5,7,4,如果采用高响比优先调度算法,请给出各进程执行顺序图示;并计算各进程周转时间和带权周转时间。
注意高相应比优先算法一般是 不抢占 的
内存空间的分配与管理。
一般用于用户空间的虚拟地址,线性分配连续的内存地址。主要的分配方法有:
连续内存分配可以用首次适应,最佳匹配法进行内存分配
进程空间的虚拟地址很大,即使有孔洞也不会怎么样,但是物理内存是非常宝贵的,一点也不能浪费,为了解决碎片问题,离散内存分配的方式就有了
离散内存分配方式:
一个简单的映射关系如下:
在一个简单的一级映射关系中,将一个虚拟地址划分为两部分,页号和页内地址:
首先通过页号,查找进程的页表,找到对应的物理页号,然后加上页内偏移,得到最终的物理地址:
例:页大小为 1024 Byte,地址长度 16 bit,虚拟地址 0x8230 对应的页表项的值(物理页号)为 0x18,求物理地址:
页面大小 1024 B,16 bit 地址长度最多可以表示 65536 B 的内存空间,于是总共有 65536 / 1024 = 64 个页面,而 64 是 2 的 6 次方,于是有 6 bit 表示页号,10 bit 表示页内偏移,查找过程如下:
最终计算:物理地址 = 物理页号 x 页大小 + 页内偏移
,注意十进制与时六进制的转换
页表也在内存中,可以用快表来加速,类似 cache 高速缓存。查页表项目的时候,同时查 TLB 和内存页表,注意是同时:
一级页表能够表示的地址范围有限,同时要存储很多页表项目,于是有了多级页表:
如果仅采用连续内存分配,代码段之后就是数据段(连续的),如果代码段要拓展,就很麻烦。
分段机制将用户的虚拟地址空间逻辑上的划分为多个段,方便拓展。每一段都是从 0 开始的虚拟地址,段内的地址空间连续,但是段之间的地址是断开的,方便段的拓展:
因为每个段都是从 0 开始的虚拟地址,所以进行地址转换的时候,每个进程要有一个段表来记录每个段的起始物理地址:
注:这里不包含分页,只是单纯的分段,所以和页表查询差不多,也是段号查起始地址,然后加段内偏移
在分段的基础上加入分页,因为每个段都是从 0 开始的线性编址,于是 每个段都要有自己的页表 ,故一个虚拟地址被逻辑上的分为了三部分:
物理地址 = 物理页号 x 页大小 + 页内偏移
如图:
内存虚拟化运行我们将大于物理内存的虚拟内存分配出去,这是因为并不是每个程序在运行时都需要全部的内存空间,发、根据局部性原理,往往提供一部分的工作集,程序就能正常
虚拟内存运行存放一部分页面在磁盘,需要的时候再调入,但是如果物理内存没有空间,那么就要从物理内存中找一页丢到磁盘上,找谁?怎么找?这些都是内存置换算法需要解决的
如果能够知晓后续的程序需要用到那些页,就可以选择那些 最迟被使用,或者之后再也不使用的页 进行换出,尽可能的减少置换次数
注:
实际编程中没有上帝视角,所以该算法是理论最佳,仅供参考。
简单的利用先进先出规则,每次换出最早 进来 的页,简单但是低效:
选取最久未使用的页换出,类似 FIFO,维护一个按照访问时间排序的队列,每次换出的都是队头的 最久未被访问 的页:
和 FIFO 最大的不同点就是 FIFO 命中了页,不做任何操作,但是 LRU 命中了页,就要把它的访问时间设置为最新,插入队尾,注意区分两者命中时的策略
LRU 算法的近似,页面拥有一个访问标志位,每次循环查找页面,根据标志位找出要换出的页面
图示如下:
除了访问控制位,新增加一位修改位。因为 被修改的页换出之前,要把改动保存到磁盘上面 代价较大,如果没有写过的页直接丢弃就行了,反正磁盘里面有它的备份。于是有四种页面:
改进型 CLOCK 算法通过多轮扫描:
算法的图解如下:
裸磁盘之提供两个接口:从指定字节读取数据,将数据写道指定字节处,十分简陋,操作系统种的文件系统,将磁盘分为若干个大小相等的盘块来进行统一管理,那么就面临两个难题:
最常见的方式就是分配一系列连续的盘块给一个文件,但是这样会造成大量的磁盘碎片问题:
为了解决碎片问题,将文件分散地存储在不同的盘块上,通过指针来索引。这种方法叫做 隐式链接法 即指针直接存储在盘块数据区之中,就像链表一样:
但是直接存储指针会消耗一部分空间,导致磁盘块不是二的幂次倍大小,不方便和内存对齐。
数据区纯粹地存储数据,那么指针就要放在一段 FAT 表中,通过在表中索引进而知晓。以下图为例,索引 A 文件,先在 FAT 表中进行查找,顺序是 0,10,3,那么文件就由这三块盘块拼接而成:
但是缺点是 FAT 表都会很大,尽管实际上并没有那么多有效的数据盘块。
于是干脆有一个文件就分配一个索引块,而不是一次性全部分配。那么对于每个文件来说,直接查找索引块里面的索引顺序,就可以按顺序找回文件内容。查找 A 文件,先找到目录下面的索引块号 20,再查 20 块内部的索引顺序位 0,10,3,最早找回盘块上的数据:
这种索引块的方式缺点就是索引块的大小不好确定,如果大了,对小文件来说浪费,如果小了,存不下大文件。
假设一个盘块 512 字节,一个索引项目 4 字节,那么一个索引块(索引块也是盘块)就可以存储 512 / 4 = 128
个索引项目 ,那么文件的总大小就不超过 128 * 512 = 65536
字节,也就是 64 MB,可以说是相当小了
于是便引入二级索引:
还是以 512 字节的盘块和 4 字节的索引项目为例,一个索引块里面有 512 B / 4 B = 128
个二级索引块,而根据上面的推论,每一个单级索引可以寻址的文件大小是 128 * 512 B = 65536 B = 64 MB
,于是二级索引一共可以表示 64 MB * 128 = 8192 MB
的文件
显然 8 GB 还是不够大,于是引入多级索引。首先是若干个直接索引,如果装不下了,就二级索引,如果还装不下,就三级,四级,以此类推:
和分页内存的分配类似,只不过要更加复制
首先是空闲表法,记录 连续 的空闲盘块的:
每次分配可以使用首次适应或者最佳匹配法,回收的时候要注意对相邻空闲区域的拼接
也可以用双向链表将空闲去进行链接,组成空闲盘块链表,但是一种更加常用的方法是使用 bitmap,也就是位图来表示。一个字节有 8 bit,可以表示 8 个盘块是否被分配:
一种更加实用的方法是分组索引法,由一个【空闲盘块栈】管理所有的索引块,而每一个索引块都保存了一组指向空闲盘块的指针:
此外,每一个盘块都可以用来存放一组索引,索引块本身也可以作为空闲盘块被分配出去
每个索引块的最底部元素(绿色标识),指向的是一个索引块,如图所示:
在分配的时候,首先分配一般的盘块(114 号盘块):
如果一般的盘块分配完了,那么分配空闲盘块栈底部的 索引块 (注意索引块也是一个盘块,可以被分配出去)但是这样会丢失索引信息,于是 将要分配的索引块的信息拷贝到空闲盘块栈 ,然后分配索引块:
同理回收的时候,如果空闲盘块栈已满,那么使用新回收的盘块,创建一个新索引块,将空闲盘块栈数据复制到新索引块,然后空闲盘块栈清空,底部指向新索引块:
和输入输出设备之间进行信息交换,管理设备,同时提供一致的访问手段,也是操作系统要解决的问题。
最原始的 IO 操作就是轮询,一直循环查询状态寄存器,直到有数据到来:
因为外部设备的速度很慢,轮询的等待代价太大,于是利用中断机制,cpu 先发出 IO 请求,然后去干别的事情,最后外部设备数据准备好的时候,发出中断,cpu 转回来处理:
从设备中读取数据到内存还是太慢了,于是出现了直接内存访问技术(DMA),通过 DMA 设备直接将数据读到内存,cpu 就不用干这些活了,只用专心处理数据即可:
引入缓冲区进一步缓解了 cpu 和 io 设备之间的矛盾。操作系统在内存中分配一块区域专门用于 IO 的信息存储,这块区域叫做缓冲区
考虑一块缓冲区的情况,数据从设备到缓冲区要经过两次转移:
对于单缓冲区的 IO 操作来说,流程为:
假设一个初始状态为【工作区满,缓冲区空】,那么系统下一次到达初始状态,中间经历的时间就是一个周期。
初始状态工作区满,就可以开始计算,同时开始将下一次的输入从设备搬到缓冲区(即 T 操作),当 T 结束之后,才可以进行传送,将缓冲区数据传送到 cpu,图解如下:
系统再次达到了初始状态【工作区满,缓冲区空】,而一个周期的时间为:
max ( T , C ) + M \max(T,C)+M max(T,C)+M
注意这里 C 是计算上一次传过来的数据,同时输入下一次的数据,为后续计算做准备,所以一个周期发生 的事情是 C 1 , T 2 , M 2 C_1, T_2, M_2 C1,T2,M2 是错开的
因为单一缓冲区要求必须先将数据从设备传输到缓冲区(T 操作),然后才能将数据传送到工作区(M 操作)
而引入两块缓冲可以打破顺序限制,在对缓冲区 1 进行 T 操作的同时可以对缓冲区 2 进行 M 操作,两者不干扰!
同样指定初始状态【一个缓冲区满,一个缓冲区空】那么首先可以开始将数据从缓冲区 1 搬到工作区(M 操作),与此同时可以将数据从 IO 设备搬到缓冲区 2(T 操作), M b u f 1 M_{buf1} Mbuf1 和 T b u f 2 T_{buf2} Tbuf2 是同时进行的,然后 M 阶段之后就可以计算(C 操作),图解如下:
于是一个周期就为:
max ( M + C , T ) \max(M+C, T) max(M+C,T)
这个公式不要嗯背,结合图片理解
实际应用中不止一个设备,也不止两个缓冲,于是采用缓冲池的方式管理若干个设备缓冲区。一个缓冲池要提供四种接口:
事实上,设备输出的数据要作为进程的输入,进程输出的数据要作为设备的输入,于是通过两条队列就可以管理:
光有缓冲区还不够,多进程,多设备的缓冲区又引入了协调问题,于是出现了 SPOOLING 技术
SPOOLing 技术又名假脱机技术,不光缓解了 cpu 和低速设备的矛盾,还对输入输出进行了同一管理,将一组并发的输入输出请求串行化
进程请求 IO 首先将数据写道磁,然后请求挂队列,然后空闲时,输入输出进程利用缓冲区,按照请求队列的顺序将请求串行化同时执行。
SPOOLing 技术将设备虚拟化,将共享设备抽象为每个进程的独占设备,这也是为何同时打印多份文档,打印结果不会乱序一样
访问机械硬盘需要三个时间代价:
其中旋转时间一般平均为半圈,而花费最多的是寻道时间。如何寻道就成了优化磁头调度的关键
一种最简单的策略就是先来先服务(FCFS),简单但是代价高,可以看到下图,磁头来回摇摆,开销很大:
另一种简单的方法就是选取离当前磁头最近的磁道进行访问,这样效率很高,看图,访问远端的磁道之前必定访问完近处的磁道,顺路把事情都办了:
但是总是访问最近的磁道,容易造成饥饿问题。磁头在 90,访问 100 和 180,首先访问 100,但是如果此时又来个一个 99 的请求,磁头距离 99 的距离最短,就不会访问 180 磁道了。故远端的磁道调度永远达不到(磁头在某个点来回摆动)
于是引入扫描调度算法(SCAN),算法要求每次访问磁道必须按照顺序, 先是从小到大,然后再从大到小 ,这样保证每一轮扫描,磁头只会掉头两次,避免了来回摆动。注意图中 只有一次掉头 :
还可以进一步引入改进。改进的 SCAN 算法(又叫 CSCAN)要求每次磁头到达最大磁道只会, 返回最小磁道 ,重新开始,看图,扫描到 184 最大了,直接 返回 18 最小磁道,重新开始:
从本质上解决磁头饥饿的问题,就是 在执行一组访问的时候,不能够插入新的请求 。于是 FSCAN 算法维护两个队列,一个是执行队列,一个是请求队列。执行的时候以执行队列为准。此外,划分 n 个队列的算法进阶版,叫做 n-step-SCAN
贴张老师画的考点图,字体有点抽象。能就看,不行就寄: