操作系统复习总结,仅供笔者复习使用,参考教材:
- 《操作系统原理》 - 何静媛编著. 西安电子科技大学出版社
- 《操作系统考研复习指导》2024年 - 王道论坛组编. 电子工业出版社
本文主要内容为:进程与线程;
计算机系统概述 部分见 操作系统复习总结1;
进程与线程 部分见 操作系统复习总结2;
内存管理 部分见 操作系统复习总结3;
文件管理 部分见 操作系统复习总结4;
输入输出管理 部分见 操作系统复习总结5;
目录
1. 进程与线程
程序是一组指令的集合,是一个静态概念,而程序在内存中执行的过程是动态的,无法从程序的字面上看出它何时执行、停顿,因此引入进程这个动态概念描述程序在内存中执行的过程。
1.1 进程
-
进程:进程是程序的运行过程,是系统进行资源分配和调度的一个独立单位。进程实体由程序段、数据段和进程控制块构成;
- 程序段:进程所调度的程序,同一程序可以被多个进程共享;
- 数据段:程序段加工处理的原始数据或是进程执行时产生的中间或最终结果;
- 进程控制块 PCB :创建进程时新建 PCB,结束进程时删除 PCB,是进程存在的唯一标识,所包含内容如下:
-
进程状态:
- 运行态:进程正在处理机上运行时所处的状态,单处理机系统中每一时刻只有一个进程处于运行态;
- 就绪态:进程获得了除处理机外的一切所需资源,一旦得到处理机,便可立即运行。系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列;
- 阻塞态:进程正在等待某一事件而暂停运行,如等待某资源为可用或等待输入 / 输出完成;
- 创建态:进程正在被创建,尚未转到就绪态。创建进程需要先申请一个空白 PCB 并向其中填写用于控制和管理进程的信息,然后为该进程分配运行时所必须的资源,最后把该进程转入就绪态并插入就绪队列;
- 终止态:进程需要结束运行时,系统首先将该进程置为终止态,然后进一步处理资源释放和回收等工作;
-
进程控制:进程控制的程序段是原语,执行期间不允许中断;
- 创建进程:分配进程标识号 PID 和进程控制块 PCB → \rightarrow → 分配所需资源 → \rightarrow → 初始化 PCB → \rightarrow → 插入就绪队列;
进程也可以创建子进程,子进程可以继承父进程的资源。撤销父进程时,同时会撤销其所有子进程。
- 撤销进程:根据 PID 检索对应进程的 PCB 并读出进程状态 → \rightarrow → 若处于运行态则终止进程并剥夺处理机 → \rightarrow → 若该进程有子孙进程则全部终止 → \rightarrow → 释放该进程的所有资源 → \rightarrow → 将 PCB 从队列中移除;
进程终止分为正常结束、异常结束和外界干预三种:
正常结束:进程的任务已完成并准备退出运行;
异常结束:进程运行时发生异常,如存储越界、非法指令、运行超时等;
外界干预:外界请求导致终止,如操作系统干预、父进程请求等;- 阻塞进程:根据 PID 检索要阻塞进程的 PCB → \rightarrow → 保护现场并将 PCB 设置为阻塞态 → \rightarrow → 插入等待队列;
- 唤醒进程:根据 PID 检索要阻塞进程的 PCB → \rightarrow → 保护现场并将 PCB 设置为阻塞态 → \rightarrow → 插入等待队列;
进程的阻塞和唤醒是通过原语 Block 和 Wakeup 执行的,必须成对使用,否则被阻塞进程永远无法唤醒。
-
进程通信:
- 共享存储:不同进程共享同一块空间,通过同步互斥工具(如 P / V)进行读写实现信息交换;
- 消息传递:进程通过操作系统提供的发送和接收消息原语进行数据交换,可以直接通信,也可以间接通信(发送进程 → \rightarrow → 中间实体 → \rightarrow → 接收进程);
- 管道通信:两个进程按生产者 - 消费者方式进行通信,生产者向管道中写,消费从管道中读;
1.2 线程
- 线程:进程下的调度实体,是一种轻量级进程,是一个基本的 CPU 执行单元,也是程序执行流的最小单元,由线程控制块 TCB、线程标识符、程序计数器、寄存器集合和堆栈组成;
进程 VS 线程
引入进程的目的是更好地使多道程序并发执行,提高资源利用率和系统吞吐量;而引入线程的目的则是减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。不同进程间地址空间独立,同一进程下的不同线程共享地址空间。
线程是被系统独立调度和分派的基本单位,线程本身不拥有系统资源,只拥有少量在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个程序可以创建多个线程执行不同的任务,多个线程分时占用 CPU 以实现多线程。
引入线程后,进程的内涵发生了改变:进程只作为除 CPU 外的系统资源的分配单元,而线程则作为处理机的分配单元,拥有线程标识符和线程控制块。线程的状态转换也与进程相同。由于一个进程内部有多个线程,若线程的切换发生在同一个进程内部,则只需要很少的时空开销。
- 线程的实现方式:
- 用户级线程 ULT:线程管理的所有工作由应用程序在用户空间中完成,内核意识不到线程的存在,调度仍以进程为单位进行。优点是线程切换不需要转换到内核空间,节省模式转换的开销;不同进程可以设置各自的调度算法;线程的实现与操作系统无关。缺点是一个线程阻塞时统一进程内的其他线程也被阻塞、内核每次只能分配一个处理机无法发挥多处理机的优势;
- 内核级线程 KLT:线程管理的所有工作在内核空间中完成,内核空间为每个内核级线程设置线程控制块。优点是能够发挥多处理机优势,内核可以同时调度多个线程并行执行;统一进程下的线程被阻塞不会影响其他线程。缺点是同一进程中的线程切换需要从用户态转到核心态执行,系统开销大。
2. 处理机调度
2.1 调度
- 调度:多道程序系统中进程数往往多于处理机个数,因此进程争用处理机时就需要操作系统对处理机进行分配,即从就绪队列中按照一定的算法选择进程并分配处理机,以实现进程并发执行,这就是调度;
- 调度的层次:调度层次分为高级、中级和低级三级;
- 高级调度:也叫 作业调度,按照一定的原则从外存中选取部分作业(即进程)调入内存,为其分配内存、I/O 设备等资源。作业从外存调入内存的过程也叫 作业提交;
- 中级调度:也叫 内存调度,将那些不能运行的进程从内存调至外存等待(即挂起),从而提高内存利用率和系统吞吐量;
- 低级调度:也叫 进程调度,按照某种算法从就绪队列中选取进程将处理机分配给该进程,处理机从这一刻 开始服务;
- 调度的方式:非抢占调度方式 和 抢占调度方式,区别在于优先级更高的进程能否抢占其他进程的处理机;
- 调度算法:
调度算法 | 算法描述 | 优点 | 缺点 | 适用场景 | 是否可抢占 |
---|---|---|---|---|---|
先来先服务(FCFS) | 选择最先进入队列的进程 | 算法简单; 有利于长作业; 有利于 CPU繁忙型作业; | 效率低; 不利于短作业; | 作业调度; 进程调度 | 非抢占 |
短作业优先(SJF) | 选择估计运行时间最短的进程 | 平均等待时间最少,效率高; | 不利于长作业; 估计时间难以确定; | 作业调度 ;进程调度 | 非抢占 |
优先级调度 | 选择优先级最高的进程 | 考虑了作业的紧迫程度; | 低优先级作业饥饿; 抢占式优先级调度频繁切换进程开销大; | 作业调度 ;进程调度 | 均可 |
高响应比优先 | 平衡 FCFS 和 SJF,选择响应比最高的作业 响应比 R p = 等待时间 + 要求服务时间 要求服务时间 \text{响应比} R_p = \frac{\text{等待时间 + 要求服务时间}}{\text{要求服务时间}} 响应比Rp=要求服务时间等待时间 + 要求服务时间 | 兼顾长短作业; | 计算响应比开销大; | 作业调度 | 非抢占 |
时间片轮转 | 就绪进程按 FCFS 策略运行一个时间片然后放回到就绪队列中 | 兼顾长短作业 | 平均等待时间长; 频繁切换进程开销大; | 分时系统 | 抢占 |
多级队列调度 | 将不同类型的进程分配到不同的就绪队列,每个队列按需实施不同的调度算法 | 满足多线程需求 | |||
多级反馈队列调度 | 设置多个优先级不同的就绪队列,优先级越高的队列的时间片越短,每个队列采用 FCFS 策略进行调度。新进程调入内存后,首先放入第 1 级队列,当轮到该进程执行时,若在时间片内完成就撤离,否则被放入下一级队列。第 1 级队列为空后才会调度第 2 级队列。 | 兼顾长短队列; 周转时间短; | 通用 | 抢占 |
进程优先级:
根据进程创建后优先级是否可以改变,可以将进程的优先级分为 静态优先级 和 动态优先级。静态优先级是进程创建时根据进程类型、资源要求和用户要求等因素确定的,在进程的整个运行期间保持不变;动态优先级在进程的运行期间根据进程的变化情况(如进程占有 CPU 时间长短、就绪进程等待 CPU 时间长短)动态调整。
优先级一般使用序号 1, 2, … 进行排序,1 是最高的优先级,向后递减。
进程的优先级设置一般参考以下原则:
- 系统进程 > 用户进程;
- 交互型进程 > 非交互型进程
- I/O 型进程 > 计算型进程
- 调度算法的评价指标:
- CPU 利用率: CPU 利用率 = CPU 有效工作时间 CPU 有效工作时间 + CPU 空闲等待时间 \text{CPU 利用率} = \frac{\text{CPU 有效工作时间}}{\text{CPU 有效工作时间 + CPU 空闲等待时间}} CPU 利用率=CPU 有效工作时间 + CPU 空闲等待时间CPU 有效工作时间;
- 系统吞吐率:单位时间内系统完成作业的数量;
- 周转时间:从作业提交到作业完成所经历的时间,包括在后备队列中排队、就绪队列中排队、处理机上运行、I/O 操作等所花的时间;
- 平均周转时间:多个作业周转时间的代数平均值;
- 带权周转时间:某个作业的周转时间与实际运行时间的比值;
- 平均带权周转时间:多个作业带权周转时间的代数平均值;
- 等待时间:进程等待处理机所经历的时间,即在就绪队列中所等待的时间;
- 服务时间:也叫运行时间,是进程占用处理机的时间;
- 响应时间:用户提交请求(即进程到达)到系统首次产生响应(即开始服务)的时间;
- 调度程序:调度程序(即 调度器)是操作系统内核程序,通常由以下三部分组成:
- 排队器:将系统中所有就绪进程按照一定的策略排成一个或多个队列,便于调度程序选择;
- 分派器:将调度程序所选择的进程从就绪队列中取出并分配 CPU;
- 上下文切换器:切换处理机时,需要保存当前进程的上下文到 PCB 中,再装入分派进程的上下文。为了较少执行大量 load 和 store 指令的消耗,通常使用两组寄存器,一组供用户使用,一组供内核使用,上下文切换时只需要改变指针;
请求调度的事件发生后,才可能运行调度程序,调度新的就绪进程后,才会进行进程切换。但如果进程在处理中断、内核临界区或者执行原子操作时,不能运行调度程序。
2.2 进程切换
- 进程切换:进程切换是在内核下完成的,通过保存当前进程状态并恢复另一个进程的状态(也叫 上下文切换)将 CPU 切换到另一个进程。进程的状态即上下文,指的是该时刻的 CPU 寄存器和程序计数器的内容。前面已经说过,上下文切换只需要改变寄存器组的指针即可;
上下文切换 VS 模式切换
上下文切换:进程切换过程中保存和恢复进程的上下文信息的操作。当进程切换发生时,操作系统保存当前进程的执行上下文,并加载新进程的执行上下文。
模式切换:CPU 在用户态和内核态之间切换的过程。
- 闲逛进程:在进程切换时,若系统中没有就绪进程,就会调度闲逛进程运行。一旦就绪进程就绪,闲逛进程就会让出处理机;
3. 同步与互斥
3.1 同步与互斥
- 临界资源:一次仅允许一个进程使用的资源叫做临界资源。访问临界资源的程序段必须互斥进行,访问临界资源的代码段称为临界区;
- 同步:为完成某种任务而建立的多个进程需要在某些位置上协调工作次序而等待、传递信息所产生的直接制约关系,如生产者 - 消费者模型;
- 互斥:一个进程进入临界区使用临界资源时,其他进程必须等待,直到占用临界资源的进程退出临界区,其他进程才允许访问该临界资源;
3.2 临界区互斥的实现
临界区互斥的实现有很多方法,软件实现主要有单标志法、双标志法先检查、双标志法后检查、Peterson’s 算法、互斥锁、信号量机制,硬件实现主要有中断屏蔽和硬件指令。
- 单标志法:设置公用整型变量 turn 表示允许进入临界区的进程编号,保证了每次只允许一个进程进入临界区;
但该方法在临界资源空闲时难以指定 turn 的默认值,如果将其设为 0,那么当 1 想访问时就一直得不到临界资源,容易陷入死循环; - 双标志法先检查:设置公用数组 flag[],flag[i] 表示进程
P
i
P_i
Pi 是否进入临界区。每个进程访问临界资源前先检查该资源是否被另一个进程占用,若没被占用则标记自己的标志;
但该方法可能会出现两个进程同时检查发现可以访问后同时进入临界区导致访问出错,即按序列①②③④执行; - 双标志法后检查:设置公用数组 flag[],flag[i] 表示进程
P
i
P_i
Pi 是否进入临界区。每个进程访问临界资源前先标记自己的标志然后检查该资源是否被另一个进程占用;
但该方法可能会出现两个进程同时标记自己标志后陷入死循环的情况,导致饥饿现象; - Peterson’s 算法:设置公用整型变量 turn 和公用数组 flag[],同时记录允许进入临界区的进程编号和进程进入临界区的情况;
Peterson’s 算法是单标志法和双标志法后检查的结合,可以解决互斥访问和饥饿现象; - 互斥锁(mutex lock):进程进入临界区时调用
acquire()
函数获得锁,退出临界区时调用release()
函数释放锁。每个临界资源维护一个互斥锁,每个互斥锁维护一个布尔变量 available,表示锁是否可用。由于acquire()
和release()
函数都是原子操作,因此互斥锁采用硬件机制实现;
互斥锁的主要缺点是忙等待,即临界资源被一个进程占用时,其他进程想要使用该资源时需要连续循环调用acquire()
函数,浪费 CPU 的周期; - 信号量机制(Semaphore):使用两个标准 原语
wait(S)
和signal(S)
访问临界资源,也叫作 P - V 操作。信号量机制有两种实现方式:一种是整型信号量,存在 “盲等” 现象;另一种是记录型信号量,遵循 “让权等待” 准则;
使用信号量机制解决同步问题(假设进程 P1 的运行结果被 P2 使用)时,设置公共信号量 S 供进程 P1 释放和 P2 申请;使用信号量机制解决互斥问题(进程 P1 和 P2 需要访问同一临界资源)时,进程 P1 和 P2 访问资源前需要申请 S,访问结束后需要释放 S:
3.3 管程
信号量机制是操作系统实现进程间同步和互斥的重要方法,为了简化每个想要访问临界资源的进程自备 PV 操作,操作系统提供了一种新的进程同步工具 —— 管程。管程把对共享资源的操作封装起来,无须程序员自己实现互斥,降低了发生死锁的可能性。每次仅允许一个进程进入管程,从而实现了互斥。
- 管程:管程是为进程提供对共享资源的 PV 操作的资源管理程序。主要由管程名称、共享数据结构说明、对数据结构操作的过程和初始化函数组成;
3.4 经典同步问题
设计程序时注意信号量的初值,因为一般只有一个临界资源,所以临界区的互斥信号量初值设为 1,如果是待生产的资源也会设为 0。在有些限制容量的情况下,也会设为 0 和 n。
-
生产者 - 消费者问题:一组生产者进程和一组消费者进程共享一个缓冲区,只有缓冲区没满时生产者才能放入消息,只有缓冲区非空时消费者才能取走消息(同步)。生产者进程和消费者进程不能同时使用缓冲区(互斥);
上面是最基础的生产者 - 消费者问题,下面再展示一个较为经典的一家人吃水果问题:
-
读者 - 写者问题:一组读进程和一组写进程共享一个文件,多个读进程可以同时访问文件,但不能与写进程共享文件。下面是读者 - 写者问题的两种写法:左边的写法是读进程优先的,因为多个读进程可以不间断访问文件导致写进程持续等待;右边的写法是写进程优先的,因为当读进程读文件过程中有写进程请求访问时,后续读进程需要等待;
-
哲学家进餐问题:一张圆桌上坐着 5 名哲学家,每两名哲学家之间的桌上摆一根筷子,两根筷子之间是一碗米饭。哲学家思考时,不影响其他人;哲学家进餐时,依次拿起左、右两根筷子,进餐完毕后,放下筷子继续思考。该问题在设计时需要 避免死锁,因此设置取筷子的信号量,保证哲学家们不能同时取筷子从而避免死锁;
-
吸烟者问题:有三个抽烟者进程和一个供应者进程,每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉已完成,此时供应者就会将另外两种材料放到桌上,如此重复(让三个抽烟者轮流地抽烟);
4. 死锁
4.1 死锁
- 死锁:多道程序系统中,多个进程因竞争资源会陷入互相等待的僵局,若无外力作用,这些进程都将无法向前推进;
- 死锁产生的原因:
- 系统资源的竞争:系统资源数量不足以满足多个进程的需要,并且资源是 不可剥夺 的;
- 进程推进顺序非法:进程请求和释放资源顺序不当,或信号量使用不当;
- 死锁产生的必要条件:以下 4 个条件只要有一个不成立,死锁就不会发生;
- 互斥条件:进程要求对所分配的资源进行排他性使用;
- 不剥夺条件:进程所获得的资源在使用完之前,不能被其他进程强行夺走;
- 请求并保持条件:进程保持了至少一个资源,但又提出新的资源请求;
- 循环等待条件:多个进程处于循环等待链中,每个进程进程已获得的资源被下一个进程所请求;
- 死锁的处理策略:
- 死锁预防:破坏死锁的 4 个必要条件之一;
- 死锁避免:在资源分配过程中,使用某种方法防止系统进入不安全状态;
- 死锁检测与解除:允许死锁,通过系统检测及时发现死锁,然后采取措施解除;
4.2 死锁预防
死锁预防只需要破坏死锁的 4 个必要条件之一即可:
- 破坏互斥条件:允许系统资源都能共享使用。但这种方法显然不切实际;
- 破坏不剥夺条件:进程保持了某些不可剥夺资源又提出新的资源请求而得不到满足时,必须释放当前保持的所有资源;
- 破坏请求并保持条件:进程在运行前一次性申请完所需的所有资源,知道所有资源都满足才投入运行;
- 破坏循环等待条件:顺序分配资源,每个进程只能按照编号递增的顺序请求资源;
4.3 死锁避免
死锁避免是在资源分配过程中,使用某种方法防止系统进入不安全状态。
- 系统安全状态:系统能按某种进程推进顺序 (P1, P2, …, Pn) 为每个进程 Pi 分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序完成。此时称 P1, P2, …, Pn 为安全序列。若系统无法找到一个安全序列,则称系统处于不安全状态;
需要注意的是,并非所有的不安全状态都是死锁状态,但当系统进入不安全状态后,便可能进入死锁状态。反之,只要系统处于安全状态,系统便不可能进入死锁状态。因此,死锁避免的核心在于如何使系统不进入不安全状态。
-
银行家算法:把操作系统视为银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。操作系统按照银行家制定的规则为进程分配资源,进程运行之前先声明对各种资源的最大需求量,当进程在执行中继续申请资源时,先测试该进程已占用的资源数与本次申请的资源数之和是否超过该进程声明的最大需求量。若超过则拒绝分配资源,若未超过则再测试系统现存的资源能否满足该进程尚需的最大资源量,若能满足则按当前的申请量分配资源,否则也要推迟分配。银行家算法使用的数据结构描述如下:
- 可利用资源向量 Available (1×m):每个元素代表一类可用的资源数目,
Available[j] = K
表示系统中现有 Rj 类资源 K 个; - 最大需求矩阵 Max (n×m):系统中 n 个进程中的每个进程对 m 类资源的最大需求,
Max[i][j] = K
表示进程 i 需要 Rj 类资源的最大数目为 K; - 分配矩阵 Allocation (n×m):系统中每类资源当前已分配给每个进程的资源数。
Allocation[i][j]=K
表示进程 i 当前已分得 Rj 类资源的数目为 K; - 需求矩阵 Need (n×m):每个进程接下来最多还需要的资源数目,
Need[i][j]=K
表示进程 i 还需要 Rj 类资源的数目为K;
上述三个矩阵间的关系如下:Need = Max- Allocation。在使用银行家算法分配资源时,Max、初始 Allocation 和 Available 都是已知的,示例如下:
- 可利用资源向量 Available (1×m):每个元素代表一类可用的资源数目,
4.4 死锁检测与解除
前面介绍的死锁预防和避免都是在进程分配资源时施加限制条件或进行检测,而死锁检测与解除则是在资源分配时不采取任何手段,允许死锁,通过系统检测及时发现死锁,然后采取措施解除。
-
资源分配图:用圆圈代表一个进程,用框代表一类资源,框中的一个圆代表一类资源中的一个资源。从进程到资源的有向边称为 请求边,表示该进程申请一个单位的该类资源;从资源到进程的边称为 分配边,表示该类资源已有一个资源分配给了该进程;
-
资源分配图的简化:简化资源分配图可以检测系统状态 S 是否为死锁状态。简化方法如下:
- 在资源分配图中,找出既不阻塞又不孤点的进程 Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于或等于系统中已有的空闲资源数量),消去它所有的请求边和分配边,使之成为孤立的结点;
- 进程 Pi 所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。若能消去图中所有的边,则称该图是可完全简化的;
-
死锁定理:S 为死锁的条件是当且仅当 S 状态的资源分配图是不可完全简化的;
-
死锁的解除:
- 资源剥夺法:挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源而处于资源匮乏的状态;
- 撤销进程法:强制撤销部分甚至全部死锁进程并剥夺这些进程的资源,撤销的原则可以按进程优先级和撤销进程代价的高低进行;
- 进程回退法:让一个或多个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而非被剥夺。要求系统保持进程的历史信息,设置还原点;