第三章 同步、通信与死锁
3.1 并发进程
3.1.1 顺序程序设计
进程内部的顺序性:进程在处理器上的执行是严格有序的
进程外部的顺序性:一个任务中多个进程的执行是严格有序的
特点:
(1)执行的顺序性
(2)环境的封闭性
(3)结果的唯一性
(4)过程的可再现性
优缺点:编写、调试方便,效率低
3.2 并发程序设计
一组进程的执行在时间上是重叠的
A(a1, a2, a3) B(b1, b2, b3)
顺序执行:a1->a2->a3->b1->b2->b3
并发执行:a1->b1->a2->b2->a3->b3
宏观:并发反映一个时间段内几个进程都在处理器上,都处于运行状态
微观:任一时刻仅有一个进程处于运行状态
并发的实质是一个处理器在几个进程之间的多路复用,是对有限的物理资源强制行驶多线程共享,消除计算机硬件之间相互等待现象,以提高计算机硬件的利用率。
分类:
无关:多个进程使用不同变量集合,一个进程的执行和其他进程的执行无关。 (Bernstein, 时间无关的充分条件)
Bernstein:
R(Pi) = {a1, a2, ..., an} (读)
W(Pi) = {b1, b2, ... , bn} (写)
R(P1)∩W(P2)∪R(P2)∩W(P1)∪W(P1)∩W(P2) = { }
交往:共享变量,一个进程的运行会影响另一个进程的运行。
优点:
单处理器:让处理器和IO设备同时工作,发挥硬部件的并行能力
多处理器:各进程在不同处理器上物理地并行,加快计算速度
缺点:与时间有关的错误:结果不唯一、永远等待
3.1.3 进程的交互
竞争与协作:互斥、同步和通信
互斥:多个进程互相争夺独占性资源所产生的竞争关系
若处理不当,会出现:死锁、饥饿
死锁:一组进程中的每一个进程都处于无限期等待,永远不会释放
饥饿:进程长时间等待。 (即使完成也没有意义:饿死)
同步:共同完成任务的并发进程需要协调他们的活动,需要在某些位置上排定执行的先后顺序而等待所产生的协作关系。
通信:进程之间减缓数据或发送信息来进行协作。
3.2 临界区管理
3.2.1 互斥与临界区
并发进程中与共享变量有关的程序段叫“临界区”,共享变量代表的资源叫“临界资源”。
如果保证进程在临界区执行时,不让另一个进程进入临界区,即各进程对共享变量的访问是互斥的,就不会造成与时间有关的错误。
临界区调度原则:
1、一次至多一个进程能够进入临界区内执行
2、如果已有进程在临界区,其他试图进入的进程应等待
3、进入临界区内的进程应在有限的时间内退出,以便在等待进程中的一个进入
3.2.4 实现临界区管理的硬件设施
关中断
是实现互斥的最简单方法,进程上下文的切换都是由中断引起的
测试并建立指令
使用硬件所提供的“测试并设置”机器指令TS。TS指令管理临界区时,可把一个临界区与一个布尔变量S相连,变量S可以看成一把锁,代表临界资源的状态
对换指令
交换两个进程的状态
以上的软件算法和硬件设施所实现的互斥方案都是忙等待方案,即当一个进程想要进入临界区时,先检查是否允许进入,如果不允许则原地等待(仍处于运行中),直到允许。因此浪费CPU时间,或者造成死锁(无限等待)
3.3 信号量与PV操作
3.3.1 同步和同步机制
同步:进程的同步指为完成共同任务的并发进程需要协调他们的活动,需要在某些位置上排定执行的先后次序而等待所产生的协作关系。互斥也可以看作是一种同步(也是一种协作)
**同步机制:**信号量、锁、管程,消息传递
3.3.2 信号量与PV操作
操作系统中,信号量表示物理资源的实体,它是一个与队列有关的整型变量
信号量是一种记录型数据结构,有两个分量:一个是信号量的值,另一个是信号量队列的队列指针
设s是一个记录型数据结构,一个分量为整形量value,另一个为信号量队列list,P和V操作的原语定义:
P(s):将信号量s减去1,若结果小于0,则调用P(s)的进程被置成等待信号量s的状态,将其加入队列list
V(s):将信号量s加1,若结果大于0,则从list中取出一个进程,并释放
注:
1、若信号量s为正值,则信号量表示为s的可使用的物理资源数(空闲数),可执行P
2、若信号量s为负值,则信号量的绝对值为s队列中等待的进程个数
3、P操作意味着请求一个资源,V操作为释放一个资源(有一个做完了)
4、如果初始时信号量的资源数量为1,那么PV操作可以用来保护临界区。所以互斥可以看作是一种同步。
3.3.3 信号量实现互斥
semaphore mutex;
mutex = 1;
cobegin
process Pi(){
P(mutex);
{
临界区
}
V(mutex);
}
coend
一些例子:
3.3.4 信号量解决五个哲学家吃通心面问题
3.3.5
3.3.6
3.4 管程
略
3.5 进程通信
进程通信概念
并发进程之间的协作包括:同步和通信
进程同步缺乏传递数据的能力
进程协同工作时,需互相交换信息,可能是少量信息,也可能是交换大批数据
进程之间互相交换信息的工作称为进程通信IPC
进程通信的方式
(1)信号通信机制
(2)管道通信机制
(3)消息传递通信机制
(4)信号量通信机制
(5)共享主存通信机制
3.5.1 信号通信机制
信号机制又称软中断,是一种简单的通信机制,通过发送一个指定信号来通知进程某个异步事件发生,以迫使进程执行信号处理程序
异步是说进程没有对信号实时监控,不必等待信号到来,事实上进程也根本不知道信号什么时候来
用户、内核和进程都能生成信号请求:
(1)用户:ctrl+c
(2)内核:当进程执行出错时,内核检测到事件并给进程发送信号
(3)进程:进程可通过系统调用kill给另一个进程发送信号,一个进程可通过信号与另一个进程通信
Linux系统信号分类
(1)与进程终止相关的信号
(2)与进程例外事件相关的信号
(3)与进程执行系统调用相关的信号
(4)与进程终端交互相关的信号
(5)用户进程发送信号
(6)跟踪进程执行的信号
进程处理信号的方式
(1)忽略信号
(2)捕获信号
(3)执行缺省操作
信号机制的实现
信号有一个产生、传送、捕获和释放的过程
- 信号接受域signal
- 信号屏蔽位blocked
- 信号发送工作由系统调用kill完成
- 用sigaction指定信号处理程序
- 信号处理时机(内核处理一个进程收到的信号是在该进程的上下文中,因此进程必须处于运行状态)
3.5.2 管道通信机制
管道是连续读写进程的一个特殊文件,写进程以字符流形式把大量数据送入管道,读进程从管道中接收数据,所以叫管道通信
管道的实质是一个共享文件,基本上可借助与文件系统的机制实现,包括文件的创建、打开、关闭和读写
管道分为匿名管道和有名管道
匿名管道
特点:
-
匿名管道是单向的,数据只能向一个方向流动;若需要双向通信,则需建立两个匿名管道
-
只能用于具有亲缘关系的进程通信(具有共同祖先,如兄弟进程或父子进程)
-
匿名管道对于管道两端的进程而言,就是一个文件
-
一个进程向管道中写入的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据
-
管道文件大小有限,存在读写阻塞
有名管道
又称FIFO,克服只能用于具有亲缘关系的进程之间通信的限制
FIFO提供一个与路径名的关联,以FIFO的文件形式存在于文件系统中,通过FIFO不相关的进程也能交换数据
FIFO遵循先进先出,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾
3.5.3 共享主存通信机制
共享主存是指在主存中开辟一个共享存储区,要进行通信的进程把自己的虚地址空间映射到共享存储区(可以多个进程)
当发送进程将消息写入共享存储区的某个位置时,接收进程可以在这个位置读取信息(但存在互斥)
由于共享存储区同时出现在多个进程的虚存储空间中,能快捷、有效地实现进程间的通信
同时需要注意保证访问的互斥
3.5.4 消息传递通信机制
当进程需要交换信息时,需要引入高级通信方式——消息传递机制(UNIX和LINUX中称其为消息队列)来实现(可在不同机器上的进程中实现)
可分为直接通信和间接通信
直接通信
发送和接收消息的进程必须互相指定
进程间利用消息传递,除了可以进行通信(交换数据),还可以进行同步
间接通信
信箱是存放信件的存储区域
3.6 死锁
3.6.1 死锁产生
-
进程推进顺序不当产生死锁
进程P 进程Q 请求读卡机 请求打印机 请求打印机 请求读卡机 … … 两进程都无法释放第一个资源
-
PV操作不当
-
资源分配不当
若有m个资源被n个进程共享,每个进程都要求k个资源,而k * n > m
-
对临时性资源不加以限制
例如 P1 -> P2 -> P3 -> P1
进程P1的运行需要P3发送消息,而P3的运行需要P2发送消息,P2则需要P1,即产生循环等待
死锁定义
如果在一个进程集合中的每个进程都在等待只能由该集合中的其他一个进程才能引发的事件,则称一组进程或系统此时发生死锁
死锁导致一组进程或系统无限期处于僵持状态,而无法向前推进
不仅与系统拥有的资源数量,进程对资源的使用要求有关、而且与资源分配策略,以及并发进程的推进顺序有关
死锁防止
通过设定一些规则,按照这些规则申请和分配资源,就一定不会产生死锁
死锁避免
不用设定规则。在资源分配前,用算法检测分配资源后是否会产生死锁,如果不会,则进行分配;否则,不予分配
死锁检测与解除
在运行过程中周期性地检测是否会发送死锁,如果会,则采取一定的措施来消除死锁(回滚等)。
3.6.2 死锁防止
系统形成死锁的四个必要条件
- 互斥条件:进程互斥使用资源
- 占有和等待条件:申请新资源得不到满足而等待时,不释放占有资源
- 不剥夺条件:一个进程不能抢夺其他进程占有的资源,资源只能由宿主释放
- 循环等待条件:存在一组进程循环等待资源(前3个条件同时存在而产生的结果)
解决方法
破坏1:使资源可以同时访问而不是互斥使用
破坏2:静态分配
破坏3:允许剥夺
两个策略:
-
层次分配策略
资源被分配成多个层次
当进程得到某一层的资源后,它只能再申请较高层次的资源
当进程要释放某层的一个资源时,必须先释放占有的较高层次的资源
当进程得到某一层的一个资源后,它想申请该层的另一个资源时,必须先释放该层中的已占资源
-
按序分配策略
把系统所有资源排序,进程不得在占用资源后再申请往后的资源
3.6.3 死锁避免
主要思想:资源分配前动态的检测资源分配状态,以确保循环等待条件不可能成立
允许进程动态地申请资源,但系统在进行资源分配前,应先计算此次分配资源地安全性,若分配不会导致系统进入不安全状态,则分配,否则等待
银行家算法
银行家拥有资金,数量为M,被N个客户共享,银行家对客户提出下列约束条件:
- 每个客户必须预先说明所要地最大资金量
- 每个客户每次突出部分资金量地申请并获得分配
- 如果银行满足客户对资金的最大需求量,那么客户在资金运作后,应在有限的时间内全部归还银行
其中:客户 -> 进程; 资金 -> 资源; 银行家 -> 操作系统
但存在限制:
- 事先需要知道资金(资源)数量
- 需要知道每个客户(进程)所需要资金(资源)的数量
- 要求所涉及的进程不交互
安全状态
如果存在一个由系统中所有进程构成的安全序列P1,…, Pn,则系统处于安全状态
例子:
最大需求 | 当前占有 | |
---|---|---|
P0 | 10 | 5 |
P1 | 4 | 2 |
P2 | 9 | 2 |
某系统有12台磁带驱动器, <P1, P0, P2>是否是安全序列?
目前有12-5-2-2=3
目前需求 | |
---|---|
P0 | 5 |
P1 | 2 |
P2 | 7 |
P1用完2个后释放出4个,也就是剩余5个;P0用完5个后释放出10个,目前剩余10个;剩余足够给P2使用,所以是安全序列
安全状态一定不是死锁状态
死锁状态一定是不安全状态
不安全状态不一定是死锁状态
银行家算法包含两个算法:
-
安全性算法
用于确定系统是否处于安全状态(寻找安全序列)
-
资源请求算法
确定系统是否批准进程的资源请求;需要调用安全性算法
有n个进程Pi和m种不同类型的资源Rj
- Available: Available[j] = k 资源类型Rj现有k个实例
- Max:Max[i, j] = k 进程Pi最多可以申请k个Rj实例
- Allocation:Allocation[i, j] = k 进程Pi现在已经分配了k个Rj实例
- Need:Need[i, j] = k 进程Pi还可能申请k个Rj实例 Need[i, j] = Max[i, j] - Allocation[i, j]
Allocation | Max | Available | Need | |
---|---|---|---|---|
A B C | A B C | A B C | A B C | |
P0 | 0 1 0 | 7 5 3 | 8 5 3 | 6 4 3 |
P1 | 2 0 0 | 3 2 2 | 5 3 2 | 1 2 2 |
P2 | 3 0 2 | 9 0 2 | 11 5 5 | 6 0 0 |
P3 | 2 1 1 | 2 2 2 | 7 4 3 | 0 1 1 |
P4 | 0 0 2 | 4 3 3 | 11 5 7 | 4 3 1 |
实例个数为10、5、7
状态\Available | A | B | C |
---|---|---|---|
在执行之前 | 3 | 3 | 2 |
可以执行P1,执行P1后 | 2+3=5 | 1+2=3 | 0+2=2 |
执行P3 | 5+2=7 | 2+2=4 | 1+2=3 |
执行P0 | 1+7=8 | 0+5=5 | 0+3=3 |
执行P2 | 2+9=11 | 5 | 3+2=5 |
执行P4 |
所以存在安全序列<P1, P3, P0, P2, P4>
3.6.4 死锁的检测和接触
系统定时允许一个“死锁检测”程序,判断系统内是否已出现死锁;如果检测到系统已发生死锁,再采取措施解除
进程-资源分配图
从进程 Pi 到资源类型 Rj 的有向边记为 Pi->Rj
,它表示进程 Pi 已经申请了资源类型 Rj 的一个实例,并且正在等待这个资源。
从资源类型 Rj 到进程 Pi 的有向边记为 Rj->Pi
,它表示资源类型 Rj 的一个实例已经分配给了进程 Pi。
有向边 Pi->Rj
称为申请边,有向边 Rj->Pi
称为分配边,矩形内的点的数量表示实例数量,申请边只指向矩形 Rj,而分配边应指定矩形内的某个圆点。
当进程 Pi 申请资源类型 Rj 的一个实例时,就在资源分配图中加入一条申请边。当该申请可以得到满足时,那么申请边就立即转换成分配边。当进程不再需要访问资源时,它就释放资源,因此就删除了分配边。
检测死锁
- 如果进程-资源分配图中⽆环路,则此时系统没有发⽣死锁。
- 如果进程-资源分配图中有环路,且每个资源类中仅有⼀个资源,则系统中发⽣了死锁,此时,环路是系统发⽣死锁的充要条件,环路中的进程便为死锁进程。
- 如果进程-资源分配图中有环路,且涉及的资源类中有多个资源,则环路的存在只是产⽣死锁的必要条件⽽不是充分条件。
进程 P1、P2 和 P3 死锁了。进程 P2 等待资源类型 R3,而它又被进程 R3 占有;进程 P3 等待进程 P1 或进程 P2 以释放资源类型 R2。另外,进程 P1 等待进程 P2 释放资源 R1。
列个表格
进程 | 分配 | 状态 |
---|---|---|
P1 | 得到R2的一个资源,申请R1 | 等待R1,R1被进程P2占用 |
P2 | 得到R1和R2的一个资源,申请R3 | 等待R3,R3被进程P3占用 |
P3 | 得到R3的资源,申请R2 | 等待R2,R2被进程P1和P2占用 |
所以,三个进程都处于等待状态,全都无法释放资源,处于死锁。
同样列个表格
进程 | 分配 | 状态 |
---|---|---|
P1 | 得到R2的一个资源,申请R1 | 等待R1,R1被进程P2和P3占用 |
P2 | 得到R1的一个资源,没有申请 | 可以释放 |
P3 | 得到R1的一个资源,申请R2 | 等待R2,R2被进程P1和P4占用 |
P4 | 得到R2的资源,没有申请 | 可以释放 |
因此,P2和P4都可以释放资源给P1和P3,不存在死锁。
有环路是检测死锁的方法
- 如果进程的资源请求可以得到满足(没有申请边),则可以消去此进程的所有分配边,使其孤立
- 如果经过一系列简化,所有进程成为孤立节点,则该图可完全简化;否则是不可完全简化的
- 系统为死锁的充分必要条件是:该状态的分配图是不可完全简化(死锁定理)
检测方法:每种资源类型只有单个实例
死锁的解除
- 结束所有进程的运行,重启
- 撤销陷于死锁的所有进程,解除后继续运行
- 逐个撤销陷于死锁的进程,回收其资源重新进行分派,直到死锁解除
- 剥夺陷于死锁的进程占用的资源,但并不撤销,直到死锁解除
- 根据系统保存的检查点回退