操作系统学习第二章进程管理

第二章进程管理

一、进程

1.1概念

  • 程序:是静态的,存放在磁盘里的可执行文件,即一系列指令集合。

  • 进程:是动态的,是程序的一次执行过程。

  • 在进程被创建时,操作系统会为进程分配一个“唯一且不重复”的标识,并且为了使每个程序都能独立运行,都配置了一个专门的数据结构来存储进程执行所需要的信息。这个数据结构称为进程控制块(PCB)

  • 所谓创建进程,实质上就是创建进程实体中的PCB。系统利用PCB来对进程进行控制和管理。

  • PCB:是进程存在的唯一标识,存储了操作系统管理和控制进程的所有信息

  • 进程实体:PCB+程序段+数据段

1.2进程特征

①动态性:是进程最基本的特征。进程是程序的一次执行过程,是动态产生和消亡的。

②并发性:内存中由多个进程实体,各进程可并发执行。

③独立性:进程是能独立运行、独立获得资源、独立接受调度的基本单位。

④异步性:各进程各自独立、不可预知的速度来向前推进。操作系统需要”进程同步机制“解决异步问题。

⑤结构性:每个进程都配有PCB。进程由PCB、程序段、数据段组成。

1.3进程的状态和转换

  • 创建态:进程正在被创建时,操作系统为进程分配资源、初始化PCB。
  • 就绪态:进程创建完成后,进入“就绪态”,已经具备了运行条件等待CPU分配资源。
  • 运行态:进程在CPU上运行时,为“运行态”。CPU执行该进程对于的程序。
  • 阻塞态:进程在运行时发生某事件暂时无法继续执行,程序进入“阻塞态”并让出CPU资源。当事件处理完成后,进程会转为“就绪态”。
  • 终止态:一个进程执行结束,可以执行exit系统调用,请求系统终止进程,进程进入“终止态”,操作系统回收内存空间、PCB等资源。

操作系统学习第二章进程管理_第1张图片

  • 运行态→阻塞态:进程自身主动的行为。

  • 阻塞态→就绪态:等待其他资源分配后才能转为就绪态,所以这是一种被动行为。

  • 运行态→就绪态:当时间片到,或处理机被强占时,进行会从运行态转为就绪态。

  • 单核CPU情况下,同一时刻只会有一个进程处于运行态,多核CPU情况下,可能有多个进程处于运行态。

1.4进程的组织

  • 为了对同一个状态下的各个进程进行统一管理,操作系统会把各个进程的PCB组织起来。

  • 进程的组织方式有:

    ①链式方式:把PCB分为多个队列,操作系统持有指向各个队列的指针。

    ②索引方式:根据进程状态不同,建立几张索引表,操作系统持有指向各个索引表指针。

①链式(接)方式

  • 执行、就绪、阻塞三个状态为三个队列,

操作系统学习第二章进程管理_第2张图片

  • 操作系统会根据阻塞原因不同,分为多个阻塞队列。如:等待打印机、等待磁盘等。

②索引方式

  • 为就绪、阻塞建立相应的索引表

操作系统学习第二章进程管理_第3张图片

1.5进程控制

  • 进程控制:主要功能是对系统中所有的进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。

  • 简单来说,进程控制就是要实现进程状态的转换。

  • 实现进程控制:用“原语”实现,原语具有”原子性“,即执行必须一气呵成,期间不可中断。可以用“关中断”和“开中断”这两个特权指令实现。

  • 进程控制主要有:进程创建、进程终止、进程阻塞和唤醒。

(1)进程创建:创建态→就绪态

  • 系统出现创建新进程请求后,调用原语Creat需要完成进程创建,需要完成功能:

​ ①申请空白PCB → ②为新进程分配需资源 → ③初始化PCB → ④将PCB插入就绪队列

  • 引起进程创建事件:

    ①用户登录:分时系统中,用户登录成功后,系统会为其建立一个新的进程

    ②作业调度:多道批处理系统中,有新作业放入内存时,会为其建立一个新进程。

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

    ④应用请求:由用户进程主动请求创建一个子进程。

(2)进程终止:就绪态/阻塞态/运行态→终止态

  • 系统需要终止一个进程,调用原语Termination终止进程,需要完成功能:

    ①从PCB集合中找到终止进程的PCB → ②若程序正在进行,剥夺CPU,将CPU分配给其他进程 → ③终止其所有子进程

    →④将该进程拥有的所有资源归还给父进程或操作系统 → ⑤删除PCB

  • 引起进程终止的事件:

    ①正常结束:表示进程的任务已完成并准备退出运行。

    ②异常结束:在进程执行过程中,发生了某种异常事件,如除0、非法使用特权指令等。

    ③外界干预:进程应外界的请求终止执行,如系统操作员干预、父进程请求和父进程终止。

(3)进程阻塞和唤醒:运行态→阻塞态;阻塞态→就绪态

  • 进程进入阻塞时,调用原语Block,需要完成功能:

    ①找到对于的PCB → ②保护进程运行现场,将其设置为“阻塞态” ,暂时停止进程执行→ ③PCB插入相应事件的等待队列

  • 引起进程阻塞事件:

    ①需要等待系统分配资源

    ②需要等待合作的其他进程完成工作

  • 阻塞要进入就绪态时,调用原语Wakeup唤醒进程,需要完成功能:

    ①在事件等待队列中找到PCB → ②将PCB从等待队列移除,设置进程为就绪态 → ③将PCB插入就绪队列,等待被调度

  • 引起进程唤醒事件:等待事件的发生

(4)进程的切换:运行态→就绪态;就绪态→运行态

  • 进程需要切换状态时候,需要完成功能:

    ①将运行环境信息存入PCB → ②PCB移入相应队列 → ③选择另一个进程执行,更新PCB → ④根据PCB恢复进程所需要环境

  • 引起进程切换事件:

    ①进程时间片到

    ②有更高优先级的进程到达

    ③当前进程主动阻塞

    ④当前进程终止

1.6进程通信

  • 进程间通信(IPC):指两个进程之间产生数据交互。

  • 进程是分配系统资源的单位,所以各进程的内存地址空间相互独立。为了保证安全,一个进程不能访问另一个进程的地址空间。若两个进程需要进行数据交互,必须要有操作系统支持才能完成进程通信。

  • 进程通信方式:共享存储、消息传递、管道通信

(1)共享存储

  • 共享存储:通信的进程之间存在一块可直接访问的共享空间,不同进程通过对共享空间进行读/写操作实现进程之间信息交换。

  • 为避免出错,各进程对共享空间的访问是互斥的。各进可使用操作系统内核提供的同步互斥工作(P、V操作)。

  • 基于存储区的共享:操作系统划分出一块共享存储区,数据交互由通信进程控制,不由操作系统。这种方式灵活性高、速度快,是一种高级通信方式。

  • 基于数据结构的共享:如共享空间只能存放一个长度为10的数组,这种共享方式灵活性低、速度慢、限制多,是一种低级通信方式。

(2**)消息传递**

  • 消息传递:进程间的数据交换以“格式化的消息”为单位。通过操作系统提供的“发送消息/接收消息”两个原语进行数据交换。

  • 格式化消息:消息头+消息体

    ①消息头:发送进程ID、接收进程ID、消息长度等格式化信息

    ②消息体:要发送的数据

  • 消息传递方式:

    ①直接通信方式:消息发送进程要指明接收进程的ID。

    【例】在PCB中有一个进程的消息队列,进程P要给进程Q发送通信,

    进程P通过将数据打包成“格式化消息“,通过原语send发送到进程Q的PCB中消息队列,进程Q通过receive原语接收。

    ②间接通信方式:通过“信箱“间接的通信,又称”信箱通信方式“。

    【例】进程P向操作系统申请一个信箱,可申请多个信箱。

    进程P将数据打包成“格式化消息“,通过send原语往信箱发送消息,进程Q使用receive从信箱A接收消息。

(3)管道通信

image-20230910224141442

  • “管道”是一个特殊的共享文件,又名pipe文件。这里在内存中开辟一个大小固定的内存缓冲,只能单向写数据单向读数据。

  • 管道数据是先进先出调用,也就是数据结构中的“队列”。各个进程要互斥进行管道访问。

  • 与共享存储的区别在于:中间的内存有无读写限制。

  • 管道只能采用半双工通信:即某一个时间内只能实现单向传输,如果要实现双向同时通信,则需要设置两个管道。

  • ①如果管道写满:写进程将被阻塞,直到读进程将管道的数据取走,即可唤醒写进程。

    ②如果管道读空:读进程将被阻塞,直到写进程将管道写入数据,即可唤醒读进程。

  • 一旦管道数据被读出,就会彻底消失。当多个进程读同一个管道时,出现错乱。对此解决方案:

    ①一个管道允许多个写进程,只一个读进程。

    ②允许有多个写进程,多个读进程,但系统会让各个读进程轮流从管道中读数据。

二、线程

  • 有的进程可能需要“同时"做很多事,而传统进程只能串行执行一系列程序。为了增加并发度,在进程中引入了”线程“,即每个进程可以有多个线程。
  • 线程:是一个基本的CPU执行单元,也是程序执行流的最小单位。
  • 引入线程后,进程是资源分配的基本单位,线程是调度的基本单位。

2.1线程实现方式

用户级线程:这是一种由线程库来实现的,在用户态中实现的。

一个简单的线程库如下C语言代码:

int main(){
    int i =0;
    while(true){
        if(i==0){处理第一个线程的代码;}
        if(i==1){处理第一个线程的代码;}
        if(i==2){处理第一个线程的代码;}
        i = (i+1) %3;
    }
}
  • 用户级线程使用线程库来实现,不是操作系统。

  • 线程切换不用不需要进程CPU用户态和核心态的转换,不用操作系统进行干涉。

  • 优点:用户级线程不需要切换到核心态,线程管理系统开销小,效率高

  • 缺点:当一个用户级线程被阻塞后,整个进程会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行。

内核级线程:由操作系统支持的线程

  • 内核级线程管理工作由操作系统来完成,线程需要从用户态转变为内核态
  • 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多个线程可以在多核处理机上并行运行。
  • 缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,线程管理成本高开销大。

2.2多线程模型

  • 将用户级线程和内核级线程相结合,在内核级线程引入线程库后,若干个用户级线程映射到内核级线程映射关系引出了多线程模型。

一对一模型:一个用户级线程映射到一个内核级线程。就像一个纯粹的内核级线程

  • 优点:当一个线程被阻塞后,别的线程可以继续执行,并发能力强。
  • 缺点:一个用户进程占用多个内核级线程,线程切换需要切换到核心态,系统开销大。

操作系统学习第二章进程管理_第4张图片

多对一模型:多个用户级线程映射到一个内核级线程,一个进程只分配一个内核级线程。就像一个纯粹的用户级线程

操作系统学习第二章进程管理_第5张图片

  • 优点:用户级线程的切换在用户空间即可完成,不需要转换为核心态,系统开销小。
  • 缺点:一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行。

多对多模型:n个用户级线程映射到m个内核级线程,一个用户进程分配m个内核级线程。n≥m。

  • 克服了多对一模型并发度不高的缺点,也客服了一对一模型占用太多内核级线程开销大的缺点。

操作系统学习第二章进程管理_第6张图片

2.3线程状态和转换

  • 线程的状态与转换与进程的状态与转换完全一致。

操作系统学习第二章进程管理_第7张图片

2.4线程的组织与控制

  • 线程组织与控制于进程组织与控制基本一致。

操作系统学习第二章进程管理_第8张图片

三、处理调度

3.1调度

  • 当有一堆任务要处理,由于资源有限没法同时处理。则需要确定某种规则来决定处理这些任务的顺序,即“调度”所要研究的问题。

  • 调度的三个层次:

    高级调度(作业调度):按照一定原则从外存的作业后备队选一个作业调入内存,并创建进程。对于每个作业只调入一次、调出一次。作业调入时创建PCB,调出时才撤销PCB。

    *作业:一个具体的任务,提交一个作业相当于让给操作系统启动一个程序

    中级调度(内存调度):按照某种策略将挂起状态的进程重新调入内存。一个进程可能会被多次调出、调入内存。

    *当内存不够时,将某些进程调到外存,进程状态为挂起状态。被挂起的进程PCB会组织成挂起队列。

    低级调度(进程调度):按照某种策略从就绪队列中选取一个进程,将处理机分配被它。进程调度是最基本的一种调度,频率很高。

    操作系统学习第二章进程管理_第9张图片

【补充】七状态模型

操作系统学习第二章进程管理_第10张图片

  • 暂时被调到外存等待的进程状态为挂起状态,挂起态又可分为就绪挂起、阻塞挂起两种状态。
  • “挂起”与“阻塞”区别为都是不能获的CPU服务,但阻塞态还在内存中,挂起是进程映像被调到外存中。

3.2调度算法指标

1.CPU利用率

  • CPU利用率:指CPU“忙碌”的时间占总时间的比例

利用率 = 有效工作时间 有效工作时间 + 等待时间 利用率=\frac{有效工作时间}{有效工作时间+等待时间} 利用率=有效工作时间+等待时间有效工作时间

2.系统吞吐量

  • 系统吞吐量:单位时间内完成作业的数量

系统吞吐量 = 总共完成作业的数量 总共花的时间 系统吞吐量=\frac{总共完成作业的数量}{总共花的时间} 系统吞吐量=总共花的时间总共完成作业的数量

3.周转时间

  • 周转时间:作业被提交给系统开始到作业完成这段时间。包括:作业在外存等待作业调度时间、进程在就绪队列等待进程调度时间、进程在CPU执行时间、进程等待I/O操作完成时间。

周转时间 = 作业完成时间点 − 作业提交时间点 平均周转时间 = 各作业周转时间之和 作业数量 带权周转时间 = 作业周转时间 作业实际运行的时间 平均带权周转时间 = 各作业带权周转时间之和 作业数量 周转时间=作业完成时间点-作业提交时间点 \\ 平均周转时间=\frac{各作业周转时间之和}{作业数量} \\ 带权周转时间=\frac{作业周转时间}{作业实际运行的时间} \\平均带权周转时间=\frac{各作业带权周转时间之和}{作业数量} 周转时间=作业完成时间点作业提交时间点平均周转时间=作业数量各作业周转时间之和带权周转时间=作业实际运行的时间作业周转时间平均带权周转时间=作业数量各作业带权周转时间之和

4.等待时间

  • 等待时间:等待处理机时间之和。对于进程来说,指进程建立后等待被服务的时间和。对于作业来说,建立进程后的等待时间加上外存队列等待的时间。

5.响应时间

  • 响应时间:从用户提出请求到首次产生响应所用时间。

3.3调度实现

(1)进程调度的时机

  • 需要进行进程调度和切换情况:

    ①主动放弃:进程正常终止、运行过程中发生异常而终止、进程主动请求阻塞

    ②被动放弃:分给进程时间片用完、有更紧急的事件要处理、有更高级的进程进入就绪队列

  • 不能进行进程调度与切换情况:

    ①在处理中断的过程中,由于中断过程复杂且与硬件相关,很难在中断处理过程进行进程切换

    ②进程在操作系统内核程序临界区中**(注意,这里不是普通的临界区,是内核程序的临界区)**

    ③在原语执行过程(原子操作过程)中

】临界资源:一个时间内只允许一个进程使用的资源,各进程需要互斥访问临界资源。

​ 临界区:访问临界区资源的那段代码

​ 内核程序临界区:用来访问某种内核数据结构,如进程的就绪队列。没有退出访问就绪序列时,就绪序列为上锁状态,此时进程调度也需要访问就绪序列,导致无法顺利进行进程调度。

(2)进程调度方式

①非剥夺调度方式:又称非抢占式。只允许进程主动放弃处理机,若有更紧迫的任务到达,必须等待当前进程终止或者主动请求进入阻塞态才能执行。无法及时处理紧急任务,适用于早起的批处理系统。

②剥夺调度方式:又称抢占式。当一个进程在处理机上执行时,若有更紧迫的任务到达,立刻暂停正在执行的进程,将处理器分给更紧迫的任务。可优先处理紧急任务,适用于分时操作系统、实时操作系统。

3.4调度算法

  • 饥饿:某个进程/作业长期得不到服务

1.先来先服务(FCFS)

【算法思想】按照到达顺序先后顺序进行服务,这是一个非抢占式算法。不会导致饥饿

适用:作业调度、进程调度

优点:算法实现简单。

缺点:对于排在长作业后面的短作业需要等待长时间,对长作业有利,对短作业不利。

2.短作业优先(SJF)

【算法思想】最短的作业/进程优先服务,也就是剩余时间短的进程优先。

适用:作业调度、进程调度

分为非抢占式算法和抢占式算法:

①非抢占式:执行完一个作业/进程后,就绪队列中剩余时间短的优先执行。

②抢占式:每当有进程加入就绪队列,或者当一个进程完成时,需要调度,如果新到达的进程剩余时间比当前剩余时间更短,则抢占处理机。

优点:可以使平均等待时间、平均周转时间“最短”

缺点:不公平。对短作业有利,做长作业不利。可能会出现饥饿现象

3.高响应比优先(HRRN)

【算法思想】每次调度时计算各作业/进程的响应比,选择响应比最高的作业/进程为其服务。这是一个非抢占式算法

适用:作业调度、进程调度

响应比=(等待时间+要求服务时间)÷要求服务时间

优点:不会导致饥饿现象

4.时间片轮转(Round-Robin)

【算法思想】按照进程到达就绪队列的顺序,轮流让各个进程执行一个时间片。进程在一个时间片未执行完则剥夺处理机,将进程重新放到就绪队列队尾重新排队。**这是一种抢占式算法。**由时钟装置发出时钟中断来通知CPU时间片已到。

适用:进程调度(只有作业放入内存建立了相应的进程后,才能被分配处理机时间片)

优点:响应时间快,常用于分时操作系统不会导致饥饿现象。

缺点:进程切换频率较高,有一定开销。

5.优先级调度

【算法思想】根据作业/进程优先级,调度时选择优先级最高的作业/进程。

适用:作业调度、进程调度。

分为非抢占式算法和抢占式算法:

①非抢占式:指需在进程主动放弃处理机时进行调度

②抢占式:在就绪队列变化时,检查是否发生抢占

优点:用优先级来区分紧急、重要程度,适用于实时操作系统

缺点:可能会导致饥饿现象。

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

【算法思想】

①设置多级就绪队列,队列之间优先级按照从高到低,时间片从小到大。

②新进程到达时先进入第1级队列,按照先来先服务排队分配时间片,时间片用完进程未结束时进入下一级队列队尾。如果已经是最下级,重新放回该队列队尾。

③只有第k级队列空时,才会为k+1级队头进程分配时间片

④这是一种抢占式算法

操作系统学习第二章进程管理_第11张图片

缺点:会导致饥饿现象

7.多级调度算法

【例子】

操作系统学习第二章进程管理_第12张图片

  • 队列之间有两种划分方式:

    ①固定优先级:高优先级队列为空才调度低优先级队列。这是不合理的

    ②时间片划分:三个队列分配时间,如50%、40%、10%。这样能保证在一段时间内,每个队列都可能会被访问一次。

  • 队列内可采用不同调度策略,例如:

    ①系统进程队列采用优先级调度

    ②交互式队列采用时间片轮转

    ③批处理队列采用先来先服务

四、进程同步

4.1概念

(1)进程同步

  • 由于并发必然导致异步,为了解决异步问题引入了同步。
  • 同步:直接制约关系,完成进程需要在某些位置上协调他们的工作次序而产生的制约关系。

(2)进程互斥

  • 临界资源:在一段时间内只允许一个进程使用的资源。

  • 互斥:间接制约关系,当一个进程进入临界区使用临界资源,另一个进程必须等待。当前访问的进程访问结束后才能进行访问。

(3)临界资源

对临界资源的互斥访问,在逻辑上分为四个部分:

①进入区:负责检查是否可进入临界区,若可以进入,则设置正在访问临界资源标志(上锁),用来阻止其他进程同时进入临界区。

②临界区:访问临界资源的那段代码。

③退出区:负责解除正在访问临界资源的标志

④剩余区:做其他处理。

注:临界区是进程访问临界资源代码段;进入区和退出区负责实现互斥的代码段。临界区也称临界段。

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

(4)互斥规则

  • 为了实现临界资源的互斥访问,保证系统整体性能,需要遵循以下规则:

    ①空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程进入临界区

    ②忙则等待:当已有进程进入临界区时,其他请求进入临界区的进程必须等待

    ③有限等待:对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿)

    ④让权等待:当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。

4.2进程互斥软件实现

1.单标志法

【算法思想】两个进程在访问完临界区后会把使用临界区权限转交给另一个进程。即每个进程进入临界区权限只能由另一个进程赋予。

int turn=0;  //用来表示当前进入临界区的进程号
 
//p0进程                   //p1进程
while(turn !=0 );         while(turn !=1 );  //进入区
critical section;         critical section;  //临界区
turn = 1;                 turn = 0;          //退出区
remainder section;        remainder section;  //剩余区

turn初始值为0,即刚开始只允许0号进程进入临界区。

①若P1进程先上处理机运行,则会卡在while(turn !=1),直到p1时间片用完,发生调度,切换p0上处理机运行。

②代码while(turn !=0),不会不会卡住,p0正常访问临界区。

③如果p0访问临界区期间切换回p1,p1依旧会卡主。

④只有p0在退出区将turn改为1后,p1才能进入临界区。

综上:此算法能够实现**“同一时刻最多只允许一个进程访问临界区”**。

注:这个算法违反了“空闲让进”

2.双标志先检查法

【算法思想】设置一个布尔型数组flag[],用来标记各进程想进入临界区的意愿。如"flag[0] = true"表示0号进程想要进入临界区。在检查后进行上锁。

bool flag[2];  //设置进入临界区意愿的数组
flag[0] = false; 
flag[1] = false;  //刚开始设置两个进程都不想进入临界区

//p0进程                    //p1进程
while(flag[1]);            while(flag[0]);   //①进入临界区前检查有没有别的进程想进入临界区
flag[0] = true;            flag[1] = true;   //②如果没有别的进程进入临界区,标记自身想要进入临界区(上锁)
critical section;          critical section; //访问临界区
flag[0] = flase;           flag[1]=flase;    //退出区
remainder section;         remainder section; 

这里的①合②都属于进入区。

注:这个算法违反了“忙则等待”:如果p0执行完while,p1执行while,接着p0执行标记,p1再进行标记是可以进行标记的。

3.双标志后检查法

【算法思想】设置一个布尔型数组flag[],用来标记各进程想进入临界区的意愿。如"flag[0] = true"表示0号进程想要进入临界区。先上锁后再检查。

bool flag[2];  //设置进入临界区意愿的数组
flag[0] = false; 
flag[1] = false;  //刚开始设置两个进程都不想进入临界区

//p0进程                    //p1进程
flag[0] = true;            flag[1] = true;   //②如果没有别的进程进入临界区,标记自身想要进入临界区(上锁)
while(flag[1]);            while(flag[0]);   //①进入临界区前检查有没有别的进程想进入临界区
critical section;          critical section; //访问临界区
flag[0] = flase;           flag[1]=flase;    //退出区
remainder section;         remainder section; 

注:这个算法解决了“忙则等待”,但是违背了“空闲让进”和“有限等待”,会让进程长期无法访问临界资源产生饥饿现象。:如果先标记p0,然后标记p1,接着两个进程的while都过不去。

4.Peterson算法

【算法思想】先标记自己想进入临界区,然后标记可以让对方先进入。检测对方是否想进入临界资源。

bool flag[2]; //表示进入临界区意愿数组,初始值都是false。
int turn = 0;  //表示优先让哪个进程进入临界区
//p0进程                      //p1进程
flag[0] = true;              flag[1] = true; //标记自己想进入临界资源
turn = 1;                    turn = 0;   //表示对方可以优先进入
while(flag[1]&&turn==1);     while(flag[0]&&turn ==1); //如果对方想进入,且turn标记对方优先,则等待。
critical section;            critical section; //访问临界区
flag[0] = flase;             flag[1]=flase;    //退出区
remainder section;           remainder section; 
  • 利用flag解决资源的互斥访问,turn解决“饥饿”现象。

4.3进程互斥硬件实现

1.中断屏蔽方法

  • 当一个进程正在执行它的临界区代码时,防止其他进程进入其临界区最简单的方法就是关中断。利用“开/关中断指令”实现。
...
关中断;  关中断后即不允许当前进程被中断,也必然不会发生进程切换。
临界区;
开中断;  进程访问完临界区,执行开中断指令,才有可能别的进程上处理器并访问临界区
...
  • 优点:简单、高效
  • 缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程。(因为“开/关中断指令”只能运行在内核态)

2.硬件指令方法

(1)TestAndSet指令

  • TestAndSet指令:这是一条原子操作,执行该代码时不允许被中断。简称TS指令或者TSL指令
  • TSL指令是用硬件实现的,执行过程不允许被中断。
//用C语言描述的逻辑如下

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

//以下是使用TSL指令实现互斥的算法逻辑
while(TestAndSet(&lock));  //上锁并检查
临界区代码段...
lock = false;  //解锁
剩余区代码段...
  • TSL指令把“上锁”和“检查”操作用硬件的方式变成了一气呵成的原子操作。即在调用的时候上锁和检查在同步骤里边。
  • 缺点:不满足让权等待原则,暂时无法进入临界区进程会占用CPU并循环执行TSL指令,导致“忙等”。

(2)Swap指令

  • Swap指令:也叫Exchang指令,简称XCHG指令。
  • Swap指令是用硬件实现的,执行过程不允许被中断。
//用C语言描述的逻辑如下

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

//以下是用Swap指令实现互斥的算法逻辑
//lock表示当前临界区
bool old = true;
while(ole == true)
    Swap(&lock,&old);
临界区代码段...
lock = false;
剩余区代码段...
  • 逻辑上看,Swap和TSL并没有太大区别。
  • 缺点:不满足让权等待原则,暂时无法进入临界区进程会占用CPU并循环执行指令,导致“忙等”。

五、进程互斥

5.1互斥锁

  • 解决临界区最简单的工具就是**“互斥锁”**,一个进程在进入临界区时获锁,在退出临界区时释放锁。
  • 定义了函数acquire()获得锁,函数release()释放锁。
acquire(){
    while(!available)
        ;  //忙等待
    available = false;   //获得锁
}
release(){
    availbale = true;    //释放锁
}
  • 互斥锁主要缺点:忙等待。
  • 需要连续循环忙等的互斥锁,都称为自旋锁。如TSL、Swap、单标志法。
  • 常用于多处理器系统,一个核忙等,其他核照常工作,并快速释放临界区。
  • 不适于单处理机,忙等待过程中不能解锁。

5.2信号量

  • 信号量机制功能性强,可用来解决互斥与同步问题。它只能被两个标准原语wait(S)和signal(S)访问。
  • 信号量实则是一个变量,用来表示系统中某种资源的数量。可以是一个整数,也可以是更复杂的记录型变量。
  • wait(S)、signal(S)原语通常简称分别为为P、V操作。即P(S)、V(S)。

1.整型信号量

  • 用一个整数型变量作为信号量,用来表示系统中某种资源的数量。
int S=1;   //初始化整型信号量S,表示当前系统中某个资源数量

void wait(int S){  //wait原语,也就是“进入区”
    while(S <=0 );  //资源不够一直循环等待
    S=S-1; //资源够跳出wihle循环,占用一个资源
}

void signal(int S){  //signal原语,也就是“退出区”
    S=S+1;  //资源使用完,在退出区释放资源。
}
  • 在”进入区“中,”检查“和”上锁“一气呵成,避免了并发、异步导致的问题。

  • 在P操作,即wait操作中,只要信号量S<=0就会一直测试,使进程出现“忙等”不满足“让权等待”

2.记录型信号量

  • 为了解决整数型信号量存在“忙等”问题,提出了“记录型信号量”,用记录型数据结构表示信号量。
//定义记录型变量
typedef struct{
    int value;  //剩余资源数
    struct process *L; //等待队列
} semaphore;

//wait操作(P操作)
void wait(semaphore){
    S.value--;
    if(S.value<0){ //若获取资源,如果资源数小于“0”,表示没有资源给进程使用
        block(S.L);  //block原语:使进程从运行态进入阻塞态,并挂到信号量S的等待队列(阻塞队列)中。
    }
}

//signal操作(V操作)
void signal(semaphore){
    S.value++;
    if(S.value<=0){  //当释放一个资源后,如果剩余资源数小于等于“0”,表示当前还有进程在等待资源
        wakeup(S.L); //wakeup原语:唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态。
    }
}
  • 对信号量S一次P操作:进程请求一次单位的该资源,资源数(S.value)先进行减1操作,若S.value<0表示没有资源给进程使用,此时进程调用block原语进行自我阻塞,并插入等待队列(阻塞队列)中。
  • 对信号量S一次V操作:进程释放一个单位的该资源,资源数(S.value)先进行加1操作,若释放资源后S.value<=0表示还有进程正在等待该类资源,则调用wakeup唤醒等待队列中第一个进程。

5.3信号量实现同步互斥

  • P(S):申请一个资源S,如果资源不够就阻塞等待
  • V(S):释放一个资源S,如果有进程在等待该资源,则唤醒一个进程。

1.实现互斥

  • 设置互斥信号量mutex,初始值1
//实现互斥
semaphore mutex=1; //初始化信号量

//p1进程
p1(){
    ...
    p(mutex);  //使用临界区资源需要加锁
    临界区代码段...
    V(mutex);  //使用临界资源后需要解锁
    ......
}
  • 对于不同临界资源需要设置不同的互斥信号量。

  • P、V操作必须成对出现:

    ①缺少P操作会不能保证临界资源互斥访问

    ②缺少V操作会导致资源永不释放,等待进程不会被唤醒。

2.实现同步

  • 进程同步:让各并发进程按要求有序地推进。

  • 用信号量实现同步:

    ①分析需要实现的同步关系,即必须保证“一前一后”执行的两个操作

    设置同步信号量S,初始值为0

    ③在先操作的步骤之后执行V操作,释放一个信号量

    ④在后操作的步骤之前执行P操作,获取信号量

//信号量实现同步机制
semaphore S=0;  //初始化同步信号量,初始值为0
//假设代码4需要在代码2之后执行
//需要在代码2之后添加一个V操作,释放一个信号量
//在代码4之前添加一个P操作,获取信号量
P1(){
    代码1;
    代码2;
    V(S);
    代码3;
}
P2(){
    P(S);
    代码4;
    代码5;
    代码6;
}

3.信号量机制实现前驱关系(多级同步)

【例】进程P1中有代码S1,进程P2中有代码S2……,这些代码要求按照前图来执行。

操作系统学习第二章进程管理_第13张图片

【分析】

每一对前驱关系都是一个进程同步问题,即需要保证”一前一后“操作。

①要对每个前驱关系设置一个同步信号量

②在“前操作”之后执行V操作

③在“后操作”之前执行P操作

例如:S1中的进程信号量

操作系统学习第二章进程管理_第14张图片

【实现】

//P1进程        //P2进程        //P3进程       //P4进程      //P5进程      //P6进程
P1(){          P2(){           P3(){         P4(){        P5(){         P6(){
    ...            ...             ...           ...          ...           ...
    S1;            P(a);           P(b);         P(c);        P(d);         P(e);
    V(a);          S2;             S3;           S4;          S5;           P(f);
    V(b);          V(c);           V(g);         V(e);        V(f);         P(g);
    ....           V(d);           ...           ...          ...           S6;
}                  ...           }              }             }             ...
                   }                                                        }

六、经典同步问题

6.1生产者—消费者问题

1.简单的生产者-消费者

【问题描述】

系统中有一组生产者进程和一组消费者进程,生产者每次生产一个产品(一组数据)放入缓冲区,消费者进程每次从缓冲区中取出一个产品使用。

生产者、消费者共享一个初始为空、大小为n的缓冲区。

  • 同步关系:

只有缓冲区没满时,生产者才可以把产品放入缓冲区,若满了需要等待。

只有缓冲区不为空时,生产者才能从中取出产品,若为空需要等待。

  • 互斥关系:

缓冲区是临界资源,各进程必须互斥地访问。

【实现】

操作系统学习第二章进程管理_第15张图片

semaphore mutex = 1;   //互斥信号量。实现对缓冲区的互斥访问
semaphore empty = n;   //同步信号量,表示空闲缓冲区的数量
semaphore full = 0;    //同步信号量,表示产品数量,也就是非空缓冲区数量。

//生产者进程
producer(){
    //生产一个产品
    P(empty);  //消耗一个空闲缓冲区
    P(mutex);  //对临界资源上锁
    把产品放入缓冲区;
    V(mutex);  //解锁
    V(full);   //增加一个产品
}

//消费者进程
consumer(){
    //消费一个产品
    P(full);  //消耗一个产品
    P(mutex);  //对临界区上锁
    从缓冲区取一个产品;
    V(mutex);   //解锁
    V(empty);   //增加一个空闲缓冲区
    使用产品;
}
  • 实现互斥的P操作一定要在实现同步的P操作之后,不然会发生“死锁"现象

  • V操作顺序颠倒不会有影响。

2.复杂的生产者-消费者的问题

  • 复杂的生产者-消费者的问题:多类生产者—多类消费者问题

【问题描述】

桌子上只有一个盘子,每次只能放入一个水果。

爸爸每次向盘子中放苹果,妈妈每次向盘子中放橘子

女儿从盘子中只拿苹果,儿子从盘子中只拿橘子

只有盘子为空,爸爸或妈妈菜可以向盘子放一个水果。

当盘子有自己需要的水果时,女儿或儿子才可以从盘子中取水果。

  • 同步关系:

父亲将苹果放入盘子后,女儿才能取苹果。

母亲将橘子放入盘子后,儿子才能取橘子。

只有盘子为空时,父亲或母亲才能放入水果。

  • 互斥关系:

对缓冲区(盘子)的访问需要互斥进行。

【实现】

操作系统学习第二章进程管理_第16张图片

semaphore apple = 0;  //盘子中苹果个数
semaphore orange = 0; //盘子中橘子个数
semaphore plate = 1;  //盘子还可以放入多少个水果

//父亲放苹果进程
dad(){
    while(1){
        准备一个苹果;
        P(plate);
        把苹果放入盘子;
        V(apple);
    }
}
//母亲放橘子进程
mom(){
    while(1){
        准备一个橘子;
        P(plate);
        把橘子放入盘子;
        V(orange);
    }
}
//女儿拿苹果进程
doughter(){
    while(1){
        P(apple);
        从盘中取苹果;
        V(plate);
        清空盘子;
    }
}
//儿子拿橘子进程
son(){
    while(1){
        P(orange);
        从盘中取橘子;
        V(plate);
        清空盘子;
    }
}
  • 这个问题中,缓冲区大小只为1。在任何时刻同步信号量都只有1,所以可以不用特意设置互斥变量。
  • 在缓冲区大于1时,需要设置互斥信号量mutex来保证互斥访问缓冲区。

3.吸烟者问题

  • 吸烟者问题:可以为“生产多个产品的单生产者“问题提供一个思路

【问题描述】

假设有三个抽烟者进程和一个供应进程。每个抽烟者进程抽一支烟需要三种材料:烟草、纸、胶水。三个抽烟者中各有一种材料且不相同。供应者进程提供缺少的材料组合放到桌子上。

实际上供应者提供了三种组合材料:

组合一:纸+胶水

组合二:烟草+胶水

组合三:纸+烟草

  • 同步关系:

    桌上有组合一→只有“烟草”进程取走东西

    桌上有组合二→只有“纸”进程取走东西

    桌上有组合三→只有“胶水”进程取走东西

    发出完成信号→供应者将下一个组合放到桌上

【实现】

操作系统学习第二章进程管理_第17张图片

semaphore offer1 = 0;  //桌上组合一的数量
semaphore offer2 = 0;  //桌上组合二的数量
semaphore offer3 = 0;  //桌上组合三的数量
semaphore finish = 0;  //抽烟完成信号量
int i = 0;  //实现“三个轮流抽烟”

//生产者进程
provider(){
    while(1){
        if(i==0){
            将组合一放桌上;
            V(offer1);
        }else if(i==1){
            将组合二放桌上;
            V(offer2);
        }else if(i==2){
            将组合三放桌上;
            V(offer3);
        }
        i = (i+1)%3;
        P(finish);
    }
}

//只有“烟草”进程
skmoer1(){
    while(1){
        P(offer1);
        从桌上拿走组合一:纸+胶水;
        V(finish);
    }
}
//只有“纸”进程
skmoer2(){
    while(1){
        P(offer2);
        从桌上拿走组合二:烟草+胶水;
        V(finish);
    }
}
//只有“胶水”进程
skmoer3(){
    while(1){
        P(offer3);
        从桌上拿走组合三:纸+烟草;
        V(finish);
    }
}

6.2读者-写者问题

【问题描述】

有读者和写者两组并发进程,共享一个文件。当两个或两个以上读进程同时访问共享数据时不会产生错误,如果**某个写进程与其他进程(读进程或者写进程)**同时访问共享数据时可能产生错误。

也就是读进程读数据不会改变数据,写进程才会改变数据。

  • 要求:

①允许多个读者可同时对文件执行读操作。

②只允许一个写者往文件中写信息。

③任一写者在完成写操作之前不允许其他读者或写者工作。

④写者执行写操作前,应让已有的读者和写者全部退出。

  • 互斥关系:

写进程—写进程;写进程—读进程。

【实现】

semaphore rw=1;  //用于实现共享文件互斥访问
int count = 0;   //用于记录当前读进程访问文件数量
semaphore mutex = 1; //用户对count互斥访问,以免读进程在上锁时阻塞

//写进程
writer(){
    while(1){
        P(rw);  //写之前加锁
        写文件...
        V(rw);  //解锁
    }
}

//读进程
reader(){
    while(1){
        P(mutex);  //第一个读进程负责加锁解锁,所以需要对count变量互斥访问
        if(count==0) P(rw); //第一个读进程负责加锁
        count ++;  //记录读进程数
        V(mutex); //对count变量解锁
        
        读文件...
            
        P(mutex);  //第一个进程解锁,对count变量互斥访问
        count--;  //读完文件,进程数-1
        if(count == 0) V(rw); //第一个读进程负责解锁
        V(mutex); //对count变量解锁
    }
}
  • 由于count变量在检查时和赋值无法一次完成,因此需要设置互斥信号量对count访问是互斥的。
  • 这个实现方法:只要读进程还在读,写进程就会一直阻塞等待,可能出现“饿死”现象。读进程是优先的。
  • 为了解决写进程被阻塞问题,增加了一个信号量。用来防止不断有读进程访问,写进程发生饿死现象。
semaphore rw=1;  //用于实现共享文件互斥访问
int count = 0;   //用于记录当前读进程访问文件数量
semaphore mutex = 1; //用户对count互斥访问,以免读进程在上锁时阻塞
semaphore w = 1; 

//写进程
writer(){
    while(1){
        p(w);   //在上锁之前申请w资源,用来防止不断有读进程访问,写进程发生饿死现象
        P(rw);  //写之前加锁
        写文件...
        V(rw);  //解锁、
        V(w);
    }
}

//读进程
reader(){
    while(1){
        P(w);  //在上锁之前申请w资源,用来防止不断有读进程访问,写进程发生饿死现象
        P(mutex);  //第一个读进程负责加锁解锁,所以需要对count变量互斥访问
        if(count==0) P(rw); //第一个读进程负责加锁
        count ++;  //记录读进程数
        V(mutex); //对count变量解锁
        V(w);
        
        读文件...
            
        P(mutex);  //第一个进程解锁,对count变量互斥访问
        count--;  //读完文件,进程数-1
        if(count == 0) V(rw); //第一个读进程负责解锁
        V(mutex); //对count变量解锁
    }
}

6.3哲学家进餐问题

【问题描述】

一张圆桌上有5名哲学家,有5根筷子,每两个哲学家之间摆一根筷子。

哲学家思考时不影响他人,需要吃饭时识图拿左、右两根筷子(一根一根拿)。

如果筷子在其他人手上,需要等待。且只有拿两根筷子才会吃饭,吃完饭后放下筷子继续思考。

【实现】

  • 通过一组PV操作进行互斥去筷子。
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex = 1;  //互斥信号量保证每个哲学家拿筷子时互斥

Pi(){
    while(1){
        P(mutex);
        P(chopstick[i]); //拿左边筷子
        P(chopstick[(i+1)%5]);//拿右边筷子
        V(mutex);
        吃饭...
        V(chopstick[i]); //放下左边筷子
        P(chopstick[(i+1)%5]);//放下右边筷子
        思考...
    }
}

七、死锁

  • 死锁:在并发环境下,各进程因竞争资源造成一种互相等待对方手里的资源,导致各进程都阻塞无法向前推进。

  • 饥饿:进程长期得不到想要的资源,无法向前推进。

  • 死循环:某进程执行过程中跳不出某个循环的现象。

7.1死锁发生条件

  • 死锁一定同时满足四个条件:

    ①互斥条件:只有对必须互斥使用资源的争抢才会导致死锁

    ②不剥夺条件:进程在获得资源未使用完之前,不能被其他进程强行剥夺,只能主动释放。

    ③请求和保持条件:进程已经至少保持了一个资源,且提出新的资源请求且该资源被其他进程占用,此时请求进程被阻塞且不释放已有资源。

    ④循环等待条件:存在一种进程资源的循环等待链。

注:发生死锁一定有循环等待,但是发生循环等待未必死锁。

  • 总之:对不可剥夺资源的不合理分配,可能导致死锁。

  • 死锁的处理策略:

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

    ②避免死锁:用某种方法防止系统进入不安全状态,如银行家算法

    ③死锁的检测与解除:允许死锁发生,操作系统负责检测出死锁的发生然后采取某种措施解除死锁。

7.2预防死锁

1.破坏互斥条件

  • 互斥条件:只有对必须互斥资源的争抢才会导致死锁

  • 破坏互斥条件:如果把只能互斥使用资源改造为允许共享使用,则系统不会进入死锁状态。如,SPOOLing技术。

  • 缺点:不是所有资源都可以改造成共享资源。并且为了系统安全,很多地方需要保护这种互斥性,所以应用范围不广。

2.破坏不剥夺条件

  • 不剥夺条件:进程在获得资源未使用完之前,不能被其他进程强行剥夺,只能主动释放。

  • 破坏不剥夺条件:

    方案一:当进程请求新的资源得不到满足时,必须立即释放持有的所有资源,需要时再重新申请。

    方案二:某个进程需要的资源被其他进程占用时,由操作系统协助,根据进程优先级对资源强行剥夺。

  • 缺点:实现起来复杂;释放已获得资源可能造成前一阶段工作实效;反复申请释放资源增加系统开销降低吞吐量。

  • 采用方案一,可能会导致饥饿。

  • 只适用于已保存和恢复状态的资源,如CPU

3.破坏请求和保持条件

  • 请求和保持条件:进程已经至少保持了一个资源,且提出新的资源请求且该资源被其他进程占用,此时请求进程被阻塞且不释放已有资源。
  • 破坏请求和保持条件:采用静态分配方法,进程在运行前,一次申请完所需全部资源。在资源未满足时,不运行。一旦运行,资源一直归它所有。
  • 缺点:有些资源使用时间短,整个进程运行期间保持所有资源会导致资源浪费,资源利用率低。
  • 这种策略会导致某些进程饥饿。

4.破坏循环等待条件

  • 循环等待条件:存在一种进程资源的循环等待链。链中每个进程已获得的资源同时被下一个进程所请求。
  • 破坏循环等待条件:采用顺序资源分配法。给系统资源编号,规定每个进程必须按编号递增顺序请求资源,同类资源一次申请完。
  • 原理:一个进程只有已占有小编号资源,才有资格申请更大编号资源。持有大编号资源进程不可能逆向申请小编号资源。
  • 缺点:不方便添加新设备;实际使用资源顺序与编号递增不一致会导致资源浪费;必须按照规定申请资源,用户编程麻烦。

7.3避免死锁

  • 在进程已经申请的资源数情况下,系统中还剩余部分系统资源可分配,当进程获取到充足的资源数执行完进程后会返还系统资源。
  • 在一个进程执行序列下,能够使得每个进程都顺利完成,这个序列即为安全序列。这个序列不是唯一的。
  • 能够找到安全序列,系统即处于安全状态。如果找不到任何一个安全序列,则系统处于不安全状态。
  • 系统处于安全状态下,一定不会发生死锁。若系统不处于安全状态下,就可能发生死锁。
  • 在资源分配之前预先判断分配之前是否导致系统进入不安全状态,以此来决定是否打印资源分配请求。

【银行家算法】用于避免死锁

  • 核心思想:进程提出资源申请时,预判此次分配是否会导致系统进入不安全状态。如果会则使进程先阻塞等待。

假设:系统有三种资源,总数为{10,5,7},在已经分配给各个进程后剩余可用资源{3,3,2},分配给进程资源如下。

操作系统学习第二章进程管理_第18张图片

依次检查剩余资源数可否满足各个进程需求:

①满足P1要求,P1加入安全队列。更新可用资源{5,3,2}。

②满足P3要求,P3加入安全队列。更新可用资源{7,4,3}

以此检查得到一个安全序列p1→p3→p0→p2→p4

所以:系统处于安全状态,暂时不可能发生死锁。

7.4死锁的检测与解除

  • 若允许死锁发生,对死锁处理应当提供两个算法:

    ①死锁检测算法:检测系统状态是否已经发生死锁。

    ②死锁解除算法:当系统确定已经发生死锁,则利用此算法从死锁中解脱出来。

1.死锁检测

  • 资源分配图:

    ①有两种点:进程结点,资源结点

    ②两种边:进程结点→资源结点:申请资源;(每个边代表一个)

    ​ 资源结点→进程结点:已分配资源。(每个边代表一个)

操作系统学习第二章进程管理_第19张图片

如果一个进程申请的资源能够得到满足,即可消除与它相关的边(申请与分配)。

如果按照分析后最终能消除所有边,这个图是可完全简化的,此时一定没有发生死锁。

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

2.死锁解除

①资源剥夺法:挂起某些死锁进程,抢占它的资源,将资源分配给其他死锁进程。且应该防止被挂起的进程长时间得不到资源而饥饿。

②撤销进程法:强制撤销部分、甚至全部死锁进程。

③进程回退法:让一个或多个死锁进程回退到可以避免死锁。

  • 决定对哪个进程动手:

    ①进程优先级

    ②已执行时间

    ③剩余执行时间

    ④进程已经使用了多少资源

    ⑤进程是交互式或者批处理式
    被下一个进程所请求。

  • 破坏循环等待条件:采用顺序资源分配法。给系统资源编号,规定每个进程必须按编号递增顺序请求资源,同类资源一次申请完。

  • 原理:一个进程只有已占有小编号资源,才有资格申请更大编号资源。持有大编号资源进程不可能逆向申请小编号资源。

  • 缺点:不方便添加新设备;实际使用资源顺序与编号递增不一致会导致资源浪费;必须按照规定申请资源,用户编程麻烦。

7.3避免死锁

  • 在进程已经申请的资源数情况下,系统中还剩余部分系统资源可分配,当进程获取到充足的资源数执行完进程后会返还系统资源。
  • 在一个进程执行序列下,能够使得每个进程都顺利完成,这个序列即为安全序列。这个序列不是唯一的。
  • 能够找到安全序列,系统即处于安全状态。如果找不到任何一个安全序列,则系统处于不安全状态。
  • 系统处于安全状态下,一定不会发生死锁。若系统不处于安全状态下,就可能发生死锁。
  • 在资源分配之前预先判断分配之前是否导致系统进入不安全状态,以此来决定是否打印资源分配请求。

【银行家算法】用于避免死锁

  • 核心思想:进程提出资源申请时,预判此次分配是否会导致系统进入不安全状态。如果会则使进程先阻塞等待。

假设:系统有三种资源,总数为{10,5,7},在已经分配给各个进程后剩余可用资源{3,3,2},分配给进程资源如下。

[外链图片转存中…(img-L5jSUOQY-1694944895209)]

依次检查剩余资源数可否满足各个进程需求:

①满足P1要求,P1加入安全队列。更新可用资源{5,3,2}。

②满足P3要求,P3加入安全队列。更新可用资源{7,4,3}

以此检查得到一个安全序列p1→p3→p0→p2→p4

所以:系统处于安全状态,暂时不可能发生死锁。

7.4死锁的检测与解除

  • 若允许死锁发生,对死锁处理应当提供两个算法:

    ①死锁检测算法:检测系统状态是否已经发生死锁。

    ②死锁解除算法:当系统确定已经发生死锁,则利用此算法从死锁中解脱出来。

1.死锁检测

  • 资源分配图:

    ①有两种点:进程结点,资源结点

    ②两种边:进程结点→资源结点:申请资源;(每个边代表一个)

    ​ 资源结点→进程结点:已分配资源。(每个边代表一个)

[外链图片转存中…(img-ntuo9MRL-1694944895209)]

如果一个进程申请的资源能够得到满足,即可消除与它相关的边(申请与分配)。

如果按照分析后最终能消除所有边,这个图是可完全简化的,此时一定没有发生死锁。

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

2.死锁解除

①资源剥夺法:挂起某些死锁进程,抢占它的资源,将资源分配给其他死锁进程。且应该防止被挂起的进程长时间得不到资源而饥饿。

②撤销进程法:强制撤销部分、甚至全部死锁进程。

③进程回退法:让一个或多个死锁进程回退到可以避免死锁。

  • 决定对哪个进程动手:

    ①进程优先级

    ②已执行时间

    ③剩余执行时间

    ④进程已经使用了多少资源

    ⑤进程是交互式或者批处理式

参考书籍:计算机操作系统(第四版)——汤小丹;王道操作系统复习指导
参考视频:王道计算机考研操作系统

你可能感兴趣的:(咖啡ice的操作系统学习记录,学习)