四、进程
1、什么是进程
1.1、进程的概念

程序:是静态的,就是个存放在磁盘里的可执行文件,如:Tim.exe。
进程:是动态的,是程序的一次执行过程,如:可同时启动多次 Tim 程序
同一个程序多次执行会对应多个进程
1.2、进程的组成 ------PCB
当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的"身份证号"------PID(Process ID,进程 ID)
操作系统要记录 PID、进程所属用户 ID(UID)
基本的进程描述信息,可以让操作系统区分各个进程
还要记录给进程分配了哪些资源(如:分配了多少内存、正在使用哪些 I/O 设备、正在使用哪些文件)
可用于实现操作系统对资源的管理
还要记录进程的运行情况(如:CPU 使用时间、磁盘使用情况、网络流量使用情况等)
可用于实现操作系统对进程的控制、调度
这些信息都被保护在一个数据结构PCB (Process Control Block)中,即进程控制块 操作系统需要对各个并发运行的进程进行管理,但凡管理时所需要的信息,都会被放在 PCB 里

1.3、进程的组成 ------ 程序段、数据段


进程是资源分配的最小单位,线程是 CPU 调度的最小单位
1.4、进程的特征

2、进程的状态与转换
2.1、创建态、就绪态

进程正在被创建时,它的状态是 "创建态",在这个阶段操作系统会为进程分配资源、初始化 PCB
当进程创建完成后,便进入 "就绪态",处于就绪态的进程已经具备运行条件,但由于没有空闲 CPU,就暂时不能运行
2.2、运行态

如果一个进程此时在 CPU 上运行,那么这个进程处于 "运行态"。
CPU 会执行该进程对应的程序(执行指令序列)
2.3、阻塞态

在进程运行的过程中,可能会
请求等待某个事件的发生
(如等待某种系统资源的分配,或者等待其他进程的响应)。
在这个事件发生之前,进程无法继续往下执行,此时操作系统会让这个进程下 CPU,并让它进入 "阻塞态"
当 CPU 空闲时,又会选择另一个 "就绪态" 进程上 CPU 运行
2.4、终止态

一个进程可以执行 exit 系统调用,请求操作系统终止该进程。此时该进程会进入 "终止态",操作系统会让该进程下 CPU,并回收内存空间等资源,最后还要回收该进程的 PCB。
当终止进程的工作完成之后,这个进程就彻底消失了。
2.5、进程的状态
进程的整个生命周期中,大部分都处于三种基本状态

但 CPU 情况下,同一时刻只会有一个进程处于运行态,多核 CPU 情况下,可能会有多个进程处于运行态
** 进程 PCB 中,会有一个变量 state 来表示进程的当前状态。** 如:1 表示创建态、2 表示就绪态、3 表示运行态...
为了对同一个状态下的各个进程进行统一的管理,操作系统会将各个进程的 PCB 组织起来。
2.6、进程状态的转换
三态模型

五态模型

3、进程组织
3.1、链接方式


3.2、索引方式

3.3、总结

4、进程控制
4.1、什么是进程控制?
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创新新进程、撤销已有进程、实现进程状态转换等功能。
简化理解:反正进程控制就是要实现进程状态转换

4.2、如何实现进程控制?
用 "原语" 实现

Q1:为什么要用 "原语" 实现?
答:原语的执行具有" 原子性 ",一气呵成
Q2:为何进程控制(状态转换)的过程要" 一气呵成 "?
答:如果不能" 一气呵成 ",就有可能导致操作系统中的某些关键数据结构信息不统一的情况,这会影响操作系统进行别的管理工作
Eg:假设 PCB 中的变量 state 表示进程当前所处状态,1 表示就绪态,2 表示阻塞态...

假设此时进程 2 等待的事件发生,则操作系统中,负责进程控制的内核程序至少需要做这样两件事:
-
将 PCB2 的 state 设为 1
-
将 PCB2 从阻塞队列放到就绪队列
完成了第一步后收到中断信号,那么 PCB2 的 state=1,但是它却被放在阻塞队列里
4.3、如何实现原语的" 原子性 "?
原语 的执行具有原子性 ,即执行过程只能一气呵成,期间不允许被中断。
可以用"关中断 指令 "和"开中断 指令 "这两个特权指令 实现原子性

正常情况:CPU 每执行完一条指令都会例行检查是否有中断信号需要处理,如果有,则暂停运行当前这段程序,转而执行相应的中断处理程序。
CPU 执行了关中断指令
之后,就不再例行检查中断信号,直到执行
开中断指令
之后才会恢复检查。
这样,关中断、开中断之间的这些指令序列就是不可被中断的,这就实现了" 原子性 "
思考:如果这两个特权指令允许用户程序使用的话,会发生什么情况?
4.4、进程控制的原语





无论哪个进程控制原语,要做的无非三类事情:
-
更新 PCB 中的信息 (修改进程状态 (state) 保存 / 恢复运行环境)
-
将 PCB 插入合适的队列
-
分配 / 回收资源
学习技巧:进程控制会导致进程状态的转换。无论哪个进程控制原语,要做的无非三类事情:
- 更新 PCB 中的信息
-
所有的进程控制原语一定都会修改进程状态标志
-
剥夺当前运行进程的 CPU 使用权必然需要保存其运行环境
-
某进程开始运行前必然要恢复期运行环境
-
将 PCB 插入合适的队列
-
分配 / 回收资源
5、进程调度
5.1、处理机调度 ------ 调度的基本概念
举个调度的例子:
银行处理用户服务时,普通用户先来先服务;VIP 用户可优先被服务
当有一堆任务要处理,但由于资源有限,这些事情没法同时处理。这就需要确定某种规则 来决定 处理这些任务的顺序,这就是 "调度" 研究的问题。
5.2、进程调度的时机
进程调度(低级调度),就是按照某种算法从就绪队列中选择一个进程为其分配处理机。

有的系统中,只允许进程主动放弃处理机
有的系统中,进程可以主动放弃处理机,当有更加紧急的任务需要处理时,也会强行剥夺处理机(被动放弃)

但是进程在普通临界区中是可以进行调度、切换的。
进程在操作系统内核程序临界区 中不能进行调度与切换(√)
(2012 年联考真题)进程处于临界区 时不能进行处理机调度(×)
临界资源:一个时间段内只允许一个进程使用的资源。各进程需要互斥地访问临界资源。
临界区:访问临界资源的那段代码。
内核程序临界区 一般是用来访问某种内核数据结构的,比如进程的就绪队列(由各就绪进程的 PCB 组成)




5.3、进程调度的方式
非剥夺调度方式 ,又称非抢占方式。即,只允许进程主动放弃处理机。在运行过程中即使有更紧迫的任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态。
实现简单,系统开销小但是无法及时处理紧急任务,适合于早期的批处理系统
剥夺调度方式 ,又称抢占方式。当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给更重要紧迫的那个进程。
可以优先处理更紧迫的进程,也可实现让各进程按时间片轮流执行的功能(通过时钟中断)。适合于分时操作系统、实时操作系统
5.4、进程的切换与过程
"狭义的进程调度" 与 "进程切换 "的区别:
狭义的进程调度 指的是从就绪队列中选中一个要进行的进程 。(这个进程可以是刚刚被暂停执行的进程,也可能是另一个进程 ,后一种情况就需要进程切换)
进程切换是指一个进程让出处理机,由另一个进程占用处理机的过程。
广义的进程调度包含了选择一个进程和进程切换两个步骤。
进程切换的过程主要完成了:
-
对原来运行进程各种数据的保存
-
对新的进程各种数据的恢复
(如:程序计数器、程序状态字、各种数据寄存器等处理机现场信息,这些信息一般保存在进程控制块)
注意:进程切换是有代价的 ,因此如果过于频繁 的进行进程调度、切换 ,必然会使整个系统的效率降低,使系统大部分时间都花在进程切换上,而真正用于执行进程的时间减少。
5.5、调度算法的评价指标
5.5.1、CPU 利用率
由于早期的 CPU 造价及其昂贵,因此人们会希望让 CPU 尽可能地工作
**CPU 利用率:** 指 CPU "忙碌" 的时间总时间的比例。
利用率=忙碌的时间总时间利用率=\frac{忙碌的时间}{总时间}利用率=总时间忙碌的时间
eg. 某计算机只支持单道程序,某个作业刚开始需要在 CPU 上运行 5 秒,再用打印机打印输出 5 秒,之后再执行 5 秒,才能结束。在此过程中,CPU 利用率、打印机利用率分别是多少?
CPU利用率=5+55+5+5=66.66%CPU利用率=\frac{5+5}{5+5+5}=66.66\%CPU利用率=5+5+55+5=66.66%
打印机利用率=515=33.33%打印机利用率=\frac{5}{15}=33.33\%打印机利用率=155=33.33%
5.5.2、系统吞吐量
对于计算机来说,希望能用尽可能少的时间处理完尽可能多的作业
系统吞吐量:单位时间内完成作业的数量
系统吞吐量=总共完成了多少道作业总共花了多少时间系统吞吐量=\frac{总共完成了多少道作业}{总共花了多少时间}系统吞吐量=总共花了多少时间总共完成了多少道作业
eg. 某计算机系统处理完 10 道作业,共花费 100 秒,则系统吞吐量为?
10/100=0.1道/秒10/100=0.1道/秒10/100=0.1道/秒
5.5.3、周转时间
对于计算机的用户来说,他很关心自己的作业从提交到完成花了多少时间。
周转时间 ,是指从作业被提交给系统开始 ,到作业完成为止的这段时间间隔。
它包括四个部分:作业在外存后备队列上等待作业调度(高级调度)的时间、进程在就绪队列上等待进程调度(低级调度)的时间、进程在 CPU 上执行的时间、进程等待 I/O 操作完成的时间。后三项在一个作业的整个处理过程中,可能发生多次。
(作业)平均周转时间=作业完成时间−作业提交时间(作业)平均周转时间=作业完成时间-作业提交时间(作业)平均周转时间=作业完成时间−作业提交时间
对于用户来说,更关心自己的单个作业的周转时间
平均周转时间=各作业周转时间之和作业数平均周转时间=\frac{各作业周转时间之和}{作业数}平均周转时间=作业数各作业周转时间之和
对于操作系统来说,更关心操作系统整体表现,因此更关心所有作业周转时间的平均值
5.5.4、带权周转时间
带权周转时间=作业周转时间作业实际运行的时间=作业完成时间−作业提交时间作业实际运行的时间带权周转时间=\frac{作业周转时间}{作业实际运行的时间}=\frac{作业完成时间-作业提交时间}{作业实际运行的时间}带权周转时间=作业实际运行的时间作业周转时间=作业实际运行的时间作业完成时间−作业提交时间
带权周转时间必然≥1。带权周转时间与周转时间都是越小越好
平均带权周转时间=各作业周转时间之和作业数平均带权周转时间=\frac{各作业周转时间之和}{作业数}平均带权周转时间=作业数各作业周转时间之和
对于周转时间相同的两个作业,实际运行时间长的作业在相同时间内被服务的时间更多,带权周转时间更小,用户满意度更高。
对于实际运行时间相同的两个作业,周转时间短的带权周转时间更小,用户满意度更高。
5.5.5、等待时间
计算机的用户希望自己的作业尽可能少的等待处理机
等待时间 ,指进程 / 作业处于等待处理机状态时间之和,等待时间越长,用户满意度越低。

对于进程 来说,等待时间就是指进程建立后等待被服务的时间之和,在等待 I/O 完成的期间其实进程也是在被服务的,所以不计入等待时间。
对于作业 来说,不仅要考虑建立进程后的等待时间,还要加上作业在外存后备队列中等待的时间。
一个作业总共需要被 CPU 服务多久,被 I/O 设备服务多久一般是确定不变的,因此调度算法其实只会影响作业 / 进程的等待时间。当然,与前面指标类似,也有 "平均等待时间" 来评价整体性能。
5.5.6、响应时间
对于计算机用户来说,会希望自己的提交的请求(比如通过键盘输入了一个调试命令)尽早地开始被系统服务、回应。
响应时间 ,指从用户提交请求 到首次产生响应所用的时间。
5.6、调度算法
5.6.1、先来先服务(FCFS,First Come First Serve)

优点:实现简单;
缺点:对长作业有利,对短作业不利;平均周转时间可能较长,没有考虑任务的紧迫性

5.6.2、短作业优先(SJF,Shortest Job First)
算法分类:
-
非抢占式调度算法:短进程优先调度算法(SPF)
-
抢占式调度算法:最短剩余时间优先算法(SRTN)
非抢占式调度算法:短进程优先调度算法(SPF)

抢占式调度算法:最短剩余时间优先算法(SRTN)

注意几个小细节:
-
如果题目中未特别说明 ,所提到的 "短作业 / 进程优先算法"默认 是非抢占式的
-
很多书上都会说 "SJF 调度算法的平均等待时间、平均周转时间最少"
严格来说,这个表述是错误的,不严谨的。之前的例子表明,最短剩余时间优先算法得到的平均等待时间、平均周转时间还要更少
应该加上一个条件 "在所有进程同时可运行 时,采用 SJF 调度算法的平均等待时间、平均周转时间最少";如果不加上述提前条件、则应该说 "抢占式的 短作业 / 进程优先调度算法(最短剩余时间优先、SRNT算法)的平均等待时间、平均周转时间最少 "
-
虽然严格来说,SJF 的平均等待时间、平均周转时间并不一定最少,但相比于其他算法(如 FCFS),SJF 依然可以获得较少的平均等待时间、平均周转时间
-
如果选择题中遇到"SJF 算法的平均等待时间、平均周转时间最少 "的选项,那最好判断其他选项是不是有很明显的错误,如果没有更合适的选项,那也应该选择该选项

5.6.3、高响应比优先(HRRN)
响应比=要求服务时间+等待时间要求服务时间=1+等待时间要求服务时间响应比=\frac{要求服务时间+等待时间}{要求服务时间}=1+\frac{等待时间}{要求服务时间}响应比=要求服务时间要求服务时间+等待时间=1+要求服务时间等待时间


5.6.4、时间片轮转(RR,Round-Robin)

时间片大小的确定:
N 为就绪队列中进程数,T 为系统响应时间,q 为时间片
T=NqT=NqT=Nq
影响因素:
-
系统的响应时间
-
就绪进程的数量
-
进程调度及切换开销
-
CPU 的运行速度

5.6.5、优先级调度算法
-
优先级进程调度算法的类型:通常用一个整数表示优先级
-
非强占式优先级调度算法
-
强占式优先级调度算法
-
-
优先级的设计方法:
-
静态优先级:进程创建时确定其优先级,整个生命周期中不改变。确定依据:进程类型;进程对资源的需求;进程的估计运行时间
-
动态优先级:进程创建时赋一个优先级初值,运行期间动态调整其权值。
-
例:有 5 个进程 P1、P2、P3、P4、P5,它们同时依次进入就绪队列,其静态优先级和需要的处理机时间如下所示,采用非抢占式的调度方式,给出调度顺序,并计算平均周转时间。

补充:
就绪队列未必只有一个,可以按照不同优先级来组织。另外,也可以把优先级高的进程排在更靠近队头的位置
根据优先级是否可以动态改变,可将优先级分为静态优先级 和动态优先级两种。
静态优先级:创建进程时确定,之后一直不变。
动态优先级:创建进程时有一个初始值,之后会根据情况动态地调整优先级。
通常:
-
系统进程优先级高于用户进程
-
前台进程优先级高于后台进程
-
操作系统更偏好 I/O 型进程(或称 I/O 繁忙型进程)
I/O 设备和 CPU 可以并行工作。如果优先让 I/O 繁忙型进程优先运行的话,则越有可能让 I/O 设备尽早地投入工作,则资源利用率、系统吞吐量都会得到提升
注:与 I/O 型进程相对的是计算型进程(或称 CPU 繁忙型进程)
可以从追求公平、提升资源利用率等角度考虑
-
如果某进程在就绪队列中等待了很长时间,则可以适当提升其优先级
-
如果某进程占用处理机运行了很长时间,则可适当降低其优先级
-
如果发现一个进程频繁地进行 I/O 操作,则可适当提升其优先级

5.6.6、多级反馈队列调度算法
系统中设置多个就绪队列,每个队列优先级不同;
每个队列有自己独立的进程调度算法;
一个进程依据其属性固定位于某个就绪队列中。

按调度级别(优先级)设置多个就绪进程队列
按优先级(或就绪队列)设置不同时间片
各级就绪队列按 FCFS 组织,按时间片调度,每个进程被调度后运行一个当前队列的时间片长度
最后一级按时间片轮转方式组织调度
各队列间按抢占式优先级算法调度


6、进程同步、进程互斥
6.1、同步与互斥的概念
6.1.1、什么是进程同步?
知识点回顾:进程具有异步性的特征。异步性是指,各并发执行的进程以各自独立的、不可预知的速度向前推进。
操作系统要提供 "进程同步机制" 来解决异步问题

6.1.2、什么是进程互斥?
进程的 "并发" 需要 "共享" 的支持。各个并发执行的进程不可避免的需要共享一些系统资源(比如内存,又比如打印机、摄像头这样的 I/O 设备)

我们把一个时间段内只允许一个进程使用 的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。
对临界资源的访问,必须互斥 地进行。互斥,亦称间接制约关系 。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。
对临界资源的互斥访问,可以在逻辑上分为如下四个部分:

注意:
临界区 是进程中访问临界资源的代码段。
进入区 和退出区 是负责实现互斥的代码段。
临界区也可称为 "临界段"。
为了实现对临界资源的互斥访问,同时保证系统整体性能,需要遵循以下原则:
-
空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区;
-
忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待;
-
有限等待。对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿);
-
让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。

6.1.3、进程互斥的软件实现方法
6.1.3.1、单标记法
两个进程 P0,P1 共享某临界资源
设立一个公用整形变量 turn,描述允许进入临界区的进程标识,假设初始化 turn=0,表示首先轮到 P0 访问临界资源

6.1.3.2、双标志先检查法
两个进程 P0,P1 共享某临界资源
设立一个标志数组 flag [2]:描述进程是否已在临界区,初值均为 0(FALSE),表示进程都不在临界区。

6.1.3.3、双标志后检查法
两个进程 P0,P1 共享某临界资源:
设立一个标志数组 flag [2]:描述进程是否已在临界区,初值均为 0 (FALSE),表示进程都不在临界区。

6.1.3.4、Peterson 算法
两个进程 P0,P1 共享某临界资源
设立一个标志数组 flag [2];描述进程是否希望进入临界区,初值均为 0 (FALSE),表示进程都不希望进入临界区。int turn=0,表示首先轮到 P0 进入临界区。

6.1.4、进程互斥的硬件实现方式
6.1.4.1、TestAndSet 指令
简称 TS 指令,也有地方称为 TestAndSetLock 指令,或 TSL 指令
TSL 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用 C 语言描述的逻辑

若刚开始 lock 是 false,则 TSL 返回的 old 值为 false,while 循环条件不满足,直接跳过循环,进入临界区。若刚开始 lock 是 true,则执行 TLS 后 old 返回的值为 true,while 循环条件满足,会一直循环,直到当前访问临界区的进程在退出区进行 "解锁"。
相比软件实现方式,TSL 指令把 "上锁" 和 "检查" 操作用硬件的方式变成了一气呵成的原子操作。
优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
缺点:不满足 "让权等待" 原则,暂时无法进入临界区的进程会占用 CPU 并循环执行 TSL 指令,从而导致 "忙等"。
6.1.4.2、Swap 指令
有的地方也叫 Exchange 指令,或简称 XCHG 指令。
Swap 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用 C 语言描述的逻辑

逻辑上来看 Swap 和 TSL 并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在 old 变量上),再将上锁标记 lock 设置为 true,最后检查 old,如果 old 为 false 则说明之前没有别的进程对临界区上锁,则可以跳出循环,进入临界区。
优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
缺点:不满足 "让权等待" 原则,暂时无法进入临界区的进程会占用 CPU 并循环执行 TSL 指令,从而导致" 忙等 "。
6.1.4.3、中断屏蔽方法
利用" 开 / 关中断指令 "实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)

优点:简单、高效
缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程(因为开 / 关中断指令只能运行在内核态,这组指令只能让用户随意使用会很危险)
6.2、信号量机制
用户进程可以通过使用操作系统提供的一对原语 来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
信号量 其实就是一个变量,可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为 1 的信号量。
原语 是一种特殊的程序段,其执行只能一气呵成,不可被中断 。原语是由关中断 / 开中断指令实现的。软件解决方案的主要问题是由 "进入区的各种操作无法一气呵成",因此如果能把进入区、退出区的操作都用 "原语" 实现,使这些操作能 "一气呵成" 就能避免问题。
一对原语:wait (S)原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名分别为 wait 和 signal,括号里的信号量 S其实就是函数调用时传入的一个参数。
wait、signal 原语常简称为 P、V 操作 (来自荷兰语 proberen 和 verhogen)。因此,做题的时候常把 wait (S)、signal (S) 两个操作分别写为P(S)、V(S)
6.2.1、整形信号量
用一个整数型的变量 作为信号量,用来表示系统中某种资源的数量。
eg:某计算机系统中有一台打印机...
与普通整数变量的区别:对信号量的操作只有三种,即初始化、P 操作、V 操作

"检查" 和 "上锁" 一气呵成,避免了并发、一部导致的问题
存在的问题:不满足 "让权等待" 原则,会发生 "忙等"

6.2.2、记录型信号量
整型信号量的缺陷是存在 "忙等" 问题,因此人们又提出了 "记录型信号量",即用记录型数据结构表示的信号量。

如果剩余资源数不够,使用 block 原语使进程从运行态进入阻塞态,并把挂到信号量 S 的等待队列(即阻塞队列)中
释放资源后,若还有别的进程在等待这种资源,则使用 wakeup 原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态
在考研题目中 wait (S)、signal (S) 也可以记为 P (S)、V (S),这对原语可用于实现系统资源的 "申请" 和 "释放"。
S.value 的初值 表示系统中某种资源的数目。
对信号量 S 的一次 P 操作 意味着进程请求一个单位的该类资源 ,因此需要执行 S.value--,表示资源数减 1,当 S.value<0 时表示该类资源已分配完毕,因此进程应调用 block 原语进行自我阻塞 (当前运行的进程从运行态 -> 阻塞态 ),主动放弃处理机,并插入该类资源的等待队列 S.L 中。可见,该机制遵循了 "让权等待" 原则,不会出现 "忙等" 现象。
对信号量 S 的一次 V 操作 意味着进程释放一个单位的该类资源 ,因此需要执行 S.value++,表示资源数加 1,若加 1 后仍是 S.value<=0,表示依然有进程在等待该类资源,因此应调用 wakeup 原语唤醒等待队列中的第一个进程 (被唤醒进程从阻塞态 -> 就绪态)
6.2.3、用信号量实现进程互斥

-
分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
-
设置互斥信号量 mutex,初值为 1
-
在进入区 P (mutex)------申请资源
-
在退出区 V (mutex)------ 释放资源
注意:对不同的临界资源 需要设置不同的互斥信号量 。P、V 操作必须成对出现。缺少 P (mutex) 就不能保证临界资源的互斥访问。缺少 V (mutex) 会导致资源用不被释放,等待进程永不被唤醒。

6.2.4、用信号量实现进程同步
用信号量实现进程同步:
-
分析什么地方需要实现 "同步关系 ",即必须保证" 一前一后 "执行的两个操作(或两句代码)
-
设置同步信号量 S,初始为 0
-
在" 前操作 "之后执行 V (S)
-
在" 后操作 "之前执行 P (S)
技巧口诀:前 V 后 P

保证了代码 4 一定是在代码 2 之后执行
若先执行到 V (S) 操纵,则 S++ 后 S=1。之后当执行到 P (S) 操作时,由于 S=1,表示有可用资源,会执行 S--,S 的值变回 0,P2 进程不会执行 block 原语,而是继续往下执行代码 4。
理解:信号量 S 代表" 某种资源 ",刚开始是没有这种资源的。P2 需要使用这种资源,而又只能由 P1 产生这种资源
若先执行到 P (S) 操作,由于 S=0,S-- 后 S=-1,表示此时没有可用资源,因此 P 操作中会执行 block 原语,主动请求阻塞。之后当执行完代码 2,继而执行 V (S) 操作,S++,使 S 变回 0,由于此时有进程在该信号量对应的阻塞队列中,因此会在 V 操作中执行 wakeup 原语,唤醒 P2 进程。这样 P2 就可以继续执行代码 4 了
6.2.5、用信号量实现前驱关系
进程 P1 中有句代码 S1,P2 中有句代码 S2,P3 中有句代码 S3...P6 中有句代码 S6。这些代码要求按如下前驱图所示的顺序来执行:
其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作)
因此,
-
要为每一对前驱关系个设置一个同步信号量
-
在" 前操作 "之后对相应的同步信号量执行 V 操作
-
在" 后操作 "之前对相应的同步信号量执行 P 操作


6.3、前驱关系
前驱关系 是进程同步中用于约束执行顺序的一种单向先后制约关系 :若进程 A 与进程 B 存在前驱关系 A→B,则规定进程 A 必须全部执行完成后,进程 B 才能开始执行,二者不可颠倒、不可交叉执行,以此保证多个进程按预设逻辑有序运行,避免因执行乱序引发错误。
7、经典进程同步问题
7.1、生产者 - 消费者问题
问题描述:
若干进程通过有限的共享缓冲区 交换数据。其中,"生产者 " 进程不断写入,而 "消费者 " 进程不断读出;共享缓冲区共有 K 个;任何时刻只能有一个进程可对共享缓冲区进行操作。

分析:
-
确定进程:进程数量及工作内容;
-
确定进程间的关系:
-
互斥:多个进程间互斥使用同一个缓冲池;
-
同步:
-
当缓冲池空时,消费者必须阻塞等待;
-
当缓冲池满时,生产者必须阻塞等待。
-
-
-
设置信号量:
-
mutex:用于访问缓冲池时的互斥,初值是 1
-
full:"满缓冲" 数目,初值为 0;
-
empty:"空缓冲" 数目,初值为 K。full+empty=K
-
算法描述:


若此时缓冲区内已经放满产品,则 empty=0,full=n。
则生产者进程执行①使 mutex 变为 0,再执行②,由于已没有空间缓冲区,因此产生者被阻塞。
由于产生者阻塞,因此切换回消费者进程。消费者进程执行③,由于 mutex 为 0,即产生者还没释放对临界资源的 "锁",因此消费者也被阻塞。
这就造成了生产者等待消费者释放空间缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现 "死锁"。
同样的,若缓冲区中没有产品,即 full=0,empty=n。按③④①的顺序执行就会产生死锁。
因此,实现互斥的 P 操作一定要在实现同步的 P 操作之后。V 操作不会导致进程阻塞,因此两个 V 操作顺序可以交换。
7.2、多生产者 - 多消费者问题
问题描述:
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。
用 PV 操作实现上述过程。

-
关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
-
整理思路。根据各进程的操作流程确定 P、V 操作的大致顺序。
-
设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为 1,同步信号量的初始值要看对应资源的初始值是多少)

互斥关系:(mutex=1)
对缓冲区(盘子)的访问要互斥地进行
同步关系(一前一后):
-
父亲将苹果放入盘子后,女儿才能取苹果
-
母亲将橘子放入盘子后,儿子才能取橘子
-
只有盘子为空 时,父亲或母亲才能放入水果
" 盘子为空 "这个事件可以由儿子或女儿触发,事件发生后才允许父亲或母亲放苹果
算法描述:


分析:刚开始,儿子、女儿进程即使上处理机运行也会被阻塞。如果刚开始是父亲进程先上处理机运行,则:父亲 P (plate),可以访问盘子 -> 母亲 P (plate),阻塞等待盘子 -> 父亲放入苹果 V (apple),女儿进程被唤醒,其他进程即使运行也都会阻塞,暂时不可能访问临界资源(盘子)-> 女儿 P (apple),访问盘子,V (plate),等待盘子的母亲进程被唤醒 -> 母亲进程访问盘子(其他进程暂时都无法进入临界区)
原因在于:本题中的缓冲区大小为 1,在任何时刻,apple,orange,plate 三个同步信号量中最多只有一个是 1。因此在任何时刻,最多只有一个进程的 P 操作不会被阻塞,并顺利地进入临界区,,,
结论:即使不设置专门的互斥变量 mutex,也不会出现多个进程同时访问盘子的现象

父亲 P (plate),可以访问盘子 -> 母亲 P (plate),可以访问盘子 -> 父亲在往盘子里放苹果,同时母亲也可以往盘子里放橘子。于是就出现了两个进程访问缓冲区的情况,有可能导致两个进程写入缓冲区的数据相互覆盖的情况。
因此,如果缓冲区大小大于 1,就必须专门设置一个互斥信号量 mutex 来保证互斥访问缓冲区。
7.3、吸烟者问题
问题描述:
假设一个系统有三个抽烟者进程 和一个供应者进程 。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽调一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水 。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它 ,并给供应者进程一个信号告诉完成了 ,供应者就会放另外两种材料在桌子上,这个过程一直重复(让三个抽烟者轮流地抽烟)
本质上这题也属于 "生产者 - 消费者" 问题,更详细的说应该是 "可生产多种产品的单生产者 - 多消费者"。
-
关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
-
整理思路。根据各进程的操作流程确定 P、V 操作的大数顺序
-
设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为 1,同步信号量的初始值要看对应资源的初始值是多少)

桌子可以抽象为容量为 1 的缓冲区,要互斥访问
组合一:纸 + 胶水
组合二:烟草 + 胶水
组合三:烟草 + 纸
同步关系(从事件的角度来分析):
桌子上有组合一→第一个抽烟者取走东西
桌子上有组合二→第二个抽烟者取走东西
桌子上有组合三→第三个抽烟者取走东西
发出完成信号→供应者将下一个组合放到桌子上
PV 操作顺序:"前 V 后 P"

算法描述:

7.4、读者写者问题
在读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作 ;②只允许一个写者往文件中写信息 ;③任一写者在完成写操作之前不允许其他读者或写者工作 ;④写者执行写操作前,应让已有的读者和写者全部退出。

-
关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
-
整理思路。根据各进程的操作流程确定 P、V 操作的大致顺序
-
设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为 1,同步信号量的初始值要看对应资源的初始值是多少)
两类进程:写进程、读进程
互斥关系:写进程 --- 写进程、写进程 --- 读进程。读进程与读进程不存在互斥问题。
算法描述:
思考:若两个读进程并发执行,则 count=0 时两个进程也许都能满足 if 条件,都会执行 P (rw),从而使第二个读进程阻塞的情况。
如何解决:出现上诉问题的原因在于对 count 变量的检查和赋值无法一起何成,因此可以设置另一个互斥信号量来保证各读进程对 count 的访问是互斥的。

实现" 写优先 "(读写公平法):

分析以下并发执行 P (w) 的情况:
读者 1 -> 读者 2
写者 1 -> 写者 2
写者 1 -> 读者 1
读者 1 -> 写者 1 -> 读者 2
写者 1 -> 读者 1 -> 写者 2
结论:在这种算法中,连续进入的多个读者可以同时读文件;写者和其他进程不能同时访问文件;写者不会饥饿,但也并不是真正的 "写优先",而是相对公平的先来先服务原则。
有的书上把这种算法称为 "读写公平法"。
7.5、哲学家进餐问题
一张圆桌上坐着 5 名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以进餐,当进餐完毕后,放下筷子继续思考。

-
关系分析。系统中有 5 个哲学家进程,哲学家与左右邻居对中间筷子的访问是互斥关系。
-
整理思路。这个问题中只有互斥关系,但与之前遇到的问题不同的事,每个哲学家进程需要同时持有两个临界资源才能开始吃饭。如何避免 临界资源分配不当造成的死锁现象,是哲学家问题的精髓。
-
信号量设置。定义互斥信号量数组
chopstick [5]={1,1,1,1,1} 用于实现对 5 个筷子的互斥访问。并对哲学家按 0~4 编号,哲学家 i 左边的筷子编号为 i,右边的筷子编号为 (i+1)%5

Q:如何防止死锁的发生呢?
A:
-
可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家去拿筷子。这样可以保证至少有一个哲学家是可以拿到左右两只筷子的
-
要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一只筷子,另一个会直接阻塞。这就避免了占有一支后再等待另一只的情况。
-
仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。

各哲学家拿筷子这件事必须互斥的执行。这就保证了即使一个哲学家在拿筷子拿到一半时被阻塞,也不会有别的哲学家会继续尝试拿筷子。这样的话,当前在吃饭的哲学家放下筷子后,被阻塞的哲学家就可以获得等待的筷子了。
8、死锁问题
8.1、死锁、饥饿、死循环的区别
死锁:各进程互相等等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程 "饥饿"。
死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑 bug 导致的,有时是程序员故意设计的。
| 区别 | |
|---|---|
| 死锁 | 死锁一定是 "循环等待对方手里的资源" 导致的,因此如果有死锁现象,那至少有两个或两个以上的进程同时发生死锁。另外,发生死锁的进程一定处于阻塞态。 |
| 饥饿 | ** 可能只有一个进程发生饥饿。** 发生饥饿的进程极可能是阻塞态(如长期得不到需要的 I/O 设备),也可能是就绪态(长期得不到处理机) |
| 死循环 | 可能只有一个进程发生死循环。死循环的进程可以上处理机运行(可以是运行态),只不过无法像期待的那样顺利推进。死锁和饥饿问题是由于操作系统分配资源的策略不合理导致的,而死循环是由代码逻辑的错误导致的。死锁和饥饿是管理者(操作系统)的问题,死循环是被管理者的问题。 |
共同点:都是进程无法顺利向前推进的现象(故意设计的死循环除外)
8.2、死锁产生的必要条件
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
** 互斥条件:** 只要对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备)。像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这种资源)。
** 不剥夺条件:** 进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
请求和保持条件:进程已将保持了至少一个资源 ,但又提出了新的资源请求,而该资源又被其他进程所请求。
注意!发生死锁时一定有循环等待,但是发生循环等待时未必死锁(循环等待是死锁的必要不充分条件)
如果同类资源数大于 1,则即使有循环等待,也未必发生死锁。但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件了。
8.3、什么时候会发生死锁
-
对系统资源的竞争。各进程对不可剥夺的资源(如打印机)的竞争可能引起死锁,对可剥夺的资源(CPU)的竞争是不会引起死锁的。
-
进程推进顺序非法。请求和释放资源的顺序不当,也同样会导致死锁。例如,并发执行的进程 P1、P2 分别申请并占有了资源 R1、R2,之后进程 P1 又紧接着申请资源 R2,而进程 P2 又申请资源 R1,两者会因为申请的资源被对方占有而阻塞,从而发生死锁。
-
信号量的使用不当也会造成死锁。如产生者 - 消费者问题中,如果实现互斥的 P 操作在实现同步的 P 操作之前,就有可能导致死锁。(可以把互斥信号量、同步信号量也看做是一种抽象的系统资源)
总之,对不可剥夺资源的不合理分配,可能导致死锁。
8.4、死锁的处理策略
1、预防死锁。破坏死锁产生的四个必要条件中的一个或几个。
2、避免死锁。用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)
3、死锁的检测和解除。允许死锁的产生,不过操作系统会负责检测出死锁的发生,然后采用某种措施解除死锁。
8.4.1、预防死锁
8.4.1.1、破坏互斥条件
** 互斥条件:** 只有对必须互斥使用的资源的争抢才会导致死锁。
如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如:SPOOLing 技术。操作系统可以采用 SPOOLing 技术把独占设备在逻辑上改造成共享设备。比如,用 SPOOLing 技术将打印机改造为共享设备...

该策略的缺点 :并不是所有的资源都可以改造成可共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性。因此,很多时候都无法破坏互斥条件。
8.4.1.2、破坏不剥夺条件
** 不剥夺条件:** 进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
破坏不剥夺条件:
方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。
方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)
该策略的缺点:
-
实现起来比较复杂
-
释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保护和恢复状态的资源,如 CPU。
-
反复地申请和释放资源会增加系统开销,降低系统吞吐量。
-
若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。
8.4.1.3、破坏请求和保持条件
请求和保持条件:进程已经保持了至少一个资源 ,但又提出了新的资源请求 ,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行,一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。
该策略实现起来简单,但也有明显的缺点:
有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率低 。另外,该策略也有可能导致某些进程饥饿。

8.4.1.4、破坏循环等待条件
循环等待条件 :存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个资源所请求。
可采用顺序资源分配法 。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。
原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。
假设系统中共有 10 个资源,编号为 1、2、...、10

该策略的缺点:
-
不方便增加新的设备,因为可能需要重新分配所有的编号;
-
进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费;
-
必须按规定次序申请资源,用户编程麻烦。
8.4.2、避免死锁
8.4.2.1、系统安全状态
安全状态定义
设系统中有 n 个进程,若存在一个进程序列<P1,P2,...,Pn><P_1,P_2,...,P_n><P1,P2,...,Pn>。使得进程Pi(i=1,2,...,n)P_i(i=1,2,...,n)Pi(i=1,2,...,n)以后还需要的资源可以通过系统现有空闲资源加上所有Pj(j<i)P_j(j<i)Pj(j<i)已占有的资源来满足,则称此时系统处于安全状态,进程序列<P1,P2,...,Pn><P_1,P_2,...,P_n><P1,P2,...,Pn>称为安全序列,因为各进程至少可以按照安全序列中的顺序依次执行完成。
如果系统无法找到这样一个安全序列,则称系统处于不安全状态。
安全状态举例
假设某系统共有 15 台磁带机和三个进程P0、P1、P2P_0、P_1、P_2P0、P1、P2,各进程对磁带机的最大需求数量、T0T_0T0时刻已经分配到的磁带机数量、还需要的磁带机数量以及系统剩余的可用磁带机数量如下表所示:
| 进程 | 最大需求 | 已分配数量 | 还需要的数量 | 剩余可用数量 |
|---|---|---|---|---|
| P0P_0P0 | 12 | 6 | 6 | 4 |
| P1P_1P1 | 5 | 2 | 3 | |
| P2P_2P2 | 10 | 3 | 7 |
由安全状态向不安全状态的转换:如果不按照安全顺序分配资源,则系统可能由安全状态进入不安全状态。
上表的安全序列有:<P1,P0,P2><P_1,P_0,P_2><P1,P0,P2>
8.4.2.2、银行家算法
8.4.2.2.1、算法基本思想
银行家算法由 Dijkstra E.W 于 1968 年提出,其模型基于一个小城镇的银行家借贷逻辑,用于避免系统进入死锁状态。算法对客户(进程)提出以下约束条件:
-
每个客户必须预先说明自己所要求的最大资金量(资源量)
-
每个客户每次可以提出部分资金量的申请
-
如果银行满足了某客户对资金的最大需求量,那么客户在资金运作完成后,应在有限时间内将全部资金归还给银行
8.4.2.2.2、核心数据结构
为了实现算法,系统需要维护以下数据结构,假设系统中有 n 个进程,m 类资源:
-
可利用资源向量
Available[m]其中的每一个元素代表一类可利用的资源数目。Available[j] = K表示系统中现有R_j类资源K个。 -
最大需求矩阵
Max[n, m]该矩阵定义了系统中n个进程对m类资源的最大需求。Max[i, j] = K表示进程i需要R_j类资源的最大数目为K。 -
分配矩阵
Allocation[n, m]该矩阵表示系统中每个进程当前已分配到的每类资源数量。Allocation[i, j] = K表示进程i当前已分得R_j类资源的数目为K。 -
需求矩阵
Need[n, m]该矩阵表示每个进程尚需的各类资源数。Need[i, j] = K表示进程i还需要R_j类资源K个,方能完成其任务。 该矩阵可通过以下公式计算: Need[i,j]=Max[i,j]−Allocation[i,jNeed[i, j] = Max[i, j] - Allocation[i, jNeed[i,j]=Max[i,j]−Allocation[i,j -
请求向量
Request_i[m]当进程i提出资源请求时,该向量表示进程i对每类资源的请求数量。
8.4.2.2.3、银行家算法流程
当进程 P_i 提出资源申请 Request_i 时,系统执行以下步骤:
-
合法性检查 1 :若
Request[i] ≤ Need[i],则转步骤 2;否则返回错误,因为进程请求超过了其声明的最大需求。 -
合法性检查 2 :若
Request[i] ≤ Available,则转步骤 3;否则,进程P_i必须等待,因为当前系统没有足够的可用资源。 -
试探性分配 :系统尝试把资源分配给进程
P_i,并修改数据结构:-
Available := Available - Request[i]; Allocation[i] := Allocation[i] + Request[i]; Need[i] := Need[i] - Request[i]; -
安全性检查:执行安全性算法,检查此次资源分配后,系统是否处于安全状态。
-
若安全,则正式完成本次资源分配。
-
若不安全,则将本次的试探分配作废,恢复原来的资源分配状态,让进程
P_i等待。
-
-
8.4.2.2.4、安全性检查算法
安全性算法是银行家算法的核心子模块,用于判断系统当前是否处于安全状态,执行步骤如下:
-
初始化工作向量:
-
工作向量
Work:表示系统可提供给进程继续运行所需的各类资源数目,初始时Work = Available。 -
完成标记
Finish[n]:表示系统是否有足够的资源分配给进程,使之运行完成。初始时Finish[i] = false,当有足够资源分配给进程时,再令Finish[i] = true。 -
寻找可执行进程: 从进程集合中找到一个能满足下述条件的进程:
-
Finish[i] = false(进程尚未完成) -
Need[i,j] ≤ Work[j](进程的剩余需求可以被当前可用资源满足) 若找到,执行步骤 3,否则执行步骤 4。
-
-
模拟资源回收 : 当进程
P_i获得资源后,可顺利执行直至完成,并释放出分配给它的资源,因此执行:-
Work[j] = Work[j] + Allocation[i,j]; Finish[i] = true; -
然后回到步骤 2,继续寻找下一个可执行进程。
-
-
结果判断 : 如果所有进程的
Finish[i] = true都满足,则表示系统处于安全状态;否则,系统处于不安全状态。
-
8.4.3、死锁的检测和解除
8.4.3.1、死锁的检测
为了能对系统是否已发生了死锁进行检测,必须:
-
用某种数据结构来保存资源的请求和分配信息;
-
提供一种算法,利用上述信息来检测系统是否已进入死锁状态。


如果系统中剩余的可用资源数足够满足进程的需求,那么这个进程暂时是不会阻塞的,可以顺利地执行下去。
如果这个进程执行结束了把资源归还系统,就可能使某些正在等待资源的进程被激活,并顺利地执行下去。相应的,这些被激活的进程执行完了之后又会归还一些资源,这样可能又会激活另外一些阻塞的进程...
如果按上述过程分析,最终能消除所有边 ,就称这个图是可完全简化的 。此时一定没有发生死锁(相当于能找到一个安全序列)

如果最终不能消除所有边 ,那么此时就是发生了死锁。
最终还连着边的那些进程就是处于死锁状态的进程。

检测死锁的算法:
-
在资源分配图中,找出既不阻塞又不是孤点的进程 Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量。如下图中,R1 没有空闲资源,R2 有一个空闲资源。若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配边,使之称为孤独的结点。在下图中,P1 是满足这一条件的进程结点,于是将 P1 的所有边消去。
-
进程 Pi 所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。在下图中,P2 就满足这样的条件。根据 1 中的方法进行一系列简化后,若能消去途中所有的边,则称该图是可完全简化的。

死锁定理:如果某时刻系统的资源分配图是不可完全简化 的,那么此时系统死锁。
8.4.3.2、死锁的解除
一旦检测出死锁的发生,就应该立即解除死锁。
补充:并不是系统中所有的进程都是死锁状态,用死锁检测算法化简资源分配图后,还连着边的那些进程就是死锁进程
解除死锁的主要方法有:
-
** 资源剥夺法。** 挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但是应防止被挂起的进程长时间得不到资源而饥饿。
-
撤销进程法(或称终止进程法)。强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。这些方式的优点是实现简单,但所付出的代价可能会很大。因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,以后还得从头再来。
-
** 进程回退法。** 让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息,设置还原点。
如何决定 "对谁动手"
-
进程优先级
-
已执行多长时间
-
还要多久能完成
-
进程已经使用了多少资源
-
进程是交互式的还是批处理式的