跳转至

第2章 进程与线程

2027 王道 · 专题阅读 · 保留代码与校订注 · 与版本锁一致

第2章 进程与线程

操作系统的核心,每年必考的重点。其中进程的概念、进程调度、信号量机制实现同步互斥、死锁是重中之重。⚠️ 本章极易出综合题,尤其是用信号量实现进程同步、调度算法分析、死锁问题。

回源 · PDF 第 49 页 · b_49_10

【知识树】第2章骨架

  • 进程与线程
  • 进程:概念与特征、组成(PCB/程序段/数据段)、内存映像、五种状态、进程控制(创建/终止/阻塞/唤醒)
  • 进程通信:共享存储、消息传递、管道、信号
  • 线程:与进程的区别、三种实现方式、多线程模型
  • CPU 调度
  • 三级调度、调度时机与方式、闲逛进程
  • 调度算法:FCFS / SJF / 优先级 / 高响应比 / 时间片轮转 / 多级反馈队列
  • 上下文切换
  • 同步与互斥
  • 临界区、四条准则
  • 软件方法、硬件方法、信号量、管程
  • 经典问题:生产者-消费者、读者-写者、哲学家进餐
  • 死锁
  • 必要条件、处理策略
  • 预防、避免(银行家算法)、检测与解除
回源 · PDF 第 49 页 · b_49_1;PDF 第 49 页 · b_49_3

2.1 进程与线程简介

为什么要引入进程?

因为并发性破坏了程序原本的封闭性,使执行过程呈现间断性,并可能导致结果不可再现。

引入进程是为了有效描述和控制程序的并发行为,支撑操作系统的两大基本特性:并发性与共享性。

⚠️ PCB 是进程存在的唯一标志 —— 创建进程的本质是创建其 PCB,撤销进程即释放该 PCB 及相关资源。 进程实体(进程映像)= 程序段 + 相关数据段 + PCB。

回源 · PDF 第 50 页 · b_50_2;PDF 第 50 页 · b_50_3

进程的正式定义是什么?

"进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。"

⚠️ 这里的"系统资源"不仅包括内存空间、I/O 设备等硬件资源,也包括 CPU 的使用权(通过时间片分配实现)。

💡 推论:正因为它是资源分配与调度的基本单位,它必然不是静态的程序,而是一个动态的、有生命周期的过程性实体。

回源 · PDF 第 50 页 · b_50_8

进程的四个特征?哪个最根本?

  • 动态性:⚠️ 动态性是进程最根本的特征 —— 有创建、就绪、运行、阻塞、终止的生命周期
  • 并发性:多个进程同时驻留内存并交替或并行执行;⚠️ 引入进程的根本目的就是支持并发
  • 独立性:能独立运行、独立获取资源、作为调度基本单位;⚠️ 任何未建立 PCB 的程序都不能被调度执行
  • 异步性:以不可预知的速度推进,可能导致结果不可再现

💡 记忆法:"动并独异"。

回源 · PDF 第 50 页 · b_50_9

PCB 里存了哪四类信息?

类别 内容
进程标识信息 PID、父进程 PPID、用户 UID
进程调度信息 当前状态、优先级、CPU 占用时间、等待时间、进入内存时间、阻塞原因
资源和内存信息 代码段/数据段/堆栈段指针、文件描述符列表、I/O 资源清单
处理机状态信息 通用寄存器、程序计数器、PSW、栈指针 SP

⚠️ 处理机状态信息也称 CPU 上下文 —— 进程被切换出 CPU 时必须完整保存到 PCB,再次被调度时恢复到 CPU。 ⚠️ PCB 常驻内存,可随时被系统访问,进程终止时被回收。

回源 · PDF 第 51 页 · b_51_3

PCB 的两种组织方式?

  • 链接方式:把相同状态的 PCB 用指针链成一个队列(就绪队列、阻塞队列);⚠️ 阻塞进程还可按阻塞原因进一步分成多个阻塞子队列
  • 索引方式:为每种状态建立一个索引表(就绪索引表、阻塞索引表),表项指向对应状态的 PCB
回源 · PDF 第 51 页 · b_51_9

程序段和数据段哪个能共享?

  • 程序段:⚠️ 程序本身是静态的,可被多个进程共享 —— 多个用户运行同一个编辑器时,内存中只保留一份代码副本,各进程通过 PCB 指向该共享代码段
  • 数据段:⚠️ 每个进程的数据段是私有的 —— 即使多个进程运行同一程序,数据段也彼此隔离

💡 这正是进程独立性与安全性的保障。

回源 · PDF 第 52 页 · b_52_0;PDF 第 52 页 · b_52_1

进程虚拟地址空间有哪几个区域? ★1考

从高地址到低地址: - 用户栈:⚠️ 从高地址向低地址扩展;存局部变量、函数参数、返回地址,LIFO,由系统自动管理 - 共享库的存储映射区:printf() 等公共函数代码,运行时动态映射,只读且可被多进程共享 - 堆:⚠️ 从低地址向高地址增长;malloc() 申请、free() 释放,大小不固定 - 读/写数据段:.data(已初始化的全局变量和静态变量,如 int x=5)、.bss(未初始化的,如 int y) - 只读代码段:.init(启动时自动调用的初始化代码)、.text(主程序指令)、.rodata(只读数据,如字符串常量)

⚠️ 两个必考细节: - PCB 不属于进程的虚拟地址空间 —— 它由内核维护、存放在系统内核区,对用户进程不可见 - 代码段和数据段的大小在程序加载时就已确定,堆和栈可在运行中动态扩展和收缩

★ 考过 2025

💡 记忆法:"栈从上往下长,堆从下往上长,中间留给共享库"。

回源 · PDF 第 52 页 · b_52_4

五种进程状态各是什么? ★1考

前三种是基本状态: - 运行态:正在 CPU 上执行;⚠️ 单 CPU 中任一时刻最多只有一个 - 就绪态:⚠️ 已获得除 CPU 外的所有必要资源,一获得 CPU 即可运行;组织成就绪队列 - 阻塞态(等待态):因等待某事件暂停;⚠️ 此时即使 CPU 空闲也无法执行 - 创建态:⚠️ 短暂的中间状态 —— 申请空白 PCB → 填写控制管理信息 → 分配资源 → 转入就绪态插入就绪队列 - 终止态:已完成执行,等待系统回收资源;⚠️ 不再参与调度,但 PCB 等信息可能暂时保留

★ 考过 2023

回源 · PDF 第 53 页 · b_53_6

就绪态和阻塞态的本质区别?

  • 就绪态:仅缺少 CPU,只要拿到 CPU 就能运行
  • 阻塞态:需要 CPU 以外的其他资源,或在等待某一事件

⚠️ 为什么要把 CPU 与其他资源分开:分时系统时间片很短(通常几毫秒),进程会频繁在运行态与就绪态之间切换;而外设分配或 I/O 完成往往需要较长时间,进入阻塞态的频率远低于就绪态。

回源 · PDF 第 53 页 · b_53_10

四种基本状态转换分别由什么触发? ★5考

  • 就绪 → 运行:被调度程序选中获得 CPU
  • 运行 → 就绪:⚠️ 两种情况 —— 时间片用完;可剥夺式调度下有更高优先级进程就绪
  • 运行 → 阻塞:⚠️ 主动行为 —— 通过系统调用请求某项服务而无法立即满足;注意并非所有系统调用都会阻塞,只有需要等待外部事件的才会
  • 阻塞 → 就绪:⚠️ 被动行为 —— 等待的事件发生后,由中断处理程序把它改为就绪态并插入就绪队列

⚠️ 不存在"阻塞 → 运行"和"就绪 → 阻塞"。

💡 做题按“事件—动作主体—去向”三列判断:时间片/抢占由调度器把运行态送回就绪;运行进程主动等待才进入阻塞;I/O 完成由中断处理把阻塞进程送回就绪,绝不直接跳到运行态。

★ 考过 2014、2015、2018、2023、2025

💡 记忆法:"睡是自己去睡(主动),醒要别人叫(被动)"。

回源 · PDF 第 54 页 · b_54_0

2.1.5 进程控制

进程控制为什么要用原语实现?

因为原语在执行期间不允许被中断,是一个不可分割的基本单位 —— 确保创建、撤销、状态转换等关键操作的原子性和一致性。

回源 · PDF 第 54 页 · b_54_7;PDF 第 54 页 · b_54_8

父进程和子进程什么关系? ★2考

子进程创建时复制父进程的当前状态:程序代码、数据段、堆栈内容、打开的文件描述符、环境变量。

⚠️ 但子进程与父进程是相互独立的实体,拥有各自的 PCB 和虚拟地址空间。

触发创建的四种情形:终端用户登录、作业调度启动批处理任务、系统提供服务、用户程序发起应用请求。

★ 考过 2010、2020、2024

回源 · PDF 第 54 页 · b_54_11

创建原语的四个步骤? ★1考

  • ① 分配唯一标识并申请 PCB:分配 PID,从系统 PCB 池申请空白 PCB;⚠️ PCB 资源耗尽则创建失败返回错误
  • ② 分配运行所需资源:内存、文件、I/O 设备(记录在 PCB 中);⚠️ 资源或从操作系统获得,或从父进程继承
  • ③ 初始化 PCB:标识信息、处理机状态信息、调度与控制信息、内存与资源信息;⚠️ 通常设为最低优先级,除非用户显式要求
  • ④ 插入就绪队列

★ 考过 2021

回源 · PDF 第 54 页 · b_54_15

终止原语的五个步骤?什么是级联终止? ★1考

  • ① 检索 PCB,读取当前状态
  • ② 若正在运行则立即剥夺 CPU,并触发调度程序选下一个
  • ③ 释放所有系统资源归还操作系统
  • ④ 若支持级联终止则递归终止所有子孙进程,否则子孙继续运行
  • ⑤ 把 PCB 从所在队列中移除

引起终止的三类事件:正常结束;异常结束(存储区越界、非法指令、特权指令违规、算术运算错误、I/O 故障、运行超时);外界干预(操作员强制终止、系统因资源不足或安全策略终止、父进程请求终止子进程)。

⚠️ Linux 不采用级联终止:父进程终止后,子进程由 init 进程(PID=1)收养并继续正常运行。

★ 考过 2024

回源 · PDF 第 55 页 · b_55_3

阻塞原语的三步?为什么说阻塞是主动的? ★3考

  • ① 保存 CPU 现场(PC、状态寄存器等)到 PCB
  • ② 把 PCB 状态改为阻塞态,插入所等待事件的等待队列
  • ③ 调用调度程序,从就绪队列另选一个进程运行

⚠️ 阻塞是进程的主动行为,所以只有处于运行态的进程才能执行阻塞操作。

常见等待事件:请求系统资源暂不可用、等待 I/O 完成、等待接收数据、等待信号量。

★ 考过 2018、2022、2023

回源 · PDF 第 55 页 · b_55_7

唤醒原语的三步?为什么必须成对使用? ★2考

  • ① 在指定事件的等待队列中找到目标进程的 PCB
  • ② 移出等待队列,状态置为就绪态
  • ③ 插入就绪队列

⚠️ Block 与 Wakeup 是一对作用相反的原语,必须成对使用 —— 若某进程 Block 后没有任何机制调用对应的 Wakeup,它将永久停留在阻塞态。

谁来唤醒:由内核的中断处理程序或合作进程调用。

★ 考过 2014、2019

回源 · PDF 第 55 页 · b_55_10

2.1.6 进程的通信

低级通信和高级通信怎么分?

  • 低级通信:传递控制信息(同步、互斥),典型代表是 PV 操作
  • 高级通信:高效传输大量数据 —— 共享存储、消息传递、管道通信、信号
回源 · PDF 第 55 页 · b_55_14

共享存储通信中,操作系统管不管同步?

⚠️ 不管。 操作系统仅负责分配共享内存区并完成地址映射,不会自动提供同步保护;读/写逻辑与同步控制都要由用户程序自行设计(如用信号量的 P、V 操作)。

原理:通过特殊的系统调用创建一段独立的内存区域,映射到多个进程的虚拟地址空间中,进程直接读写该区域。

💡 教材类比:甲乙之间放一个公共大布袋 —— 甲放进去、乙取出来,但乙不能直接伸手拿甲手里的东西。

回源 · PDF 第 56 页 · b_56_0;PDF 第 56 页 · b_56_1

消息传递的两种模式?

数据交换以格式化的消息为单位,通过发送原语和接收原语进行。 - 直接通信:⚠️ 发送方指定接收进程;内核把消息从发送方复制到接收方的内核缓冲队列 - 间接通信:⚠️ 双方不直接指定对方,通过中间实体"信箱"交换 —— 发送方投递到信箱,接收方从信箱取

⚠️ 重要性:消息传递是目前应用最广泛的 IPC 机制;微内核与服务器之间的交互正是基于消息传递;也是多处理器系统、分布式系统、计算机网络中的主流方式。

💡 类比:直接通信 = 邮差把信交到本人手里;间接通信 = 投进门口的邮箱。

回源 · PDF 第 56 页 · b_56_3;PDF 第 56 页 · b_56_4

管道到底是文件还是内存? ★1考

⚠️ 编程接口上像文件(支持 read()/write(),是一种特殊的共享文件),但实现上并非磁盘文件,而是内核维护的一块固定大小的内存缓冲区(如环形缓冲区 64KB)。

基于先进先出原则,常用于有亲缘关系的进程(父子进程)之间,以生产者-消费者模式交换数据。

⚠️ 为什么子进程能用:管道由父进程通过 pipe() 系统调用创建,子进程创建时继承父进程的文件描述符表,因此能访问同一管道。

★ 考过 2014

回源 · PDF 第 56 页 · b_56_14

管道必须提供哪三种协调能力?

  • 互斥:⚠️ 任一时刻只允许一个进程对管道进行读或写
  • 同步:管道满 → 写进程阻塞,直到读进程取走部分数据;管道空 → 读进程阻塞,直到写进程写入新数据
  • 写端关闭检测:⚠️ 写端关闭后,读端 read() 返回 0,表明无数据可读

⚠️ 缓冲区大小固定,不会像普通文件那样无限增长 —— 满了 write() 就阻塞。

回源 · PDF 第 57 页 · b_57_0

管道读取有什么特殊之处?

  • ⚠️ 从管道读数据是一次性操作 —— 数据一旦被读出,缓冲区空间即被释放,供后续写入
  • ⚠️ 普通管道仅支持单向通信;要双向通信必须创建两个管道

⚠️ 还有个自动阻塞规则:读进程消费快于写进程时,管道被读空,read() 会阻塞等待写入。

回源 · PDF 第 57 页 · b_57_6

信号是怎么记录和屏蔽的?

在 PCB 中用一个 n 位向量(Linux 用一个 32 位整型变量)记录当前待处理的信号:发送信号时内核把对应位置 1,处理成功后清零。

⚠️ PCB 中还有另一个 n 位向量记录被阻塞(屏蔽)的信号 —— 某位为 1 表示该信号不会被立即处理,而是暂存起来直到解除阻塞。

⚠️ Linux 内核实现了约 30 种信号,编号通常 1~30。

回源 · PDF 第 57 页 · b_57_7;PDF 第 57 页 · b_57_8

信号什么时候被检查和处理?

⚠️ 操作系统从内核态返回用户态时(如系统调用结束),检查当前进程是否存在未被阻塞的待处理信号;若有则立即处理其中一个(通常优先处理序号较小的)。

两种发送方式:内核发送(检测到系统事件时,如执行非法指令发 SIGILL,序号 4);进程发送(调用 kill() 并提供目标 PID 和信号序号,也可以给自己发)。

两种处理方式:执行默认的信号处理程序(如 SIGILL 默认终止进程);执行进程自定义的信号处理程序。

⚠️ 信号处理程序执行结束后,通常返回到原程序被中断的位置继续执行。

回源 · PDF 第 57 页 · b_57_13

2.1.7 线程和多线程模型

引入线程是为了解决什么? ★3考

引入进程是为了让多道程序并发执行,提高资源利用率和吞吐量; 引入线程是为了进一步降低程序并发执行时的时空开销,提升并发性能。

线程是"轻量级进程",是程序执行流的最小单元,也是处理器调度的基本单位。

⚠️ 内涵变化:引入线程后,进程成为"除 CPU 外"的系统资源分配单位,线程成为 CPU 调度和执行的基本单位。 ⚠️ 进程处于"执行"状态,实际上是指该进程中的某个线程正在运行。

★ 考过 2011、2019、2024

回源 · PDF 第 58 页 · b_58_0

线程的五个属性?

  • 轻量级执行实体:⚠️ 不拥有独立的系统资源,但拥有运行必需的私有数据结构 —— 线程 ID、程序计数器、寄存器集合、栈,以及 TCB
  • 共享代码段:同一进程的多个线程可并发执行相同的程序代码(如 Web 服务器为每个用户请求开一个线程)
  • 资源共享:共享进程的代码段、数据段、堆、文件描述符 → 通信和数据共享非常高效,无须复杂的 IPC
  • 独立调度单位:单 CPU 下时间片轮转,多 CPU 下可并行运行于多个核心
  • 生命周期:创建 → 就绪/运行/阻塞 → 终止
回源 · PDF 第 58 页 · b_58_1

进程和线程的六点比较? ★1考

维度 进程 线程
调度 传统 OS 中既是资源拥有者又是调度单位 调度的基本单位;⚠️ 同进程内线程切换不引起进程切换,跨进程则会
并发性 进程之间可并发 进程内多线程也可并发,跨进程的线程也能并发
拥有资源 资源分配的基本单位 不拥有独立资源,只有必需的私有数据结构
独立性 地址空间完全独立,其他进程无法直接访问 共享地址空间,紧密协作;⚠️ 一个进程的线程对其他进程完全不可见
系统开销 创建/撤销要分配回收 PCB、地址空间、I/O 资源,开销显著 仅需分配栈和少量内核对象,开销很小;切换只需保存恢复寄存器
多处理器 单线程进程任一时刻只能在一个 CPU 上执行 多线程可同时调度到多个 CPU 并行

★ 考过 2012

回源 · PDF 第 58 页 · b_58_14

TCB 里存什么?线程能访问别的线程的栈吗? ★1考

TCB 包含:① 线程标识符;② 寄存器集合(程序计数器、状态寄存器、通用寄存器);③ 线程当前状态;④ 调度优先级;⑤ 线程局部存储区;⑥ 堆栈指针(指向私有栈)。

⚠️ 一个高频陷阱:每个线程有独立的私有栈,但这些栈都位于进程的地址空间内 —— 技术上一个线程可以访问另一个线程的栈内容,只是严重违反编程规范,实际开发中应严格避免。

⚠️ 同一进程的所有线程共享地址空间和全局变量。

★ 考过 2024

回源 · PDF 第 59 页 · b_59_11

线程终止后资源马上释放吗?

⚠️ 不会。 大多数操作系统中,线程终止后并不立即释放其占用的资源 —— 只有当进程中的其他线程执行了相应的分离操作后,资源才被回收。

⚠️ 更反直觉的一点:已被终止但尚未释放资源的线程仍可被其他线程调用,使其重新恢复运行。

四种终止原因:正常执行完主函数、发生未处理的异常、主动调用线程终止函数、被其他线程通过取消机制强制终止。

回源 · PDF 第 59 页 · b_59_8

用户级线程的调度为什么不公平? ★1考

⚠️ 根源:采用用户级线程时,调度仍以进程为单位 —— 调度器只能感知进程,看不到进程内部的多个用户级线程,只给整个进程分配一个时间片。

教材算例(时间片 100ms):进程 A 只有 1 个用户级线程 → 独占 100ms;进程 B 有 100 个用户级线程 → 每个线程平均只有 1ms → A 的线程获得的 CPU 时间是 B 的 100 倍。

★ 考过 2019

回源 · PDF 第 60 页 · b_60_1

用户级线程的三优两缺?

优点: - ⚠️ 线程切换在用户空间完成,无须切换到内核态,避免模式切换开销 - 调度策略可由应用程序定制,不同进程可用不同算法 - ⚠️ 实现与操作系统无关,线程管理代码是用户程序的一部分,可移植性好

缺点: - ⚠️ 系统调用的阻塞问题:某个线程执行阻塞型系统调用时,整个进程被内核挂起,进程中所有线程都被阻塞 - ⚠️ 无法发挥多处理器的并行优势:内核只把该进程调度到一个 CPU 上

💡 一句话:内核完全感知不到用户级线程的存在。

回源 · PDF 第 60 页 · b_60_0;PDF 第 60 页 · b_60_1

内核级线程的三优一缺?

优点: - 能发挥多处理器的并行优势:可把同一进程的多个线程同时调度到不同核心 - 阻塞处理更灵活:⚠️ 某个线程被阻塞时,内核仍可调度该进程中的其他线程运行 - 内核自身可采用多线程技术

缺点:⚠️ 线程切换需从用户态陷入内核态 —— 每次切换都涉及模式转换和上下文保存/恢复,开销较大。

⚠️ 内核为每个内核级线程维护一个 TCB,通过它感知和控制线程。

回源 · PDF 第 60 页 · b_60_4;PDF 第 60 页 · b_60_6

三种多线程模型的优缺点? ★1考

模型 映射 优点 缺点
多对一 多个 ULT → 1 个 KLT 切换效率高(用户空间完成) ⚠️ 一个线程阻塞则整个进程挂起;无法多处理器并行
一对一 每个 ULT → 1 个 KLT 阻塞隔离,支持真正并发与多 CPU 并行 ⚠️ 每建一个用户线程就要建一个内核线程,开销大,限制最大线程数
多对多 n 个 ULT → m 个 KLT(n ≥ m) 兼具两者优点:切换高效 + 多核并行 + 阻塞隔离 —

⚠️ 多对一模型中:每个进程仅拥有一个内核级线程,每次只允许一个线程进行映射。

★ 考过 2019

回源 · PDF 第 61 页 · b_61_3;PDF 第 61 页 · b_61_8

线程库的两种实现和三大主流库?

两种实现: - 纯用户空间实现:全部代码和数据结构在用户态,调用库函数仅触发本地过程调用,无须陷入内核 → 对应用户级线程 - 内核支持实现:API 调用通常触发系统调用,由内核完成实际操作 → 对应内核级线程

三大主流库: - POSIX Pthreads:⚠️ 具体实现可基于用户级或内核级 - Windows 线程 API:⚠️ 直接构建于 Windows 内核之上,属内核级 - Java 线程 API:⚠️ 通过调用宿主系统的线程库实现(Windows 上用 Windows API,类 UNIX 上用 Pthreads)

回源 · PDF 第 61 页 · b_61_14;PDF 第 61 页 · b_61_15

引入进程解决了并发的哪三个问题?

  • 失去封闭性:进程通过地址空间隔离,为每个程序提供独立、受保护的执行环境
  • 执行过程的间断性:进程用 PCB 保存和恢复执行上下文,确保中断后能准确继续
  • 结果不可再现性:隔离机制 + 调度策略共同保障可控性与可再现性
回源 · PDF 第 62 页 · b_62_8

2.2 CPU 调度

三级调度分别在哪两级存储之间?

  • 高级调度(作业调度):⚠️ 内存与外存之间的调度 —— 从外存后备队列挑作业,分配资源并创建进程;⚠️ 每个作业只被调入一次、调出一次;⚠️ 传统多道批处理系统才配备,分时/实时系统一般不设
  • 中级调度(内存调度):⚠️ 实际上就是存储器管理中的对换功能 —— 内存紧张时把暂时不能运行的进程换出到外存(挂起态),条件满足再换入恢复为就绪态
  • 低级调度(进程调度):从就绪队列选进程分配 CPU;⚠️ 最基础、最频繁,所有操作系统都必须有,通常每几十毫秒执行一次

⚠️ 调度频率:作业调度最低 < 中级调度 < 进程调度最高。

💡 记忆法:"高级管进门、中级管换气、低级管上机"。

回源 · PDF 第 78 页 · b_78_17

进程调度的三个任务?

  • 保存 CPU 现场信息:把当前进程的 PC、通用寄存器等完整保存到 PCB
  • 选取待运行进程:按算法从就绪队列选一个,状态由就绪态改为运行态
  • 完成 CPU 分配:由分派程序把选中进程 PCB 中的 CPU 现场加载到处理器寄存器,移交控制权
回源 · PDF 第 79 页 · b_79_11

调度程序由哪三部分组成?

  • 排队器:把所有就绪进程按策略组织成一个或多个就绪队列
  • 分派器:从就绪队列移出目标进程、触发上下文切换、移交 CPU 控制权
  • 上下文切换器:⚠️ 一次 CPU 切换会发生两对上下文切换 —— ① 保存当前进程上下文到 PCB,装入分派程序的上下文;② 移出分派程序的上下文,装入新进程的 CPU 现场

⚠️ 为什么开销大:要执行大量 load 和 store 指令保存寄存器内容。 硬件优化:用两组寄存器(一组内核用、一组用户用),切换时只需改变指针指向。

回源 · PDF 第 80 页 · b_80_3

什么时候需要进行调度与切换? ★2考

  • 创建新进程后:父子都就绪,要决定先运行谁
  • 进程正常结束或异常终止后:⚠️ 若没有就绪进程,则运行系统提供的闲逛进程
  • 进程因 I/O 请求、信号量操作等被阻塞时
  • I/O 设备准备就绪发出中断后:原等待进程变就绪,要决定是让它运行还是让被中断的进程继续

支持抢占的系统还有两种:更高优先级进程进入就绪队列;当前进程时间片用完。

★ 考过 2012、2021

回源 · PDF 第 80 页 · b_80_10

哪两种情况下不能进行调度与切换?

  • ⚠️ 处理中断的过程中:中断处理逻辑复杂难以安全切换;且中断处理属于内核工作,逻辑上不隶属于任何用户进程,不应被剥夺 CPU
  • ⚠️ 执行原子操作的过程中:加锁解锁、中断现场的保护与恢复等需要屏蔽中断保证原子性 —— 连中断都禁止了,更不应调度

⚠️ 处理办法:若此期间发生了调度条件,系统设置一个调度请求标志,待临界区或中断处理完成后再执行调度。

回源 · PDF 第 81 页 · b_81_1

进程切换具体切什么?

内核把原进程的上下文(PC、寄存器状态等)保存到其 PCB 或内核栈;然后从新进程的 PCB 或内核栈加载上下文,更新地址空间相关信息(如页表基址),重置程序计数器,开始执行。

💡 三个步骤的顺序:请求调度的事件发生 → 运行调度程序 → 调度出新进程后进行切换;⚠️ 但实际内核中,即使发生了引发调度的条件,也不一定能立即调度与切换。

💡 PCB 枢纽链:由当前 PCB 的状态/优先级决定是否调度 → 从就绪队列选下一 PCB → 保存旧 PC/寄存器 → 恢复新寄存器、页表基址和栈信息。做综合题时要把“调度决策”和“实际切换”分成两步。

回源 · PDF 第 81 页 · b_81_2;PDF 第 81 页 · b_81_9

抢占式和非抢占式的分界?

  • 非抢占(非剥夺):即使有更紧迫的进程进入就绪队列,仍让当前进程执行到完成,或因等待 I/O、执行 Block 原语而主动阻塞才让出 CPU
  • 优点:实现简单、系统开销小,适用于早期批处理系统
  • ⚠️ 缺点:无法满足分时系统和大多数实时系统对及时响应的要求
  • 抢占(剥夺):出现更高优先级就绪进程时可暂停当前进程
  • ⚠️ 抢占不是任意行为,必须遵循原则:优先级原则、短进程优先原则、时间片原则
回源 · PDF 第 81 页 · b_81_4

闲逛进程有什么特点?

  • PID 为 0,优先级最低,仅在无其他就绪进程时运行
  • 一旦有新进程进入就绪队列就立即让出 CPU
  • 主要任务是在空闲循环中不断检测中断请求
  • ⚠️ 不占用除 CPU 外的其他系统资源,也不会因等待事件而被阻塞
回源 · PDF 第 81 页 · b_81_8;PDF 第 81 页 · b_81_9

两种线程调度的开销差多少?

  • 用户级线程调度:内核以进程为单位分配 CPU 时间,由进程内部的线程库决定运行哪个线程;切换只需保存少量寄存器,开销极小;⚠️ 一个线程阻塞则整个进程阻塞
  • 内核级线程调度:内核直接调度特定线程,每个线程有独立内核上下文、可单独赋予时间片;⚠️ 涉及完整上下文切换、地址空间更新(跨进程时)、可能的 TLB 刷新和缓存失效,开销通常高出数倍
回源 · PDF 第 81 页 · b_81_11

调度的五个衡量指标怎么算? ★6考

  • CPU 利用率 = CPU 有效工作时间 / (CPU 有效工作时间 + CPU 空闲等待时间)
  • 系统吞吐量 = 单位时间完成的作业数;⚠️ 长作业降低吞吐量,短作业提升吞吐量
  • 周转时间 = 作业完成时间 − 作业提交时间(含等待、排队、执行、I/O 全部时间)
  • 带权周转时间 = 周转时间 / 作业实际运行时间
  • 等待时间 = 进程在就绪队列中等待 CPU 的总时间;⚠️ 调度算法不影响实际执行时间和 I/O 时间,只影响等待时间
  • 响应时间 = 从提交请求到系统首次产生响应的时间;⚠️ 交互式系统中比周转时间更有意义

⚠️ 算作业完成时间时的大坑:CPU 与 I/O 设备之间、不同 I/O 设备之间可以并行执行。

⚠️ 不存在一种调度算法能同时满足所有需求。

★ 考过 2012、2016、2018、2019、2023、2024

回源 · PDF 第 82 页 · b_82_2

上下文切换的五个步骤? ★3考

  • ① 挂起当前进程,把 CPU 上下文保存到其 PCB
  • ② 把该 PCB 移入相应队列(就绪队列或阻塞队列)
  • ③ 选择另一个进程执行,更新其 PCB 信息
  • ④ 恢复新进程的 CPU 上下文
  • ⑤ 跳转到新进程 PCB 中程序计数器所指的位置开始执行

⚠️ 进程上下文由 PCB 表示,包括 CPU 寄存器的值、进程状态、内存管理信息。 ⚠️ 开销:单次仅微秒量级,但高负载下每秒数十上百次,累积消耗可观;优化办法是多个寄存器组,切换寄存器组指针。

★ 考过 2023、2024、2025

回源 · PDF 第 83 页 · b_83_3

模式切换和上下文切换的区别? ★1考

  • 模式切换:⚠️ 发生在同一进程内部(用户态↔内核态),当前进程并未改变,因此不涉及上下文切换
  • 上下文切换:⚠️ 从一个进程切换到另一个进程,必须保存旧进程上下文、恢复新进程上下文;⚠️ 只能由内核完成,整个过程运行在内核态

⚠️ 还要分清调度与切换:调度是决策行为(决定 CPU 给谁);切换是执行行为(实际转移 CPU 控制权)——先调度决策,再触发切换。

★ 考过 2023

回源 · PDF 第 83 页 · b_83_19;PDF 第 83 页 · b_83_21

2.2.5 CPU 调度算法

FCFS 对什么样的作业有利? ★5考

每次选最早进入队列的作业/进程,属于非抢占式算法。

⚠️ 特性判断(高频): - 实现简单,但效率较低 - 对长作业有利,对短作业不利(相比 SJF 或高响应比) - ⚠️ 对 CPU 繁忙型作业有利,对 I/O 繁忙型作业不利 —— I/O 型进程本可及时让出 CPU 提高并发,但排在长作业后面仍要长时间等待 - ⚠️ 不适合分时系统和实时系统,但常被结合到其他策略中(如优先级相同的进程按 FCFS 调度)

★ 考过 2009、2011、2014、2017

回源 · PDF 第 83 页 · b_83_22

FCFS 例题的四个平均值怎么算? ★2考

4 个作业提交时间 8.0 / 8.4 / 8.8 / 9.0,运行时间 2 / 1 / 0.5 / 0.2:

作业 提交 运行 开始 等待 完成 周转 带权周转
1 8 2 8 0 10 2 1
2 8.4 1 10 1.6 11 2.6 2.6
3 8.8 0.5 11 2.2 11.5 2.7 5.4
4 9 0.2 11.5 2.5 11.7 2.7 13.5

平均等待 1.575,平均周转 2.5,平均带权周转 5.625。

★ 考过 2012、2016

💡 解题模板:等待 = 开始 − 提交;周转 = 完成 − 提交;带权 = 周转 / 运行。

回源 · PDF 第 84 页 · b_84_0

SJF 例题为什么平均值更优? ★1考

同一组作业改用 SJF:

作业 提交 运行 开始 等待 完成 周转 带权周转
1 8 2 8 0 10 2 1
2 8.4 1 10.7 2.3 11.7 3.3 3.3
3 8.8 0.5 10.2 1.4 10.7 1.9 3.8
4 9 0.2 10 1 10.2 1.2 6

平均等待 1.175,平均周转 2.1,平均带权周转 3.525 —— 三项全面优于 FCFS。

⚠️ 结论要记牢:所有作业同时到达且运行时间已知时,SJF 的平均等待时间和平均周转时间是最优的。

★ 考过 2017

回源 · PDF 第 84 页 · b_84_12

SJF 的三个缺点?什么是饥饿? ★1考

  • ⚠️ 对长作业不利,甚至产生饥饿现象 —— 长作业进入后备队列后不断有新短作业到达,长作业长期得不到服务
  • 未考虑作业的紧迫程度(完全忽略截止时间)
  • ⚠️ "长短"由用户提交的估计值决定,用户可能故意低估以获取调度优势

⚠️ 饥饿 vs 死锁:饥饿是调度策略导致的无限期等待,与死锁中的环形等待有本质区别。

⚠️ SPF 默认是非抢占式;抢占式版本叫最短剩余时间优先 —— 新进程到达时,若其估计运行时间比当前进程的剩余执行时间更短就抢占。

★ 考过 2016

回源 · PDF 第 85 页 · b_85_0;PDF 第 85 页 · b_85_10

高响应比优先怎么兼顾两头?

\[\text{响应比} = \frac{\text{等待时间} + \text{估计运行时间}}{\text{估计运行时间}}\]
  • 等待时间相同时:运行时间越短响应比越高 → 有利于短作业(像 SJF)
  • 运行时间相同时:等待时间越长响应比越高 → 体现 FCFS 的公平性
  • ⚠️ 长作业的响应比随等待时间不断提高,最终能被调度 → 有效避免饥饿

⚠️ 它主要用于作业调度,是 FCFS 和 SJF 的综合平衡。

💡 记忆法:"等得越久、干得越快,越先上"。

回源 · PDF 第 85 页 · b_85_6;PDF 第 85 页 · b_85_7

优先级调度的两种抢占方式? ★3考

  • 非抢占式优先级:更高优先级进程到达时,⚠️ 仍让当前进程执行到自身让出 CPU(任务完成或等待事件)
  • 抢占式优先级:⚠️ 立即暂停当前进程,把 CPU 给优先级更高的

★ 考过 2018、2022、2023、2025

回源 · PDF 第 85 页 · b_85_13;PDF 第 85 页 · b_85_18

静态优先级和动态优先级怎么选? ★2考

  • 静态优先级:创建时确定,整个运行期不变;依据进程类型、资源需求、用户指定
  • 优点实现简单、开销小;⚠️ 缺点不灵活,可能出现饥饿
  • 动态优先级:创建时给初始值,随进程推进或等待时间动态调整 —— 例如优先级随等待时间增加而提高,让低优先级进程等久了也能被调度

★ 考过 2010、2016

回源 · PDF 第 86 页 · b_86_1

优先级设置的三条原则?为什么 I/O 型优先? ★1考

  • 系统进程 > 用户进程
  • 交互型进程 > 非交互型进程(前台 > 后台)
  • ⚠️ I/O 型进程 > 计算型进程

为什么 I/O 型要更高:I/O 设备速度远低于 CPU —— 让 I/O 型进程尽快拿到 CPU 并启动 I/O 操作,使设备尽早开始工作,从而提高系统整体吞吐量。

★ 考过 2013

回源 · PDF 第 86 页 · b_86_7

时间片轮转的调度时机? ★3考

就绪进程按 FCFS 组织成队列,每隔固定时间片(例如 30ms)产生一次时钟中断激活调度程序。⚠️ 时间片用完即使没运行完也必须被剥夺 CPU,放到就绪队列末尾。

两种调度时机: - 时间片结束前已运行完成 → 调度程序立即被激活进行切换 - 时间片用尽 → 由时钟中断处理程序触发调度

⚠️ RR 主要适用于分时系统,最大特点是公平性。

★ 考过 2017、2021、2024

回源 · PDF 第 86 页 · b_86_10

时间片过大或过小会怎样? ★1考

  • ⚠️ 过大:大到每个进程都能在一个时间片内跑完时,RR 退化为 FCFS,无法满足短作业和交互式用户的快速响应需求
  • ⚠️ 过小:频繁进行调度和上下文切换,系统开销大增,真正执行用户程序的 CPU 时间反而减少

取值要综合考虑三点:系统的期望响应时间、就绪队列中的进程数量、系统的处理能力。 💡 经验法则:时间片应略长于一次典型交互所需的时间(如输入一个字符、按下回车,通常几十到上百毫秒)。

★ 考过 2017

回源 · PDF 第 86 页 · b_86_13

多级队列调度是怎么分队列的?

把就绪进程划分为多个独立的就绪队列,每队对应一类相似特性的进程(系统进程、交互型、批处理): - ⚠️ 每个队列可独立采用适合自身特性的调度算法 —— 交互型队列常用 RR,批处理队列可用 FCFS 或 SJF - ⚠️ 各队列本身具有不同优先级:总是优先调度高优先级队列,只有高优先级队列全空才调度低优先级队列

回源 · PDF 第 87 页 · b_87_0;PDF 第 87 页 · b_87_1

多级反馈队列的三条实现机制? ★2考

  • ① 设置多个就绪队列,优先级依次降低;⚠️ 优先级越高的队列时间片越小,第 i+1 级队列的时间片通常是第 i 级的 2 倍
  • ② 每个队列内部用 RR;⚠️ 新进程先进第 1 级队列末尾;时间片内完成就退出;用完还没完成就降到下一级队列末尾;最低级队列行为接近 FCFS,但仍用 RR 保证公平
  • ③ 按队列优先级调度:⚠️ 仅当第 1 ~ i−1 级队列均为空时才调度第 i 级;⚠️ 新进程总是进第 1 级,所以若当前运行的是低优先级队列进程,新进程会立即抢占它(被抢占者放回原队列末尾)

★ 考过 2019、2020

回源 · PDF 第 87 页 · b_87_10

多级反馈队列对三类用户各有什么好处?

  • 交互型(终端型)用户:进程短且频繁 I/O,能在高优先级队列中快速完成
  • 短批处理作业用户:优先级高、时间片小,迅速执行完毕,周转时间短
  • 长批处理作业用户:虽最终降到低优先级队列,但已在高优先级队列获得部分 CPU 时间;⚠️ 这只能改善初始响应,不能无条件保证以后不饥饿

⚠️ 校订/边界:若高优先级任务持续到达,低队列作业仍可能长期等不到 CPU;只有在到达负载有限,或系统采用老化、周期性优先级提升(priority boost)时,才能更可靠地避免饥饿。

💡 它的三大优势:兼顾短进程(提高吞吐、缩短周转)、兼顾 I/O 型进程、无须事先知道进程的运行时间。

回源 · PDF 第 87 页 · b_87_5

保证调度和公平分享调度差在哪?

  • 保证调度算法:对进程公平 —— n 个并发进程时每个进程获得约 1/n 的 CPU 时间。实现四步:跟踪已获得的 CPU 时间 → 计算应获得的(创建以来时间 ÷ n)→ 算公平比率 → 选比率最小的运行,直到其比率超过最接近它的进程
  • 公平分享调度算法:对用户公平 —— ⚠️ 保证调度未必对用户公平:用户 1 有 4 个进程、用户 2 有 1 个,RR 下用户 1 拿 80%、用户 2 只拿 20%

公平分享下的调度序列示例(两用户等分):A E B E C E D E …(用户 1 的 4 个进程与用户 2 的 E 交替)。

回源 · PDF 第 88 页 · b_88_3

五种调度算法的总对照表?

FCFS SJF 高响应比 时间片轮转 多级反馈队列
能否抢占 否 可以 否 可以 队列内算法不一定
优点 公平、实现简单 平均等待/周转最优 兼顾长短作业 兼顾长短作业 兼顾长短作业、响应好、可行性强
缺点 不利于短作业 长作业饥饿、估计时间不易确定 计算响应比开销大 平均等待较长、切换浪费时间 最复杂
适用于 无 批处理系统 无 分时系统 相当通用

💡 这张表几乎每年都在选择题里出现,务必背熟"能否抢占"和"适用于"两行。

回源 · PDF 第 88 页 · b_88_16

AMP 和 SMP 的区别?

  • 非对称多处理机 AMP:主从式架构 —— 一个主 CPU 负责全部调度决策和系统管理,从 CPU 只执行任务;内核一般运行在主 CPU 上,从 CPU 空闲时向主 CPU 发请求信号;⚠️ 实现简单,但主 CPU 在高负载下容易成为瓶颈
  • 对称多处理机 SMP:所有 CPU 地位对等,均可参与调度,任意就绪进程可分配给任意空闲 CPU
回源 · PDF 第 89 页 · b_89_2;PDF 第 89 页 · b_89_11

处理器亲和性和负载平衡为什么矛盾?

  • 处理器亲和性:进程从一个 CPU 迁到另一个时,要把第一个 CPU 的缓存设为无效,再重新填充第二个 CPU 的缓存,代价高 → 应尽量让进程运行在同一个 CPU 上
  • 负载平衡:把负载平均分配到所有 CPU,否则有的 CPU 空闲、有的高负载

⚠️ 矛盾点:负载平衡通常会抵消处理器亲和性带来的好处(迁移就丢了缓存)。 ⚠️ 折中办法:只有当不平衡达到一定程度后,才触发进程迁移。

回源 · PDF 第 89 页 · b_89_6;PDF 第 89 页 · b_89_7

公共就绪队列和私有就绪队列各有什么代价?

  • 公共就绪队列:负载平衡好(CPU 一空闲就取一个跑);⚠️ 缺点是进程可能频繁在不同 CPU 间切换,亲和性不好
  • 补救:软亲和(调度程序尽量保持在某 CPU,但可迁移)、硬亲和(用户进程通过系统调用主动绑定到固定 CPU);⚠️ Linux 实现了软亲和,也支持硬亲和的系统调用
  • 私有就绪队列:亲和性好;⚠️ 缺点是必须进行负载平衡
  • 两种迁移:推迁移(系统程序周期性检查,从超载 CPU"推"出进程)、拉迁移(低负载 CPU 主动"拉"取);⚠️ 实际系统中两者常结合使用
回源 · PDF 第 89 页 · b_89_11

哪些算法适合分时系统?哪些适合实时系统?

  • ⚠️ FCFS 和 SJF 都不适用:无法保证在限定时间内响应用户请求
  • 实时系统:优先级调度算法(通常需配合抢占机制确保关键任务及时执行)
  • 分时系统:以时间片轮转、多级反馈队列为典型;它们依靠时间片与反馈机制改善交互响应
  • ⚠️ 高响应比优先(HRRN)通常是非抢占的作业调度算法,用于兼顾长短作业,不靠时间片保证分时响应

⚠️ 校订/边界:不要因为名称里有“响应比”,就把 HRRN 归入分时系统。

回源 · PDF 第 90 页 · b_90_5

2.3 同步与互斥

访问临界资源的四个部分? ★3考

while(true){
    entry section;      //进入区:检查是否允许进入,允许则设置"正在访问"标志
    critical section;   //临界区:实际访问临界资源的代码段
    exit section;       //退出区:清除标志,表示已释放资源
    remainder section;  //剩余区:其余部分
}

临界资源 = 同一时刻仅允许一个进程访问的资源(打印机等物理设备、被多进程共享的变量和数据结构)。

★ 考过 2016、2021、2023、2024

回源 · PDF 第 111 页 · b_111_9

同步和互斥的本质区别?

  • 同步(直接制约关系):⚠️ 源于进程之间的相互合作 —— 为完成某任务需要协调彼此的运行次序
  • 例:A 通过单缓冲向 B 提供数据 —— 缓冲区空时 B 阻塞,A 送入数据后唤醒 B;缓冲区满时 A 阻塞,B 取走数据后唤醒 A
  • 互斥(间接制约关系):⚠️ 源于共享资源的竞争 —— 一个进程在临界区时,其他进程必须等待
  • 例:只有一台打印机时,A 要打印但打印机已给 B,A 阻塞;B 释放后唤醒 A 转为就绪态

💡 记忆法:"同步是合作排队,互斥是竞争排他"。

回源 · PDF 第 111 页 · b_111_16

同步机制的四条准则?哪条不是必须的? ★1考

  • 空闲让进:临界区空闲时,应允许一个请求进入的进程立即进入
  • 忙则等待:已有进程在临界区时,其他进程必须等待
  • 有限等待:保证在有限时间内能进入临界区,避免无限期等待
  • 让权等待:⚠️ 原则上应遵循,但非必须 —— 等待时应主动放弃 CPU 转为阻塞态,而不是原地循环测试(避免忙等待)

★ 考过 2020

💡 记忆法:"空闲让进、忙则等待、有限等待、让权等待" —— 前三条硬性,第四条只是"最好做到"。

回源 · PDF 第 112 页 · b_112_4

单标志法违反了哪条准则?

// P0:                    // P1:
while(turn != 0);         while(turn != 1);   //进入区
critical section;         critical section;   //临界区
turn = 1;                 turn = 0;           //退出区

用一个公用整型变量 turn 指示当前允许进入的进程编号,退出时把使用权转让给对方。

⚠️ 能保证互斥,但违背"空闲让进" —— 两个进程必须交替进入;若 P1 没有进入打算而 turn 一直是 1,则即使临界区空闲,P0 也无法再进入。

回源 · PDF 第 112 页 · b_112_11

双标志先检查法违反了哪条准则?

// P0:                    // P1:
while(flag[1]);   ①      while(flag[0]);   ②   //先检查对方
flag[0]=true;     ③      flag[1]=true;     ④   //后设置自己
critical section;         critical section;
flag[0]=false;            flag[1]=false;

优点:无须交替进入,可连续使用临界区。

⚠️ 违背"忙则等待":按 ①②③④ 顺序执行时,两个进程都先检查(发现对方 false)再设置自己 → 同时进入临界区。 ⚠️ 根本原因:"检查对方标志"和"设置自己标志"两个操作不是原子的,中间可能发生进程切换。

💡 交错题通法:保持每个进程内部指令次序不变,只枚举进程间合法交错;x=x+1 若题目允许拆分,就按 load → add → store 追踪。原子指令则不能再擅自拆开。

回源 · PDF 第 112 页 · b_112_15

双标志后检查法又出了什么问题?

// P0:                    // P1:
flag[0]=true;     ①      flag[1]=true;     ②   //先设置自己
while(flag[1]);   ③      while(flag[0]);   ④   //后检查对方

⚠️ 按 ①②③④ 顺序:两个进程都先设置了标志,再互相检查,发现对方也想进 → 彼此等待,谁也进不去。

同时违背两条准则:"空闲让进"(临界区空着却没人能进)和"有限等待"(可能永远进不去,产生饥饿)。

💡 一句话对比:先检查后设置 → 双双进入(不互斥);先设置后检查 → 双双卡死(饥饿)。

回源 · PDF 第 113 页 · b_113_2

Peterson 算法的关键一步是什么? ★2考

// P0:                              // P1:
flag[0]=true;                       flag[1]=true;
turn=1;                             turn=0;          // ← 关键:主动"谦让"给对方
while(flag[1] && turn==1);          while(flag[0] && turn==0);
critical section;                   critical section;
flag[0]=false;                      flag[1]=false;

思路:用 flag[] 解决互斥,用 turn 解决饥饿。

⚠️ 关键在 turn=j 这一步 —— 主动把进入机会"谦让"给对方。两个进程同时请求时,turn 的最终值由后执行赋值的进程决定,因此总有一个进程的 while 条件不成立。

💡 断点证明法:分别停在两边 flag[i]=true 和 turn=j 之后,记录最新的 flag[]/turn,再代入两个 while 条件。两个进程同时请求时,最后一次 turn 赋值只会让一边等待,另一边必能进入。

⚠️ 评价:遵循"空闲让进""忙则等待""有限等待"三条准则,但因采用忙等待,没有遵循"让权等待" —— 在纯软件互斥算法中是满足前三条方面最完善的方案。

★ 考过 2010、2018

回源 · PDF 第 113 页 · b_113_7

中断屏蔽法的三个缺点? ★1考

做法:关中断 → 临界区 → 开中断。原理是 CPU 仅在响应中断时才可能发生进程切换。

三个缺点: - 限制了 CPU 的并发执行能力,显著降低系统效率 - ⚠️ 不能把关中断权限开放给用户进程 —— 若某进程关中断后未及时开中断,可能导致系统死锁或崩溃 - ⚠️ 不适用于多处理器系统 —— 在某一 CPU 上关中断,无法阻止其他 CPU 上的进程访问同一临界区

★ 考过 2021

回源 · PDF 第 114 页 · b_114_4

TestAndSet 指令做了什么? ★2考

boolean TestAndSet(boolean *lock) {
    boolean old;
    old = *lock;    //保存旧值
    *lock = true;   //置为 true
    return old;     //返回旧值
}

使用方式:

while(TestAndSet(&lock));  //尝试获取锁(忙等待)
进程的临界区代码段;
lock = false;              //解锁

  • 返回 false → 原来没人在临界区,成功获得锁
  • 返回 true → 已有进程在临界区,循环等待

优点:⚠️ 由硬件保证"检查锁"与"加锁"的原子性;⚠️ 因为 lock 是共享变量,适用于多处理器系统(这点强于关中断)。 缺点:⚠️ 忙等待,无法实现"让权等待",浪费 CPU。

★ 考过 2016、2018

回源 · PDF 第 114 页 · b_114_12

Swap 指令和 TS 指令有什么不同? ★2考

void Swap(boolean *a, boolean *b) {
    boolean temp = *a;  *a = *b;  *b = temp;
}

用法:共享变量 lock 初值 false,每个进程有局部变量 key,每次进入前置为 true;执行 Swap(&lock,&key) 后 key 中保存 lock 的旧值: - key = false → 此前无人持锁,成功获得访问权 - key = true → 锁已被占用,继续循环等待

⚠️ 本质与 TS 指令相同:通过一次原子操作,既获取锁的当前状态,又把锁置为占用。 ⚠️ 说明:教材对 TS 和 Swap 的 C 描述仅为功能示意,实际由硬件逻辑实现,不会被中断。

★ 考过 2018、2023

回源 · PDF 第 115 页 · b_115_6

硬件指令法的三优两缺?

优点: - 实现简单,正确性易于验证 - ⚠️ 支持任意数量的进程,并适用于多处理器系统 - 系统中可存在多个临界区,只需为每个临界区分配独立的 lock 变量

缺点: - ⚠️ 忙等待,无法实现"让权等待" - ⚠️ 锁的获取顺序不确定,可能产生"饥饿"

💡 说明:这些代码并非实际编程接口,而是抽象模型 —— 真实系统中已封装在信号量、互斥锁等高级同步原语里。

回源 · PDF 第 115 页 · b_115_9

互斥锁(自旋锁)适合什么场景?

acquire() { while (!available) ; available=false; }  //忙等待后获得锁
release() { available=true; }                        //释放锁

⚠️ acquire() 和 release() 的执行必须是原子操作,因此互斥锁通常采用硬件机制实现。

它也叫自旋锁,主要缺点是忙等待(同类的还有单标志法、TS 指令、Swap 指令)。

⚠️ 适用场景:多处理器系统 —— 一个线程可以在一个处理器上"旋转",而不影响其他线程执行。 优点:等待锁期间没有上下文切换;若持有锁的时间较短,等待代价较低。

回源 · PDF 第 115 页 · b_115_10;PDF 第 115 页 · b_115_11

整型信号量为什么不好? ★1考

wait(S)   { while (S<=0); S=S-1; }   //进入区(原子操作)
signal(S) { S=S+1; }                 //退出区(原子操作)

用整型变量 S 表示某类资源的可用数量,只允许三种操作:初始化、wait、signal。

⚠️ 缺陷:S ≤ 0 时进程持续循环测试,处于忙等待 —— 未遵循"让权等待",浪费 CPU,单 CPU 系统尤其低效。

★ 考过 2010

回源 · PDF 第 116 页 · b_116_5

记录型信号量的 wait/signal 怎么写? ★2考

typedef struct{ int value; struct process *L; } semaphore;

void wait(semaphore S) {          //申请资源
    S.value--;
    if (S.value < 0) { add this process to S.L; block(S.L); }
}
void signal(semaphore S) {        //释放资源
    S.value++;
    if (S.value <= 0) { remove a process P from S.L; wakeup(P); }
}
  • wait:先减 1;⚠️ 减完 < 0 说明资源已分配完,调用 block 自我阻塞并加入等待队列 S.L → 实现"让权等待"
  • signal:先加 1;⚠️ 加完 ≤ 0 说明仍有进程在等待,调用 wakeup 唤醒一个

⚠️ 两个判断条件不一样:wait 判 < 0,signal 判 ≤ 0,这是最容易记反的地方。

★ 考过 2018、2023

回源 · PDF 第 116 页 · b_116_12

用信号量实现互斥的三条注意? ★1考

semaphore S = 1;              //互斥信号量初值为 1
P1(){ ... P(S); 临界区; V(S); ... }
P2(){ ... P(S); 临界区; V(S); ... }

⚠️ 两个进程竞争时 S 的取值只可能是 1、0、−1: - S = 1:两个进程都没进临界区 - S = 0:有一个进程正在临界区中 - S = −1:一个在临界区,另一个已阻塞在等待队列

三条注意: - 不同的临界资源应配置独立的互斥信号量 - ⚠️ P(S) 与 V(S) 必须成对出现 —— 缺 P(S) 无法互斥;缺 V(S) 资源永不释放,等待进程永久阻塞 - ⚠️ 若有多个同类资源,信号量初值应设为资源总数

★ 考过 2024

回源 · PDF 第 117 页 · b_117_4

用信号量实现同步,初值为什么是 0? ★1考

semaphore S = 0;              //同步信号量初值为 0
P1(){ x;  V(S); ... }         //x 完成后通知
P2(){ ... P(S);  y; ... }     //等 x 完成再执行 y

⚠️ 初值 0 的含义:初始时 P2 所需的资源尚未满足,而该资源只能由 P1 的执行结果提供。

两种执行顺序都正确: - P1 先 V(S):S 由 0 变 1;P2 执行 P(S) 时 S=1 有资源,减为 0 后不阻塞,直接执行 y - P2 先 P(S):S 由 0 减为 −1,调用 block 阻塞;P1 执行 V(S) 后 S 由 −1 变 0,调用 wakeup 唤醒 P2

💡 PV 使用总口诀: - 同步问题:提供资源的行为之后 V,使用资源的行为之前 P - 互斥问题:⚠️ P、V 必须紧夹使用临界资源的那个行为,中间不得插入任何无关代码

★ 考过 2024

回源 · PDF 第 118 页 · b_118_5

前驱关系怎么用信号量表达? ★2考

规则:为每条前驱边设置一个同步信号量,初值均为 0;前驱段执行完后 V 通知所有直接后继;后继段开始前 P 等待所有直接前驱。

教材例(S1→S2, S1→S3, S2→S4, S2→S5, S3→S6, S4→S6, S5→S6):

semaphore a12=0,a13=0,a24=0,a25=0,a36=0,a46=0,a56=0;
S1(){ ...; V(a12); V(a13); }
S2(){ P(a12); ...; V(a24); V(a25); }
S3(){ P(a13); ...; V(a36); }
S4(){ P(a24); ...; V(a46); }
S5(){ P(a25); ...; V(a56); }
S6(){ P(a36); P(a46); P(a56); ...; }   //三个前驱都要等

💡 数边即可:有几条箭头就设几个信号量,箭尾 V、箭头 P。

★ 考过 2020、2022

回源 · PDF 第 118 页 · b_118_6

分析同步互斥问题的三个步骤?

  • ① 关系分析:识别参与并发的进程,厘清是互斥关系(竞争同一临界资源)、同步关系(要等对方结果)还是前驱关系(明确执行顺序约束)
  • ② 思路梳理:确定关键同步点,初步规划 P、V 位置;类比典型场景(生产者-消费者、读者-写者、前驱图)
  • ③ 信号量设置:定义信号量、明确语义与初值,把 P、V 嵌入适当位置,避免死锁与饥饿

💡 落代码前先分流:普通资源计数/前驱同步 → 基本 PV;请求路径取决于当前共享状态 → “辅助变量 + mutex + 资源信号量”;同类可并发、异类互斥 → 尝试读者—写者模板。

💡 每个动作再问四遍:①什么条件不满足会阻塞;②对应哪个信号量;③初值是多少;④谁在什么动作后 V 它。写完后检查 P/V 是否守恒、是否持有 mutex 再等待其他资源、是否可能饥饿。

回源 · PDF 第 119 页 · b_119_1

生产者-消费者要设哪三个信号量? ★8考

约束条件:缓冲区未满生产者才能写;缓冲区非空消费者才能读;缓冲区是临界资源必须互斥访问。

三个信号量: - mutex = 1:控制对缓冲区的互斥访问 - empty = n:记录空缓冲区的数量 - full = 0:记录满缓冲区的数量

⚠️ 关系分析的关键:生产者与消费者对缓冲区既是互斥关系(共享临界资源),又是同步关系(消费者必须等生产者生产)。 ⚠️ 必须严格控制 P 和 V 的执行顺序,以避免死锁。

★ 考过 2009、2011、2013、2014、2015、2017、2019、2025

回源 · PDF 第 119 页 · b_119_4

生产者-消费者的标准代码?

semaphore mutex=1, empty=n, full=0;
producer(){ while(1){
    生产一个产品
    P(empty);   //要用什么,P 一下
    P(mutex);   //互斥夹紧
    将产品放入缓冲区
    V(mutex);   //互斥夹紧
    V(full);    //提供什么,V 一下
}}
consumer(){ while(1){
    P(full);  P(mutex);
    从缓冲区取出一个产品
    V(mutex); V(empty);
    消费产品
}}

💡 王道口诀:"要用什么 P 一下,提供什么 V 一下,互斥夹紧"。

回源 · PDF 第 120 页 · b_120_0

为什么 P(empty) 必须在 P(mutex) 之前? (死锁陷阱)

⚠️ 顺序颠倒会死锁。

反例:缓冲区已满(empty=0)时,若生产者先 P(mutex) 拿到锁,再 P(empty) 被阻塞 —— 此时消费者想取产品,却因 mutex 被生产者持有而进不了临界区,执行不了 P(full) 和消费操作。 结果:生产者等消费者腾空位,消费者等生产者放 mutex,互相阻塞。

同理,缓冲区为空(full=0)时消费者先 P(mutex) 也会死锁。

💡 结论一句话:必须先申请资源状态(empty/full),再申请互斥访问(mutex) —— 确保进程不会在持有互斥锁的同时等待资源。

回源 · PDF 第 120 页 · b_120_2

吃水果问题为什么不能只用一个 full?

题目:盘子最多放一个水果;爸爸放苹果、妈妈放橘子;女儿只吃苹果、儿子只吃橘子。

关系分析:爸爸和妈妈之间互斥;爸爸↔女儿、妈妈↔儿子分别是同步关系;⚠️ 儿子和女儿之间既无互斥也无同步(各等各的水果)。

⚠️ 关键点:不同类型的产品要由不同的消费者消费,所以不能只用一个 full 表示满状态,必须为每种产品设置独立的同步信号量。

信号量:plate=1(盘子互斥)、apple=0、orange=0。

回源 · PDF 第 120 页 · b_120_5

吃水果问题的代码和 V(plate) 的位置?

semaphore plate=1, apple=0, orange=0;
dad(){ while(1){ 准备苹果; P(plate); 放入苹果; V(apple); }}
mom(){ while(1){ 准备橘子; P(plate); 放入橘子; V(orange); }}
son(){ while(1){ P(orange); 取出橘子; V(plate); 吃; }}
daughter(){ while(1){ P(apple); 取出苹果; V(plate); 吃; }}

⚠️ 注意 V(plate) 在孩子进程里,不在父母进程里 —— 只有当女儿拿走苹果或儿子拿走橘子之后,才释放盘子供下一次使用。

💡 因此 dad() 和 daughter()、mom() 和 son() 必须成对执行。

回源 · PDF 第 121 页 · b_121_1;PDF 第 121 页 · b_121_6

读者-写者问题的四条要求和三个信号量?

四条要求:允许多个读者同时读;任一时刻只允许一个写者写;写者写时不允许任何其他读者或写者访问;写者开始前必须等所有已进入的读者和写者退出。

关系分析:读者↔写者互斥;写者↔写者互斥;读者↔读者不互斥可并发。

💡 陌生题识别法:先画“同类/异类关系矩阵”。若某类内部可并发,而它与另一类互斥,就把前者视作“读者”、后者视作“写者”;若同类内部还有容量上限,再叠加计数信号量。

信号量: - count = 0:⚠️ 记录当前读者数量的计数器(这是本题的灵魂) - mutex = 1:控制对 count 的互斥访问 - rw = 1:控制读者与写者对文件的互斥访问

💡 解题思想:第一个读者进来时阻塞写者,最后一个读者离开时释放写者。

💡 count 只负责选择“第一人/最后一人”路径,本身不会让进程睡眠;它是可修改的共享变量,所以检查和增减必须由 mutex 保护,真正阻塞仍由 rw 完成。

回源 · PDF 第 121 页 · b_121_3

读者优先的代码?它有什么问题?

int count=0; semaphore mutex=1, rw=1;
writer(){ while(1){ P(rw); 写文件; V(rw); }}
reader(){ while(1){
    P(mutex);
    if(count==0) P(rw);   //第一个读者,阻止写者
    count++;
    V(mutex);
    读文件;
    P(mutex);
    count--;
    if(count==0) V(rw);   //最后一个读者,放行写者
    V(mutex);
}}

⚠️ 问题:写者饥饿 —— 只要已有读者在读,后续读者都能立即进入,写者要等所有读者退出;读者持续到达时,写者可能被无限期推迟。

回源 · PDF 第 122 页 · b_122_1;PDF 第 122 页 · b_122_3

写者优先靠哪个信号量?为什么读者要提前 V(w)?

在读者优先基础上增加互斥信号量 w = 1,作用是控制所有进程进入"准备访问文件"阶段的权限(相当于"门卫"):

writer(){ while(1){ P(w); P(rw); 写文件; V(rw); V(w); }}
reader(){ while(1){
    P(w);                        //有写者持有或等待 w 时,新读者被挡住
    P(mutex);
    if(count==0) P(rw);
    count++;
    V(mutex);
    V(w);                        //立即释放 w
    读文件;
    P(mutex); count--; if(count==0) V(rw); V(mutex);
}}

⚠️ 读者为什么在更新 count 后立即 V(w):只要存在活跃读者(count>0),写者已被 rw 阻塞,提前释放 w 可避免不必要地阻碍其他进程。

⚠️ 它只是相对优先,不是绝对优先:若多个进程同时阻塞在 w 上,谁先被唤醒取决于信号量实现;只有题设明确采用 FIFO 等待/唤醒时,才能据到达次序讨论“公平”。本质是在防止写者饥饿的前提下给予有限优先。

💡 通用解题启发:读者-写者问题的关键是"受互斥保护的计数器 count" —— 复杂同步问题可考虑引入计数器动态判断资源状态。

回源 · PDF 第 123 页 · b_123_0;PDF 第 123 页 · b_123_1

哲学家进餐问题为什么会死锁?

semaphore chopstick[5]={1,1,1,1,1};
Pi(){ do{
    P(chopstick[i]);            //拿左侧筷子
    P(chopstick[(i+1)%5]);      //拿右侧筷子
    进餐;
    V(chopstick[i]); V(chopstick[(i+1)%5]);
    思考;
} while(1); }

⚠️ 死锁场景:5 名哲学家同时饥饿,几乎同时执行完 P(chopstick[i]) —— 每人各持一根左筷、都在等右筷 → 循环等待 → 全部阻塞。

编号约定:哲学家 0~4,第 i 位左筷编号 i,右筷编号 (i+1)%5。

回源 · PDF 第 123 页 · b_123_3

防止哲学家死锁的三种办法?

  • ① 限制最多 4 名哲学家同时尝试进餐 —— 确保至少有一人能拿到两根筷子,从而打断五人首尾相接的循环等待
  • ② 奇数号先拿左筷再拿右筷,偶数号相反 —— ⚠️ 打破循环等待条件
  • ③ 仅当左右两根筷子都空闲时,才允许同时拿起

⚠️ 校订/边界:①、②都通过不同方式破坏循环等待;③要求两根同时可得,本质上避免“先占一根再等另一根”的请求并保持。

回源 · PDF 第 124 页 · b_124_0

哲学家问题的"同时拿两根"怎么写?

semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex=1;                    //取筷子的信号量
Pi(){ do{
    P(mutex);                         //整个"取筷"动作互斥
    P(chopstick[i]);                  //拿左筷
    P(chopstick[(i+1)%5]);            //拿右筷
    V(mutex);
    进餐;
    V(chopstick[i]); V(chopstick[(i+1)%5]);
    思考;
} while(1); }

⚠️ 关键在 mutex 把“取筷流程”串行化:某位哲学家可以先拿到左筷、再在持有 mutex 时等待右筷;因此它并不保证“两根同时获得或一根也不占”。它真正保证的是不会有多名哲学家并发形成首尾相接的取筷环。

⚠️ 校订/边界:若题目明确要求“要么两根同时取得,要么一根也不拿”,应使用能原子检查两根状态的方案,而不是仅用上述 mutex 包住两个 P 操作。

💡 哲学家问题的本质是预防循环等待。

回源 · PDF 第 124 页 · b_124_5

2.3.6 管程

引入管程是为了解决信号量的什么问题? ★1考

信号量机制下每个进程都要自行编写 P/V 操作 —— 分散的同步代码增加编程复杂度,且极易因操作顺序不当引发死锁。

管程的两个改进: - 把共享资源及其操作封装于一体,自动保证互斥访问,程序员无须显式编写互斥代码 - 通过条件变量实现灵活的进程同步,降低死锁风险

★ 考过 2016

回源 · PDF 第 124 页 · b_124_9

管程由哪四部分组成?

  • ① 管程的名称
  • ② 局部于管程内部的共享数据结构说明
  • ③ 对该数据结构进行操作的一组过程(函数)
  • ④ 对共享数据进行初始化的语句

两个基本特性(与"类"高度相似): - 封装性:⚠️ 内部共享数据只能被管程自身的过程访问,外部进程必须调用这些过程 - 互斥性:⚠️ 任一时刻仅允许一个进程执行管程内的过程 —— 这由编译器自动保证

回源 · PDF 第 125 页 · b_125_2;PDF 第 125 页 · b_125_11

管程和进程的六点区别?

维度 进程 管程
数据结构 私有(如 PCB) 公共(如缓冲区)
任务 通用计算 同步控制与资源管理
引入目的 实现并发 解决共享资源的互斥访问
主被动 主动执行的实体 ⚠️ 被动调用的模块
并发性 多个进程可并发执行 ⚠️ 对同一管程的多个调用是互斥的
生命周期 动态(创建到撤销) ⚠️ 静态资源管理模块
回源 · PDF 第 125 页 · b_125_11

为什么必须有条件变量?

⚠️ 因为进程进入管程后若因条件不满足要等待,它必须释放对管程的占用 —— 否则其他进程无法进入管程,系统停滞。

两种操作: - x.wait():条件不满足时把自己加入 x 的等待队列,并自动释放管程 - x.signal():条件可能已满足时唤醒一个阻塞在 x 上的进程

⚠️ 管程中可以设置多个条件变量(不同阻塞原因分别抽象),每个条件变量维护一个等待队列。

回源 · PDF 第 126 页 · b_126_0;PDF 第 126 页 · b_126_1

条件变量和信号量的区别?

  • 相似点:wait/signal 与 P/V 都能实现进程的阻塞与唤醒
  • ⚠️ 不同点:信号量有整数值,反映可用资源数量;条件变量不维护数值,仅用于排队和通知 —— 在管程中资源数量由共享变量(如 S)显式记录,条件变量只负责同步

💡 一句话:信号量既记账又排队,条件变量只排队。

回源 · PDF 第 126 页 · b_126_5

2.4 死锁

死锁的定义和典型例子?

死锁:多个进程因竞争资源而陷入相互等待的僵局 —— 每个进程都在等待其他进程所占有的资源,而这些资源不会被释放,形成循环等待链,所有进程被永久阻塞。

💡 生活类比:窄巷两端各驶入一辆车,都不肯倒车,彼此阻挡。 💡 系统例子:只有一台打印机和一台扫描仪,P1 占扫描仪要打印机,P2 占打印机要扫描仪。

回源 · PDF 第 162 页 · b_162_1

死锁和饥饿的两点区别?

对比 死锁 饥饿
进程数量 ⚠️ 必须涉及两个或更多进程,且有循环等待 ⚠️ 可以只影响单个进程
进程状态 ⚠️ 必定处于阻塞态 可能就绪态(如 SPF 下长期拿不到 CPU),也可能阻塞态

饥饿的根源:资源分配策略不公平,无法保证"等待时间有上界"。例:打印调度用最短作业优先,长文件请求可能永远排不上。

⚠️ 饥饿时系统可能仍在正常运行,只是个别进程被持续忽略;共同点是相关进程都无法顺利推进。

回源 · PDF 第 162 页 · b_162_6;PDF 第 162 页 · b_162_9

死锁产生的两大原因? ★2考

  • 系统资源的竞争:⚠️ 死锁的发生与不可剥夺资源密切相关(打印机、磁带机);对可剥夺资源(如 CPU 时间片),系统可强制回收,单纯竞争这类资源一般不会死锁
  • 进程推进顺序非法:即使资源充足,请求和释放顺序不合理仍可能死锁(P1 占 R1 要 R2,P2 占 R2 要 R1)

⚠️ 还有一种易忽略的情形:同步机制使用不当 —— A 等 B 的消息、B 等 A 的消息;消息通道或同步对象可视为一种逻辑资源。

★ 考过 2009、2014

回源 · PDF 第 162 页 · b_162_10

死锁的四个必要条件?

  • 互斥条件:资源要求排他性使用
  • 不可剥夺条件:已获得的资源用完前不能被强行剥夺,只能主动释放
  • 请求并保持条件:持有至少一个资源的同时又请求新资源,被阻塞时对已占有的资源保持不放
  • 循环等待条件:存在进程集合,每个进程等待的资源正被下一个进程占有,形成环

💡 教材类比:别人不能抢走你手上的苹果 = 不可剥夺;你攥着第一个苹果去要第二个 = 请求并保持。

⚠️ 只要任一条件不成立,死锁就不可能发生。

回源 · PDF 第 163 页 · b_163_1

资源分配图有环就一定死锁吗?

⚠️ 不一定。

根本原因:某类资源有多个实例时,即使图中有环,系统仍可能通过资源重分配打破僵局 —— 教材例:输出设备有两个实例,P0 和 PK 各持一台,只要任一方释放,等待者即可获得资源,环被解除。

💡 结论要背准: - 每类资源只有一个实例时,环是死锁的充分必要条件 - 否则,环的存在仅表明潜在风险,未必实际死锁

回源 · PDF 第 163 页 · b_163_8

死锁的三种处理策略?

  • 预防死锁:设置严格限制条件,破坏四个必要条件中的一个或多个
  • 避免死锁:动态分配时判断分配后是否仍处于安全状态,安全才分配
  • 检测及解除死锁:不加限制,定期运行检测算法,发现后终止部分进程或剥夺资源

⚠️ 还有第三种态度:完全忽略死锁问题 —— 大多数通用操作系统(Linux、Windows)采用这种策略。

⚠️ 预防和避免都属于事前防范:预防限制严格、实现简单,但效率和资源利用率低;避免限制宽松,但每次分配都要算,实现复杂。

回源 · PDF 第 163 页 · b_163_11

三种死锁处理策略的优缺点对照?

资源分配策略 主要优点 主要缺点
死锁预防 保守,宁可资源闲置 无须运行时检测,实现简单 资源利用率低;剥夺可能频繁;不适用于动态请求
死锁避免 预防和检测的折中(运行时判断) 无须资源剥夺,利用率较高 ⚠️ 须预知最大资源需求;进程可能被无限期推迟
死锁检测 宽松,只要允许就分配 不限制申请,无启动延迟 检测与解除有额外开销;终止或剥夺导致工作丢失
回源 · PDF 第 164 页 · b_164_2

破坏互斥和破坏不可剥夺可行吗? ★1考

  • 破坏互斥条件:对打印机、磁带机等本质不可共享资源通常不可行;但对只读/可重入资源,或经 SPOOLing 虚拟化后的逻辑设备,可以通过设计消除原来的物理互斥
  • 破坏不可剥夺条件:请求新资源失败时强制释放已占有的全部资源;⚠️ 实现复杂、代价高昂 —— 强制释放打印机可能使已完成的工作失效甚至设备状态混乱;频繁申请释放增加开销、延长周转时间、降低吞吐量;实际系统中极少采用

★ 考过 2019 [校订/边界] “破坏互斥不可行”只适用于本质不可共享资源,不能扩大到所有资源。

回源 · PDF 第 164 页 · b_164_5

破坏请求并保持的两种做法?

  • 静态资源分配:⚠️ 运行前一次性申请全部资源,不满足就不投入运行,运行期不再提新请求
  • 优点实现简单;⚠️ 缺点:资源浪费严重(末期才用甚至从未用的资源被长期占着)、易引发饥饿
  • 动态释放后申请:获得初期资源即可开始执行,但必须先释放所有已用完的资源,才能申请新资源
回源 · PDF 第 164 页 · b_164_12

资源有序分配法怎么破坏循环等待?

规则:为每类资源赋予唯一编号,进程必须按编号递增的顺序请求,且同类资源须一次性申请 —— 即只有在未持有任何资源、或仅持有较小编号资源时,才能申请更大编号的资源。

⚠️ 为什么有效:任何已持有大编号资源的进程都无法再申请小编号资源 → 资源分配图中不可能出现环。

三个缺点: - 资源编号需相对稳定,不利于新增设备类型 - ⚠️ 实际使用顺序可能与编号次序不一致 → 资源提前占用而长期闲置 - 增加了用户编程的复杂性

回源 · PDF 第 164 页 · b_164_16

什么是安全状态和安全序列? ★1考

安全状态:存在一个进程执行序列 (P1,…,Pn),使得对每个 Pi,在它之前的进程全部执行完并释放资源后,系统仍有足够的可用资源满足 Pi 的尚需资源量(最大需求 − 已分配) —— 这样的序列称为安全序列(可能存在多个)。找不到任何安全序列则为不安全状态。

⚠️ 三句话必须背准: - 系统处于安全状态时,一定不会发生死锁 - 处于不安全状态时,死锁可能发生,但并非必然发生 - 死锁发生时,系统必定处于不安全状态

★ 考过 2018

回源 · PDF 第 165 页 · b_165_6

12 台磁带机那道经典例题?

P1/P2/P3 最大需求 10/4/9,已分配 5/2/2,可用 3。

T0 时刻安全,安全序列 (P2, P1, P3): - 可用 3 ≥ P2 尚需 2 → P2 完成,释放 4,可用变 5 - 5 ≥ P1 尚需 5 → P1 完成,释放 10,可用变 10 - 10 ≥ P3 尚需 7 → P3 完成

⚠️ 若再分配 1 台给 P3(P3 已分配变 3,可用降为 2)→ 进入不安全状态: 把剩下 2 台给 P2 → P2 完成释放 4,可用 4;但 P1 尚需 5、P3 尚需 6,都不满足 → 相互等待,最终死锁。

💡 解题模板:每轮找一个 Need ≤ Available 的进程,让它跑完并归还 Allocation,反复直到全部完成。

回源 · PDF 第 165 页 · b_165_4;PDF 第 165 页 · b_165_8

银行家算法的四个数据结构? ★2考

设 n 个进程、m 类资源: - Available:长度 m 的向量,当前各类可用资源数 - Max:n×m 矩阵,每个进程对各类资源的最大需求 - Allocation:n×m 矩阵,已分配给每个进程的资源数 - Need:n×m 矩阵,每个进程尚需的资源数

⚠️ 核心关系式:Need[i,j] = Max[i,j] − Allocation[i,j]

⚠️ 前提条件:每个进程运行前必须声明最大需求量,且该最大需求量在整个生命周期内不得更改(否则银行家算法的安全性保证失效),总量不得超过系统资源总量。

💡 比喻:操作系统 = 银行家,资源 = 资金,进程请求资源 = 用户申请贷款。

★ 考过 2013、2019

回源 · PDF 第 165 页 · b_165_14

银行家算法的四个检查步骤?

设 Request_i 是 Pi 的资源请求向量: - ① 合法性检查:Request_i ≤ Need[i]?否则非法请求(申请量超过预先声明的最大需求) - ② 资源可用性检查:Request_i ≤ Available?否则资源不足,Pi 必须等待 - ③ 试探性分配: Available −= Request_i;Allocation[i] += Request_i;Need[i] −= Request_i - ④ 安全性检查:执行安全性算法;⚠️ 安全则正式确认分配,不安全则撤销分配、恢复各数据结构原值,让 Pi 等待

💡 做题时第一步永远是由 Max − Allocation 算出 Need 矩阵。

回源 · PDF 第 166 页 · b_166_0;PDF 第 166 页 · b_166_1

安全性算法的四步怎么跑?

  • ① 设两个向量:Work(长度 m,初值 = Available)、Finish(长度 n 的布尔向量,初值全 false)
  • ② 找一个同时满足 Finish[i] = false 且 Need[i] ≤ Work 的进程
  • ③ 找到则:Work = Work + Allocation[i];Finish[i] = true,返回步骤 ②
  • ④ 全部 Finish[i] = true → 安全状态;否则不安全

💡 直觉:先让"要得最少的人"跑完还钱,钱变多了再让下一个人跑。

回源 · PDF 第 166 页 · b_166_15;PDF 第 166 页 · b_166_16

安全性算法的经典例题(5 进程 3 类资源)? ★5考

总量 A/B/C = 10/5/7,T0 时 Available = (3,3,2):

进程 Max Allocation Need
P0 7 5 3 0 1 0 7 4 3
P1 3 2 2 2 0 0 1 2 2
P2 9 0 2 3 0 2 6 0 0
P3 2 2 2 2 1 1 0 1 1
P4 4 3 3 0 0 2 4 3 1

Work 变化:(3,3,2) →P1→ (5,3,2) →P3→ (7,4,3) →P4→ (7,4,5) →P2→ (10,4,7) →P0→ (10,5,7)

安全序列 {P1, P3, P4, P2, P0},系统处于安全状态。

⚠️ 初始时 P1(1,2,2) 和 P3(0,1,1) 都满足条件,选谁都行 —— 安全序列可能不唯一。

★ 考过 2011、2012、2018、2020、2022

回源 · PDF 第 167 页 · b_167_1

P1 请求 (1,0,2) 时怎么判?(批准的例子)

  • ① Request₁(1,0,2) ≤ Need₁(1,2,2) ✓
  • ② Request₁(1,0,2) ≤ Available(3,3,2) ✓
  • ③ 试探分配:Available=(2,3,0),Allocation₁=(3,0,2),Need₁=(0,2,0)
  • ④ 安全性检查:Work 从 (2,3,0) 出发 → 安全序列 {P1, P3, P4, P0, P2} → 安全

结论:正式批准分配。

⚠️ 注意这次的安全序列 {P1,P3,P4,P0,P2} 与 T0 时刻的 {P1,P3,P4,P2,P0} 不同 —— 分配会改变后续可行顺序。

回源 · PDF 第 168 页 · b_168_0

P4 请求 (3,3,0) 和 P0 请求 (0,2,0) 为什么都被拒?

P4 请求 (3,3,0) —— 卡在第 ② 步: - 合法性 (3,3,0) ≤ Need₄(4,3,1) ✓ - ⚠️ 可用性 (3,3,0) > Available(2,3,0) ✗ → 资源不足,让 P4 等待

P0 请求 (0,2,0) —— 卡在第 ④ 步: - 合法性 ✓、可用性 (0,2,0) ≤ (2,3,0) ✓ - 试探分配后 Available=(2,1,0),Allocation₀=(0,3,0),Need₀=(7,2,3) - ⚠️ Available(2,1,0) 无法满足任何进程的尚需资源 → 不安全 → 拒绝请求,撤销试探性分配,恢复原状态

💡 这两例正好演示了两种不同的拒绝理由:一个是"现在没货",一个是"给了会出事"。

回源 · PDF 第 168 页 · b_168_11

死锁避免和死锁检测的根本区别? ★1考

  • 死锁避免:要求始终确保系统不进入死锁状态 → ⚠️ 需要进程预先声明对每类资源的最大需求 Max
  • 死锁检测:仅判断当前时刻是否已发生死锁 → ⚠️ 无须预知未来请求,只依据当前的资源分配和请求情况

💡 一句话:避免是"提前算好不出事",检测是"出事了再说"。

⚠️ 校订/边界:银行家算法不要求预知未来每一次请求的时刻和顺序,只需最大需求、当前分配与当前可用量。

★ 考过 2015

回源 · PDF 第 169 页 · b_169_5

资源分配图怎么画? ★2考

  • 圆圈 = 进程,矩形框 = 一类资源;⚠️ 某类资源有多个实例时,框内用多个圆点表示
  • 请求边:从进程指向资源,表示申请一个单位
  • 分配边:从资源指向进程,表示一个实例已分配给它

⚠️ 空闲资源数的算法:该类资源总数 − 已分配实例数(即资源节点发出的分配边数量)。

💡 获取顺序题先画图:进程已经占有的资源画“资源→进程”,下一步申请画“进程→资源”;若所有等待边首尾闭合,再结合每类资源实例数判断环是否已经构成死锁。

★ 考过 2016、2021

回源 · PDF 第 169 页 · b_169_10

资源分配图的化简步骤和死锁定理?

  • ① 找一个所有资源请求都能被当前空闲资源满足的进程 Pi,删除它的全部请求边和分配边,使之成为孤立节点
  • ② 它释放的资源可能使其他阻塞进程转为可执行,重复上述过程
  • ③ 若最终能删除图中所有边,则称该图可完全简化

死锁定理:系统处于死锁状态,当且仅当其资源分配图不可完全简化。

教材例:R1 总数 3、出度 3 → 无空闲;R2 总数 2、出度 1 → 有 1 个空闲 → P1 只请求 1 个 R2,可继续执行;P1 释放 R1 后 P2 的请求也能满足。

⚠️ 经化简后仍有边相连的进程,就是死锁进程。

回源 · PDF 第 170 页 · b_170_2;PDF 第 170 页 · b_170_8

解除死锁的三种方法? ★1考

  • 资源剥夺法:挂起部分死锁进程,抢占其资源重新分配;⚠️ 需防止被挂起的进程长期得不到资源而饥饿
  • 撤销进程法:强制终止部分或全部死锁进程并回收资源;终止顺序按优先级或撤销代价(资源占有量等);⚠️ 实现简单但代价可能很高 —— 接近完成的进程被终止,已完成的工作全部丢失
  • 进程回退法:让死锁进程回退到之前某个足以回避死锁的状态,回退中自愿释放资源;⚠️ 需记录历史状态并设置还原点,实现较复杂

★ 考过 2019

回源 · PDF 第 170 页 · b_170_4

【疑难点】第2章

进程和程序的四点区别?

  • 进程是动态概念(一次执行活动),程序是静态实体(一组有序指令的集合);⚠️ 脱离程序,进程便无意义
  • 进程有生命周期、暂时存在;程序可长期保存、永久存在
  • ⚠️ 一个进程通常执行一个程序,而一个程序可被多个进程并发执行;进程能创建新进程,程序不能
  • 组成不同:进程 = 程序 + 数据 + PCB
回源 · PDF 第 187 页 · b_187_3

同步和互斥各体现什么关系?

并发进程的两类制约关系: - 互斥 = 竞争关系:竞争使用临界资源,必须互斥访问 - 同步 = 协作关系:协同完成任务,在关键点等待对方的信号以协调执行

回源 · PDF 第 187 页 · b_187_10

【易混点】第2章成对概念对照

概念 A 概念 B 一句话切分
进程 程序 动态、有生命周期、含 PCB vs 静态指令集合
进程 线程 资源分配单位 vs CPU 调度单位
程序段 数据段 静态可共享 vs 每进程私有
就绪态 阻塞态 只缺 CPU vs 缺其他资源或等事件
运行→阻塞 阻塞→就绪 主动(自己发系统调用) vs 被动(等中断处理程序)
创建态 终止态 短暂中间态 vs 不再调度但 PCB 暂留
用户级线程 内核级线程 内核看不见、切换快、一阻全阻 vs 内核可见、能多核并行、切换慢
多对一 / 一对一 / 多对多 — 快但不能并行 / 能并行但开销大 / 折中
共享存储 消息传递 OS 只映射内存,同步要自己做 vs 发送/接收原语封装在内核
直接通信 间接通信 指定接收进程 vs 投递到信箱
高级调度 中级调度 外存→内存,只一次 vs 对换,挂起态
调度 切换 决策:给谁 vs 执行:真正转移 CPU
模式切换 上下文切换 同一进程内、用户态↔内核态 vs 换进程、必在内核态完成
周转时间 带权周转时间 完成−提交 vs 周转 ÷ 实际运行
FCFS SJF 利于长作业、CPU 繁忙型 vs 平均周转最优但长作业饥饿
饥饿 死锁 可以只一个进程、可就绪态 vs 至少两进程、必阻塞态、循环等待
空闲让进/忙则等待/有限等待 让权等待 前三条硬性 vs 第四条原则上应遵循但非必须
双标志先检查 双标志后检查 同时进入(违反忙则等待) vs 同时卡死(违反空闲让进)
TS 指令 关中断 共享 lock,适用于多处理器 vs 只对本 CPU 有效
整型信号量 记录型信号量 忙等待 vs block/wakeup,实现让权等待
wait 判 S<0 signal 判 S≤0 减完为负才阻塞 vs 加完非正说明还有人等
P(empty) 在前 P(mutex) 在前 正确 vs 死锁
读者优先 写者优先 写者饥饿 vs 靠信号量 w 挡新读者(相对优先)
信号量 条件变量 有整数值记录资源数 vs 无值,只排队和通知
死锁预防 死锁避免 破坏必要条件、静态限制 vs 动态判断安全状态
死锁避免 死锁检测 需预知全部最大需求 vs 只看当前分配与请求
安全状态 不安全状态 一定不死锁 vs 可能死锁但不必然

【必背数字】第2章

数字 含义
三部分 进程实体 = 程序段 + 数据段 + PCB(PCB 是存在的唯一标志)
四类信息 PCB:标识 / 调度 / 资源内存 / 处理机状态
五种状态 运行、就绪、阻塞、创建、终止(前三种为基本状态)
PID = 1 / PID = 0 Linux 的 init 进程 / 闲逛进程
约 30 种、32 位向量 Linux 信号种类 / PCB 中记录待处理信号的位向量
SIGILL = 4 非法指令信号
64KB Linux 管道内核环形缓冲区典型大小
三级调度 高级(作业)、中级(内存/对换)、低级(进程,频率最高)
周转 = 完成 − 提交;带权 = 周转 ÷ 运行 调度计算两大公式
响应比 = (等待 + 运行) ÷ 运行 高响应比优先
2 倍 多级反馈队列第 i+1 级时间片是第 i 级的 2 倍
四条准则 空闲让进、忙则等待、有限等待、让权等待
mutex=1, empty=n, full=0 生产者-消费者三信号量初值
plate=1, apple=0, orange=0 吃水果问题三信号量初值
count=0, mutex=1, rw=1(+w=1) 读者-写者信号量(写者优先加 w)
5 根筷子,最多允许 4 人同时取 哲学家进餐
四个必要条件 互斥、不可剥夺、请求并保持、循环等待
Need = Max − Allocation 银行家算法核心关系式
{P1,P3,P4,P2,P0} 教材 T0 时刻安全序列(Available=(3,3,2))