408操作系统学习笔记——进程与线程、处理机调度、同步与互斥(PV操作)、死锁

目录

1.进程与线程 

1.1.进程的概念

1.2.进程的特征

​编辑

1.3.进程的状态

1.4.进程状态的转换

1.5.进程控制

1.6.进程通信

1.6.1.共享存储

1.6.2.消息传递

1.6.3.管道通信

1.7.线程的概念

1.8.线程的实现方式和多线程模型

1.8.1.用户级线程

1.8.2.内核级线程(一对一模型)

1.8.3.多线程模型——多对一模型

1.8.4.多线程模型——多对多模型

1.9.线程的状态与转换

2.处理机调度

2.1.调度的概念、层次

2.2.进程调度的时机、切换与过程、方式

2.2.1.进程调度的时机

2.2.2.进程调度的切换过程

2.2.3.进程调度的方式

2.3.调度器和闲逛进程

2.3.1.调度器/处理程序

2.3.2.闲逛进程

2.4.调度算法的评价指标

2.5.调度算法

2.5.1.先来先服务(FCFS)

2.5.2.短作业优先(Shortest Job First)

2.5.3.高响应比优先(Highest Response Ratio Next)

​编辑

2.5.4.时间片轮转调度算法(Round-Robin)

2.5.5.优先级调度算法

2.5.6.多级反馈队列调度算法

2.5.7.调度算法的对比

2.5.8.多级队列调度算法

3.同步与互斥

3.1.进程同步、进程互斥的概念

3.2.进程互斥的软件实现方法

3.2.1.单标志法

3.2.2.双标志先检查法(先检查后上锁)

3.2.3.双标志后检查法(先上锁后检查)

3.2.4.Peterson算法

3.3.进程互斥的硬件实现方法

3.3.1.中断屏蔽方法

3.3.2.TestAndSet指令(TS,也称TSL,即TestAndSetLock)

3.3.3.swap指令(也称exchange指令或XCHG指令)

3.4.互斥锁

3.5.信号量机制

3.5.1.整型信号量

3.5.2.记录型信号量

3.6.用信号量实现进程互斥、同步和前驱关系

3.6.1.用信号量实现进程互斥

3.6.2.用信号量实现进程同步

3.6.3.用信号量实现前驱关系 ​编辑

3.7.生产者-消费者

3.8.多生产者-多消费者

3.9.吸烟者问题

3.10.读者写者问题

3.11.哲学家进餐问题

3.12.管程

4.死锁

4.1.死锁的概念

4.2.预防死锁

4.3.避免死锁(银行家算法)

4.4.死锁的检测和解除

4.4.1.死锁的检测

4.4.2.死锁的解除


1.进程与线程 

1.1.进程的概念

1.进程:程序的一次执行过程

2.PCB(Process Control Block):进程控制块

3.进程实体(进程映像)由PCB、程序段和数据段组成

进程实体反应了进程某个时刻的状态,因此,进程是动态的,进程实体是静态的

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

4.同一个程序被打开多次,则被建立多个不同的进程,它们的程序段相同,而PCB和数据段不同

1.2.进程的特征

1.3.进程的状态

1.创建态:为进程分配资源、初始化PCB

2.就绪态:创建完成后,进入就绪态(除处理机外的资源都具备)(CPU忙,无法为该进程服务);可能有多个就绪态;CPU空闲时,选择一个就绪进程运行

3.运行态:在CPU上运行的进程

4.阻塞态:进程运行过程中,可能会请求某个事情的发生(既没有处理机资源,也没有某种系统资源)主动,等待某种系统资源的分配),操作系统就会剥夺该进程CPU的使用权,使其进入阻塞态(同时CPU就会进入空闲状态,从而选择另外一个处于就绪态的进程执行);若该进程等待的事情处理完成后,该进程就重新从阻塞态变为就绪态

5.终止态:进程可以通过执行exit系统调用,请求操作系统终止该进程,同时该进程进入终止态;操作系统剥夺该进程CPU的使用权,回收内存空间等系统资源,并回收该进程的PCB

6.PCB中有一个变量state表示当前进程的状态

1.4.进程状态的转换

1.就绪态→运行态:

处于就绪态的进程被调度后,获得处理机资源,于是进程由就绪态切换为运行态

2.运行态→就绪态:

情况1:处于运行态的进程在时间片用完后,不得不让出处理机,进而转换为就绪态

情况2:在可剥夺的操作系统中,当有更高优先级的进程就绪时,调度程序将正在执行的进程转换为就绪态,让更高优先级的进程执行

3.运行态→阻塞态(主动行为)

进程请求某一资源(如外设)的使用或等待某一事件的发生(如I/O操作的完成)时,它就从运行态转换为阻塞态

进程以系统调用的形式请求操作系统提供服务,这是一种特殊的,由用户态程序调用操作系统内核过程的形式

4.阻塞态→就绪态(被动行为:需要其他相关进程的协助)

进程等待的事件到来,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞态转换为就绪态

1.5.进程控制

1.PCB的state中连接各种不同的进程状态:

①通过指针的方式

②通过索引表方式

2.进程的控制需要用原语:原语的执行具有原子性(通过关中断指令、开中断指令的方式,实现期间不可被中断)

进程在控制过程中,需要两步操作(设采用指针方式):

①修改PCB中的state ②将阻塞队列放到就绪队列

如果不采用原语,则可能发生第一步完成后,就转向处理中断

3.原语:原语的执行具有原子性(通过关中断指令、开中断指令的方式,实现期间不可被中断

①创建原语:创建态→就绪态

(1)申请空白PCB:创建PCB(PCB是进程存在的唯一标志)

(2)为进程分配所需资源:内存空间等

(3)初始化PCB:分配PID、设置UID等

(4)将PCB插入就绪队列:创建态→就绪态

②引发创建原语的事件:

(1)用户登录:用户登陆成功后,系统将会建立一个新的进程

(2)作业调度:从外存中挑选一个进程放入内存中运行

(3)提供服务:用户向操作系统提出某些请求时,新建一个进程处理该请求

(4)应用请求:用户进程主动请求创建子进程

③撤销原语:就绪态/阻塞态/运行态→终止态→无

(1)找到该进程的PCB

(2)若该进程正在运行,剥夺其CPU的使用权,并将CPU分配给其他进程

(3)终止其所有子进程,并将其资源归还给父进程或操作系统

(4)删除PCB

④引发撤销原语的事件:

(1)正常结束:进程自己请求终止(exit系统调用)

(2)异常结束:整数除0、非法使用特权命令(异常)

(3)外界干预:用户选择关闭进程(任务管理器结束)

⑤阻塞原语:运行态→阻塞态

(1)找到该进程的PCB

(2)运行进程保护现场,将PCB中的信息改为阻塞态,暂停进程运行

(3)将PCB插入相应事件的等待序列

⑥引起阻塞原语的事件:

(1)需要等待某种系统资源的分配

(2)需要等待合作进程

⑦唤醒原语:阻塞态→就绪态

(1)找到该进程的PCB

(2)将PCB中从等待序列中删除,将PCB中的信息改为就绪态

(3)将PCB插入就绪队列,等待被调度

⑧引起唤醒原语的事件:等待的事件发生(引起该进程阻塞的事件)

(阻塞原语和唤醒原语成对出现)

⑨切换原语:进程A:运行态→就绪态;进程B:就绪态→运行态(两个进程状态改变)

(1)将运行环境信息某些寄存器中的内容,PSW、PC等)存入PCB,将PCB插入相应序列

(2)选择另一个进程运行,并更新其PCB(例如状态信息),并根据其PCB恢复运行环境信息

⑩引起切换原语的事件:

(1)当前进程时间片到

(2)有更高优先级的进程出现(可剥夺系统)

(3)当前进程主动阻塞

(4)当前进程终止

1.6.进程通信

1.各进程的内存地址空间相互独立

2.一个进程不可以直接访问另一个进程的地址空间

1.6.1.共享存储

1.开辟一个公共的地址空间用于信息交换(对于该公共空间进行操作必须是互斥的,例如PV)

2.基于数据结构(低级通信):限制多,速度慢(例如数组)

基于存储区(高级通信):操作系统只负责划分出一块内存区域用于共享存储,而如何共享存储区中数据的形式、数据如何存放等细节问题由进程决定,因此限制少,速度快

1.6.2.消息传递

1.进程间的数据交换以格式化的消息为单位

2.格式化的消息由消息头和消息体构成

消息头(概要信息):由发送进程ID(由谁发送)、接受进程ID(发送给谁)、消息长度(整个消息的长度)等格式化的消息

消息体(具体信息):具体的数据

3.(1)直接通信方式:发送方进程需要指明接收方进程的ID(指明由谁接受)

设发送方为p,接收方为q 

①p通过发送原语将消息发送到q(指明接收方)的pcb的消息队列中(q的pcb在操作系统内核的地址空间) 

②通过接受原语将消息从pcb的消息队列中找到p(指明发送方)发来的消息,并取到q的地址空间中

(2)间接通信方式:通过“信箱间接通信,指明信箱(因此又称信箱通信方式 )

①信箱存放在操作系统内核的地址空间中

②发送方指明发往哪个信箱;接收方指明从哪个信箱中接收

1.6.3.管道通信

1.数据的流向只能是单向(半双工通信),如果需要进行双向数据传输,则需要两个管道

2.管道是一个特殊的文件(本质是在内存中开辟一个大小固定的内存空间):管道满时,写阻塞;管道空时,读阻塞

3.管道和共享存储的区别:共享存储方式中,数据的读/写的在地址空间的位置是任意的;管道需要遵循FIFO的方式按顺序读/写(本质上是循环队列)

4.进程对管道的访问是互斥

5.允许多个写进程,仅允许一个读进程

1.7.线程的概念

1.线程是程序执行流的最小单位(每个线程可以对应不同的代码,并发执行),也是基本的CPU执行单元(CPU调度/服务的对象为线程)——即线程是调度的基本单位

2.进程是除CPU外的系统资源的分配单元(系统资源是分配给进程的,而不是分配给线程)——即进程是资源分配的基本单位

3.进程间(切换系统开销大)、线程间(切换系统开销小)都可以并发

4.线程可以占用不同的CPU

5.每个线程都有线程ID(对应进程ID)、线程控制块TCB(对应进程的PCB)

6.线程有就绪、阻塞和运行三种状态

7.线程几乎不拥有系统资源(进程是资源分配的基本单位):线程使用进程的系统资源,同一进程的不同线程共享系统资源

8.共享内存地址空间→线程间通信无需系统干预

9.同一进程间的线程切换,不引起进程切换(系统开销小);不同进程间的线程切换,引起进程切换(系统开销大)

9.内核级线程是处理机分配的基本单位(操作系统能意识到的只有内核级线程)

1.8.线程的实现方式和多线程模型

1.8.1.用户级线程

1.程序员自己写一个程序库实现逻辑上的线程操作系统的视角只看得到进程(因此操作系统意识不到用户级线程的存在)

2.线程的管理工作由应用程序完成(通过线程库)

3.优点:线程的切换不需要操作系统参与,由应用程序完成,因此不需要变态 

4.缺点:①当一个用户级线程被阻塞后,整个程序都会被阻塞(程序需要按代码顺序执行)

 ②多个线程不可以在多核处理机上运行(操作系统的视角下只能看到进程,因此,此时操作系统的基本调度单位仍然是进程,而非线程)

1.8.2.内核级线程(一对一模型)

1.线程的管理工作由操作系统完成

2.线程的切换需要CPU变态(线程的调度和切换等需要内核负责)

3.操作系统能够意识到内核级线程的存在(操作系统为每个内核级线程建立相应的TCB)

4.优点:①当一个线程被阻塞后,其余线程仍可工作(每个用户线程都有相对的内核级线程)

多个线程可以在多核处理机上运行(操作系统可以意识到内核级线程的存在,此时,线城是调度的基本单位)

5.缺点:线程切换需要变态,系统开销大

1.8.3.多线程模型——多对一模型

1.8.4.多线程模型——多对多模型

1.9.线程的状态与转换

1.线程的状态转换和对应的进程转换一致

2.线程切换时需要保存/恢复:PC(程序执行到哪)、其他寄存器(程序当前运行的结果)和堆栈指针(下处理机保存到PCB,上处理机从PCB中恢复)

①堆栈用于保存函数调用信息,例如A调用B、B调用C和函数返回地址,即C执行完后应返回B的哪一句代码等

②堆栈还用于保存局部变量

③堆栈是内存中一个很大的地址空间,保存堆栈指针可以使得我们找到该程序在堆栈中的哪个位置

2.处理机调度

2.1.调度的概念、层次

1.调度:处理机有一堆程序需要处理,根据某种规则决定处理的顺序

2.调度的三个层次:

①高级调度(作业调度):从外存的作业中选择一个作业调入内存,并创建进程;每个作业只调入和调出一次;调入时建立PCB,调出时撤销PCB

②低级调度(进程调度/处理机调度):从就绪队列中选择一个进程上处理机;频率很高

③中级调度(内存调度):将处于挂起状态进程重新调入内存

挂起状态:内存不够,将某些进程的数据调入外存

2.2.进程调度的时机、切换与过程、方式

2.2.1.进程调度的时机

1.正在运行的进程主动请求:①正常终止 ②发生异常终止 ③请求阻塞

2.正在运行的进程被动放弃:①时间片用完 ②更紧急事件需要处理(I/O中断等) ③更高优先级进程进入就绪队列

3.不能进行调度的时机:①处理中断的过程中 ②操作系统内核临界区中 ③原子操作过程中

4.临界资源:一段时间内只允许一个进程使用的资源互斥访问

临界区:访问临界资源的代码(各个进程需要互斥的进入临界区)

内核程序临界区一般用来访问某种内核的数据结构:例如进程的就绪队列

5.设进程A处于内核程序临界区,并且该临界区访问的是进程的就绪队列,在访问前进程A将会将就绪队列上锁(达到互斥访问的效果);此时若发生进程调度,就需要访问就绪队列,但是就绪队列被A上锁,因此无法发生进程调度——因此,进程在操作系统内核临界区中不能进行调度和切换

6.本质上是若进程访问的是操作系统内核的临界区,操作系统希望进程能够尽快的完成访问操作,从而不影响操作系统的正常工作,因此,通过不允许进程调度和切换的方式达到这一目的;而若进程访问的是慢速I/O设备,即不会影响操作系统的情况下,就允许进行进程调度和切换,并且还有一个角度可以解释,即此情况下若不允许进行进程的调度和切换,将会严重影响操作系统的运行效率,其他进程需要等待此慢速I/O设备的完成

2.2.2.进程调度的切换过程

1.狭义的进程调度:从就绪队列中选择一个要运行的进程

广义的进程调度:①选择一个进程 ②进程切换

2.进程切换:一个进程让出处理机,另一个进程使用处理机

进程切换的过程:①对原进程各种数据的保存 ②对新进程各数据的恢复

进程切换带有系统开销,频繁的进程切换将会导致系统效率降低

2.2.3.进程调度的方式

1.非剥夺调度方式(非抢占式):只允许进程主动放弃处理机

缺点:无法及时处理紧急事件

2.剥夺调度方式(抢占式):进程可以被更高优先级的进程剥夺处理机使用权

优点:可以优先处理紧急事件,并且可以实现进程按时间片轮流执行

2.3.调度器和闲逛进程

2.3.1.调度器/处理程序

1.调度器/调度程序是操作系统的一个程序模块

2.①就绪态→运行态:被调度程序选中

②运行态→就绪态:时间片用完

①和②的状态的转换由调度程序完成

3.调度程序根据调度算法决定让哪个进程运行,根据时间片大小决定让该进程运行多长时间

4.调度时机,即什么事件会触发调度程序

①创建新进程:就绪队列发生改变,新进程若优先级更高,可能抢占处理机

②进程退出:进程退出导致处理机空闲,调度程序需要决定接下来让哪个进程上处理机运行

③正在运行的进程阻塞:阻塞导致处理机空闲,调度程序需要决定接下来让哪个进程上处理机运行

④I/O中断发生:某个之前处于阻塞态的进程转换到就绪态,就绪队列发生改变,该进程若优先级更高,可能抢占处理机

若是非抢占式调度策略,则仅②③会触发调度程序;抢占式,则每间隔若干个时钟周期就会触发一次调度程序(用于检查就绪队列是否有新进程出现)

2.3.2.闲逛进程

1.若就绪队列中没有其他进程,调度程序则选择闲逛进程上处理机运行

2.闲逛进程特点:①优先级 ②0地址指令(占用完成的指令周期,即带有中断周期,在中断周期例行唤醒调度程序,用来检查就绪队列中是否有新进程出现) ③能耗低

2.4.调度算法的评价指标

1.CPU利用率 = 忙碌时间 / 总时间

2.系统吞吐量 = 总共完成多少作业 / 总共花了多少时间(表示单位时间内完成作业的数量)

3.周转时间:指从作业被提交给系统开始到作业完成为止的时间

周转时间由四个部分组成:

①作业在外存中等待作业调度(高级调度)的时间

②进程在就绪队列上等待进程调度(低级调度)的时间(就绪态)

③进程在CPU上运行的时间(运行态)

④进程等待I/O操作完成的时间(阻塞态)

②③④可能发生多次,而①只会有一次

(1)周转时间 = 作业完成时间 - 作业提交时间

(2)平均周转时间 = 各作业周转时间之和 / 作业数

(3)带权周转时间 = 作业周转时间(即作业完成时间 - 作业提交时间) / 作业实际运行的时间

(4)平均带权周转时间 = 各作业带权周转时间之和 / 作业数

4.等待时间:作业/进程等待处理机时间之和

①对于进程:等待I/O完成的期间(阻塞态)也是在被服务的,因此不计入等待时间

②对于作业:不仅需要考虑作业建立进程后的等待时间,还要考虑调入内存前的等待时间

5.响应时间:从提出请求到首次响应的时间

2.5.调度算法

2.5.1.先来先服务(FCFS)

2.5.2.短作业优先(Shortest Job First)

1.非抢占式短作业优先

2.抢占式短作业优先(最短剩余时间优先)

2.5.3.高响应比优先(Highest Response Ratio Next)

2.5.4.时间片轮转调度算法(Round-Robin)

 1.时间片大小为2:时间片设置太小将会导致频繁切换,从而系统开销增大

2.时间片大小为5:时间片选择太大,则会退化成FCFS算法 

2.5.5.优先级调度算法

 1.非抢占式:仅进程主动放弃处理机时调度

2.抢占式:①进程主动放弃处理机时 ②就绪队列发生改变时

3.补充

2.5.6.多级反馈队列调度算法

1.P1(1):0时刻,P1到达,放入第一级队列,P1执行1个时间片后,放入第二级队列队尾

2.P2(1):1时刻,P2到达,放入第一级队列,P2执行1个时间片后,放入第二级队列队尾(第二级:P1→P2)

3.P1(2):2时刻,第一级队列为空,执行第二级队列的队首进程,P1执行2个时间片后,放入第三季队列队尾(第二级:P2;第三级:P1)

4.P2(1):4时刻,第一级队列为空,执行第二级队列的队首进程,P2执行1个时间片后,P3到达,P3优先级大于P2,P2被剥夺处理机的使用权,并放回第二级队列队尾(第二级:P2;第三级:P1)

5.P3(1):5时刻,P3到达,放入第一级队列,P3执行1个时间片后,P3进程结束,调出内存(第二级:P2;第三级:P1)

6.P2(2):6时刻,第一级队列为空,执行第二级队列的队首进程,P2执行2个时间片后,P2进程结束,调出内存(第三级:P1)

7.P1(4):8时刻,第一级、第二级队列为空,执行第三级队列的队首进程,P1执行4个时间片后,将P1重新放回第三级队列的队尾(第三级:P1)

8.P1(1):12时刻,第一级、第二级队列为空,执行第三级队列的队首进程,P1执行1个时间片后,P1进程结束,调出内存

2.5.7.调度算法的对比

2.5.8.多级队列调度算法

1.系统设置多个就绪队列,调度方式:①选中队列→②选中该队列中进程

2.选中队列的方式:①优先级高低 ②时间片轮转

3.每个队列中选中进程的方式可以不同

3.同步与互斥

3.1.进程同步、进程互斥的概念

1.同步(直接制约):两个或者多个进程需要按照某种顺序执行

2.互斥(间接制约):A进程访问某种临界资源时,若进程B也想访问该临界资源,进程B则必须等待进程A访问完成后才能访问该临界资源(一段时间内只能有一个进程访问

①进入区:检查是否可以进入临界区,若能进入临界区,则设置正在访问临界资源的标志(即上锁),防止其他进程进入该临界区

②临界区:访问临界资源的代码(例如需要打印机执行打印操作,则对打印机进行写输出的代码,就在临界区中)

③退出区:解除正在访问临界资源的标志(即解锁)

④剩余区:做其他处理

进入区和退出区是实现互斥的代码段;临界区是访问临界资源的代码段

do {
    entry section;    //进入区
    critical section;    //临界区
    exit section;    //退出区
    remainder section;    //剩余区
} while(true)

 3.互斥应该遵循两个原则:

空闲让进:当临界区空闲时,有进程请求进入则立即允许该进程访问

忙则等待:当临界区已有进程正在访问时,其余想要访问该临界区的进程则必须等待

有限等待:等待访问临界区的进程必须在有限时间内访问该临界区(防止饥饿)

让权等待:当进程不能访问临界区时,必须让出处理机

3.2.进程互斥的软件实现方法

3.2.1.单标志法

1.算法思想:两个进程在访问完临界区后会把该临界区的使用权转交给另一个进程临界区的使用权只能由另一个进程赋予

int turn = 0;    //turn = 0表示允许P0进程进入;turn = 1表示允许P1进程进入

//P0进程
while (turn != 0) ;    //进入区,判断是否当前允许turn是否为0
critical section;    //临界区,访问临界资源
turn = 1;    //退出区,将临界区的访问权让给P1
remainder section;    //剩余区

//P1进程
while (turn != 1) ;    //进入区,判断是否当前允许turn是否为1
critical section;    //临界区
turn = 1;    //退出区
remainder section;    //剩余区

2.执行过程:设初始状态下turn = 0,即刚开始允许访问临界区的是P0进程

①若首先让P1上处理机运行,则P1进程的代码将会卡在while循环处turn != 1为真,死循环)直到P1的时间片用完

②切换至P0后,P0进程的while循环不满足条件(turn != 0为假),执行临界区代码,开始访问临界资源;若直到P0的时间片用完,P0还未退出临界区,切换回P1进程,P1依然会卡在while循环处直到P1时间片用完(因为turn仍为0,即临界区的访问权还是P0的)

③当P0访问完临界区后,在退出区的代码中将会修改turn = 1(即将临界区的访问权让给P1)

3.缺点:违反空闲让进原则临界区的使用权只能由另一个进程赋予,即P0若想访问临界区,必须在P1访问完临界区,将turn改为0

3.2.2.双标志先检查法(先检查后上锁)

1.算法思想:设置一个bool数组,用于标记各个进程是否想要访问临界区;在各个进程访问临界区前,依次检查该bool数组,看是否有其他进程想要访问临界区,若没有,则将自己的标志改为true,并开始访问临界区

bool flag[2];    //标记各个进程是否想要访问临界区
flag[0] = false;
flag[1] = false;    //初始化P0和P1为不想要访问临界区

//P0进程:
while(flag[1]);    //进入区,判断P1是否想要进入临界区
flag[0] = true;    //将自身flag改为true,表示自己想要进入临界区
critical section;    //临界区
flag[0] = false;    //退出区,将自身flag改为false
remainder section;

//P1进程:
while(flag[0]);    //进入区,判断P0是否想要进入临界区
flag[1] = true;    //将自身flag改为true,表示自己想要进入临界区
critical section;    //临界区
flag[1] = false;    //退出区,将自身flag改为false
remainder section;

进入区做了两件事:①需要检查对方是否想要进入临界区 ②如果对方并不想进入临界区,则表达自己想要进入临界区的意愿(即上锁,并在在退出临界区后解锁)

2.缺点:违反忙则等待的原则(原因是检查和上锁是分成两步做的,即不是一气呵成的,而进程是并发执行的,可能发生进程的切换

设初始状态下两个flag都为false,并且从P0进程开始运行:

①P0的while不满足条件(flag[1] = false),准备执行flag[0] = true

②由于进程是并发执行的,若此时切换到P1程序,P1的while循环也不满足条件(flag[0] = false,P0并未来得及修改自身的标志位)

③切换回P0进程执行flag[0] = true,并且进入临界区

④切换回P1进程执行flag[1] = true,并且进入临界区

此时P0和P1都已进入临界区,即违反忙则等待原则

3.2.3.双标志后检查法(先上锁后检查)

1.算法思想:与双标志先检查法相比,相同点:进入区仍是分两步,即上锁和检查;不同点:双标志先检查法是①检查 ②上锁,而双标志后检查法是①上锁 ②检查

bool flag[2];    //标记各个进程是否想要访问临界区
flag[0] = false;
flag[1] = false;    //初始化P0和P1为不想要访问临界区

//P0进程:
flag[0] = true;    //将自身flag改为true,表示自己想要进入临界区
while(flag[1]);    //进入区,判断P1是否想要进入临界区
critical section;    //临界区
flag[0] = false;    //退出区,将自身flag改为false
remainder section;

//P1进程:
flag[1] = true;    //将自身flag改为true,表示自己想要进入临界区
while(flag[0]);    //进入区,判断P0是否想要进入临界区
critical section;    //临界区
flag[1] = false;    //退出区,将自身flag改为false
remainder section;

2.缺点:违背空闲让进和有限等待(将会导致饥饿)原则

设初始状态下两个flag都为false,并且从P0进程开始运行:

①P0将自己的flag改为true,表示自己想要访问临界区,此时发生进程切换,开始执行P1进程

②P1将自己的flag改为true,表示自己想要访问临界区

③此时P0和P1的flag都为true,即P0和P1都想要访问临界区,而现实情况是两个进程都还没能访问临界区(也意味着无法将自己的flag改为false)

④P0和P1继续执行就会分别卡在自己的循环语句中(对方的flag无法更改,死循环)进而导致饥饿

3.2.4.Peterson算法

1.算法思想:如果双方都想访问临界区,则让对方访问临界区

bool flag[2];    //标记进程是否想要访问临界区,初始值为false
int turn = 0;    //turn表示优先让哪个进程进入临界区

//P0进程:
flag[0] = true;    //表示自己想要进入临界区
turn = 1;    //表示优先让对方进入临界区
while (flag[1] && turn == 1);    //如果对象想要进入,并且是自己让对方进入的,则等待
critical section;
flag[0] = false;    //退出临界区后,表示自己不想进入临界区
remainder section;

//P1进程:
flag[1] = true;
turn = 0;
while (flag[0] && turn == 0);
critical section;
flag[1] = false;
remainder section;

2.缺点:并未遵循让权等待原则(若想要进入临界区的进程无法进入临界区,则会卡在while循环,即不断地查询是否能进入临界区)

3.3.进程互斥的硬件实现方法

3.3.1.中断屏蔽方法

1.实现方法:在访问临界区前关中断,在访问临界区后开中断(不允许中断等于不发生进程切换)

2.缺点:

①不适用于多处理机:关中断指令对一个处理机有效,即若A处理机执行关中断指令使进程a访问临界区C后,B处理机上的进程b此时仍可以访问该临界区C

②只适用于操作系统内核进程,不适用于用户进程:开/关中断指令属于特权指令,需要在内核态下才能运行

3.3.2.TestAndSet指令(TS,也称TSL,即TestAndSetLock)

1.实现方法:①通过硬件实现  ②将上锁和检查“一气呵成的执行”(双标志先后检查的升级版)

//bool类型的共享变量lock表示当前临界区是否被加锁
//true表示已被加锁,false表示未被加锁
bool TestAndSet (bool *lock) {
    bool old;
    old = *lock;    //用old存放原来lock的值(上锁还是未上锁)
    *lock = true;    //无论之前上锁与否,都将上锁
    return old;    //返回原来lock的值
}

while (TestAndSet (&lock));
critical section;    //临界区
lock = false;    //退出区,解锁
remainder section;    //剩余区

通过old变量记录之前是否上锁,无论上锁与否都将进行上锁操作:若之前上锁,TestAndSet的上锁操作不影响临界区上锁的状态;若之前未上锁,TestAndSet的上锁操作表示自己将要访问临界区

(1)若刚开始lock的值为false(即未上锁):

①TestAndSet函数的返回值为false,即while语句的条件为假,跳出循环进入临界区

②在①执行的过程中,即在TestAndSet函数中通过*lock = true(即上锁)

③退出临界区将lock重新改为false(即解锁)

(2)若刚开始lock的值为true(即上锁):

TestAndSet函数的返回值为True,即while语句的条件为真,进程将会卡在while循环,直到正在访问临界区的进程退出临界区(即等待正在访问临界区的进程执行lock = false)

2.优点:适用多处理机环境

缺点:违反让权等待的原则(若有进程正在访问临界区,则想要访问该临界区的其他进程将会卡在while循环语句一直等待其退出,因为while此时是死循环)

3.3.3.swap指令(也称exchange指令或XCHG指令)

1.实现方法:①通过硬件实现 ②“一气呵成”的交换两个变量的值

//Swap指令的作用是交换两个变量的值
Swap (bool *a, bool *b) {
    bool temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

//Swap指令的实现互斥访问的示例
//lock表示当前临界区是否上锁
bool old = true;
while (old == true) Swap (&lock, &old);
critical section;    //临界区代码段
lock = false;    //退出区,将lock改为false
remainder section;    //剩余区代码段

通过old变量记录当前是否上锁(即当前是否由于别的进程访问临界区),并通过lock变量上锁

2.swap指令和TestAndSet指令逻辑上是等价的,因此,它们的优缺点相同

3.4.互斥锁

1.一个进程在进入临界区时通过acquire()获得锁,在退出临界区时通过release()释放锁

2.通过bool类型的available表示当前是否可以获得锁:available = true时,则可通过acquire()获得锁,并且available改为false;available = false时,则acquire()会被阻塞,直到被改为true

acquire() {    //获得锁
    while (!available);    //判断当前锁是否可用
    available = false;    //将锁的状态修改为不可用
}

release() {    //释放锁
    available = true;    //将锁的状态修改为可用
}

3.互斥锁的缺点为忙等(即违反让权等待原则):若已有进程正在访问临界区,则其他想要访问该临界区的进程将会一直卡在while循环中,等待其退出临界区,直到时间片用完前不会让出处理机

4.自旋锁:需要连续忙等的互斥锁(TSL指令、SWAP指令和单标志法)

5.特点:

违反让权等待原则(有限等待,在该进程时间片用完时,仍会被剥夺处理机的使用权,切换至另一个进程)

②适用多处理机系统:例如进程P1正在访问临界区A,进程P2等待访问临界区A(自旋等待)

P2自旋等待即可以在P1退出临界区A时直接进入该临界区A,而不用切换环境(切换环境需要大量的系统开销)

③不适用单处理机系统:单处理机系统只能有一个进程占用处理机,即P1只可能在自己的时间片完成对临界区的访问,也就是说P2不可能在自己时间片等到P1访问完临界区

3.5.信号量机制

1.信号量:一种变量,用来表示系统中某种资源的数量

2.原语:一气呵成的执行,不可中断(通过关/开中断指令实现)

3.wait(S)和signal(S)原语:对信号量进行操作

①wait和signal分别为函数名,信号量S为传入的参数

②wait和signal原语简称为P、V操作,可以分别简写为P(S)和V(S)

3.5.1.整型信号量

1.对信号量的操作只能有三种:①初始化 ②P操作 ③V操作

2.wait原语的作用是:

循环检查当前资源是否够用(即满足访问需求):若够用,则跳出循环;若不够用,则将会不断检查(卡在循环)直到够用

②将该资源分配给当前进程(执行②时必然该资源够用),即系统资源数 - 1

3.signal原语的作用是将进程占用的系统资源归还,即系统资源数 + 1

4.在P0进程执行wait原语后(即开始使用打印机),若切换到其他进程,其他进程仍然会执行wait原语,但会卡在wait原语中的while循环语句(因为当前S的值为0),即循环等待P0使用完打印机

5.信号量本质上是双标志先检查法,只是执行上能够一气呵成:

①while(S <= 0) 代表检查

②S = S - 1 代表上锁

6.违背让权等待原则(与记录型信号量主要的区别):若有进程正在访问临界区,其它想要该临界区的进程访问将会卡在循环

3.5.2.记录型信号量

1.记录型信号量解决整型信号量中存在忙等现象

①在执行wait原语时,若剩余资源不够分配,将会把该进程从运行态变为阻塞态,并且把该进程挂到信号量S对应的等待队列

②执行signal原语时,多了一个当前剩余资源的判断(在归还系统资源后),若当前剩余资源仍为<= 0,说明还有其他进程在等待使用该系统资源,则从信号量S对应的等待队列中唤醒一个进程,并将该进程从阻塞态变为运行态(阻塞态→就绪态→运行态)

2.wait(S)和signal(S)对应P(S)和V(S)

3.S.value的初值表示某种系统资源的数量

4.对信号量S的一次P操作表示有一个进程请求该系统资源

P操作要执行S.value--(表示该系统资源的数量 - 1)

②当S.value < 0时,表示该系统资源分配完毕

③当S.value < 0时,需要执行block(阻塞原语,并非每次P操作都要执行主动放弃处理机的使用权(运行态→阻塞态

(因此,遵循让权等待原则)

5.对信号量S的一次V操作表示有一个进程释放该系统资源

V操作要执行S.value++(表示该系统资源的数量 + 1)

②当S.value <= 0时(在执行S.value--后),说明还有其他进程在等待使用该系统资源,需要执行weak(唤醒原语,并非每次V操作都要执行)将等待队列的第一个进程唤醒,并将其上处理机运行(阻塞态 → 就绪态 → 运行态

3.6.用信号量实现进程互斥、同步和前驱关系

3.6.1.用信号量实现进程互斥

1.用单独的一个变量mutex实现互斥访问:mutex = 1表示该资源当前没有进程访问;mutex <= 0表示该资源有进程正在访问

2.semaphore代表的是记录型信号量,即带有排队和阻塞功能,不会忙等

3.不同的临界资源需要设置不同的互斥信号量:例如打印机(mutex1)和摄像头(mutex2)

4.PV操作必须成对出现:缺P无法互斥;缺V将会无法唤醒

3.6.2.用信号量实现进程同步

3.6.3.用信号量实现前驱关系 

3.7.生产者-消费者

1.缓冲区满:生产者不能生产(阻塞,同步关系),消费者可以消费

2.缓冲区不满(非空):生产者可以生产,消费者可以消费

3.缓冲区空:生产者可以生产,消费者不能消费(阻塞,同步关系)

4.各进程对缓冲区的访问是互斥的:防止对缓冲区的某个相同区域进行操作(缓冲区是临界资源

5.互斥关系在同一个进程内进行(即PV操作在同一个进程内);同步关系在不同的进程间运行(即一个进程P,另一个进程V)

//互斥信号量,用于消费者和生产者互斥访问缓冲区
semaphore mutex = 1;
//同步信号量,表示缓冲区的空闲数量,初始缓冲区为空,即empty = n
//生产者每生产一个就-1,即P(empty)
//消费者每消费一个就+1,即V(empty)
semaphore empty = n;
//同步信号量,表示缓冲区的当前数量,初始缓冲区为空,即full = 0
//生产者每生产一个就+1,即V(full)
//消费者每消费一个就-1,即P(full)
semaphore full = 0;

producer() {
    while(1) {
        生产一个产品;
        P(empty);    //检查缓冲区的空闲数量,有空闲则 - 1;没有则阻塞
        P(mutex);    //开始访问缓冲区(互斥)
        把产品放入缓冲区;
        V(mutex);    //结束访问缓冲区
        V(full);    //缓冲区当前数量 + 1
    }
}

consumer() {
    while(1) {
        P(full);    //检查缓冲区的当前数量,有产品则 - 1;没有则阻塞
        P(mutex);    //开始访问缓冲区(互斥)
        将产品从缓冲区中取出;
        V(mutex);    //结束访问缓冲区
        V(empty);    //缓冲区空闲数量 + 1
    }
}

6.实现互斥的P操作一定要在实现同步的P操作之后:防止死锁

7.生产/使用产品最好在PV操作之前或之后,这样就可以减少临界区的代码数量,降低访问临界区的时间,从而提高整体的实行速度

8.V操作并不会导致进程阻塞

3.8.多生产者-多消费者

1.互斥关系:对盘子(缓冲区)的访问是互斥的

2.同步关系:

①父亲先放苹果,女儿后取苹果

②母亲先放橘子,儿子后取橘子

③女儿/儿子取出水果后(缓冲区we),父亲/母亲才能放水果

semaphore orange = 0;    //同步关系,盘子中橘子的数量
semaphore apple = 0;    //同步关系,盘子中苹果的数量
semaphore plate = 1;    //同步关系,盘子中水果的数量
semaphore mutex = 1;    //互斥关系,实现对盘子(缓冲区)的互斥访问

dad() {
    while(1) {
        准备一个苹果;
        P(plate);    //检查盘子是否还能放水果,能则 - 1;不能则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将苹果放入盘子;
        V(mutex);    //结束使用盘子
        V(apple);    //苹果数量 + 1
    }
}

mom() {
    while(1) {
        准备一个橘子;    
        P(plate);    //检查盘子是否还能放水果,能则 - 1;不能则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将橘子放入盘子;
        V(mutex);    //结束使用盘子
        V(orange);    //橘子数量 + 1
    }
}

son() {
    while(1) {
        P(apple);    //先检查苹果数量是否够,够则 - 1;不够则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将苹果取出盘子;
        V(mutex);    //结束使用盘子
        V(plate);    //盘子中水果数量 - 1
        吃掉苹果;
    }
}

daughter() {
    while(1) {
        P(orange);    //先检查橘子数量是否够,够则 - 1;不够则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将橘子取出盘子;
        V(mutex);    //结束使用盘子
        V(plate);    //盘子中水果数量 - 1
        吃掉橘子;
}

3.当缓冲区(此题为盘子)数量 = 1时,有时可以不需要用mutex实现互斥访问(此题就行);而当缓冲区数量 > 1时,则必须要用mutex实现互斥访问(最好都使用mutex)

4.要用事件角度思考同步关系 

3.9.吸烟者问题

1.互斥关系:供应者和吸烟者需要互斥的访问桌子(此题中缓冲区大小为1,因此不需要单独设置mutex实现互斥访问)

2.同步关系:

(1)吸烟者①②③都需要在供应者分别在桌上提供相应的组合才能取走东西(吸烟者①对应offer1;吸烟者②对应offer2;吸烟者③对应offer3。即每个吸烟者对应的offer信号不同

(2)每个吸烟者完成后需要给一个完成信号告诉供应者可以开始下次供应(即每个吸烟者的完成信号相同

3.通过对i信号变量的自增和取余操作实现吸烟者①②③的轮流吸烟操作

semaphore offer1 = 0;    //同步信号,桌上组合一的数量
semaphore offer2 = 0;    //同步信号,桌上组合二的数量
semaphore offer3 = 0;    //同步信号,桌上组合三的数量
semaphore finish = 0;    //同步信号,当前抽烟者是否完成抽烟
int i = 0;    //用于实现吸烟者轮流抽烟

provider() {
    while(1) {
        if (i == 0) {    //当i = 0时
            将组合一放到桌上;
            V(offer1);    //同步信号,提供组合一,即组合一数量 + 1
        }
        else if (i == 1) {//当i = 1时
            将组合二放到桌上;    
            V(offer2);    //同步信号,提供组合二,即组合二数量 + 1
        }
        else {//当i = 2时
            将组合三放到桌上;
            V(offer3);    //同步信号,提供组合三,即组合三数量 + 1
        }
        P(finish);    //同步信号,等待某个吸烟者完成吸烟
        i = (i + 1) % 3;    //每次循环先执行i = i + 1,然后i对3取余
    }
}

smoker1() {
    while(1) {
        P(offer1);    //同步信号,检查桌上的组合是不是组合一,是,则拿走;不是,则阻塞
        拿走桌上的组合一;
        卷烟;
        抽烟;
        V(finish);    //完成吸烟,给供应者发出完成信号
    }
}


smoker2() {
    while(1) {
        P(offer2);    //检查桌上的组合是不是组合二,是,则拿走;不是,则阻塞
        拿走桌上的组合二;
        卷烟;
        抽烟;
        V(finish);    //同步信号,完成吸烟,给供应者发出完成信号
    }
}

smoker3() {
    while(1) {
        P(offer3);    //同步信号,检查桌上的组合是不是组合三,是,则拿走;不是,则阻塞
        拿走桌上的组合三;
        卷烟;
        抽烟;
        V(finish);    //同步信号,完成吸烟,给供应者发出完成信号
    }
}

3.10.读者写者问题

1.允许多个读者可以同时对文件进行读操作:读操作不会改变数据

2.同一时间只允许写者对文件进行写操作:同时写,发生覆盖

3.任一写者在完成写操作前不允许其他写者或者读者工作:同时写,发生覆盖;同时读写,导致读出的数据不一致

4.写者进行写操作前,已有的写者和读者必须停止工作:同时写,发生覆盖;同时读写,导致读出的数据不一致

5.互斥关系:写-读、写-写读-读可以同步进行

6.申明一个count变量用于记录当前有几个读者正在进行读操作:由第一个将要进行读操作的读者负责读/写互斥访问操作,即上锁,并且每个将要进行读操作的读者都需要判断自己是不是第一个将要进行读操作的读者,即判断count是否为0

7.对count变量的判断和加锁需要一气呵成,即需要用mutex实现对count访问的互斥操作:如果不能一气呵成进行,则可能发生当count = 0时,在读者A对count进行判断后,执行上锁操作前,切换成读者B进程,读者B也对count进行判断,然后进行上锁操作,此时就将导致读者A阻塞(因为读者B进行P(rw)操作后,rw = 0,切换回读者A进程后,读者A也要进行P(rw),而此时rw = 0,阻塞,需要等待读者B及后续可能存在的其他读者进程完成读操作才能进行读者A的读操作)

8.读写应该公平,防止写进程饿死:增加一个同步信号w,w初始值为1;其作用就是当有写者被阻塞时,该写者之后出现的读者进程将被阻塞,等待①之前的读者全部读完 ②读者开始写并写完 ,这些才能被唤醒,即执行读操作(即实现按顺序进行读/写操作)

①读者A→读者B:读者B在读者A执行V(W)前会被阻塞在P(W),读者A执行V(W)后读者B被唤醒

②写者A→写者B:写者B在写者A执行V(W)前会被阻塞在P(W),写者A执行V(W)后写者B被唤醒

③写者A→读者B:读者B在写者A执行V(W)前会被阻塞在P(W),写者A执行V(W)后读者B被唤醒

④读者A→写者B→读者C:读者A开始读文件时,也就意味着按顺序执行了P(W)、P(RW)和V(W);写者B可以正常执行P(W),但会被阻塞在P(RW)读者C将会被阻塞在P(W)

读者A在读完文件后(即执行V(RW)后),写者B将会被唤醒;在读者B写完文件后(即执行V(W)后),读者C将会被唤醒

如果没有添加同步信号w,将会写者B将会在读者C后执行

⑤写者A→读者B→写者C:写者A开始写文件时,也就意味着按顺序执行了P(W)和P(RW);读者B将会被阻塞在P(W)写者C也将会阻塞在P(W)

由于信号量具有排队功能,读者B执行P(W)后W = -1,写者C执行P(W)后W = -2(即读者B在写者C之前执行P(W)),写者A在写完文件后(即执行V(W)后),先唤醒读者B,读者B在读完文件后(即执行V(W)后),再唤醒写者C

semaphore w = 1;    //互斥信号,实现读/写公平
semaphore rw = 1;    //互斥信号,实现读/写不同时进行
semaphore mutex = 1;    //互斥信号,实现读者对count变量的互斥访问
int count = 0;    //标记当前有几个读者正在进行读操作

writer() {
    while(1) {
        P(w);    //检查是否有其他读/写者,有则排队
        P(rw);    //检查是否有读者正在读
        写操作;
        V(rw);    
        V(w);
    }
}

reader() {
    while(1) {
        P(w);    //检查是否有其他读/写者,有则排队
        P(mutex);    //互斥访问count变量
        if (!count) P(rw);    //若是第一个读者,则上锁
        count++;    //读者数量 + 1
        V(mutex);
        V(w);
        读操作;
        P(mutex);    //互斥访问count变量
        count--;    //读者数量 - 1
        if (!count) V(rw);    //若是最后一个读者,则解锁
        V(mutex);
    }
}

3.11.哲学家进餐问题

有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。

约束条件

(1)只有拿到两只筷子时,哲学家才能吃饭

(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子

(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子

1.每个进程需要两个或者多个临界资源

2.①限制最多有4个哲学家进餐

semaphore cur = 4;    //表示当前最多还能允许几个人几餐
semaphore chopstick[5] = {1, 1, 1, 1, 1};
i() {    //第i个哲学家
    while(1) {
        P(cur);
        P(chopstick[i]);
        P(chopstick[(i + 1) % 5];
        进餐;
        V(chopstick[i]);
        V(chopstick[(i + 1) % 5];
        V(cur);
    }
}

用mutex实现对临界资源(拿筷子)的互斥访问:如果当前A正在进行拿左边筷子的操作,即A已经执行了P(mutex),但还未执行V(mutex),此时mutex = 0,若发生进程切换,切换到B后,将会阻塞在P(mutex),也就不会发生死锁,直到切换回A执行完V(mutex)后,B将会被唤醒

semaphore mutex = 1;    //互斥信号,用于实现一次仅能有一个人进行拿筷子的操作
semaphore chopstick[5] = {1, 1, 1, 1, 1};
i() {
    while(1) {
        P(mutex);    
        P(chopstick[i]);
        P(chopstick[(i + 1) % 5];
        V(mutex);
        进餐;
        V(chopstick[i]);
        V(chopstick[(i + 1) % 5];
    }
}

3.12.管程

1.管程用于实现进程的同步和互斥(与信号量作用相同,但更加方便)

2.管程是一种特殊的软件模块,由①②③④组成:(类似于类)

①局部于管程的共享数据结构说明(例如生产者和消费者模型中的缓冲区,可以通过数据结构表示该缓冲区,对该缓冲区进行管理;若使用,则需要定义一种与该缓冲区相对应的数据结构)

②对该数据结构进行操作的一组过程(过程可以理解为函数,即管程中需要有一组对①中数据结构进行操作的函数)

③对局部于管程的共享数据设置初始值的语句(管程中需要有对①中数据结构初始化的语句)

④管程需要有个名字

3.管程的基本特征:

①局部于管程的数据只能被局部于管程的过程所访问

②一个进程只有通过调用管程内的过程才能进入管程访问共享数据

每次仅允许一个进程在管程内执行某个内部过程(管程中虽然可能定义了很多个函数,但是同一时间只可能有一个进程在使用管程的某个函数,其余的进程若想执行管程的其他函数,需要等待该进程执行完该管程函数)

①②的意思是:管程中定义的数据结构只能被管程的函数修改,即想要修改管程中的数据需要调用管程的函数间接修改

③的作用是实现对缓冲区(临界资源)的互斥访问

4.死锁

4.1.死锁的概念

1.死锁:在并发环境下,各进程因抢占资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,无法继续执行的现象(每个进程都等待对方手里的资源,但是不放弃自己手里的资源,如果没有外力干涉,这些进程就无法继续向前推进)

2.死锁、饥饿、死循环的区别和联系:

3.死锁产生的必要条件:

互斥条件:只有对必须互斥使用的资源的争夺才会导致死锁的发生(哲学家的筷子);可以多个进程同时使用的资源不会发生死锁(内存)

不剥夺条件:进程所获得的资源在其使用完之前,不能由其他进程强行拿走,只能主动释放

请求和保持条件:进程已经至少持有一种资源的条件下,又申请了别的资源的请求,而该资源被其他进程所占有,此时请求进程被阻塞,但是对该进程已持有资源保持不放

循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求(例如哲学家问题中,若每个人都持有左边的筷子,都在等待右边的筷子)

同类资源 > 1时:发生死锁时一定有循环等待链,但有循环等待链不一定发生死锁(必要非充分条件);同类资源 = 1时,循环等待链的出现一定导致死锁的发生,死锁的发生一定有循环等待链(充分必要条件)

4.死锁的处理策略:

预防死锁:破坏死锁产生的四个必要条件中的一个或多个

避免死锁:用某种算法防止系统进入不安全状态,从而避免死锁(银行家算法)

死锁的检测和解除:允许死锁发生,操作系统会检测出死锁的发生,并且解除死锁        

4.2.预防死锁

1.破坏互斥条件:

2.破坏不剥夺条件:

3.破坏请求和保持条件:导致饥饿是因为如果有源源不断的新的A类和B类进程,那么C类进程就将永远无法执行,即饥饿 

4.破坏循环等待条件:

①实际使用资源的顺序可能和编号递增顺序不一致会导致资源浪费的原因:假设在实际使用过程中进程P3是先使用7号后使用5号,但根据编号递增顺序的要求,P3必须先申请使用5号,再申请使用7号,则在P3实际运行中5号某些时间是空闲的

②用户编程麻烦的原因:不同主机对不同设备的编号可能不同,但编号的不同会影响申请资源的顺序,即程序需要根据编号的不同而变化

4.3.避免死锁(银行家算法)

1.安全状态→不会死锁(充分条件);死锁→不安全状态(不安全状态并不一定导致死锁)

2.只要有一个安全序列,就是安全状态(安全序列可能有多个)

3.银行家算法的思想:在资源分配前先预判这次分配是否会导致进入不安全状态,若会,则不进行此次分配‘若不会,则进行此次分配

4.①找到(3,3,2)能满足的进程→P1、P3加入安全队列,并且将P1、P3已分配资源拿回→(7,4,3)

②找到(7,4,3)能满足的进程(除P1、P3外)→P0、P2、P4都可以→安全队列:P1、P3、P0、P2、P4(不唯一)

 

4.4.死锁的检测和解除

4.4.1.死锁的检测

1.进程结点(圆):一个结点对应一个进程

资源结点(矩形):一个结点对应一类资源,该结点中的一个小结点(矩形中的圆)对应该类资源的一个资源(三个小结点则表示该类资源共有三个)

请求边(进程指向资源的边):表示该进程对该类资源的请求,一条边对应一个资源

分配边(资源指向进程的边):表示该类资源对该进程已经分配了几个资源,一条边对应一个资源

2.如果能消除所有边,则一定不会发生死锁;如果不能消除所有边,则发生死锁

(1)可以消除所有边,即不会发生死锁:

P1向R2请求一个资源,P2向R1请求一个资源;R1给P1分配了两个资源,R2给P2分配了一个资源

①P2申请R1的1个资源,而R1此时没有剩余资源,因此P2阻塞

②R2剩余1个资源,可以满足P1的申请→P1执行完后,就可以将之前占用的两个R1归还

③R1剩余2个资源,可以满足P2的申请,即P1和P2的运行不会发生死锁

(2)不可以消除所有边,即发生死锁:

P1请求R2的两个资源,而R2的两个资源一个分配给P2,另一个分配给P3(P3的R2资源可以归还,但仍差一个);P2请求R1的一个资源,而R1的三个资源两个分配给P1,一个分配给R1,无空闲的R1资源,发生死锁

3.检测死锁的方法:

①在资源分配图中,找到既不阻塞(该点申请资源的数量可以满足它的需求,如P1)又不是孤点(该点至少与一个有向边相连,P1和P2都不是孤点)的进程:P1符合条件

②消去该进程的所有请求边和分配边,使之成为孤点:消去P1所有的边,P1成为孤点

③消去该进程的边即释放该点的资源,因此,可以唤醒某些等待这些资源而被阻塞的进程:R1此时可以满足P2的申请,P2也可以消去所有的边,成为孤点

④所有边都可以消去,则该图是可完全简化;若该图是不可完全简化,则系统死锁

4.4.2.死锁的解除

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值