进程与线程

考点定位

  • 大纲章节:进程与线程(概念、组成、状态转换、控制、通信、线程模型);处理机调度(时机、切换、三级调度、典型算法);同步与互斥(临界区、实现方法、信号量、经典同步问题);死锁(条件、预防、避免、检测、解除)
  • 常见题型:选择题(状态转换、调度算法计算、死锁条件辨析)+ 大题两巨头(PV 信号量同步应用题、银行家算法/死锁分析题),调度算法还常出表格化计算小题
  • 考查频率:极高(OS 部分大题的最主要出处,几乎每年综合应用题二选一落在本章)
  • 工程师读者注意:你写过 pthread、用锁无数,但考试要的是信号量语义的严格陈述与P 操作次序的死锁分析、银行家算法的表格化推演——工程直觉只帮你理解,答题必须按模板
  • 前置知识:操作系统概述(内核态/中断是理解进程切换的基础)

一、进程的概念与组成

1.1 进程定义与 PCB

定义(考点口径):进程是程序在一个数据集合上的一次运行过程,是系统进行资源分配和调度(未引入线程时)的独立单位。程序是静态的指令集合,进程是动态的执行过程——进程有生命周期(创建→运行→消亡),程序可以长期保存。

进程实体的组成(进程映像):

组成部分内容
程序段程序代码
数据段全局变量、静态数据、进程运行所需数据
PCB(进程控制块)操作系统管理进程的唯一标志;PCB 存在则进程存在,PCB 撤销则进程消亡

PCB 的内容(简答背诵清单,四大类):

  1. 进程描述信息:进程标识符 PID、用户标识符 UID
  2. 进程控制和管理信息:进程当前状态、优先级、代码入口地址、程序在外存的地址、进入内存时间、已用 CPU 时间
  3. 资源分配清单:代码段/数据段/堆栈段指针、打开文件列表、使用的 I/O 设备
  4. 处理机相关信息(处理机上下文):通用寄存器、PC、PSW、用户栈指针——进程切换时保存/恢复的就是这部分

组织方式:线性方式、链接方式(按状态挂成就绪队列/阻塞队列等)、索引方式。

1.2 进程状态与转换(五态模型,高频图题)

逐边分析(选择题按"边"命题,每条边的触发者与方向必须精确):

边触发条件关键辨析
创建→就绪OS 完成创建(分配 PCB、内存等),纳入就绪队列"接纳"与否取决于性能与内存考虑
就绪→运行被调度程序选中(进程调度,处理机分配)唯一途径;进程自己无法从就绪直接运行
运行→就绪时间片用完(时钟中断)、被更高优先级抢占不是进程自身意愿,被动失去 CPU
运行→阻塞进程主动请求等待某事件(P 操作、I/O 请求、等待某资源)主动行为;"运行→阻塞不存在被动"
阻塞→就绪等待的事件发生(如 I/O 完成中断、V 操作)由别人(中断服务/其他进程)唤醒,阻塞进程自己无法推进
运行→终止正常结束 exit、异常(越界、非法指令)、被 kill—

不存在的转换(高频判断):就绪→阻塞、阻塞→运行、阻塞→终止(严格五态口径下阻塞进程不能直接终止需先就绪——408 教材口径,判断题按"阻塞→运行不可能"为准即可)。

引入挂起态(七态模型):内存不足或用户/系统需要时,进程被换出到外存对换区。

  • 就绪 → 静止就绪(挂起就绪):激活(active)前不能被调度
  • 阻塞 → 静止阻塞(挂起阻塞):即使等待的事件发生,仍需激活回到活动阻塞/就绪
  • 区分阻塞(等待事件)与挂起(换出内存):阻塞是等待 I/O 或资源,挂起是内存管理行为,两个正交维度

1.3 进程控制(原语)

进程控制用原语实现——原语是执行期间不允许中断的原子操作,靠关中断指令实现(关中断是特权指令,故原语运行在内核态)。

原语主要步骤
创建原语申请空白 PCB → 分配资源(内存等)→ 初始化 PCB → 插入就绪队列
撤销原语找到 PCB → 若在运行立即剥夺 CPU → 撤销子进程 → 归还资源(PCB 归还队列)→ 删除 PCB
阻塞原语找到 PCB → 保护现场至 PCB → 置阻塞态 → 插入等待队列 → 调度新进程
唤醒原语从等待队列摘下 PCB → 置就绪态 → 插入就绪队列
切换原语保存当前进程上下文到 PCB → 更新其状态入相应队列 → 调度选择新进程 → 恢复新进程上下文

进程切换的过程(动态过程,分步静态图 + 文字推演占位,待动画化):

  • 第 0 步:触发切换的事件(时钟中断 / 主动阻塞 / 更高优先级就绪)
  • 第 1 步:保存当前进程的处理机上下文(寄存器、PC、PSW)到其 PCB
  • 第 2 步:修改其 PCB 状态并移入相应队列(就绪/阻塞)
  • 第 3 步:调度程序按算法从就绪队列选新进程
  • 第 4 步:恢复新进程 PCB 中的上下文,更新其状态为运行态
  • 第 5 步:CPU 执行新进程(可能需切换页表基址/地址空间,见内存管理章)

注意:"调度"与"切换"不同——调度是决定谁上(选择),切换是换上去(执行);广义调度含选择与切换。切换有成本(纯开销),切换过于频繁降低效率。

1.4 进程通信(IPC)

方式原理关键性质
共享内存OS 在物理内存划一块区域映射到两个进程的虚拟地址空间,进程直接读写最快的 IPC(数据不复制进内核,一次建立后零内核介入);需用户自己用信号量等保证互斥同步
消息传递send/receive 原语,消息经内核转发:直接通信方式(直挂对方信箱/队列)与间接通信方式(信箱 mailbox 中转)每次通信两次拷贝(发送方→内核→接收方),较慢;天然互斥,适合分布式
管道内存中开辟固定大小缓冲区(如 4 KB),一个进程写、另一个读,pipe() 创建;数据流式先进先出半双工,双向需两个管道;写满则写阻塞、读空则读阻塞;必须是亲缘进程(父子)使用匿名管道;共享管道实现的基础是 fork 后文件描述符继承
信号异步通知机制,内核/进程向目标进程发信号(如 SIGKILL),打断其正常执行转信号处理函数异步、量小,适合事件通知,不适合传数据
套接字/命名管道跨网络/无亲缘进程通信408 大纲口径以前四种为主

选择题高频结论:共享内存最快(因为少一次内核中转拷贝);管道是共享存储的一种特殊形式(共享缓冲区);消息传递可适用于分布式(共享内存不行)。

二、线程

2.1 线程概念

定义:线程是进程中的一个执行流,是 CPU 调度和分派的基本单位;引入线程后,进程仅作为资源分配的基本单位(408 标准口径:引入线程后,处理机调度单位是线程,资源分配单位是进程)。

线程自身基本不拥有系统资源,只保有运行必需的一点点:线程 ID、PC、寄存器组、栈。同一进程内的线程共享进程的地址空间(代码段、数据段、打开文件),但各自私有栈与寄存器。

线程的属性(选择题):轻型实体、独立调度分派单位、可并发执行、共享进程资源。同进程内线程切换不引起进程切换(不换地址空间),开销小;跨进程线程切换需换地址空间,开销大。

2.2 多线程模型(用户级/内核级/组合,高频对比)

模型实现者内核是否感知优缺点
用户级线程(ULT)用户空间线程库(切换不需内核介入)内核完全不知道,仍按进程调度切换极快;一个线程阻塞则整个进程阻塞(内核按进程分配 CPU);无法利用多核并行(多线程只能在一个核上分时)
内核级线程(KLT)内核管理,线程是调度单位内核感知,每个线程独立调度能利用多核并行;一个线程阻塞进程内其他线程仍可运行;切换需进入内核态(用户态→内核态),开销大于用户级
组合模型(多对多)nn 个用户级线程映射到 mm 个内核级线程(m≤nm \le n)内核感知 mm 个兼得两者:可并行可快速切换

映射关系(选择送分点):多对一(用户级)、一对一(内核级,如 Linux pthread、Windows)、多对多。

2.3 线程状态与组织

线程同样有就绪、运行、阻塞三态(及创建/终止),转换规则与进程类似。线程组织:TCB(线程控制块,含线程 ID、寄存器、栈指针、状态)挂入进程的线程队列;同进程线程切换不改变进程状态。

关键判定清单:

  • 进程切换(换地址空间)必然涉及线程切换;线程切换未必是进程切换。
  • 用户级线程的切换不需要内核介入,可在用户态完成;内核级线程的切换需要。
  • "同一进程内的线程切换不需要保存/恢复整个进程的地址空间"——正确。
  • 进程间通信的所有机制线程都能用之外,同进程线程还可直接读写共享全局变量通信(因此更需同步)。

三、处理机调度

3.1 三级调度

层级决定的事频率开销
高级调度(作业调度)外存后备队列的作业调入内存,创建进程(接纳谁)最低(分钟级)大
中级调度(内存调度)内存↔外存对换区:挂起(换出)与激活(换入),提高内存利用率与吞吐量中中
低级调度(进程调度)就绪队列中选进程分配 CPU最高(毫秒级),OS 中最基本、不可或缺的调度最小

3.2 调度时机、方式与切换过程

不能进行进程调度与切换的情况(高频多选,背清单):

  1. 处理中断/异常的过程中(中断处理不属于进程上下文)
  2. 进程在操作系统内核临界区中(如就绪队列本身正在被修改)
  3. 其他完全不可抢占的内核关键路径上

可以调度切换的时机归纳:当前进程主动放弃(阻塞/终止)、被动失去 CPU(时间片到、被抢占、更高优先级到达)。

调度方式:

  • 非抢占(不可剥夺):一旦 CPU 分给进程,直到完成或阻塞才让出。实现简单、开销小,但紧急任务无法及时响应。
  • 抢占(可剥夺):按某种原则(优先级/短作业/时间片)剥夺当前进程 CPU。可保证紧急任务及时响应,切换开销大。

进程切换的完整过程见 1.3 节切换原语。注意"调度 ≠ 切换":狭义调度只做"选进程",切换才是"换进程",切换包含上下文保存/恢复(纯开销,无有效计算)。

3.3 调度算法性能指标(计算题基础)

设作业 ii 到达时间 tarrt_{arr}、开始时间 tstartt_{start}、完成时间 tfinisht_{finish}、运行时间 trunt_{run}:

  • 周转时间 Ti=tfinish−tarrT_i = t_{finish} - t_{arr}(含等待)
  • 带权周转时间 Wi=Ti/trun≥1W_i = T_i / t_{run} \ge 1(越接近 1 越好)
  • 平均周转时间 Tˉ=1n∑Ti\bar T = \frac{1}{n}\sum T_i;平均带权周转时间 Wˉ=1n∑Wi\bar W = \frac{1}{n}\sum W_i
  • 等待时间 = 周转时间 − 运行时间(纯排队);响应时间 = 首次获得 CPU 时刻 − 到达时刻
  • 吞吐量 = 单位时间完成的作业数;CPU 利用率 = 忙碌时间 / 总时间

3.4 典型调度算法(必考,含计算套路)

算法规则可否抢占指标倾向主要缺点
FCFS 先来先服务按到达顺序,非抢占否公平、简单对短作业不利( convoy effect:短作业排在长作业后带权周转极大);不会饥饿
SJF 短作业优先每次选预计运行时间最短的;平均等待/周转时间最优(可证明)有抢占变体 SRTF(最短剩余时间优先)平均周转最优对长作业不利,可能饥饿(不断有短作业插队);运行时间靠估计,无法保证紧迫任务
优先级调度选优先级最高者;分静态/动态优先级均有紧迫任务优先饥饿(低优先级进程可能永不被执行);经典解法:老化 aging(等待越久优先级越高)。注意区分抢占式/非抢占式两种,以及优先级高低数值约定(408 通常数值小 = 优先级高)
高响应比优先 HRRN响应比 R=等待时间+要求服务时间要求服务时间R = \dfrac{\text{等待时间}+\text{要求服务时间}}{\text{要求服务时间}},每次调度时计算选最高否(非抢占)兼顾长短作业(FCFS 与 SJF 折中)每次调度要算响应比,开销
时间片轮转 RR就绪 FIFO,时间片到则排队尾是(时间片到强制)响应时间好,适合分时系统时间片太大退化为 FCFS;太小则切换开销占比过高
多级反馈队列 MLFQ多个优先级递减、时间片递增的队列(ii 级时间片常为 2i2^{i});新进程入队首,用完时间片降到下一级;最低级内轮转;高队列空才服务低队列是(高优先级可抢占)折中一切:终端型用户(短作业)满意、批处理型(长作业)不饥饿、紧迫任务及时实现复杂

SJF 最优性口径:在所有作业同时到达(或非抢占情形按给定到达序列局部最优)时,SJF 平均等待时间/平均周转时间最小——选择题直接考这个结论。

计算题标准流程(甘特图法):画时间轴甘特图 → 每个作业标完成时刻 → 逐个算 Ti,WiT_i, W_i → 求平均。RR 计算注意:按时间片边界推进,同刻到达与转回的入队次序按题面约定(一般新到达先入队、用完的排其后——以题面为准)。

例(模拟题):作业 1/2/3 到达时刻 0/1/2,运行时间 8/4/9。

  • FCFS 甘特图:1(0–8),2(8–12),3(12–21)。T=8+11+19=38T = 8 + 11 + 19 = 38,Tˉ≈12.67\bar T \approx 12.67。
  • 抢占式 SJF(SRTF):0–1 运行 1(剩 7);1 时刻 2 到达(4 < 7)抢占,1–5 运行 2;5–12 运行 1(完成于 12);12–21 运行 3。Tˉ=12+4+193≈11.67\bar T = \frac{12 + 4 + 19}{3} \approx 11.67,优于 FCFS。

四、同步与互斥

4.1 基本概念

  • 临界资源:一段时间内只允许一个进程使用的资源(打印机、共享变量等)。
  • 临界区:进程中访问临界资源的那段代码。进入临界区前申请(进入区),退出时释放(退出区)。
  • 同步:多个协作进程因工作次序需要而相互等待、传递信息("你先我后");互斥:多个进程因独占临界资源而相互排斥("一进一出")。互斥可视为特殊同步。
  • 同步机制四准则(必背):空闲让进、忙则等待、有限等待(不能饿死)、让权等待(进不去应释放 CPU,避免忙等——软件方法均不满足此条)。

4.2 互斥实现方法

软件方法(理解思想,选择考缺陷):

方法规则缺陷
单标志法轮流进入(turn 变量)必须交替进入:对方不进入则自己也无法再进入(违反空闲让进)
Peterson 算法表达意图(flag)+ 谦让(turn = 对方),双方都谦让时 turn 决定满足空闲让进/忙则等待/有限等待,但忙等,违反让权等待
双标志先检查先看对方 flag 再置自己 flag双方同时检查通过则同时进入(违背互斥)
双标志先修改先置 flag 再检查可能相互谦让都进不去(死锁式互等待)

硬件方法:

方法原理特点
中断屏蔽关中断实现原子性简单;不适用于多处理机(只关本核中断);滥用危险;用户态不能用(关中断是特权指令)
TestAndSet(TS/TSL)硬件原子指令 bool TS(bool *lock){ old=*lock; *lock=true; return old; } 进入区 while(TS(&lock));满足互斥;忙等(违反让权等待);多处理机可用
Swap(XCHG)原子交换寄存器与内存值,进入区 key=true; do{ swap(&lock,&key);}while(key);同 TS

硬件方法共同缺点:忙等待;且 TS/Swap 的 while 期间不释放 CPU。

4.3 信号量机制(PV 操作,大题核心)

整型信号量:整数 S,wait: while(S<=0); S--;,signal: S++;——仍忙等,不满足让权等待。

记录型信号量(考试默认):

typedef struct {
    int value;            // 资源数
    struct process *list; // 等待队列
} semaphore;

void wait(semaphore S) {      // P 操作
    S.value--;
    if (S.value < 0)
        block(S.list);        // 自我阻塞,入等待队列(让权等待)
}
void signal(semaphore S) {    // V 操作
    S.value++;
    if (S.value <= 0)
        wakeup(S.list);       // 唤醒一个等待进程
}

P/V 语义严格陈述(答题必背):

  • P(wait):S.value 减 1;若减后 S.value < 0,则该进程阻塞并进入该信号量的等待队列。
  • V(signal):S.value 加 1;若加后 S.value ≤ 0(说明仍有等待者),从等待队列唤醒一个进程。
  • S.value 的物理意义:S>0S > 0 表示当前可用资源数量;S<0S < 0 时 ∣S∣|S| 表示等待该资源的进程数;S=0S = 0 表示资源刚好用完且无等待。
  • P 操作的次序不可随意交换:先 P 互斥再 P 同步资源会造成死锁(见下文死锁陷阱);V 操作次序相对不敏感(不影响安全性,但影响唤醒时机)。
  • P、V 操作(对同一信号量)必须成对出现;P 可能阻塞、V 可能唤醒,二者本身均为原语。

4.4 用信号量实现互斥与同步(前驱关系)

  • 互斥:临界资源设 semaphore mutex = 1;,进入区 P(mutex),退出区 V(mutex)。
  • 同步(保证 a 操作先于 b 操作):semaphore S = 0; 先方做完 a 后 V(S),后方 b 前 P(S)。
  • 前驱关系图:每条边一个初值 0 的信号量,前操作完成后对所有出边 V,后操作开始前对所有入边 P。

4.5 经典同步问题(PV 大题模板,完整建模)

PV 大题解题模板(背下来套用):

  1. 定义信号量并说明每个的物理意义(互斥量初值 1;资源量初值 = 资源数;同步量初值 0)
  2. 写初值理由(一句话/个)
  3. 写伪代码:每个进程一个代码块,P/V 与动作交错,同一信号量的 P(mutex) 紧贴临界区
  4. 验证:检查 P 次序(先资源后互斥)、P/V 配对、V 的位置(完成动作后立即 V)

生产者-消费者(多类资源的 P 次序死锁陷阱)

缓冲区容量 nn,生产者放入,消费者取出,缓冲区是临界资源。

semaphore mutex = 1;   // 互斥访问缓冲区(初值 1:任一时刻仅一进程进出)
semaphore empty = n;   // 空闲缓冲区数(资源信号量,初值 n)
semaphore full  = 0;   // 产品数(同步信号量,初值 0:初始无产品)

producer() {
    while(true) {
        生产一个产品;
        P(empty);      // 申请空位(先资源)
        P(mutex);      // 再互斥(顺序绝不可反!)
        放入缓冲区;
        V(mutex);
        V(full);       // 产品数 +1,唤醒消费者
    }
}
consumer() {
    while(true) {
        P(full);       // 申请产品(先资源)
        P(mutex);      // 再互斥
        取出产品;
        V(mutex);
        V(empty);      // 释放空位,唤醒生产者
    }
}

死锁陷阱验证:若生产者写成 P(mutex); P(empty);——当缓冲区满(empty=0)时,生产者拿到 mutex 后阻塞在 P(empty);消费者想取产品却因拿不到 mutex 而阻塞——互相等待,死锁。故对资源的 P 必须在对互斥的 P 之前。V 操作的两个次序交换不会死锁(可自证)。

读者-写者(含写优先变体)

允许多读者同时读,写者与所有人互斥。

读优先(读者可无限插队,写者可能饥饿):

int count = 0;         // 正在读的读者数
semaphore mutex = 1;   // 保护 count 的互斥
semaphore rw = 1;      // 读写互斥(对文件)

writer() {
    while(true) {
        P(rw);         // 独占文件
        写;
        V(rw);
    }
}
reader() {
    while(true) {
        P(mutex);
        if (count == 0) P(rw);  // 第一个读者锁住文件(挡写者)
        count++;
        V(mutex);
        读;
        P(mutex);
        count--;
        if (count == 0) V(rw);  // 最后一个读者解锁
        V(mutex);
    }
}

关键点:第一个读者负责加锁、最后一个负责解锁,中间读者直接进出;count 的自增/自减必须在互斥下进行。

写优先(写优先变体:有写者等待时阻止新读者进入,避免写者饿死)——加一个 w = 1 的信号量控制"进入读的资格":

semaphore w = 1;       // 实现写优先(读者进入前要过这道门)
// writer:
P(w); P(rw); 写; V(rw); V(w);
// reader:
P(w);                 // 若有写者在等/在写,读者在此排队,不再插队
P(mutex);
if (count == 0) P(rw);
count++;
V(mutex);
V(w);                 // 读者进入后立即放行后续(此时已有读者在读)
读;
P(mutex);
count--;
if (count == 0) V(rw);
V(mutex);

(严格"读写公平"版本可再扩展,考试掌握上述两级即可。)

哲学家进餐(5 人 5 筷)

每支筷子一个信号量 chopstick[i] = 1。直接版(每人先拿左再拿右)存在全体同时拿左导致死锁的风险。正确解法任选其一:

  1. 至多允许 4 人同时拿筷:semaphore room = 4; 拿筷前 P(room),放筷后 V(room)
  2. 奇数先拿左、偶数先拿右(破坏"同向竞争")
  3. 一次性拿两支(对拿筷过程整体互斥:P(mutex); P(左); P(右); V(mutex);)
semaphore chopstick[5] = {1,1,1,1,1};
semaphore room = 4;                        // 解法 1
philosopher(i) {
    while(true) {
        思考;
        P(room);
        P(chopstick[i]);                   // 左
        P(chopstick[(i+1)%5]);             // 右
        进餐;
        V(chopstick[i]);
        V(chopstick[(i+1)%5]);
        V(room);
    }
}

死锁根因:五人同时各持一支、各等一支——四要素齐备(互斥、请求保持、不可剥夺、循环等待),解法即破坏其一。

吸烟者问题

供应者轮流在桌上放"两样材料",三个吸烟者各持有一种材料,凑齐者取走卷烟并抽(后示意供应者再放)。

semaphore offer1 = 0;  // 桌上放有 纸+胶水(持烟草者可抽)
semaphore offer2 = 0;  // 桌上放有 烟草+胶水(持纸者可抽)
semaphore offer3 = 0;  // 桌上放有 烟草+纸(持胶水者可抽)
semaphore finish = 0;  // 吸完一根(供供应者开始下一轮)
int r = 0;

provider() {
    while(true) {
        r = (r + 1) % 3;
        if (r == 0) V(offer1);
        else if (r == 1) V(offer2);
        else V(offer3);
        P(finish);     // 等吸烟者抽完再放下一组
    }
}
smoker1() { // 持烟草
    while(true) {
        P(offer1);
        取纸和胶水,卷烟,抽掉;
        V(finish);
    }
}
// smoker2、smoker3 对称

要点:供应者每次只放一组(offer 互斥由"只放一个"保证),用 finish 保证"一桌一组"。

五、死锁

5.1 死锁概念与四要素(必背)

死锁:多个进程因竞争资源而造成的一种互相等待的僵局,若无外力介入这些进程都无法推进。

产生死锁的四个必要条件(缺一不可,全背):

  1. 互斥:资源一次只能被一个进程使用
  2. 请求并保持(占有并等待):进程已保持至少一个资源,又提出新请求且被阻塞,但对已占资源保持不放
  3. 不可剥夺(不可抢占):资源只能由占有者用完自愿释放,不能被强行夺走
  4. 循环等待:存在进程-资源的循环等待链(注意:循环等待不一定死锁——若链外每类资源有多个实例、资源够分则不死锁;但死锁必有循环等待)

死锁、饥饿、活锁辨析:饥饿是进程长期得不到调度(不一定互等,如优先级调度下的低优先级进程);死锁必互等成环;活锁是进程不断改变状态却无进展。

5.2 死锁预防(静态,破坏四条件之一)

策略破坏条件做法缺点
破坏"请求并保持"2一次性申请全部所需资源(静态分配)资源利用率低,可能饥饿
破坏"不可剥夺"3申请不到就释放已占资源(或由 OS 强行剥夺)实现复杂、开销大,前功尽弃
破坏"循环等待"4资源顺序编号,按序申请(资源有序分配)编号难定、与实际使用顺序冲突而浪费

互斥条件一般不可破坏(临界资源本质),故预防不提破坏互斥。三个可破坏条件中,资源有序分配法最常考(判断"按序申请可预防死锁")。

5.3 死锁避免(动态):银行家算法(大题核心,完整流程)

数据结构(nn 个进程,mm 类资源):

  • Available(1×m1\times m):当前可用各类资源数
  • Max(n×mn\times m):各进程最大需求
  • Need(n×mn\times m)= Max − Allocation:尚需
  • Allocation(n×mn\times m):已分配
  • Request(n×mn\times m):本次请求向量

安全性检查算法(推演流程):

  1. 初始化:Work = Available;Finish[i] = false(∀i)
  2. 找一个满足 Finish[i] = false 且 Need_i ≤ Work 的进程 i(找不到则转 4)
  3. 假装放行:Work = Work + Allocation_i;Finish[i] = true;记下 i,回到 2
  4. 若所有 Finish[i] = true,则安全(记录的顺序即安全序列);否则不安全

请求处理流程(进程 PiP_i 请求资源 Request_i):

  1. Request_i ≤ Need_i?否则出错(超过声明的最大需求)
  2. Request_i ≤ Available?否则等待
  3. 试探分配:Available −= Request_i;Allocation_i += Request_i;Need_i −= Request_i
  4. 执行安全性检查:安全则正式分配;不安全则回滚(恢复三组数据),令 PiP_i 等待

表格化推演示例(完整示范,模拟题):系统有 A/B/C 三类资源,总量 (10, 5, 7),当前状态:

进程Max (A,B,C)Allocation (A,B,C)Need (A,B,C)
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)

Allocation 列和 = (7, 2, 5),故 Available = (10,5,7) − (7,2,5) = (3, 3, 2)。

安全性推演(Work 从 (3,3,2) 出发):

步选中判定条件Work 变化(执行后 Work += Allocation)完成后 Work
1P1Need(1,2,2) ≤ (3,3,2) ✓(3,3,2) + (2,0,0)(5,3,2)
2P3Need(0,1,1) ≤ (5,3,2) ✓(5,3,2) + (2,1,1)(7,4,3)
3P0Need(7,4,3) ≤ (7,4,3) ✓(7,4,3) + (0,1,0)(7,5,3)
4P2Need(6,0,0) ≤ (7,5,3) ✓(7,5,3) + (3,0,2)(10,5,5)
5P4Need(4,3,1) ≤ (10,5,5) ✓(10,5,5) + (0,0,2)(10,5,7)

全部 Finish = true,安全序列 P1→P3→P0→P2→P4(不唯一,任给一个合法序列即可)。

追加请求示例:若 P1 请求 (1, 0, 2):① (1,0,2) ≤ Need_1 (1,2,2) ✓;② ≤ Available (3,3,2) ✓;③ 试探分配后 Available = (2,3,0),Allocation_1 = (3,0,2),Need_1 = (0,2,0);④ 重做安全检查可找到序列(如 P1→P3→P0→P2→P4),故可以分配。若 P4 请求 (3,3,0):试探后 Available = (0,0,2),任何进程的 Need 均无法满足(如 P0 需 (7,4,3) > (0,0,2)),不存在安全序列,拒绝并回滚,P4 等待。

5.4 死锁检测与解除

资源分配图:圆点=进程,方框=资源类(框内圆点=实例),请求边(进程→资源)、分配边(资源→进程)。

化简(检测)步骤:1) 找一个非孤立且非阻塞(其所有请求都能满足)的进程 P,它可运行完并释放全部资源 → 2) 删去 P 的所有请求边与分配边(P 变孤立点)→ 3) 重复;若能消去所有边(图完全化简),则无死锁;否则不能化简的进程即死锁进程。

死锁解除:1) 资源剥夺:从其他进程抢资源给死锁进程(被抢者回退);2) 撤销进程:按代价(优先级/已运行时间/还要多久)逐个撤销死锁进程直至破环,可全部撤销;3) 进程回退:让进程退到足以避开死锁的检查点(需记录历史信息)。

死锁处理四种策略对比(选择送分):

策略时机代价备注
预防事前静态资源利用率低破坏四条件之一
避免事中动态(银行家)每次请求都要安全性检查需预知 Max
检测+解除事后死锁发生了才处理资源分配图化简
鸵鸟策略不处理—多数通用 OS(如 Linux)实际采用

例题

例题 1(模拟题) 下列进程状态转换中,不可能发生的是( )。 A. 运行→就绪 B. 阻塞→就绪 C. 就绪→阻塞 D. 运行→阻塞

解:C。就绪态进程未占有 CPU,谈不上请求 I/O 而阻塞;阻塞只能由运行态进入(主动请求等待),唤醒只能到就绪态。

例题 2(模拟题) 下列关于管道通信的说法,错误的是( )。 A. 管道是内存中的固定大小缓冲区 B. 管道通信是半双工的,双向通信需两个管道 C. 写满缓冲区后写进程阻塞 D. 任意两个进程间都可以用 pipe() 建立的管道通信

解:D。匿名管道基于 fork 的描述符继承,限亲缘进程;无亲缘进程需命名管道/FIFO。

例题 3(模拟题) 作业到达与运行时间如下,采用非抢占 SJF 与 FCFS 分别求平均周转时间:J1(0, 7)、J2(2, 4)、J3(4, 1)、J4(5, 4)。

解:FCFS 次序 J1(0–7)、J2(7–11)、J3(11–12)、J4(12–16);T=7,9,8,11T = 7, 9, 8, 11,Tˉ=8.75\bar T = 8.75。SJF(0 时刻只有 J1):J1(0–7);7 时刻就绪 J2/J3/J4,选最短 J3(7–8);再选 J2(8–12);J4(12–16);T=7,10,4,11T = 7, 10, 4, 11,Tˉ=8\bar T = 8,更优。

例题 4(模拟题) 面包师问题变式:桌上有一盘容量为 1 的水果,爸爸放苹果,妈妈放橘子,女儿只吃苹果,儿子只吃橘子。用信号量给出同步方案。

解:

semaphore plate = 1;   // 盘子空位(初值 1:盘可放一次)
semaphore apple = 0;   // 盘中苹果(初值 0:初始无苹果)
semaphore orange = 0;  // 盘中橘子(初值 0)

dad()   { while(true){ 准备苹果; P(plate); 放苹果; V(apple); } }
mom()   { while(true){ 准备橘子; P(plate); 放橘子; V(orange); } }
son()   { while(true){ P(orange); 取橘子; V(plate); 吃; } }
daughter(){ while(true){ P(apple); 取苹果; V(plate); 吃; } }

验证:盘容量 1 由 plate 保证互斥放置(放前 P、取后 V 成对);apple/orange 初值 0 保证"先放后取"次序;不存在"先占互斥再等资源"的次序问题(plate 即资源本身),无死锁。

例题 5(模拟题) 系统中有同类资源 12 个,进程 P1/P2/P3 各需最多 8/6/9 个,已分别持有 4/3/3 个。该状态是否安全?P3 再请求 2 个能否分配?

解:Available = 12 − 10 = 2。Need:P1 = 4,P2 = 3,P3 = 6。安全检查:P2 需 3 > 2 不行;P1 需 4 > 2 不行;P3 需 6 > 2 不行——无进程可满足,当前已不安全(实际此状态已死锁边缘:无人能完成)。若 P3 请求 2:试探后 Available = 0,更不安全,不能分配。(出题教训:先做安全检查判断当前态,再处理请求。)

常见坑

  1. P(mutex) 与 P(资源) 的次序:先资源后互斥,反了必死锁(生产者-消费者满缓冲场景);V 的次序不影响安全性。
  2. 阻塞→运行、就绪→阻塞不存在;阻塞进程由别人唤醒且唤醒后进就绪不是运行。
  3. 挂起 ≠ 阻塞:挂起是换出内存(内存维度的行为),阻塞是等待事件;静止阻塞收到事件后先回到(活动)阻塞/就绪,需先激活。
  4. 信号量 S<0S<0 时 ∣S∣|S| = 等待进程数;S>0S>0 = 可用资源数。P 后 S<0 才阻塞(减 1 后判负);V 后 S≤0 才唤醒(说明队列里有人)。
  5. 记录型信号量不存在忙等(block/wakeup 实现让权等待);TS/Swap/单标志/Peterson 都忙等。让权等待只有记录型信号量满足。
  6. 调度算法饥饿判定:SJF、优先级调度会饥饿;FCFS、RR、HRRN 不会饥饿;多级反馈队列若设计得当(最低级轮转)不饥饿。
  7. RR 时间片的两端极端(过大=FCFS、过小=切换开销大)是判断题常客;"时间片越小越好"必错。
  8. 循环等待 ≠ 死锁(每类资源单实例时才等价);死锁四条件是"必要"条件,破坏任一即可预防。
  9. 银行家算法三步:先查 Need、再查 Available、试探分配后必须重做全局安全检查;只看请求本身能否满足就分配是典型错误。
  10. 安全序列不唯一,答任意一个合法序列即可;"不存在安全序列"才是不安全状态(不安全 ≠ 已死锁,只是可能死锁)。
  11. 进程是资源分配单位、线程是调度单位(引入线程后);"线程切换必须进入内核态"对内核级线程成立,对用户级线程不成立。
  12. 管道:半双工、亲缘进程、写满/读空阻塞;"全双工""任意进程"都是错误选项。

自测

  1. PCB 中哪部分信息用于进程切换时的现场保存与恢复?进程控制原语为什么必须用关中断实现原子性?
  2. 画出五态图并说明每条边的触发者(自己/其他进程/OS)。
  3. 某系统采用 RR(时间片 2,同刻"新到达者先入队、用完片者排其后"),作业 A/B/C 到达时刻 0/1/3,运行时间 4/3/2,画甘特图并求平均周转时间。
  4. 生产者-消费者中若将生产者的 P(empty) 与 P(mutex) 交换,构造一个死锁场景并说明其满足了死锁四要素中哪些。
  5. 用银行家算法判断:系统有同类资源 9 个,P1/P2/P3 的 Max 分别为 7/5/4,已分配 3/2/2,当前是否安全?给出推演。

自测答案与简析

  1. 处理机上下文(PC、PSW、通用寄存器、栈指针)。原语期间若被中断打断,进程管理数据结构(如就绪队列)会处于不一致的中间状态,故关中断保证原子性。
  2. 五态图见 1.2 节 Mermaid 图;创建→就绪与运行→终止由 OS 触发;就绪→运行由调度(OS)触发;运行→就绪由时钟中断/抢占(OS,被动);运行→阻塞由进程自身主动;阻塞→就绪由其他进程 V 操作或 I/O 中断(他人)触发。
  3. 逐片推演:0 时刻队 [A],A 用 0–2(剩 2);2 时刻 B(1 时刻已入队)在前,A 归队尾 → [B, A],B 用 2–4(剩 1);3 时刻 C 入队 → [A, C];4 时刻 B 归队尾 → [A, C, B],A 用 4–6(剩 0,完成);6–8 C(完成);8–9 B(完成)。完成时刻:A=6、B=9、C=8。周转:TA=6T_A=6、TB=9−1=8T_B=9-1=8、TC=8−3=5T_C=8-3=5,Tˉ=6+8+53=193≈6.33\bar T=\frac{6+8+5}{3}=\frac{19}{3}\approx 6.33。做 RR 题必须逐片列队画甘特图,"同刻到达与归队者的先后"按题面约定执行。
  4. 缓冲区满(empty=0)时:生产者 P(mutex) 成功后再 P(empty) 阻塞(持 mutex 等空位);消费者 P(mutex) 阻塞(等 mutex)——满足互斥、请求并保持(生产者持 mutex 等 empty)、不可剥夺(mutex 不能强抢)、循环等待(生产者等消费者放空位、消费者等生产者放 mutex),四要素全部满足,死锁。
  5. Available = 9 − (3+2+2) = 2。Need:P1 = 7−3 = 4,P2 = 5−2 = 3,P3 = 4−2 = 2。推演:P3(需 2 ≤ 2)✓ → Work = 2+2 = 4;P1(需 4 ≤ 4)✓ → Work = 4+3 = 7;P2(需 3 ≤ 7)✓ → Work = 9。全部完成,安全,安全序列 P3→P1→P2(不唯一)。