第 2 章 进程管理¶
依据:《操作系统概论》(2017 年版)第 2 章、配套自考大纲及附录答案。下文编号指大纲“考核内容与考核要求”;标注“自编”的算例为学习辅助,不是真题。
复习导航:大纲要求与笔记位置¶
| 大纲条目 | 层次 | 位置 | 学完应能做到 |
|---|---|---|---|
| 1.1 顺序执行、1.2 并发执行 | 识记、领会 | 第 1 节 | 对比特点,解释结果为何可能不同 |
| 2.1 进程定义与结构、2.2 PCB | 识记 | 第 2 节 | 定义、组成、PCB 的作用与信息 |
| 2.3 三态及转换、2.4 组织 | 识记 | 第 3 节 | 状态、转换条件、链接与索引 |
| 3.1–3.4 进程控制 | 识记 | 第 4 节 | 创建、撤销、阻塞、唤醒的条件与步骤 |
| 4.1 内核、4.2 中断定义、4.3 中断处理 | 识记、领会 | 第 5 节 | 内核功能、中断含义;断点、现场、中断入口与返回 |
| 4.4 两种执行态、系统调用、4.5 调用过程 | 识记、领会 | 第 6 节 | 态转换及系统调用与函数调用的区别 |
| 4.6 时钟体系、4.7 时钟机制 | 识记、领会 | 第 7 节 | RTC、PIT、时钟驱动程序 |
| 5.1 线程概念与比较、5.2 线程控制 | 领会 | 第 8 节 | 进程与线程、两种线程实现、控制过程 |
| 6.1 同步基本概念与四条准则 | 领会 | 第 9 节 | 临界资源、临界区与协调 |
| 6.2 整型信号量、6.3 记录型信号量 | 综合应用 | 第 10 节 | 会设初值、写 P/V、跟踪数值和阻塞 |
| 6.4 生产者—消费者、读者—写者 | 综合应用 | 第 11 节 | 能独立建立同步程序 |
| 6.5 管程及两类应用 | 领会 | 第 12 节 | 条件变量、缓冲池、哲学家进餐 |
| 6.6 进程通信 | 识记 | 第 13 节 | 共享存储器、管道、消息缓冲队列 |
全章十三节分四块铺开:第 1–4 节定义进程、PCB、三态转换与控制原语;第 5–7 节讲内核支撑机制(中断、系统调用、时钟);第 8 节把调度单位拆到线程;第 9–13 节处理并发协作,涵盖信号量 P/V、管程与进程通信。教材另说明,本章出现的 Linux 内核源码片段不纳入考核范围。
本章学习目标¶
进程用于描述程序的一次运行,PCB 保存其执行状态和资源信息,三态模型描述它何时就绪、执行或阻塞。内核通过中断、时钟和系统调用控制执行,进程之间再通过同步与通信完成协作。
先掌握进程三态转换和 PCB 的四类信息,再学习进程控制、调度、阻塞与唤醒。信号量部分需要动手建模:设定初值、安排 P/V 次序,并逐步跟踪 value 的变化;改变缓冲区大小或售票规则后,也应能按条件重新推导。
1 顺序执行与并发执行(1.1、1.2,识记、领会)¶
| 执行方式 | 特点 | 理解 |
|---|---|---|
| 顺序执行 | 顺序性 | 前一操作结束,才能进行后一操作 |
| 顺序执行 | 封闭性 | 在教材的独占资源模型中,运行环境的资源状态仅由本程序改变 |
| 顺序执行 | 可再现性 | 初始条件和环境相同,重复执行得到相同结果 |
| 并发执行 | 间断性 | 因共享有限资源,执行过程可能暂停再继续 |
| 并发执行 | 失去封闭性 | 共享数据和系统状态可由其他进程改变 |
| 并发执行 | 不可再现性 | 缺乏适当控制时,指令交错顺序不同可能产生不同结果 |
并发指多个程序在同一时间间隔内推进;单核上靠快速交替实现,并不是同一时刻都在 CPU 上跑。
以共享变量 counter(初值 0)为例,两个进程各执行一次非原子的 counter++。在本例的简化执行模型中,每次自增分为读入寄存器、加 1、写回内存三步。一次可能的交错如下:
| 步骤 | 甲进程 | 乙进程 | 内存中的 counter |
|---|---|---|---|
| 1 | 读到 0,随后被切换 | — | 0 |
| 2 | 暂停 | 读到 0,加 1 并写回 | 1 |
| 3 | 用先前读到的 0 加 1,再写回 | 已完成 | 1 |
两次自增只得到 1,丢失了一次更新;若甲完成全部三步后乙才执行,结果则为 2。一行高级语言语句未必是原子操作,缺少同步时,结果可能随执行交错而变化。这里用于说明并发机制,不将具体语言的共享变量行为当作保证。
2 进程与进程控制块(2.1、2.2,识记)¶
程序代码本身不能记录一次运行的执行位置、数据和资源状态,因此需要引入进程。进程是允许并发执行的程序在某个数据集合上的一次运行过程,其实体由三部分组成:
- 正文段:被执行的机器指令。
- 用户数据段:执行时直接操作的数据。
- PCB(进程控制块):记录运行环境与管理信息,供操作系统控制进程。
某一时刻进程在内存中的全部内容合称进程映像。进程具备并发性、动态性、独立性、异步性和结构特征五项属性。在未引入线程的传统模型里,进程一身兼两职,既是资源分配的基本单位,也是 CPU 调度运行的基本单位;引入线程后,这两项职责才拆开(第 8 节详述)。
| 比较 | 程序 | 进程 |
|---|---|---|
| 性质 | 静态指令集合 | 程序的一次动态执行过程 |
| 存在时间 | 可长期保存在外存 | 创建后存在,结束时撤销 |
| 实体 | 程序代码 | 正文段、用户数据段、PCB |
| 对应关系 | 同一程序可供多个进程执行 | 一个进程也可先后执行不同程序 |
每个进程都有一个 PCB,操作系统通过它识别、管理和控制进程。创建进程需要建立 PCB,撤销进程需要回收 PCB,因此教材称 PCB 是进程存在的唯一标志。
| 信息类别 | 主要内容 | 用途 |
|---|---|---|
| 标识符信息 | 本进程、父进程、子进程标识 | 识别进程及关系 |
| 处理机状态信息 | 通用寄存器、PC、PSW、栈指针等 | 保存与恢复执行现场 |
| 调度信息 | 状态、优先级、调度所需数据 | 决定何时运行、运行多久 |
| 控制信息 | 程序和数据地址、同步通信、资源清单、链接指针 | 管理资源、通信及队列 |
切换进程时,内核保存旧进程的通用寄存器、PC、PSW 等状态,再恢复新进程的已保存状态,使其从原来的执行位置继续运行。本节以 PCB 保存这些管理信息来说明过程。
3 三种基本状态与进程组织(2.3、2.4,识记)¶
PCB 的状态字段记录进程所处阶段。三态模型依据是否具备运行条件、是否已获得 CPU,将进程分为就绪、执行、阻塞。
| 状态 | 已满足的条件 | 还在等待什么 |
|---|---|---|
| 就绪态 | 具备运行条件 | CPU |
| 执行态 | 已获得 CPU | 正在执行 |
| 阻塞态 | 因资源或事件暂不能运行 | I/O 完成、数据到达、资源可用等 |
图2-1 进程状态转换图
| 转换 | 原因 |
|---|---|
| 就绪 → 执行 | 被调度并获得 CPU |
| 执行 → 就绪 | 时间片用完或被抢占 |
| 执行 → 阻塞 | 等待资源或事件 |
| 阻塞 → 就绪 | 等待条件满足,被唤醒 |
三态模型中没有阻塞 → 执行和就绪 → 阻塞两条直接转换。等待 I/O 的进程被唤醒后,先进入就绪队列,再由调度程序分配 CPU;就绪进程尚未执行,也就不能主动发起 I/O 请求或执行 wait 而进入阻塞态。数量上,单核 CPU 同一时刻最多有 1 个进程处于执行态,N 核最多 N 个;阻塞或尚未调度时,执行态也可以为 0,就绪队列则可能为空,也可能容纳全部进程。
操作系统管进程,落到数据结构上就是管 PCB。常用两种排法:链接方式靠 PCB 里的指针把同状态的进程串成链表(就绪队列常按优先级拆成多条,阻塞队列常按等待的事件拆成多条);索引方式则按状态各建一张索引表,表项里存各个 PCB 的地址。
4 进程控制(3.1–3.4,识记)¶
进程创建、终止和状态转换由内核的进程控制原语完成。关键状态和队列更新需要保证原子性,避免其他执行流看到尚未完成的中间状态。
| 操作 | 触发条件 | 教材的一般步骤 |
|---|---|---|
| 创建 | 用户登录、作业调度、提供服务、应用请求 | 申请空白 PCB → 分配资源 → 初始化 PCB → 插入就绪队列 |
| 阻塞 | 请求服务未满足、等待操作完成、数据未到达、等待消息等 | 状态改阻塞 → 插入相应阻塞队列 → 转进程调度 |
| 唤醒 | 所等待的事件发生或资源可用 | 移出阻塞队列 → 改就绪态 → 插入就绪队列 |
| 撤销 | 正常结束、其他进程请求终止等;异常也可能导致结束 | 读取状态 → 若在执行则停止 → 按系统策略处理子孙进程 → 释放资源 → 移出 PCB |
创建其他进程的进程称为父进程,被创建的称为子进程,同一父进程的子进程互称兄弟进程。父子进程可以并发执行,父进程也可等待子进程结束。地址空间及其他资源是复制、共享还是重新建立,取决于创建方式和系统实现。
“父进程结束,子进程必定终止”并非普遍规则。有些系统采用级联终止;在教材介绍的 UNIX 模型中,失去父进程的孤儿进程由 init 接管,子进程可以继续运行。另外,fork 用于创建新进程,execve 则用于替换当前进程执行的程序,两者作用不同。
5 操作系统内核与中断(4.1–4.3,识记、领会)¶
操作系统并非全部代码都需常驻内存。经常执行、直接支撑硬件与进程管理的核心模块组成内核,主要承担两类工作:
- 支撑功能:中断处理、时钟管理、原语操作。
- 资源管理功能:进程管理、存储器管理、设备管理。
原语是具有原子性的一段操作序列,其关键操作对其他执行流呈现为不可分割的整体。普通用户态代码不会仅因写在同一个函数中,就自动具有这种保证。
中断使 CPU 暂停当前顺序执行,转去处理事件。设备就绪后发出中断,CPU 可在等待期间执行其他任务,不必持续查询设备状态;这是支持多道程序的重要机制。
| 类型 | 来源 | 例子 |
|---|---|---|
| 同步中断/内部中断/异常 | 与当前指令执行有关 | 除零、溢出、调试等 |
| 异步中断/外部中断 | 外部硬件产生 | I/O 完成、时钟等 |
| 外部可屏蔽中断 | 可受中断屏蔽控制 | 常见 I/O 中断 |
| 外部不可屏蔽中断 | 紧急硬件事件 | 某些硬件故障 |
“关中断”通常指屏蔽外部可屏蔽中断。清除中断允许位并不能屏蔽当前指令引发的内部异常,也不能屏蔽掉电、内存校验错等事件可能触发的不可屏蔽中断(NMI)。外部中断通常在指令执行结束时检查,内部异常则与当前指令的执行过程有关。
单重中断处理过程:本次处理期间不响应新的中断,教材假定处理后返回原断点。
整个过程分为硬件响应和软件处理两段。区分两段的执行主体,有助于记清各步骤的次序。
第一段:硬件的中断隐指令。全自动,不可被打断。
- 关中断。清除 PSW 中的中断允许位 IF,此后到达的可屏蔽中断只能排队等着。
- 保护断点。把当前 PC(下一条待执行指令的地址)和 PSW 压入内核栈。
- 按中断向量号索引中断向量表,取出该中断服务程序的入口地址(32 位 x86 上是 IDT)。
- 入口地址送 PC。程序计数器一改,CPU 下一条取的就是服务程序的第一条指令。
以上四步由硬件完成,不属于操作系统代码。中断控制器提供向量号后,硬件据此索引表项并取得入口地址;此时中断服务程序尚未开始执行。
第二段:软件的中断服务程序 ISR。
- 保护通用寄存器现场。断点 PC 和 PSW 已由硬件压栈,通用寄存器的值要软件自己存。
- 执行具体的设备服务,比如把网卡收到的帧搬进缓冲区、把键盘扫描码翻译成字符。
- 恢复通用寄存器现场。
- 执行中断返回指令(如
IRET)。它从栈里弹回 PSW 与 PC,处理器回到断点。
在这一单重中断模型中,中断允许状态随最后的返回操作恢复。IRET 从保存的 PSW 中恢复原进程的 IF 位,并恢复 PC,不应笼统写成“先开中断、再返回”。分开执行会留下中断已允许、返回过程尚未完成的时间窗口。教材将过程合为四步,本节为说明分工,展开为“硬件隐指令四步 + ISR 四步”。
口径说明。 教材 §6 把查中断向量排在保护现场之后,§7 又写 CPU 按中断号取得中断向量值,两处叙述次序不一致。本笔记正文按 §7 说明:硬件在第 3 步查表,取得入口后才执行软件服务程序。若题目要求教材 §6 的处理流程,应按该节四步作答;图2-8 采用正文口径,分为两个硬件框和两个软件框。
图2-8 单重中断处理流程
断点记录程序该从哪儿恢复执行,现场则是恢复执行所需的寄存器状态。中断向量标识中断源,中断描述符表 IDT 存着各服务程序的入口信息。教材 32 位 x86 实例里,表项地址是 IDT 基址+8×中断向量,8 字节正好一个表项——第 3 步硬件算的就是这个下标。
6 用户态、系统态与系统调用(4.4、4.5,识记、领会)¶
关中断、修改页表、读写磁盘扇区等操作会影响整个系统,因此需要硬件特权保护。教材区分两种 CPU 执行状态:用户态只能执行非特权指令、访问允许的用户空间;系统态(又称核心态)拥有执行内核代码所需的硬件特权。用户程序通过系统调用请求内核提供受保护的服务。
| 比较 | 用户程序的一般函数调用 | 系统调用 |
|---|---|---|
| 执行状态 | 通常保持用户态 | 进入系统态执行内核服务 |
| 入口方式 | 转移到函数入口 | 通过受控的陷入/系统调用入口 |
| 查找依据 | 函数地址 | 系统调用号等 |
| 开销 | 常规调用开销 | 另有执行态转换、现场处理等开销 |
一次系统调用通常经过以下步骤。具体入口和参数传递方式取决于处理器架构与操作系统;传统 x86 的 int 0x80 是一种陷入方式。
- 用户程序调用库函数,由库函数准备系统调用号和参数。
- 程序执行陷入指令,处理器切换到系统态。
- 内核保存所需状态,根据系统调用号分派到相应服务例程,例如处理
getpid()的内核服务。 - 服务完成后,内核返回结果并恢复用户态执行。
库函数、系统调用和进程切换需要分开理解。strcpy() 这样的库函数可以完全在用户态执行;教材以 getpid() 为例说明系统调用时,进入内核的仍是当前进程自己,只是从用户态切换到系统态执行内核代码,完成后通常返回当前进程。只有调用导致阻塞,或返回前需要调度等情况下,才可能发生进程切换。
教材把系统调用归成五类:进程控制、文件操作、设备管理、信息维护和通信,作用都是把内核服务收拢到统一且带权限检查的入口后面。
7 时钟体系与时钟机制(4.6、4.7,识记、领会)¶
在抢占式系统中,时钟中断为时间片调度提供触发时机,使 CPU 密集型进程也能按规则让出处理器。操作系统还利用时钟维护日期时间、生成时间戳、处理定时报警,并统计各进程的 CPU 使用时间。
| 部分 | 作用 |
|---|---|
| RTC/CMOS 时钟 | 电池供电,断电也不停,开机时供系统读取初始日期时间 |
| OS 时钟 | 开机后由操作系统接手维护,教材用 PIT 周期中断来说明 |
| PIT 可编程间隔定时器 | 由晶振、计数器、保持寄存器构成,周期性向 CPU 发时钟中断 |
| 时钟驱动程序 | 响应每次时钟中断,更新系统时间与各类定时计数 |
PIT 的硬件节奏很简单:晶振按固定频率吐出脉冲,每来一个脉冲计数器减 1;减到 0 就触发一次时钟中断,同时把保持寄存器里的初值重新装回计数器,周而复始。
图2-12 PIT 的逻辑结构
每次时钟中断到来,时钟驱动程序主要处理四件事:更新日期和时间;递减当前进程的时间片余量,减到 0 时设置调度标志;累计当前进程的 CPU 使用时间;递减定时报警计数器。时间片用完后,进程让出 CPU 并返回就绪态,并不意味着进程被撤销。
例如,PIT 每秒产生 100 次中断,则一个滴答(tick)为 10 ms,50 ms 的时间片对应 5 个滴答。教材提到的 Linux 2.4 变量 jiffies 记录开机以来累计的滴答数,而非年月日时分秒。
8 线程(5.1、5.2,领会)¶
进程同时承担资源管理和执行任务。同一应用若需要并发接收网络数据、渲染画面和响应用户操作,为每项任务建立独立进程会增加地址空间、页表及切换开销。引入线程后,进程仍是拥有资源的基本单位,线程成为调度和分派的基本单位。
同一进程中的线程共享地址空间、打开的文件和全局变量;每个线程保留自己的 PC、寄存器和私有栈。因此,线程的创建、撤销及同一进程内的切换开销通常小于进程。
| 比较角度 | 进程 | 同一进程中的线程 |
|---|---|---|
| 基本角色 | 资源拥有的基本单位 | 执行、调度与分派的基本单位 |
| 地址空间 | 进程间通常相互独立 | 共享所属进程地址空间 |
| 通信 | 使用进程通信机制 | 可直接读写共享变量,但仍须同步互斥 |
| 并发 | 不同进程间可以并发 | 同一进程内不同线程也可以并发 |
| 开销 | 创建、撤销、切换开销较大 | 通常较小,尤其同进程线程切换 |
对应 PCB,每个线程也有自己的线程控制块 TCB,记着线程标识、寄存器状态、调度优先级和记账信息,照样走就绪、运行、阻塞三态转换,同状态 TCB 串成队列管理。
按“内核知不知道线程存在”,教材对比了两种纯粹的实现模型:
| 比较 | 用户级线程(教材的纯用户线程模型) | 内核级线程 |
|---|---|---|
| 谁管理 | 用户空间线程库管,内核眼里只有外层进程 | 内核亲自建 TCB 并管理 |
| 谁调度 | 内核把 CPU 分给进程,线程库再在进程内部排班 | 内核直接按线程调度 |
| 切换 | 用户态内就能切完,不用陷入内核,快 | 要进出内核态,开销大些 |
| 阻塞式系统调用 | 一个线程发起阻塞调用,整个进程跟着一起挂起 | 只阻塞发起调用的那个线程,同进程其他线程照跑 |
| CPU 时间分配 | 同进程的多个线程合分这一个进程的时间片 | 按线程拿时间片,线程多的进程分到的总时间更多 |
| 多处理器利用 | 内核只按进程派核,同进程线程没法在多核上真并行 | 同进程的多个线程可同时派到不同 CPU 核上并行 |
在本节的多对一用户级线程模型中,内核只管理外层进程。若一个线程发起阻塞式系统调用,整个进程可能被阻塞,其他用户级线程也无法运行。应注意这一结论的模型前提。
线程控制包括创建、终止、阻塞和唤醒:创建时建立 TCB 并加入就绪队列;终止时停止执行、移出队列并准备回收;阻塞时保存现场、加入阻塞队列并调度其他线程;唤醒时移回就绪队列。用户级线程的这些操作由用户态线程库完成,内核级线程则由操作系统内核完成。
9 同步与互斥的基本概念(6.1,领会)¶
并发访问共享数据需要避免冲突,合作任务也可能存在先后依赖,例如甲必须等乙产生结果后才能继续。同步机制处理这两类关系:让争用临界资源的进程互斥进入,以及让合作进程按约定次序协调推进(狭义同步)。
| 概念 | 含义 |
|---|---|
| 临界资源 | 一次只允许一个进程访问的共享资源,如打印机、共享计数器 |
| 临界区 | 进程中访问临界资源的那段代码 |
| 进入区 | 临界区前面检查并申请进入许可的代码 |
| 退出区 | 临界区后面归还许可、通知等待者的代码 |
| 互斥 | 同一临界资源任意时刻至多一个进程在内 |
| 协调/狭义同步 | 合作进程按逻辑先后推进,如生产者先放数据、消费者再取 |
互斥方案通过进入区申请访问临界资源的权限,在退出区释放权限。衡量方案时,主要看四条准则:空闲让进(临界区空闲时允许进入)、忙则等待(已有进程访问时,其他进程等待)、有限等待(申请者不能无限期等待)、让权等待(无法进入时让出 CPU,避免忙等)。
“忙则等待”和“让权等待”分别约束等待的条件与方式。前者要求临界资源已被占用时,其他申请者必须等待;后者要求等待者让出 CPU。整型信号量中的 while S <= 0: 忙等 满足前者,却一直占用 CPU,因此不满足后者。
10 信号量与 P/V 操作(6.2、6.3,综合应用)¶
信号量表示资源数量、使用状况或事件关系。除初始化外,通过原子的 P/V 操作修改(P 即 wait,V 即 signal);以下操作均针对共享的同一个信号量对象。
10.1 整型信号量¶
P(S):
while S <= 0:
忙等
S = S - 1
V(S):
S = S + 1
P 操作中的条件检查和资源取得需要由底层机制保证原子性。上述代码仅表示操作语义;若直接用普通循环和赋值实现,多核环境中仍可能出现竞争。互斥信号量初值为 1,N 个同类资源初值为 N。整型机制的问题是忙等,不满足让权等待。
10.2 记录型信号量¶
S = { value: 整数, L: 阻塞队列 }
P(S):
S.value = S.value - 1
if S.value < 0:
把当前进程加入 S.L 并阻塞,放弃 CPU
V(S):
S.value = S.value + 1
if S.value <= 0:
从 S.L 唤醒一个进程
记录型 P 操作先将 value 减 1,再根据结果是否小于 0 决定阻塞。V 操作则先加 1,结果小于等于 0 时唤醒等待者。阻塞的是调用 P 的当前进程;被唤醒的进程转为就绪,不等于立即取得 CPU,也不应重新执行一次已经完成扣减的 P。
- value ≥ 0:表示当前可用资源数量。
- value < 0:其绝对值表示该信号量队列中的等待进程数。教材相关句子省略绝对值,理解时需补全。
| 操作示例,初值 1 | 操作后 value | 结果 |
|---|---|---|
| A 执行 P | 0 | A 取得许可 |
| B 执行 P | −1 | B 阻塞 |
| C 执行 P | −2 | C 阻塞 |
| A 执行 V | −1 | 唤醒一个等待者 |
| 获许可者完成并 V | 0 | 唤醒另一个等待者 |
| 最后一个完成并 V | 1 | 无等待者,许可归还 |
10.3 两种基本建模¶
互斥:mutex = 1,每个访问同一资源的进程都执行 P(mutex) → 临界区 → V(mutex)。
先后关系:要求 A 的 S1 完成后,B 才能执行 S2,设 done = 0;A 执行 S1 → V(done),B 执行 P(done) → S2。V 位于产生事件的进程,P 位于等待事件的进程,两者配合表达这段先后约束。
写同步题时,先确定资源和先后关系,再定义信号量、写初值、标出临界区并安排 P/V。记录型信号量消除了忙等,但错误的 P/V 顺序仍可能造成死锁,也不保证公平。
教材另介绍 AND 型信号量,用一次操作同时申请多个资源;大纲 6.2、6.3 明确列出的应用重点是整型和记录型机制。
10.4 PV 建模五步¶
建立 PV 模型时,可按以下五步整理条件和操作:
- 列出资源与先后约束:按时间顺序写出各进程的动作,标出共享资源以及必须等待的事件。
- 定义信号量并设初值:互斥信号量初值为 1;资源计数按可用资源数设置;表示尚未发生事件的同步信号量初值为 0。
- 写出进程动作:暂不插入 P/V,先按执行顺序列出操作,并标明临界区。
- 插入 P/V 操作:先等待资源或事件,再申请互斥;先释放互斥,再发出资源或事件许可。具体次序仍以题设的并发条件为准。
- 检查死锁条件:检查是否有进程持有其他进程所需的资源,同时等待对方持有的资源;若有,调整申请次序或采用合适的集中申请方式。
先列动作顺序,再放置 P/V。这样可以把“进程要做什么”和“何时申请资源”分开检查。
11 两个经典同步模型(6.4,综合应用)¶
11.1 生产者—消费者¶
生产者把数据放入含 N 个槽位的缓冲池,消费者从中取出数据。双方共享缓冲区和读写指针,访问这些状态时需要互斥。缓冲池满时生产者等待,缓冲池空时消费者等待,这又构成资源数量上的同步约束。
| 信号量 | 含义 | 初值 |
|---|---|---|
| mutex | 缓冲池访问许可 | 1 |
| empty | 可申请的空缓冲区 | N |
| full | 可申请的消息 | 0 |
生产者循环:
在私有变量中生产 item
P(empty)
P(mutex)
buffer[in] = item
in = (in + 1) mod N
V(mutex)
V(full)
消费者循环:
P(full)
P(mutex)
从 buffer[out] 取到私有变量 item
out = (out + 1) mod N
V(mutex)
V(empty)
在临界区外消费 item
图2-18 生产者—消费者共享缓冲池
必须先申请资源,再申请互斥权。缓冲池已满时,若生产者先 P(mutex) 再 P(empty),它会持有 mutex 并等待空位。消费者此时取不到 mutex,也就取不走数据、无法释放空位。双方因此死锁。缓冲池为空时,消费者反过来也会遇到同样的问题。
生产和消费放在临界区外,临界区只保护缓冲区及共享指针。P/V 执行过程中,资源可能已被预留但尚未发布,因此不能在任意时刻用 \(\text{empty.value} + \text{full.value} = N\) 判断状态;稳定状态下,实际空位数和消息数之和才是 N。
11.2 读者—写者¶
多个读者可以同时读取数据;只要有写者访问数据区,就需要排除其他读者和写者。设 readcount = 0,rmutex = 1 保护计数器,wmutex = 1 控制共享数据区。
可以用自习室理解这几个变量:wmutex 控制整间数据区,readcount 记录读者人数,rmutex 只保护人数登记,避免多个读者同时修改计数。
第一个读者进入时,若 readcount == 0,便申请 wmutex,阻止写者进入;后续读者只需登记人数,可以并发读取。最后一个读者离开、使 readcount 减到 0 时,释放 wmutex。写者则在写入前申请 wmutex,完成后释放,全程独占数据区。
rmutex 只保护 readcount 的修改,不限制同时读取的人数。实际读操作应放在 rmutex 临界区外;否则读者会被串行化,失去并发读取的效果。
这类“计数器 + 互斥量”的结构也可用于分析通行问题,但不能直接照搬。遇到独木桥、单行桥或窄道题目时,应重新核对哪些对象可以并发、哪些必须互斥,以及是否存在容量或优先级限制,再决定计数器和 P/V 操作的位置。
读者:
P(rmutex)
if readcount == 0: P(wmutex)
readcount++
V(rmutex)
读共享数据
P(rmutex)
readcount--
if readcount == 0: V(wmutex)
V(rmutex)
写者:
P(wmutex)
写共享数据
V(wmutex)
第一个读者替整群读者申请数据区,最后一个读者归还;中间进出的读者完全不碰 wmutex,这就是读者能并发的来源。而 readcount 本身是所有读者共改的共享变量,增减前后那对 rmutex 一步也不能省。
注意这套写法是读者优先:只要屋里还有读者没走完,后来的读者就能一直插队进来,守在门外的写者可能长期饿死。题目若要求写者优先,就得再加一层拦住后续新读者的锁。
11.3 教材综合题:单缓冲区读文件并打印¶
pA 每次从磁盘读一条记录到公共缓冲区,pB 每次取出并打印一条;缓冲区只容一条。它是 \(N=1\) 的生产者—消费者问题,设 empty=1, full=0, mutex=1。
pA 循环:
从文件读记录到私有变量
P(empty)
P(mutex)
写入公共缓冲区
V(mutex)
V(full)
pB 循环:
P(full)
P(mutex)
从公共缓冲区复制到私有变量
V(mutex)
V(empty)
打印私有变量中的记录
与附录一致,取出到私有变量后就可以归还 empty,不必等待打印完成;如果直接从公共缓冲区打印,则必须保证打印结束前不会被覆盖。终止协议由题目另外约定,以上表达逐条交替的同步关系。
11.4 填空式 PV 专项¶
两卷的综合 36 均为代码填空:202410 为“火车订票”,10 空、每空 1 分;202504 为“售票”,5 空、每空 2 分。练习时需核对每次 wait、signal 的对象和次序。以下两题为自编,题型参考这两卷,题面可直接用于练习。
自编题一(自编,题型同 202410 综合 36,10 空):火车订票。 某车站有两个售票窗口 W1、W2,一个退票窗口 R 和一个统计进程 S,共用一个余票记录 remain,初值 100。任何时刻只允许一个进程访问 remain。售票窗口在余票为 0 时等待,退票窗口每退回 1 张就唤醒一个等待中的售票窗口,自己不必等待。统计进程每轮读一次 remain 并累加,读的时候同样不许别人改。请补齐下面程序中的 ①—⑩,一空只写一个 wait 或一个 signal。
semaphore mutex = ___①; /* 保护余票记录 remain */
semaphore tickets = ___②; /* 当前可售的余票数 */
售票窗口 Wi:
while (1) {
取到一个乘客请求;
___③;
___④;
remain = remain - 1;
打印车票;
___⑤;
}
退票窗口 R:
while (1) {
收到一张退票;
___⑥;
remain = remain + 1;
___⑦;
___⑧;
}
统计进程 S:
while (1) {
收到一次统计命令;
___⑨;
读出 remain 并累加;
___⑩;
}
自编题二(自编,题型同 202504 综合 36,5 空):两个售票点卖同一种票。 两个售票点 A、B 和一个退票点 R 共用余票记录 remain,初值 200,任何时刻只允许一个进程访问;余票为 0 时售票点等待,退票点每退回 1 张就唤醒一个售票点。已知 semaphore tickets = 200;、semaphore mutex = 1;,退票点末尾的 signal(tickets) 已给出,只补 ①—⑤。
售票点 A:
while (1) {
___①;
___②;
售出一张车票,remain = remain - 1;
___③;
}
退票点 R:
while (1) {
___④;
退回一张车票,remain = remain + 1;
___⑤;
signal(tickets);
}
填写本组 P/V 空格时,可依次检查以下三点:
- 本组练习用
mutex保护共享记录,每条访问路径都应先wait(mutex),退出临界区后再signal(mutex)。检查正常分支与条件分支是否遗漏释放。 - 依据具体约束确定谁等待、谁发信号。例如,售票窗口等待余票,退票窗口增加余票后发出许可。不能推广为“同一信号量的 P/V 必须分处两个进程”;资源计数信号量也可能在同一进程的不同阶段被申请和归还。
- 题设限定每空只填一个
wait或一个signal。若候选答案不符合空格数量,应回查初值、已有语句和约束;空格数量本身不能证明模型正确。
参考答案
自编题一:
① 1 ② 100 ③ wait(tickets) ④ wait(mutex)
⑤ signal(mutex) ⑥ wait(mutex) ⑦ signal(mutex) ⑧ signal(tickets)
⑨ wait(mutex) ⑩ signal(mutex)
自编题二:
① wait(tickets) ② wait(mutex) ③ signal(mutex)
④ wait(mutex) ⑤ signal(mutex)
两题需避免两种错误:把 wait(mutex) 放在 wait(tickets) 之前,或在售票窗口执行后增加 signal(tickets)。前者会在无票时持有互斥量等待,使退票窗口无法进入,也就无法补回票;后者会增加并不存在的余票,使信号量计数与实际票数不符。
12 管程(6.5,领会)¶
信号量的 P/V 操作分散在各个进程中,遗漏操作或颠倒次序都可能导致死锁。管程将共享数据结构及其操作过程封装在同一模块中,外部进程只能通过入口过程访问;管程机制保证任意时刻最多只有一个进程在管程内部活跃执行,由此集中处理互斥。
条件不满足时,管程使用条件变量等待:x.wait() 阻塞当前进程,并释放管程的独占权;x.signal() 唤醒队列中的一个等待者。它与信号量的 V 操作不同:V 会使计数加 1,即使没有等待者也会保留这个计数;条件变量不保存许可,若调用 x.signal() 时队列为空,这次通知不会留给后来的等待者。
12.1 管程实现生产者—消费者¶
管程将缓冲池、计数器 count 以及 put(放入)、get(取出)过程封装在一起。教材的条件变量 full 和 empty 与 11.1 节信号量同名,但含义不同:信号量 full 表示可取消息数,条件变量 full 表示“缓冲池已满”这一等待条件:
- 放入过程:见
count == N就在full上wait();有空位后写入数据、count加 1,再调empty.signal()叫醒因池空而等待的消费者。 - 取出过程:见
count == 0就在empty上wait();有数据后取出数据、count减 1,再调full.signal()叫醒因池满而等待的生产者。
教材伪代码用 if 检查 count == N 和 count == 0,其唤醒语义见 12.2 节。采用 Java、C++、POSIX 条件变量编写实际代码时,应以 while 循环重新检查等待条件。
12.2 哲学家进餐¶
5 人围坐,每人进餐需要左右两支筷子。若所有人各拿一支再等另一支,就会死锁。教材管程改成“左右两支都空闲才一次拿走”:为每人设 thinking、hungry、eating 三种状态和条件变量 self[i],左右邻居下标分别是 \((i+4) \bmod 5\) 与 \((i+1) \bmod 5\)。
test(i):
if state[i] == hungry and 左右邻居都不是 eating:
state[i] = eating
self[i].signal()
pickup(i):
state[i] = hungry
test(i)
while state[i] != eating:
self[i].wait()
putdown(i):
state[i] = thinking
test(左邻居)
test(右邻居)
test 中的 if 仅负责检查并更新状态,可以保留;pickup 中的等待判断则应改用 while。这一区别与管程的唤醒语义有关。
| 语义 | signal 之后 | 典型实现 | 等待处写什么 |
|---|---|---|---|
| Hoare,signal-and-wait | 唤醒者立刻让出管程,被唤醒者马上接手运行,条件此时必然成立 | 教材未命名语义;其 if 写法隐含 Hoare 式不可抢占假设(据代码推断) |
if 够用 |
| Mesa,signal-and-continue | 唤醒者继续把管程过程跑完,被唤醒者只回到就绪队列重新抢锁 | Java、C++、POSIX 条件变量的实际实现 | 必须 while |
Mesa 语义下,被唤醒者需要重新竞争管程锁。从发出 signal 到重新获得锁之间,其他线程可能已经改变状态,使等待条件再次失效。此外,实际条件变量实现还可能允许“虚假唤醒”,即没有对应通知也从 wait 返回。因此,返回后必须重新检查条件。
若实际代码只用 if,从 wait 返回后就会直接继续执行,即使 state[i] 已不再是 eating。教材的 if 写法依赖 Hoare 式唤醒后立即交权的假设;按教材模型作答时需说明这一前提,使用 Mesa 语义的实际代码则应写 while。
留意哲学家思考和吃饭都在管程外头,进管程只为改状态、验邻居。由于相邻两人绝不可能同时进入 eating 状态,这套方案根除了“人手一支筷子互等”的死锁;但若左右邻居轮番交替进餐,夹在中间的人仍可能长期轮不上,光靠这三段代码还不能保证绝对不饥饿。
13 进程通信(6.6,识记)¶
信号量和管程传递的只是“能不能走”的信号,真要在进程之间搬运成块的数据,得靠进程通信(IPC)机制。大纲点名要求识记共享存储器、管道和消息缓冲队列三项,教材另把消息传递作为大类一并交代:
| 方式 | 主要思想 | 注意 |
|---|---|---|
| 共享存储器 | 划出共享数据结构或共享内存区,各进程直接读写交换数据 | 系统只给场地,读写互斥与先后同步得由进程自己用 P/V 等机制兜底 |
| 消息传递 | 调用操作系统提供的 send/receive 原语收发格式化消息 |
分直接通信(点名发给目标进程)和间接通信(发往中间信箱) |
| 管道 | 连接读写进程的特殊文件(pipe 文件),按先进先出的字节流传送 | 虽挂着“文件”名头,实则在内存缓冲区周转,不等于落盘保存;半双工、读空写满会阻塞 |
| 消息缓冲队列 | 发送方申请消息缓冲区填好正文,挂入接收方 PCB 下的消息队列 | 队列本身是临界资源,挂入和摘取同样离不开互斥信号量与消息计数信号量 |
消息缓冲区里一般装着四样东西:发送进程标识、消息长度、消息正文和指向下一缓冲区的指针;接收进程的 PCB 里则要相应配齐消息队列队首指针、保护队列的互斥信号量,以及统计队列里现有几条消息的资源信号量。
14 考核要求¶
本章共 25 条考核条目:识记 14 条、领会 8 条、综合应用 3 条。3 条综合应用集中在 6.2–6.4(整型与记录型信号量、生产者—消费者、读者—写者)。复习时先掌握进程组成、三态转换、单重中断处理流程及两类线程模型,再练习 PV 建模与代码填空,并区分管程条件变量和信号量是否保留计数、唤醒后是否需要重新检查条件。
15 教材本章习题¶
题目取自教材第 2 章“习题”(共 16 题),答案取自教材附录“参考答案”(全部收录)。
15.1 单项选择题¶
1. 下列选项中,操作系统提供给应用程序的接口是【 】。
答案与解析
参考答案: A。系统调用是应用程序取得内核服务的程序接口;库函数可能只是对系统调用的封装。
2. wait(s) 操作中的 block(s, L) 阻塞的进程是【 】。
答案与解析
参考答案: C。执行 P 操作后发现资源不可用时,当前调用 P 的进程进入阻塞队列。
3. 在一个单处理机系统中存在 5 个进程,处于就绪队列中的就绪进程数最多为【 】。
答案与解析
参考答案: C。题目只问就绪队列,5 个进程可以全部处于就绪态;单处理机最多同时执行其中 1 个,并不意味着必须有 1 个执行态进程。
4. 若记录型信号量 s 初值为 2,当前值为 −1,则 s 的阻塞队列中有【 】个进程。
答案与解析
参考答案: B。记录型信号量值为负时,负值的绝对值表示阻塞队列中的进程数。
15.2 填空题¶
5. 进程是由正文段、___和___构成的实体。
答案与解析
参考答案: 用户数据段;进程控制块。
6. 进程的 3 种基本状态是执行态、___态和___态。
答案与解析
参考答案: 就绪;阻塞。
7. 整型信号量的值只能被___和___改变。
答案与解析
参考答案: wait 操作;signal 操作。
8. 临界区是指访问___的代码。
答案与解析
参考答案: 临界资源。
15.3 简答题¶
9. 什么是进程?说明进程与程序的区别和联系。
答案与解析
参考答案: 进程是允许并发执行的程序在某个数据集合上的运行过程,由正文段、用户数据段和 PCB 组成。程序是静态的指令集合,进程是动态的执行过程;程序可长期保存在外存,进程有创建和撤销;一个程序可以对应多个进程,一个进程通常对应一次程序执行。
10. 操作系统在什么时候创建进程?如何创建?
答案与解析
参考答案: 用户登录、作业调度、提供服务、应用请求时可创建。步骤为:申请空白 PCB → 分配资源 → 初始化 PCB → 插入就绪队列。
11. 时钟中断信号如何产生?时钟驱动程序有什么功能?
答案与解析
参考答案: PIT 的晶振产生固定频率脉冲,计数器随脉冲递减,减到 0 后产生时钟中断,并从保持寄存器重新装载计数值。时钟驱动程序维护日期时间,递减并检查当前进程时间片,记录 CPU 使用情况,递减报警计数器。
12. 请说明单重中断的处理过程。
答案与解析
参考答案: 硬件阶段先关中断(清 IF),把断点 PC 与 PSW 压栈,再按中断向量号索引中断向量表取出服务程序入口,入口送 PC;随后进入软件阶段,保护通用寄存器,执行设备服务,恢复现场,最后执行中断返回指令,由硬件原子恢复 PSW 与 PC。教材把这两段合成四步叙述,顺序上硬件响应在前、软件处理在后。
13. 进程具有哪些特征?
答案与解析
参考答案: 并发性、动态性、独立性、异步性、结构特征。
14. 什么是线程?为什么要引入线程?
答案与解析
参考答案: 线程是进程中的实体,是独立调度和分派的基本单位,拥有 PC、寄存器、栈等少量资源并共享所属进程资源。引入线程可以减少进程创建、撤销和切换的开销,提高并发程度。
15. 实现进程互斥的基本原理是什么?
答案与解析
参考答案: 把访问临界资源的代码置于临界区;进入前申请访问许可,临界区内一次只允许一个进程,退出时释放许可并唤醒等待者,同时满足空闲让进、忙则等待、有限等待、让权等待。
15.4 综合应用¶
16. 有两个进程 pA、pB 合作解决文件打印问题:pA 将文件记录从磁盘读入主存缓冲区,每次读一个记录;pB 将缓冲区内容打印出来,每次打印一个记录。缓冲区大小等于一个记录大小。请用记录型信号量的 wait(s) 和 signal(s) 保证正确打印,并写出同步代码。
答案与解析
参考答案: 设置 mutex 保护缓冲区,empty 表示空缓冲区,full 表示已有记录。初值为:
pA:
while 还有记录:
从磁盘读一条记录到私有变量
wait(empty)
wait(mutex)
将私有变量中的记录写入公共缓冲区
signal(mutex)
signal(full)
pB:
while 还有记录:
wait(full)
wait(mutex)
从公共缓冲区复制一条记录到私有变量
signal(mutex)
signal(empty)
打印私有变量中的记录
要点:三个信号量的含义和初值正确;生产者先申请 empty,消费者先申请 full;访问公共缓冲区时使用 mutex;取出记录后释放 empty。
16 闭卷复习与跨章联系(补充)¶
一、闭卷自答(合上书逐题口述)
- 顺序执行与并发执行在封闭性、可再现性上差在哪,为什么同一段程序并发执行结果可能不同?
- 进程由哪三部分组成,PCB 的四类信息各在什么时候用到?
- 三态之间的四条基本转换各由什么事件触发,被唤醒的进程一定在运行吗?
- 中断隐指令四步与中断服务程序四步分别是什么,开中断在哪一步完成、由谁完成?
- 用户级线程与内核级线程在被谁调度、阻塞式系统调用上的表现有什么差别?
- 记录型信号量 value 为负时,绝对值表示什么?在生产者—消费者模型中,若先申请互斥再等待空位或消息,为什么可能死锁?
二、本章易错
- 忙则等待管的是“进不去必须等”,让权等待管的是“等的时候要把 CPU 让出去”;整型信号量忙等满足前者,却违反后者(第 9 节)。
- 查中断向量表由硬件隐指令完成,开中断则随中断返回指令(如
IRET)恢复 PSW 一并原子完成,别写成软件里先开中断再返回(第 5 节)。 - 记录型信号量
P操作阻塞的是调用它的当前进程;被V唤醒的进程只是回到就绪队列,不会立刻抢到 CPU,醒来后也不再重复扣减value(第 10.2 节)。
三、本章骨架
| 层 | 一句话 |
|---|---|
| 记账单位 | 进程=正文段+用户数据段+PCB,PCB 是系统感知进程存在的唯一标志 |
| 状态 | 就绪、执行、阻塞,只走四条基本转换,唤醒只回到就绪 |
| 接管 | 内核用中断隐指令、时钟中断和系统调用控制执行过程 |
| 并发协作 | 信号量 P/V 表达互斥与先后,记录型消除忙等,管程用条件变量封装等待 |
| 拆分 | 线程是调度与分派单位,进程是资源拥有单位 |
四、跨章指路