赞
踩
目前正在备考24考研,现将24计算机王道的408学习整理的知识点进行汇总整理。
博主博客文章目录索引:博客目录索引(持续更新)
进程的概念:
举例:我们启动QQ程序的三个进程,如下所示:
进程的组成:PCB、数据段、程序段。
说明:PCB是给操作系统使用,程序段、数据段是给进程自己用的。
问题:操作系统是这些进程的管理者,那么它是怎么区分的各个进程?
PID
(Process ID,进程ID),也就是进程ID,它就相当于我们人类世界的身份证号,我们每个人的身份证号都是唯一的,对于操作系统不仅仅记录PID、进程所属用户ID(UID,通过基本的进程描述信息,可以让操作系统区分各个进程),还要记录给进程分配了哪些资源,如内存、使用的I/O设备以及使用的文件等;还要记录进程运行情况,如CPU时间时间、磁盘使用情况、网络流量使用情况等。
每一次新建一个进程都会给它分配一个不重复的唯一的ID。在很多操作系统当中PID的分配都是每一次加一,都是采用这样的一个很简单的策略。
对于每一个进程,操作系统还会记录进程的其他信息,例如使用了多久的CPU,由哪个用户所创建的,还有一些各个进程对内存的使用信息、磁盘、网络情况等等。
PCB
(Process Control Block,进程控制块):对于上述所说对每个进程分配的PID、资源以及使用情况,这些数据都被保存到一个数据结构PCB
中。
看一下实际Linux中PCB结构体的实现,其中包含了对应的进程状态、信息字段,线程信息等等:
除了PCB之外进程还有两个很重要的组成:程序段与数据段
程序段
:除了PCB,指定程序中的所有指令序列会读到内存中,这一系列的指令序列,将它称为程序段,之后程序执行的过程里,CPU就会从内存当中读入一条一条指令,接着来执行指令。数据段
:在执行指令的过程中,会有一些中间数据,例如定义了变量等,这些变量内容也会放入到主存中,此时还会有一个叫做数据段的区域用来存放这个程序运行过程中所产生的所需要使用的各种数据。进程实体:一个进程实体(进程映像)由PCB、程序段、数据段组成。
程序段、数据段、PCB三个部分组成了进程实体(进程映像)。
引入进程实体的概念后,可把进程定义为:进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
注意:PCB是进程存在的唯一标志。
举例:若是同时挂三个QQ 那么对应三个QQ进程,它们的PCB、数据段各不相同,但是进程段的内容都是相同的(都是运行着QQ程序)。
程序是静态的,进程是动态的,相对比程序,进程具有以下的特征:
进程的五种状态如下:
对于运行态的说明以及一些状态的别名如下图:
进程的状态是如何表示的?
创建态:进程正在被创建时,它的状态是"创建态",在这个阶段操作系统会为进程分配资源、初始化PCB。
就绪态:当进程被创建完成之后,便进入了"就绪态",处于就绪态的进程已经具备了运行条件,但由于没有空闲CPU,暂时不能够进行运行。
运行态:当CPU空闲时,操作系统就会选择一个就绪程序,让它上处理机运行。
对于阻塞态并不是所有的进程都会进入到该状态,何时会进入到该状态呢,例如当前CPU正在执行进程的指令,此时进程的一条指令是要请求打印机,传输数据让打印机打印,此时由于打印机已经被其他进程占用了,此时程序就无法继续往下运行,此时操作系统会剥夺这个进程对CPU 的使用,让这个进程进入到新的状态下也就是阻塞态。
阻塞态:在进程运行的过程中,可能会请求等待某个事件的发生(例如等待某种系统资源的分配,或者等待其他进程的响应)。在这个事件发生之前,进程无法继续往下执行,此时操作系统会让这个进程下CPU,并让它进入到"阻塞态"。
若是此时在CPU上运行的进程1结束了,此时在运行结束的时候,①会发出一个exit系统调用,请求操作系统终止这个进程。②此时这个进程的状态就会变成终止态。③接着操作系统会让这个进程下CPU作一系列善后的工作,回收这个进程所有的资源、内存空间、打印机设备等所有资源,最终还会回收PCB此时就从系统中彻底消失了。
终止态:一个进程可以执行exit系统调用,请求操作系统终止该进程,此时进程会进入到"终止态",操作系统接下来会让该进程下的CPU回收内存空间等等,最后回收该进程的PCB,一旦终止进程的工作完成后,这个进程彻底消失。
进程状态的转换:
对于上面五中状态之间转换的补充:
注意:
还有一种特殊情况:有时候进程可以直接从运行态转换成就绪态,例如操作系统给进程分配的时间片用完的时候,此时进程就会从运行态转换成就绪态了。
如何将进程组织起来?
进程的组织包含有两种方式:组织方式、索引方式
简洁:操作系统会管理一系列的队列,每一个队列都有指向相应的状态的进程PCB。
举例一个执行指针就会执行正在处于运行态的进程的PCB:
就绪队列指针则会指向处于就绪态的进程PCB,为了方便这些就绪进程的调度,操作系统会将优先级更高的进程PCB放在队列的队头。
阻塞队列指针也同样会指向处于阻塞状态的PCB如下:
在很多操作系统中实际会根据阻塞原因的不同,将阻塞队列分为多个,例如等待打印机、等待磁盘的:
操作系统采用这样子链式的方式将进程统一组织管理起来
操作系统会给各种状态的进程建立相应的索引表,每个索引表的表项则是指向一个PCB,如下图所示:
实际应用中大多数还是使用的链式存储。
对于绿框的则是比较重要的考点:
进程控制本质:实现进程的状态转换。
创建新进程就是创建态的过程,撤销已有进程则是终止态。
如何实现进程控制?用"原语"来实现
操作系统中有内核部分,在内核里有原语。
原语:是一种特殊的程序,其执行具有原子性,这段程序的运行必须是一气呵成的,不可中断。
为什么进程控制(状态转换的过程)需要一气呵成?
这里对于数据结构中状态信息不统一情况来用一个阻塞唤醒原理举例子:
首先有两个状态指针采用的是链式分别指向一个PCB:
若是在阻塞唤醒转换过程中执行了步骤1后此时来了一个中断信号,若是没有使用原语,此时操作系统就会对这个中断进行处理,注意此时第②步骤并没有完成,当前两个指针指向的状态如下:
此时可以发现数据结构中的状态字段信息不统一。
如何实现原语的原子性?:通过使用关中断与开中断指令两个特权指令实现原子性,被这两个指令包裹的指令中间不会被中断,而是会一气呵成的完成指令。
原理:CPU执行关中断指令后,就不再例行检查中断信号,直到执行中断指令之后才会恢复检查。通过这样子的关中断、开中断指令序列就是不可被中断的,这就实现了"原子性"。
如何这两个特权指令允许用户程序使用的话,会发生什么事情?
若是操作系统想要创建一个进程,那么就必须使用创建原语,下面是对应步骤:
对于一些事件实际会引起操作系统的进程创建,此时就会间接的调用创建原语,下面是一些事例举例:
①用户登录:在分时系统中,若是有用户登录成功,此时系统会建立一个新的进程。
②作业调度:多道批处理系统中,有新的作业放入内存时,会为其建立一个新的进程。
③提供服务:用户向操作系统提出某些请求时,会新建一个进程处理该请求。
④应用请求:由用户进程主动请求创建一个子进程。
撤销原语:主要是终止进程的时候需要进行使用。
状态转换:就绪态/阻塞态/运行态—>终止态->无
。
撤销原语实现的步骤:
可能会引起进程终止的事件:
父子进程的实际例子,任务管理器即可查看到:
父子进程之间分配关联:一开始,对于装入进程launch几乎整个内存空间如8GB都是它的,之后它在创建进程的时候会将对应的资源分配给它的子进程,如50MB分配给进程A,50MB分配给进程B,一旦这些子进程终止了之后,此时会将自己的手里的这些资源还给它们的父进程。
状态转换:运行态—>阻塞态
阻塞原语过程:
引起进程阻塞的事件:
唤醒原语的状态转换:阻塞态—>就绪态
唤醒原语的步骤:
引起进程唤醒的事件:等待的事件发生(因何事阻塞,就应由何事唤醒)。
切换原语的状态转换:运行态—>就绪态、就绪态—>运行态。
切换原语的步骤:
认识进程的运行环境:
示例,下图对x++这条C语言最终会转变为4条机器指令,当执行到指令3的时候,完成寄存器的+1操作,此时通用寄存器的值为2,注意此时执行完指令3时间片用完了,此时需要切换到给其他进程来进行使用,那么对于当前进程的状态信息我们是需要进行保存的,这个就是保存现场,用于之后切换到该进程的时候恢复现场使用。
根据上述内容,我们保护现场应当存储的部分数据为PSW状态寄存器、PC指令、通用寄存器。保存完之后由于CPU执行其他的进程,此时原先的寄存器值会被覆盖掉如下:
当该进程执行完毕,此时切换到原先进程,恢复先前进程保存的值(PSW状态寄存器、PC、IR指令、当前运算的操作数等),最终成功的写入到了指定的主存地址:
进程通信(Inter-Process Communication, IPC):是指的两个进程之间产生数据。
实际例子:微博中的图文分享到其他应用当中。
进程之间的通信如何实现?需要操作系统的支持。
为什么进程通信需要操作系统支持?
进程:是分配系统资源的单位(包括内存地址空间),各个进程拥有的内存地址空间相互独立。
首先对于进程访问的地址空间是有限制的,如下图进程P开辟的空间是P地址,进程Q开辟的空间是Q地址,那么对于进程P是无法访问进程Q地址空间的:
目的:一个进程无法随意修改其他进程之间的数据。
规定的限制:各个进程之间只能访问自己的一片内存地址空间,不能访问其他进程的内存地址空间,无论是读或者写数据都不行。此时若是想要两个进程之间进行通信,那么就需要操作系统的支持才可以完成数据通信。
三种进程之间的通信:共享存储、消息传递、管道通信
共享存储:各个进程只能访问自己的空间,如果操作系统支持共享存储这种功能,那么进程是可以申请一片共享存储器,在这片共享存储器中,也可以被其他进程所共享。
流程:若是进程p要给进程q传送数据,那么p就可以先将数据写到这一片共享存储区域中,p进程本身是可以访问该区域的,接着进程q也从这片区域中读出数据即可。
实现共享存储的Linux代码:
同样按照流程中的所说,进程p会使用下面的open函数来进行系统调用申请一片共享内存区:
接着进程p与进程q都想要使用这一片共享内存区,此时就可以使用这个系统调用mmap,使用这个系统调用后,将这一片存储区域映射到自己的虚拟地址空间当中。
如何应对多个进程都对同一块共享存储区域写操作?
对于共享存储也分为多种方式:
①基于存储区的共享:操作系统在内存中划出了一块共享存储区,数据的形式、存放位置都由通信进程控制,而不是操作系统,对于这种共享方式速度很快,是一种高级通信方式。
优点:速度快。
②基于数据结构的共享:例如共享空间里只能够放长度为10的数组,这种共享方式速度慢、限制多,是一种低级通信方式。
缺点:进程之间通信自由度不高,速度比较慢。
第二种进程之间通信的方式:消息传递
消息传递:进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的"发送消息/接收消息"两个原语进行数据交换。
格式化的消息则是分为两个部分:消息头与消息体。
消息传递之间的通信包含两个:直接通信方式与间接通信方式。
方式一:直接通信方式
:消息发送进程要指明接收进程的ID。
下面描述进程P给进程Q发送消息,以及Q接收消息的过程:
1、首先进程P在自己的内存空间构建一个消息(包含消息头、消息体),接着进程P给进程Q发送一个消息,此时就是用发送原语send来指明message消息发送给进程Q
2、指明了接收者之后,经过【send(Q, msg)发送原语】会让操作系统的内核接收到这个消息并将刚才的消息挂到进程Q的消息队列里边。实际这个过程则是消息从进程p的这个空间复制到了内核空间。
3、此时进程Q则可以使用【接收原语receive(P, &msg)】来接收消息,此时操作系统就会检查进程Q的消息队列看一下在消息队列中有哪几个是进程P发送过来的,接着会将发送方P的消息体从操作系统的内核区复制到进程Q的用户去这里。
方式二:间接通信方式
:以"信箱"作为中间实体来进行信息传递。
下面描述进程P给进程Q发送消息,以及Q接收消息的过程:
1、进程P首先在自己的空间完善得到一个msg对象,等待消息体赋值完成之后,此时可以使用发送原语send(A, msg),指明要发送到A邮箱。
2、执行完send后,操作系统会将当前进程P地址空间的信息复制到操作系统内核空间中。
3、接着进程Q则可以执行接收原语receive(A, & msg),表示要从邮箱A中接收一个msg,此时操作系统会将邮箱A中的消息复制到进程Q的内存空间。
管道:是一个特殊的共享文件,又叫pipe文件,实际就是在内存空间中开辟一个大小固定的内存缓冲区。
数据的流向:只能是单向的,而不是双向的。
与共享内存的区别:对于共享内存对于读取没有限制;对于管道内是遵照先进先出的,可以理解为循环队列。在进程里边写数据和读数据要遵循先进先出的规则。
特点:
1、管道只能够采用半双工通信,某一时间段内只能够实现单向的传输,不能够实现双向传输。若是要实现双向的传输,则需要设置两个管道(即为全双工)。
下面是想要实现双向的示例图:
2、各个进程对管道的访问是互斥进行的,这个互斥是由操作系统实现的。
3、当管道写满时,写进程将会被阻塞,直到读进程将管道中的数据取走,即可唤醒写进程。
4、当管道读空时,读进程将阻塞,直到写进程往管道中写入数据,即可唤醒读进程。
5、管道中的数据一旦被读出,就彻底消失。因此当多个进程读同一个管道的时候,就可能会错乱。
(特点的第5点解答)针对于不同的操作系统,对于这个可能会造成错乱的问题提出了解决方案:
方案1:对于system five系统,一个管道允许多个写进程,读进程只允许有一个,不允许有多个读进程同时读这个管道【2014年408真题高教社官方答案】。
方案2:在linux系统中,允许有多个写进程,多个读进程,操作系统会自己来控制各个读进程轮流读数据的过程。
如何选择方案呢?对于实际应用角度来看是采用方案2的,但是对于应试考试,我们则依据官方的答案标准来记也就是允许多个写进程,1个读进程。
三种通信功能都需要操作系统的底层支持
什么是线程,为什么要引入线程?
引入了进程之后,想要实现进程的并发,那么我们对于时间片都是直接分配给某个进程,此时就能够实现边聊QQ和边听音乐。
在深入一个进程看,对于QQ这一个应用实际上要完成很多事情,例如视频、文字聊天、发文件。
进程是程序的一次执行,但是这些功能显然并不可能是由一个程序顺序处理就能够实现的,若是采用进程的顺序执行,那么我们就不能够感知到一个QQ是同时发送这些事情的。
对于传统的进程来说,进程是执行的最小单位:
对于部分进程可能需要同时做很多事情,对于传统进程只能够串行的执行一系列的程序,此时就引入了线程的概念用于增加并发度。此时CPU的调度对象不再是进程,而是进程之中的线程,每一个进程中包含多个线程,之后CPU会轮流的使用一定的算法来为这些线程进行服务:
线程:理解为是一种"轻量级的进程",是一个基本的CPU执行单元,也是程序执行流的最小单位,以前CPU调度的是进程,此时以线程为单位。
特点:引入线程之后,不仅仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以处理各个任务(如QQ视频、文字聊天、传文件)。
注意:引入线程之后,进程不再作为CPU的调度单位,而是作为除了CPU之外的系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)。
**如何理解"进程作为除了CPU之外的系统资源的分配单元"?**此时作为系统资源的基本分配单位,比如对于这些如打印机等资源分配给左边进程,内存条与另一个打印机分配给右边进程,此时分配资源给到的进程作为操作系统的基本分配单位。
引入了线程机制后,有什么变化?
着重点:对于同一进程内的线程切换,不需要切换进程环境,系统开销更小。
如何理解切换进程的运行环境?举例:在图书馆看书
线程的属性:
1、线程是处理机调度的单位。
2、是否占用相同的CPU?多CPU计算机中,各个线程可占用不同的CPU。
3、线程组成:每个线程都有一个线程ID、线程控制块(TCB)。
4、线程三种阻塞状态:同样有就绪、阻塞、运行三种基本状态。
5、线程是否拥有系统资源?线程集合不拥有系统资源。(线程只是处理机调度的基本单位,而系统资源分配是分配给进程的)
6、同一进程的不同线程共享进程的资源。(系统都是在进程,线程锁使用的系统资源则是同一个进程里的)
7、线程通信是否需要干预?由于共享内存地址空间,同一进程中的线程间通信甚至无需操作系统干预。
8、关于进程切换:若是同一进程中的线程切换,不会引起进程切换;若是不同进程中的线程切换,则会引起进程切换。
9、线程切换的开销:切换同进程内的线程,系统开销很小;切换进程的话开销十分大。
用户级线程(User-Level Thread, ULT)
历史背景:早期的操作系统(如:早期UnIx)只支持进程,不支持线程,此时"线程"是由线程库来实现的。
此时有一个需求对于QQ中的功能包含有视频、文字聊天、传送文件工作,在这个阶段如何去实现并发工作?
由于三个进程代码都是while循环,此时我们实际可以只用一个进程加上一个while循环,将三个功能写到一个进程中,此时根据if判断来完成相应的不同功能:
说明:通过这样子一段while循环,我们实际上完成了一个最简单的"线程库",实际在很多编程语言中提供了用户级线程比当前这个while复杂的多,程序员可以借助线程库来实现一系列的工作。
对于实现一个最简单的线程库,通过使用while循环来实现提出下面几个问题:
1、线程的管理工作由谁来完成?
2、线程切换是否需要CPU变态?
3、操作系统能否意识到用户级线程的存在?
4、这种线程实现方式的优缺点?
优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高。
缺点:当一个用户级线程被阻塞后,此时整个进程都会被阻塞,并发读不高,多个线程不可在多个处理机上并行运行。
用户级线程的调度单位:对于这种早期操作系统实现的这种用户级线程方式,CPU的调度单位依旧是进程,操作系统会给进程来分配CPU时间,并且即使电脑是多核,由于进程为调度基本单位,一个进程也只能够被分配一个核心,所以这些线程不能够并行运行!
最大的问题:这种用户级线程的实现方式在遇到进程中的一个线程阻塞时,那么会导致整个进程进入到阻塞,那么如何解决这样子的问题呢?
内核级线程(Kernel-Level Thread,KLT,又称"内核支持的线程"):其是由操作系统支持的线程。
应用:目前大多数现代操作系统实现了内核级线程,如windows、Linux。
针对内核级线程,提出以下几个问题:
1、线程的管理工作由谁来完成?
2、线程切换是否需要CPU变态?
3、操作系统是否能够意识到内核级线程的存在? 能够意识到。
4、实现方式的优点与缺点。
优点:当一个线程被阻塞后,同一进程的其他线程依旧可以继续执行,并发能力强。多线程可以在多核处理机上并行执行。
缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态。所以线程的管理成本高,开销大。
用户线程级与内核级结合在一起:在支持内核级线程的系统中,再引入线程库,此时就可以实现将若干个用户级线程映射到某一个内核级线程中。
一对一模型:一个用户级线程映射到一个内核级线程,每个用户进程有与用户级线程同数量的内核级线程。
优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强,多线程可在多核处理机上并行执行。
缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。
多对一模型(等同用户级线程):多个用户级线程映射到一个内核级线程,且一个进程只被分配一个内核级线程。
优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高。
缺点:当一个用户线程被阻塞后,整个进程都会被阻塞,并发读不高,多个线程不可同时在多核处理机上并行运行,因为只有一个内核级线程。
重点:操作系统只"看得见"内核级线程,只有内核级线程才是处理机分配的单位。
多对多模型:n用户级线程映射到m个内核级线程上。每个用户进程对应m个内核级线程。
优点:克服了一对一模型中一个用户进程占用一个内核级线程,开销太大(太多的内核会导致过多的变态操作)的缺点;克服了多对一模型并发度不高的缺点(一个阻塞全体阻塞)。
深入理解:用户级线程是"代码逻辑"的载体,内核级线程是"运行机会"的载体。
与进程状态与转换类似。
最核心、主要的有三个状态:就绪、运行、阻塞。
①运行—>阻塞态:若是在运行请求某个资源需要进行等待,此时就会进入到阻塞态。
②运行态—>就绪:若是一个系统支持线程,若是运行态的线程分配的时间用完了,此时就会下处理机进入到就绪态。
③就绪—>运行态:若是就绪态的线程被调度程序选中,此时就绪态回到运行态。
④阻塞—>就绪态:若是等待的事件可以被分配了,那么此时就会从阻塞态转为就绪态。
原本调度单位为进程的时候,操作系统会对进程的PCB进行一个管理,其中包含了进程的各种信息,那么对于线程来说,管理线程,就需要给这个线程就爱哪里一个对应的数据结构,即为TCB(线程控制块)。
TCB的组成:
线程表:将多线程的TCB组织起来就可以构成一个线程表,可以将所有的线程组织成一张线程表,也可以按照线程状态的不同,组织成不同的线程表,不同的系统可以采取不同的策略,来根据需求组织分类管理。
调度:当有一堆任务要处理,但由于资源有限,这些事情没法同时处理,此时就需要确定某种规则来决定处理这些任务的顺序,这就是"调度"研究的问题。
举例1:银行很多人排队取款,此时若是有一个VIP用户也来办理服务,此时就会被优先服务。
举例2:例如宿舍的厕所只有一个位置,那么此时可以根据各自要使用的时间越短的越优先,若是时间一致的则根据先排队的先使用次序。
作业:一个具体的任务。
发生场景:若是此时内存空间有限,无法将用户提交的作业全部放入内存时该如何进行选择?
高级调度(作业调度):按一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程,每个作业在整个生命周期中只调入一次,调出一次。作业调入时会建立PCB,掉出时才撤销PCB。
低级调度:
发生场景:在内存中有很多进程,但是系统里CPU资源有限,操作系统需要制定某一种策略,从我们的进程就绪队列中挑选出一个进程,将处理机分配给它。
低级调度(进程调度/处理机调度):按照某种策略从就绪队列中选取一个进程,将处理机分配给它。进程调度是操作系统最基本的一种调度,在一般的操作系统中都必须配置进程调度。
进程调度的频率:几十毫秒一次。这样才能够让用户在宏观上来看感觉是同时进行的。
发生场景:当要使用某个进程时,此时计算机会出现内存资源不足的情况,此时会将一些不紧急、不太重要的进程先将它们从内存调出到外存,之后再由中间调度来将进程数据调回到内存。
中级调度(内存调度):经过上面的调出操作之后,此时有了空闲的内存资源,操作系统会通过中级调度来决定是否要把外存某个进程的数据先把它调回内存。
举例:平时使用手机时会经常执行切换应用的操作,有时候切换进程十分流程,有时候会有一些卡顿,对于切换十分流畅的说明这个进程的数据本身就是内存中;对于有一些卡顿的那么就是在进行中级调度,选中的那个进程会从外存调入到内存来进行执行。
生命周期:进程运行生命周期中,可能会出现多次调出以及多次调入。
发生频率:比高级调度高。
对于408中要掌握的是五状态模型,一些自命题可能会要求七状态模型。
挂起状态(suspend):暂时调到外存等待的进程状态为挂起状态。
挂起状态分为两种状态:就绪挂起、阻塞挂起两种状态。
其他一些特殊状态转换:
①阻塞挂起—>就绪挂起
:若是此时某个进程为阻塞挂起,此时它等待的资源已经释放出现,此时就会转为就绪挂起。
②运行态—>就绪挂起
:有些进程当它处于运行态时,运行结束之后可能进程下处理机的时候就会被直接放到外存当中去,让它进入就绪挂起的状态。
③创建态—>就绪挂起
:有的时候一个处于创建态的进程,当它常见结束之后,创建完PCB此时可能出现内存空间不够的情况此时就会直接进入到就绪挂起的状态。
注意:"挂起"和"阻塞"的区别,两种状态都是暂时不能够获得CPU的服务了,挂起态是将进程映像调到外存去,而阻塞态下进程映像还在内存当中。
关于队列创建:有的操作系统会将就绪挂起、阻塞挂起分为两个挂起队列,甚至会根据阻塞原因的不同再把阻塞挂起进程进一步细分为多个队列。
CPU利用率:表示CPU处于忙碌的时间,精确指的是CPU"忙碌"的时间占总时间的比例。
目的:由于早期的CPU造价及其昂贵,因此人们会希望让CPU尽可能多的工作。
公式:利用率 = 忙碌的时间 / 总时间
实际例题:
系统吞吐量:单位时间内完成作业的数量
目的:对于计算机来说,希望能够使用尽可能少的时间处理完尽可能多的作业。
公式:系统吞吐量 = 总共完成了多少道作业 / 总共花了多少时间
,其中实际计算的是每秒能够完成的道数。
实际例题:
周转时间:是指从作业被提交给系统开始,到作业完成为止的这段时间间隔。
目的:对于计算机的用户来说,十分关心自己的作业从提交到完成花了多少时间。
包含四个部分:
说明:对于后三项在一个作业的整个处理过程中,可能发生多次。
公式一:(作业)周转时间 = 作业完成时间 - 作业提交时间
公式二:平均周转时间 = 各作业周转时间之和 / 作业数
思考:对于有的作业运行时间短,有的作业运行时间长,因此在周转时间相同的情况下,运行时间不同的作业,给用户的感觉肯定是不一样的。
举例:例如排队等厕所,用户A排队等10分钟,使用厕所1分钟,那么整个周转过程花费11分钟,用户B排队等1分钟,使用厕所10分钟,整个周转过程同样花费11分钟。对于用户B来说其中只有1分钟等待,实际感受并没有很糟糕。【那么对于周转时间相同的情况下,作业的实际运行时间长短不同,会导致对于用户的感受也是有区别的,此时提出了另外一个指标:带权周转时间】
公式三:带权周转时间 = 作业周转时间 / 作业实际运行的时间 = (作业完成时间 - 作业提交时间) / 作业实际运行的时间
。
公式四:平均带权周转时间 = 各作业带权周转时间之和 / 作业数
等待时间:指进程/作业处于等待处理机状态时间之和,等待的时间越长,用户满意度越低。
目的:计算机的用户希望自己的作业尽可能少的等待处理机。
过程:作业在后备队列等待被服务(高级调度),接着作业调入内存中,建立对应的进程(PCB),这个进程会被CPU服务,也会被I/O设备服务,也会有等待被服务的时候。
计算通俗易懂解释:
①对于进程来说,等待时间就是指进程建立后等待被服务的时间之和,在等待I/O完成的期间其实进程也是在被服务的,所以不计入等待时间。
②对于作业来说,不仅要考虑建立进程后的等待时间,还要加上作业在外存后备队列中等待的时间。
注意:对于调度算法而言其实只会影响作业/进程的等待时间(因为一个作业总共需要被CPU服务多久,被I/O设备服务多久一般是确定不变的)。也可以使用"平均等待时间"来评价整体性能。
响应时间:从用户提交请求到首次产生响应所用的时间。
目的:对于计算机用户来说,会希望自己的提交的请求(如通过键盘输入了一个调试指令)尽早的开始被系统服务、回应。
进程调度(低级调度)
:就是按照某种算法从就绪队列中选择一个进程为其分配处理机。
需要进行进程调度与切换的情况:
情况1:当前运行的进程主动放弃处理机,下面是一些实际情况。
情况2:当前运行的进程被动放弃处理机,下面是一些实际情况。
不能够进行进程调度与切换的情况:
1、在处理中断的过程中,中断处理过程复杂,与硬件密切相关,很难做到在中断处理过程中进行进程切换。
2、进程在操作系统内核程序临界区(临界区指的是同一时间只能供一个进程访问的代码块)。
3、在原子操作过程中(原语),原子操作不可中断,要一气呵成(如之前某一个原语为两步:①修改PCB中进程状态标志。②并把PCB放到相应队列。对于这个原语过程中我们不能够进行进程调度与切换,否则会造成一个队列中状态不一致的情况)
关于下面表述内容的正确与否:
表述1:进程在操作系统内核程序临界区中不能进行调度与切换。【正确】
表述2:(2012年联考真题)进程处于临界区时不能够进行处理机调度。【错误】
分析:
首先了解几个名词:
临界资源
:一个时间段只允许一个进程使用的资源, 各进程需要互斥的访问临界资源。临界区
:访问临界资源的那段代码。内核程序临界区
:一般是用来访问某种内核数据结构的,例如进程的就绪队列(由各就绪进程的PCB组成)内核临界区举例:当一个进程若是处于一个内核程序临界区,此时这个临界区是要访问就绪队列的话,那么访问之前它会把这个就绪队列上锁,在上锁期间若是发生调度,此时进程调度相关程序会需要访问就绪队列从队列中挑选一个进程,但由于这个就绪队列在上锁状态那么是无法进行的,只有解锁了之后才可以。
普通临界资源举例:若是此时进程访问的是一种普通临界资源,如打印机,那么同样访问它的时候会先上锁,打印机完成打印之前,进程是一直在临界区的,此时依旧保持着对打印机访问,还在上锁中,由于打印机是一种慢速的I/O设备,对于这种情况若是不允许进程调度与切换,那么会导致处理机一直等待IO结束,这个时间段会一直霸占着CPU,CPU一直是在空闲状态。
通过这段描述,那么对于上面两个表述就能够直到正确与错误的原因了。
为什么有的系统中,只允许进程主动放弃处理机?有的系统中,进程可以主动放弃处理机,当有更紧急的任务要处理时,也会强心剥夺处理机(被动放弃)?
非剥夺调度方式
:又称为非抢占方式,即只允许进程主动放弃处理机,在运行过程中即便有更加紧迫的任务到达,进程会继续使用处理机,直到该进程或主动要求进入阻塞态。
剥夺调度方式
:又称为抢占式方式。当一个进程正在处理机上执行时,如果有一个更重要或更紧迫的进程需要使用到处理机,那么会立即暂停正在执行的进程,将处理机分配给更重要紧迫的进程。
若是要分配处理机时,在进程与进程切换的过程中,又会发生什么事情?
主要分为"狭义的进程调度"与"进程切换"的区别:
狭义进程调度
:指的是从就绪队列中选中一个要运行的进程。(这个进程可以是刚刚被暂停执行的进程,也可能是另一个进程,后一种情况需要进程切换)。广义的进程调度
:包含了选择一个进程与进程切换两个步骤。【狭义进程调度 + 进程切换,①选择进程②一个进程让出处理机③另一个进程占用处理机】进程切换
:指的是一个进程让出处理机,由另一个进程占用处理机的过程。实际题目中指的是是广义的进程调度。
进程切换的过程主要完成了:
注意:对于进程的切换并不是一瞬间可以完成的,需要付出一定的时间代价,不能简单理解进程切换越频繁,进程并发度越高,因为若是进程的切换调度过于频繁,其实会导致整个系统效率变低,因为系统将大部分的时间都花在这个进程切换开销上,真正用于执行进程时间反而比较少。
调度器
:操作系统的调度程序主要就是用来管调度的,当前运行的进程是否要让他下处理机,若是下处理机了,那么接下来就绪队列中的进程要谁上处理机运行?
调度时机:在何时回触发"调度程序"?
①创建新进程:当创建新进程的时候,此时就绪队列会发生改变,就有可能让新进程抢占正在运行的这个进程CPU。
②进程退出:若是进程终结了自己,此时CPU就会空闲,这个时候调度程序会来进行调度。
③运行进程阻塞。
④I/O中断发生(可能唤醒某些阻塞进程)。
⑤非抢占式调度策略,只有运行进程阻塞或退出才触发调度程序工作。
⑥抢占式调度策略,每个时钟中断或k个时钟中断会触发调度程序工作。
共同点:当前CPU运行的进程主动退出或者被动退出(可能进入到不同的状态),由于为了不让CPU空闲,那么都会去调用调度程序。
若是系统不支持线程,那么调度处理单位就是进程;若是系统支持线程,那么调度的处理单位则是内核级线程,此时进程作为资源分配的基本单位。
闲逛进程(Idle process)
:在计算机系统中,处于空闲状态并且没有任何可执行任务的进程,通常用于使用空闲的处理器时间来执行一些系统维护任务,例如更新系统状态、清理内存、检查硬件设备等。
时机:若是就绪队列中没有其他就绪进程,调度程序会选中闲逛进程, 让其上处理机运行。
闲逛进程的特性:
说明:在执行闲逛进程的每一条指令之后,这个指令周期的末尾也会例行的检查中断,这个中断就可以周期性的唤醒调度程序,让调度程序来检查,若是有,那么就让其他进程上处理机,闲逛进程下处理机。
先来先服务算法(FCFS,First Come First Service)
算法思想:主要从"公平"的角度考虑(类似于生活着排队买东西)。
算法规则:按照作业/进程到达的先后顺序进行服务。
用于作业/进程调度:
是否可抢占:非抢占式的算法。
优点:公平、算法实现简单。
缺点:排在长作业(进程)后面的短作业需要等待很长时间,带权的周转时间很大,对短作业来说用户体验不好,即FCFS算法对长作业有利,对短作业不利。
是否导致饥饿:不会。
饥饿
:某进程/作业长期得不到服务。题目:
解题:
首先是调度顺序,每一个进程根据到达时间确认执行的顺序,之后每一个进程需要等待前一个进程执行完成之后才能够被调度和执行:
根据调度算法来分别计算周转时间、带权周转时间、等待时间、平均周转/带权周转/等待时间:
周转时间
:每一个进程最终的完成时间来减去该进程一开始到达的时间
带权周转时间
:借助周转时间来计算,除数为实际该进程的运行时间
等待时间
:借助周转时间来计算,减去实际的运行时间即可
等待时间 = 周转时间 - 运行时间 - I/O操作时间
。平均周转、带权、等待时间计算
:其中的除数就是任务的数量
说明:通过上面计算,可以注意到其中P3进程的带权周转时间特别长,高达8,也就表示是这个进程本来是只需要很少时间就能够执行完成的,但是等待了很长的时间才可以被处理完,对于该进程的用户来说体验十分糟糕。
先来先服务对于带权周转时间、平均等待时间这些指标并不是很优先,此时就有了短作业优先算法的提出。
短作业优先(SFJ,Shortest Job First)
算法思想:追求最少的平均等待时间,最少的平均周转时间、最少的平均带权周转时间。
算法规则:最短的作业/进程优先得到服务(所谓"最短",是指要求服务时间最短)。
用于作业/进程调度:即可用于作业调度,也可用于进程调度。用于进程调度的可称为"短进程优先(SPF,Shortest Job First)算法
"。
是否可抢占:SJF与SPF是非抢占式的算法,但是也有抢占式的版本(最短剩余时间优先算法 SRTN,Shortest Remaining Time Next
)。
优点:"最短的"平均等待时间、平均周转时间。
缺点:不公平,对短作业有利,对长作业不利,可能产生饥饿现象。另外,作业/进程的运行时间由用户提供(这个用户可以把自己本来应该是长作业,但是自己提交的数据写的很短),并不一定真实,不一定能够做到真正的短作业优先。
是否会导致饥饿:会,如果源源不断的有短作业/进程到来,可能使长作业/进程长时间得不到服务,产生"饥饿"现象,如果一直得不到服务,则称为"饿死"。
短作业优先算法(非抢占式的,对于SJF与SPF可以指的是作业或进程)
例题说明:用于进程调度,这里实际上由于是面向进程,应该是非抢占式的短进程优先调度算法,而不是短作业优先,但是很多题目并不会很在意这个细节,实际这类的规则一样。
分析:
短作业/进程优先调度算法
:每次调度时选择当前已到达且运行时间最短的作业/进程。
调度顺序思路如下:
最终的顺序为:P1->P3->P2->P4
那么对于一系列的指标计算如下:
可以看到通过使用短作业优先算法,对于平均相关的指标时间都更低。
抢占式的短作业优先算法又称为"最短时间优先算法(SRTN)"
题目:针对抢占式的
分析:
最短剩余时间优先算法:每当有进程加入就绪队列改变时就需要调度,如果有新到达的进程剩余时间比当前运行的进程剩余时间更短,则由新进程抢占处理机,当前运行进程重新回到就绪队列,另外,当一个进程完成时也需要调度。
调度顺序思路如下:
0时刻(P1到达,P1开始执行) 最新各进程剩余执行时间:P1(7)
2时刻(P2到达,P2开始执行) 最新各进程剩余执行时间:P1(5)、P2(4) | 执行时间段:P1:0->2
4时刻(P3到达,P3开始执行) 最新各进程剩余执行时间:P1(5)、P2(2)、P3(1) | 执行时间段:P2:3->4
5时刻(P3执行完,P4到达,P2执行) 最新各进程剩余执行时间:P1(5)、P2(2)、P4(4) | 执行时间段:P3:5
----接下来之后没有新的进程到达,依次就是找到剩余执行时间短的先执行,之后依次找
7时刻(P2执行完,P4执行) 最新各进程剩余执行时间:P1(5)、P4(4) | 执行时间段:P2:6->7
11时刻(P4执行完,P1执行) 最新各进程剩余执行时间:P1(5) | 执行时间段:P4:8->11
16时刻(P1执行完) 最新各进程剩余执行时间:无 | 执行时间段:P1:12-16
那么根据上面思路推导出的各进程及执行顺序如下:
P1:0->2
P2:3->4
P3:5
P2:6->7
P4:8->11
P1:12-16
此时根据图来进行计算各个指标:
说明:相对比非抢占式的,对于抢占式的短作业优先算法最终得到的几个平均时间的指标更低。
注意细节:
1、若是题目中没有特别说明,那么所提到的**"短作业/进程优先算法"默认是非抢占式**的。
2、对于书上写"SJF调度算法的平均等待时间、平均周转时间最少"严格意义上来说是错误不严谨的。根据上面实际例子来看对于最短剩余时间优先算法得到的平均等待时间、平均周转时间还要更少。
上面对于非抢占式的,则都需要加上相应的条件才可。
若是对于抢占式的则无需增加条件,应该说:抢占式的短作业/进程优先调度算法(最短剩余时间有限,SRNT算法
)的平均等待时间、平均周转时间最少。
3、虽然严格来说,SJF的平均等待时间、平均周转时间并不一定最少,但对比其他(如先来先服务算法,FCFS
),SJF依旧可以获得比较少的平均等待时间、平均周转时间。
4、对于选择题中若是出现"SJF算法的平均等待时间、平均周转时间最少"的选项【最佳的为:抢占式短作业优先算法SRNT的平均等待时间、平均周转时间最少,该含义仅仅指的是非抢占式的】,最好判断其他选项是不是有明显的错误,若是没有更合适的选项才去选择该选项。
对FCFS和SJF两种算法的思考:
RCFS算法
:每次调度的时候选择一个等待时间最长的作业/进程为其服务,但是没有考虑作业的运行时间,因此导致了对短作业不友好的问题。SJF算法
:选择一个执行时间最短的作业为其服务,但是又完全不考虑各个作业的等待时间,因此导致了对长作业不友好的问题,甚至还会造成饥饿问题。此时有没有算法能够既考虑各个作业的等待时间,也能够兼顾运行时间?
高响应比优先算法(HRRN,Highest Response Ratio Next)
算法思想:综合考虑作业/进程的等待时间和要求服务的时间。
算法规则:在每次调度时先计算各个作业/进程的响应比,选择响应比最高的作业/进程为其服务。
响应比 = (等待时间+要求服务时间) / 要求服务时间
,响应比>=1用于作业/进程调度:即可用于作业调度,也可用于进程调度。
是否可抢占:非抢占式的算法,因此只有当前运行的作业/进程主动放弃处理机时,才需要调度,才需要计算响应比。
优点:
1、综合考虑了等待时间和运行时间(要求服务时间)。
2、等待时间相同时,要求服务时间短的优先(SJF的优点)。
3、要求服务时间相同时,等待时间长的优先(FCFS的优点)。
4、对于长作业来说,随着等待时间越来越久,其响应比也会越来越大,避免了长作业饥饿的问题。
是否会导致饥饿:不会
题目:
分析:
高响应比优先算法
:非抢占式的调度算法,只有当前运行的进程主动放弃CPU时(正常/异常完成,或主动阻塞),才需要进行调度,调度时计算所有就绪进程的响应比,选响应比最高的进程上处理机。
本题中并没有涉及到I/O相关阻塞情况,所以本题中无需考虑阻塞的时间。
进程调度的顺序分析:
0时刻:只有P1到达就绪队列,P1上处理机
7时刻(p3执行):0-7这个过程中P2、、P3、P4都已到达,此时依次计算响应比:p2(2.25) p3(4) p4(1.5)
p2:等待时间=7-2=5(当前是7时刻,到达时间为2时刻) 要求服务时间为4,即为 (5+4)/4=2.25
p3:等待时间=7-4=3(当前是7时刻,到达时间为4时刻) 要求服务时间为1 即为 (3+1)/1=4
p4:等待时间=7-5=2(当前是7时刻,到达时间为5时刻) 要求服务时间为4 即为 (2+4)/4=1.5
| 实际运行时间:p1:0-7
8时刻(p2执行):就绪队列有P2、P4,此时依次计算响应比:P2(2.5) P4(2.25)
P2:等待时间=8-2=6(当前是8时刻,到达时间为2时刻) 要求服务时间为4 即为 (6+4)/4=2.5
P4:等待时间=8-5=3(当前是8时刻,到达时间为5时刻) 要求服务时间为4 即为 (3+4)/4=2.25
| 实际运行时间:p2:8
12时刻(P4执行):就绪队列有P4,无需计算
| 实际运行时间: p2:8-12
16时刻(全部执行完毕)
| 实际运行时间: p4:12-16
根据上述分析来梳理出每个进程执行的时间:
p1:0-7
p2:8
p2:8-12
p4:12-16
小结论:P2和P4要求服务时间一样,但P2等待时间长,所以P2的响应比更大。
注意:
1、缺陷点:这几种算法主要用于早期的批处理系统,由于计算机十分昂贵,人们更追求系统的整体表现,而并没有过多的关注用户交互的体验与交互性。
2、关心点:这几种算法主要关心的是对用户的公平性、平均周转时间,平均等待时间等评价系统整体性能的指标,并不关心"响应时间",也不区分任务的紧急程度。
3、对于其中FCFS算法通常结合其他算法一起使用,目前也扮演着十分重要的角色,下一章节则是讲解比较适合用于交互式系统的调度算法。
时间片轮转(RR,Round-Robin)
算法思想:公平地、轮流地为各个进程服务,让每个进程在一定时间间隔内都可以得到响应。
算法规则:按照各进程到达就绪队列的顺序,轮流让各个进程执行一个时间片(如100ms)。若进程未在一个时间内执行完,则剥夺处理机,将进程重新放到就绪队列队尾重新排队。
用于作业/进程调度:用户进程调度(只有作业放入内存建立了相应的进程后,才能够被分配处理机时间片)。
是否可抢占?若是进程未能够在时间片内运行完,将被强行剥夺处理机使用权,因此时间片轮转调度算法属于抢占式的算法,由时钟装置发出时钟中断来通知CPU时间片已到。
优点:公平,响应快,适用于分时操作系统。
缺点:由于高频率的进程切换,因此有一定开销,不区分任务的紧急程度。
是否会导致饥饿:不会。这种算法会轮流地为各个进程服务。
补充:对于时间片太大以及太小的情况如下:可见下面例题会给出当时间片为5时退化成先来先服务算法。
①若是时间片太大情况:会增大这些进程的响应,就会失去时间片轮转调度算法最大的优点。
②若是时间片太小情况:对于进程调度、切换是有时间代价的(保存、恢复运行环境),因此如果时间片太小,会导致进程切换过于频繁,系统会花大量的时间来处理进程切换,从而导致实际用于进程执行的时间比例减少,可见时间片也不能太小。
那么如何设计相对合适的时间片大小呢?
题目:
分析:
时间片轮转调度算法:轮流让就绪队列中的进程一次执行一个时间片(每次选择的都是排在就绪队列队头的进程)。
情况1:时间片大小为2时的情况
调度顺序分析:
0时刻 就绪队列:P1(5) P1上处理机运行一个时间片
2时刻 就绪队列:P2(4)->P1(3) P2上处理机运行一个时间片 | 执行完时间段:P1:0-2
4时刻 就绪队列:P1(3)->P3(1)->P2(2) P1上处理机运行一个时间片 | 执行完时间段:P2:3-4
5时刻(时间片没用完,来了一个进程) 就绪队列:P3(1)->P2(2)->P4(6)
6时刻 就绪队列:P3(1)->P2(2)->P4(6)->P1(1) P3上处理机运行一个时间片 | 执行完时间段:P1:5-6
7时刻(P3进程结束,时间片提前结束)P2(2)->P4(6)->P1(1) P2上处理机运行一个时间片 | 执行完时间段:P3:6-7
9时刻(P2进程结束) 就绪队列:P4(6)->P1(1) P4上处理机运行一个时间片 | 执行完时间段:P2:7-9
11时刻 就绪队列:P1(1)->P4(4) P1上处理机运行一个时间片 | 执行完时间段:P4:10-11
12时刻(P1进程结束,时间片提前结束) 就绪队列:P4(4) P4上处理机运行一个时间片 | 执行完时间段:P2:11-12
14时刻 就绪队列:P4(2) P4上处理机运行一个时间片 | 执行完时间段:P4:12-14
16时刻(P4进程结束) 就绪队列:无 | 执行完时间段:P4:14-16
解答1:第二行中若是某个进程分配到时间片为,此时时间片用完的时刻正好又来了一个新的进程,此时如何确定顺序?
解答2:第六行中若是时间片没有用完就结束了,此时会由于进程的主动放弃来重新发生一次调度选择。
接着根据上面调度顺序写出时刻表:
P1:0-2
P2:3-4
P1:5-6
P3:6-7
P2:7-9
P4:10-11
P2:11-12
P4:12-14
P4:14-16
情况2:时间片大小为5时的情况
调度顺序分析:
0时刻 就绪队列:P1(5) P1上处理机运行一个时间片
2时刻(时间片未结束,来P2) 就绪队列:P2(2)
4时刻(时间片未结束,来P3) 就绪队列:P2(4)->P3(1)
5时刻(P1结束,来P4) 就绪队列:P2(4)->P3(1)->P4(6) P2上处理机运行一个时间片 | 执行完时间段:P1:0-5
9时刻(P2结束,时间片提前结束) 就绪队列:P3(1)->P4(6) P3上处理机运行一个时间片 | 执行完时间段:P2:5-9
10时刻(P3结束,时间片提前结束) 就绪队列:P4(6) P4上处理机运行一个时间片 | 执行完时间段:P3:9-10
15时刻(时间片结束) 就绪队列:P4(1) P4上处理机运行一个时间片 | 执行完时间段:P4:10-15
16时刻(P4结束,时间片提前结束) 就绪队列:无 此时全部结束 | 执行完时间段:P4:15-16
接着根据上面调度顺序写出时刻表:
P1:0-5
P2:5-9
P3:9-10
P4:10-15
P4:15-16
我们再看下先来先服务调度算法的时序图:
可以发现与时间片设置为5的时候是一模一样,为什么会造成这种情况呢?
算法思想:随着计算机的发展,特别是实时操作系统的出现,越来越多的应用场景需要根据任务的紧急程度来决定处理顺序。
算法规则:每个作业/进程都有各自的优先级,调度时选择优先级最高的作业/进程。
用于作业/进程调度:即可用于作业调度,也可用于进程调度以及I/O调度。
是否可抢占:抢占式、非抢占式都有。
优缺点:
是否会导致饥饿:会。
补充:就绪队列未必只有一个,可以按照不同优先级来组织,也可以把优先级高的进程排在更靠近队头的位置。
根据优先级是否可以动态改变,可以将优先级分为静态优先级、动态优先级两种。
思考1:如何合理的设置各类进程的优先级?
为什么更偏好繁忙型进程?
思考2:如果采用的是动态优先级,那么什么时候应该调整?
对于优先数与优先级概念并不一样,有些题目优先数越大,优先级越高;但是有些题目优先数越小,优先级越高,根据题目而定。
题目:
分析:
实现方式一:非抢占式的优先级调度算法:每次调度时选择当前已到达且优先级最高的进程。当前进程主动放弃处理机时会进行调度。
调度顺序分析:
0时刻 已来临进程:P1 P1上处理机
7时刻(P1结束) 已来临进程:P2(4)、P3(1)、P4(4) P3先上处理机 | 执行完时间:P1:0-7
8时刻(P2结束) 已来临进程:P2(4)、P4(4) P2、P4优先级相同,P2先来 P2先上处理机 | 执行完时间:P2:7-8
12时刻(P2结束) 已来临进程:P4(4) P4先上处理机 | 执行完时间:P2:8-12
16时刻(P4结束) 已来临进程:无 | 执行完时间:P4:12-16
接着根据上面调度顺序写出时刻表:
P1:0-7
P3:7-8
P2:8-12
P4:12-16
实现方式二:抢占式的优先级调度算法:每次调度时选择当前已到达且优先级最高的进程。当前进程主动放弃处理机时发生调度。另外,当就绪队列发生改变时也需要检查是否发生抢占。
调度顺序分析:
0时刻 已来临进程:P1(7) P1上处理机运行
2时刻(来进程) 已来临进程:P1(5)、P2(4) 由于P2优先级高,P2上处理机运行 | 执行完P1:0->2
4时刻(来进程) 已来临进程:P1(5)、P2(2)、P3(1) 由于P3优先级高,P3上处理机运行 | 执行完P2:2->4
5时刻(P3执行结束,来进程) 已来临进程:P1(5)、P2(2)、P4(4) 由于P2、P4优先级一样P2先来,先执行P2 | 执行完 P3:4->5
7时刻(P2执行完毕) 已来临进程:P1(5)、P4(4) P4进程上处理机运行 | 执行完P4:5->7
11时刻(P4执行完毕) 已来临进程:P1(5) P1进程上处理机运行 | 执行完P4:7-11
16时刻(P1执行完毕) 已来临进程:无 | 执行完P1:11-16
引出多级反馈队列
首先对上面的四个算法优点来进行汇总:
是否能够对这些算法都能够折中权衡,得到一个综合表现优先平衡的算法呢?
介绍多级反馈队列调度算法
此时,提出了多级反馈队列调度算法。
算法思想:对其他调度算法的折中权衡。
算法规则:
用于作业/进程调度:用于进程调度。
是否可抢占:抢占式的算法。在k级队列的进程运行过程中,若更上级的队列(1~k-1级)中进入了一个新进程,则由于新进程处于优先级更高的队列,因此新进程会抢占处理机,原先运行的进程放回k级队列队尾(也就是原先最近一次的k级)。
优点:①对各类型进程相对公平(FCFS的优点);②每个新到达的进程都可以很快得到响应(RR优点);③短进程只用较少的时间就可完成(SPF的优点)。④不必实现估计进程的运行时间,避免用户作假。⑤可灵活地调整对各类进程的偏好程度,如CPU密集型、I/O密集型进程。
是否会导致饥饿:会。
设置多级就绪队列,各级队列优先级从高到低,时间片从小到大,如下图所示:
流程梳理:
详细调度过程:
时刻为0时,由于是第一级队列,那么就只有1个时间片,当时间片用完时,他还没有结束此时就会进入到下级队尾(也就是第二级队列)。
第1级队列:P1(8)
第2级队列:无
第3级队列:无
P1上处理机,分配时间片为1
时刻为1,P2进程也已经到达,此时会进入到第一级的队尾。此时开始进行调度,从优先级高的第1级队列开始,由于其中不为空,此时选择队尾的P2进程并分配给其时间片上CPU。
第1级队列:P2(4)
第2级队列:P1(7)
第3级队列:无
P2上处理机,分配时间片为1 | 执行任务:P1:0-1
时刻为2:当运行了1个时间单位后,P2进程会下处理机放置到下一级队列也就是第二级队列的队尾中。由于第1级队列为空,此时则会取第2级队列中的第一个进程,此时由于是第二级其时间片为2
第1级队列:无
第2级队列:P2(3)->P1(7)
第3级队列:无
P1上处理机,分配时间片为2 | 执行任务:P2:1-2
时刻为4时,P1进程下处理机,此时将P1放入到第三级队列末尾,此时P2上处理机
第1级队列:无
第2级队列:P2(3)
第3级队列:P1(5)
P2上处理机,分配时间片为2 | 执行任务:P1:2-4
时刻为5时(进程到来,尝试抢占CPU),P3进程到达第一级的队尾,此时会与处在CPU的进程2优先级进行比较,由于P3优先级为3,P2为2,此时会发生抢占情况,此时会将P2放回到原来队列2的末尾,之后让P3上处理机抢占CPU:
第1级队列:P3(1)
第2级队列:P2(2)
第3级队列:P1(5)
P3上处理机,分配时间片为1 | 执行任务:P2:4-5
时刻为6时,P3执行完任务,此时开始调度,由于第1级队列中没有任务,此时会调度第2级队列中的任务:
第1级队列:无
第2级队列:P2(2)
第3级队列:P1(5)
P2上处理机,分配时间片为2 | 执行任务:P3:5-6
时刻为8时,P2完成任务,此时开始调度,由于第1、2级队列没有任务,此时会调度第三级队列任务:
第1级队列:无
第2级队列:无
第3级队列:P1(5)
P1上处理机,分配时间片为4 | 执行任务:P2:6-8
时刻为12时,P1时间片使用完,由于此时P1本身已经在最下面一级队列,此时会将其依旧是放入到队列3中的末尾继续被调度:
第1级队列:无
第2级队列:无
第3级队列:P1(1)
P1上处理机,分配时间片为4 | 执行任务:P1:8-12
时刻为13时,P1任务完成,此时所有进程结束
第1级队列:无
第2级队列:无
第3级队列:无
所有任务完成 | 执行任务:P1:12-13
所有进程各自的执行顺序:
P1:0-1
P2:1-2
P1:2-4
P2:4-5
P3:5-6
P2:6-8
P1:8-12
P1:12-13
多级队列调度算法:按照进程类型设置多个队列,并且给不同的队列设置不同的优先级(优先级从高向下依次降低)。并且每一次调度发生的时候,首先要选中一个队列,接着根据这个队列内的调度算法来选取队列内的某一个进程,让它上处理机运行。
重点1:不同的队列之间可以采取不同方式:
①固定优先级:高优先级为空时,低优先级进程才能够被调度。
②时间片划分:如三个队列分配时间各自为50%、40%、10%。
重点2:对于一个队列中有很多同类的进程,如何抉择?此时可以采用不同的调度策略:
① 系统进程队列可以采用优先级调度算法。(可以保证优先级更高的先执行)
②交互队列采用RR时间片轮转算法。(很短的时间周期内都能够被响应一次)
③批处理队列可以采用FCFS先来先服务调度策略。
回顾:进程具有异步性的特征,对于异步性指的是各并发执行的进程以各自独立的、不可预知的速度向前推进。
例1:一个人约会两个人,对于心只有一个。
那么对于心先给1号还是先给2号,我们无法进行预测,此时就会由如下两种情况:
这就是程序异步性的一个体现。
此时我们设置一个条件:心首先得给1号,之后再给2号,此时根据这个条件,那么渣男在并发执行这两个约会进程的时候,就必须保证"1号的指令2"一定要在2号的"指令1"之前执行,此时如下:
可以通过设置条件,从而保证各个进程之间的推进次序是按照我们预想的方式进行。
例2:进程通信中的管道通信
对于管道通信中读进程与写进程是并发运行的,由于并发必然导致异步性,对于"写数据"与"读数据"两个操作的执行顺序是不确定的,实际应用中,又必须按照"写数据—>读数据"的顺序执行。
**如何解决这种异步问题?**进程同步。
对于以上有特定顺序的要求,此时操作系统提供"进程同步机制"来实现上述的需求。
进程的"并发"需要"共享"的支持,各个并发执行的进程不可避免的需要共享一些系统资源,如:内存、打印机、摄像头等I/O设备。
主要包含两种资源共享方式:互斥共享、同时共享
临界资源
:将一个时间段内只允许一个进程使用的资源。许多物理设备(如摄像头、打印机)都属于临界资源,对于还有一些变量、数据、内存缓冲区等都属于临界资源。
对临界资源的访问,必须互斥地进行。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待,当前访问临界资源的进程访问结束,释放资源后,另一个进程才能够去访问临界资源。
互斥
:间接制约关系。对于临界资源的互斥访问,可以来逻辑上分为下面四个部分:
注意:临界区是进程中访问临界资源的代码段。进入区和退出区是负责实现互斥的代码段。临界区也称为"临界段"。
若是一个进程暂时不能进入临界区,是否该进程能够一直占着处理机?该进程有没有可能一直进不了临界区。
为了实现对临界资源的互斥访问,同时保证系统的整体性能,需要遵循以下原则:
若是进程中没有进程互斥?
过程:若是进程A、进程B并发运行,由于两个进程中都包含有使用打印机资源,此时先调度A上处理机运行,当A在使用打印机的过程中,分配给它的时间片使用完了,接下来操作系统又调度B让它上处理机运行,进程B也在使用打印机。
最终结果:此时会导致A、B的打印内容会混在一起。
若是我们想要实现进程A在访问打印机的时候,B不会再访问,此时就需要使用到进程互斥。
接下来使用软件代码的方式来尝试实现互斥的访问资源,介绍四种,对于这四种都还是有一定的缺陷。
算法思想:两个进程在访问完临界区后把使用临界区的权限交给了另一个进程,也就是说每个进程进入临界区的权限只能被另一个进程赋予。
通过使用一个变量来表示的对应自己的进程号,首先turn的初值为0,表示刚开始只允许0号进程进入临界区。
流程:
更加通俗易懂的理解:
特征:通过该算法可以实现"同一时刻最多只允许一个进程访问临界区",进程是互相之间轮流使用的。
缺点:各个进程只能够轮流的使用临界区,每个进程在退出区这部分代码表示一个谦让的动作让对方使用,但是对方一直不使用这个资源的时候可以发现会一直轮不到自己。最大的问题就是违反了"空闲让进"的原则。
算法思想:设置一个布尔型数组flag[2],数组中各个元素用来标记各进程想要临界区的意愿,比如"flag[0]=true"意味着此时0号进程P0想要进入到临界区。每个进程再进入临界区之前先检查当前有没有别的进程想要进入到临界区,若是没有,则把自身对应的标志flag[i]设为true,之后开始访问临界区。
对于进入区中实际做了两个事情:①检查是否对方想进入临界区。②表示自己进入临界区。
通俗易懂理解:
与单标志的区别在于:
问题:若是两个进程并发运行,按照①⑤②⑥③⑦…的顺序执行,则会导致出现问题两个进程都会进入到临界区。
缺点:违反了"忙则等待"原则。原因在于,进入区的"检查"和"上锁"两个处理不是一气呵成的,"检查"后,"上锁"前可能发生进程切换。
算法思想:双标志先检查法的改变,前一个算法的问题是先"检查"后"上锁",对于这两个操作无法一气呵成,此时导致了两个进程同时进入临界区的问题。因此,人们想到先"上锁"后"检查"的方法,来避免上述问题。
若是按照①⑤②⑥…的顺序,此时会出现进程P0、P1都进入不到临界区的问题。
问题:双标志后检查法虽然解决了"忙则等待"的问题,但是又违背了"空闲让进"和"有限等待"原则,会因各进程都长期无法访问临界资源而产生"饥饿"的现象。
算法思想:结合双标志位、单标志法的思想。如果双方都争着想进入临界区,那么可以让进程尝试谦让,做一个有礼貌的进程。
通俗易懂描述:每个进程首先表示我要使用,接着再进行谦让一下,最后执行谦让的进程,那么另一个进程则会先进入到临界区。
举两个实际收红包的例子,最终是否收到红包是看最后谁谦让的:
例1:最终是阿姨谦让,然后自己收下
例2:最终是自己谦让,然后阿姨收回红包
我们尝试使用不同的顺序穿插执行如下,不会出现之前的都能够进入临界区以及两个都阻塞在临界区外的情况:
对于代码再次进行深入解读下:
每个进程的【进入区】都分为三个步骤:
写一遍条件:
//进程P0
flag[0] = true;//表示P0进程自己想要使用资源
turn = 1;//表示谦让给P1进程使用
while (flag[1] && turn == 1);//若是进程P1想要使用 同时 最后一步是自己(P0)谦让的,那么进入到循环
//进程P1
flag[1] = true;//表示P1进程自己想要使用资源
turn = 0;//表示谦让给P0进程使用
while (flag[0] && turn == 1);//若是进程P0想要使用 同时 最后一步是自己(P1)谦让的,那么进入到循环
优点:使用Peterson算法解决了进程互斥问题、遵循了空闲让进、忙则等待、有限等待三个原则,但是依然未遵循让权等待的原则。
评价:Peterson算法是相较于之前三种软件解决方案来说是最好的,但依然不够好。
实现思路:利用"开/关中断指令"实现。
优点:简单、搞笑。
缺点:不适用于多处理机。由于关中断、开中断是内核指令,所以只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令)只能够运行在内核区,主要原因是若是不运行在内核让用户使用会很危险。
简介:TSL指令,也有地方称为TestAndSetLock指令,或TSL指令。
实现原理:TSL指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。
下面使用C语言描述其硬件实现的逻辑,并非真实就是这么实现的!!!
实现流程:若刚开始lock是false,则TSL返回的old值为false,while循环条件不满足,直接跳过循环,进入临界区。若刚开始lock是true,则执行TLS后old返回的值为true,此时while循环满足,会一直循环,直到当前访问临界区的进程在退出区进行"解锁"。
与软件实现相比:实际TSL指令把"上锁"和"检查"操作用硬件的方式变成了一气呵成的原子操作。
优点:实现简单,无需像软件实现方法那样严格检查是否有逻辑漏洞,适用于多处理机环境。
缺点:不满足"让权等待"原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致"忙等"。【那这个算法实际上不就是我们之前的Peterson算法嘛,无非是硬件与软件不同的实现区别,并且它们都会出现忙等情况】
对于TestAndSet指令与总线的关系:
简介:有些地方叫做Exchange指令,或简称为XCHG指令。
实现原理:Swap指令用硬件实现,执行的过程不允许被中断,只能够一气呵成。
下面使用C语言描述逻辑,并非真实实现:
对TSL指令实现流程:都是先记录下此时临界区是否已经被上锁(记录在old变量上),再将上锁标记lock设置为true,最后检查old,如果old为false则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。
优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞,适用于多处理机环境。
缺点:不满足"让权等待"原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致"忙等"。
实现目的:用于实现进程互斥。
互斥锁介绍:解决临界区最简单的工具就是互斥锁(mutex lock)。一个进程在进入临界区时应获得锁;在退出临界区时释放锁。
自旋锁
:需要连续循环忙等的互斥锁,都可称为自旋锁,如TSL指令、swap指令、单标志法。
实现原理:acquire()获得锁,函数release()释放锁。每个互斥锁有一个布尔变量available,表示锁是否可用,如果锁是可用的,调用acquire()会成功,且锁不再可用。当一个进程试图获取不可用的锁时,会一直自旋,直到锁被释放。
优点:等待期间可以(并不是一定)不用切换进程上下文,多处理器系统中,若上锁的时间短,则等待代价很低。
缺点:忙等待,当一个进程在临界区中,任何其他进程在进入临界区必须连续循环调用acquire()。当多个进程共享同一CPU时,就浪费了CPU周期,因此,互斥锁通常用于多处理器系统,一个线程可以在一个处理器上等待,不影响其他线程的执行。
特性:
1、需要忙等,进程时间片用完才下处理机,违反"让权等待"。
2、常用于多处理器系统,一个核忙等,其他核照常工作,并快速释放临界区。
3、不太适用于单处理机系统,忙等的过程中不可能解锁。
问题1:是否能够解决之前在软件实现的进程互斥前三种方法由于检查、上锁操作无法一气呵成的问题?
问题2:是否能够使用某种方式令并发执行进程能够遵守让权等待的原则?
解答:1965年,荷兰学者Dijkstra提出了一种卓有成效的实现进程互斥、同步的方法:信号量机制。
信号量:实际就是一个变量(可以是一个整数,也可以是一个复杂的记录性变量),可以使用信号量来表示系统中某种资源的数量。例如, 系统中只有一台打印机,就可以设置一个初始值为1的信号量。
原语
:是一种特殊的程序段,其执行能够一气呵成,不可被中断。一对原语:wait(S)、signal(S),可以将原语理解为自己写的函数,其中的信号量S就是函数调用时传入的一个参数(若是考试出现P(S)、V(S)的操作,没有其他指定S是整型的话,默认S为记录型信号量!!!)。
本节主要讲的内容为:
介绍:用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。例如计算机系统中有一台打印机,那么可以使用一个整型1来表示。
具体实现(代码模拟):
对于wait()函数中的检查、上锁实际上与双标志检查法做的是同样的事情,只不过不一样的是在wait()中实现的检查和上锁步骤是一气呵成的,那么就避免了并发、异步导致的问题。
举例:若是此时进程P0通过了进入去,访问临界区时,其实其他的进程会阻塞等待在wait()中,只有等到进程P0使用V操作释放资源后其他进程才可进入。
存在的问题:不满足"让权等待"原则,会发生"忙等"。
对于之前的整型信号量有很大的缺陷:若是一个进程暂时进不了临界区的资源,此时会一直占用处理机循环检查,进而导致盲等的情况不满足"让权等待"的原则。
记录型的信号量定义如下:
接下来是wait与signal实现,wait原语中包含了阻塞原语block,signal原语中包含了唤醒原语wakeup(代码来实现逻辑):
wait实现:
block原语
:将当前的这个进程,阻塞起来,主动的阻塞放弃处理机。如果剩余资源数不够,使用block原语使进程从运行态进入阻塞态,并把挂到信号量S的等待队列(即阻塞队列)中。signal实现:
wakeup原语
:释放资源后,若是还有别的进程在等待该资源,则使用wakeup去唤醒等待队列中的一个进程,该进程会从阻塞态—>就绪态。默认的初始状态如下所示:由两台打印机以及四个进程并发执行
1、首先P0进程被分配,资源数-1为1,使用打印机;接着P1进程也被分配,资源数-1为0,使用打印机2,此时如下:
2、此时进程P2又被分配给了处理器,此时资源数-1为-1,由于-1<0,那么会执行wakeup原语,并将进程P2放入到等待队列中(进程P2进入到阻塞态):
3、此时进程P3也被调度,资源数-1为-2,又-2<0,那么又会执行wakeup原语,此时进程P3会放入到等待队列中(进程P3也会进入到阻塞态):
4、接着时间片又切回给了P0进程,在使用完打印机后,此时就会执行signal原语,此时首先会进行资源+1为-1,由于<=0,释放资源后就会从等待队列中取出队头的进程并将资源分配给它:
5、此时会执行P2进程,当使用完打印机之后,此时也会执行signal原语,资源数+1为0,由于0<=0此时在释放资源的同时又会从等待队列中取出P3进程并将资源分配给它:
6、此时经过了一段时间CPU又分给了P1进程使用,使用打印机完之后此时资源+1为1, 由于1>0表示没有资源在阻塞等待,此时正常结束:
7、最终P3进程执行完毕之后,在执行signal原语时资源+1为2,最终回到了初始状态:
对于记录型信号量中wait原语若是没有资源可用时,是进行阻塞等待的!
注意:若是考试出现P(S)、V(S)的操作,没有其他指定S是整型的话,默认S为记录型信号量!!!
注意点:应当去理解信号量背后的含义,一个信号量对应一种资源。
信号量的值=这种资源的剩余数量(信号量的值如果<0,说明此时有进程在等待这种资源)
对于P、V操作的含义:
信号量机制实现进程互斥的步骤:
1、分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)。
2、设置互斥信号量mutex(表示进入临界区的名额),初值为1。
3、在进入区P(mutex):申请资源。
4、在退出区V(mutex):释放资源。
三个注意点:
注意点1:实际对于信号量的定义,可以直接使用semaphore来表示记录型信号量。
原本是专门定义了一个数据结构:
实际的话可以将信号量声明简写为如下:
注意点2:对于访问不同的资源,我们设置的信号量也要不同,例如打印机设置为mutex1,摄像头设置为mutex2
注意点3:PV操作应该成对出现,若是缺少P(mutex)就不能保证临界资源的互斥访问;缺少V(mutex)会导致资源永不被释放,等待进程永不被唤醒。
由于两个进程是并发运行,那么对于他们代码的顺序无法预知。
目标:让代码执行顺序想要的那种方式进行。
使用信号量实现进程同步步骤:
1、分析在什么地方实现"同步关系",即代表保证"一前一后"执行的两个操作(或两句代码)。
2、设置同步信号量S,初始为0。
3、在"前操作"之后执行V(S)。
4、在"后操作"之前执行P(S)。
巧记:前V后P。
案例目标:想要让P1进程的代码2在P2进程的代码4之前运行。
思路:①初始话信号量为0。②在代码2之后设置V操作,在代码4之前设置P操作。
核心流程梳理,下面列举两种情况看是否能够实现进程同步:
题目:如何使用信号量来实现下面前驱图能够顺序执行?
实际在每一个前驱关系中设置一个信号量,然后都是前V后P顺序即可实现!
实际设计步骤:
1、每一对前驱关系各设置一个同步信号量。
2、在"前操作"之后对相对应的同步信号量执行V操作。
3、在"后操作"之前对相对应的同步信号量执行P操作。
示例代码如下:
S1与S2、S3有前驱关系,那么当S1执行完临界区之后,此时使用V指令释放相对应的S2、S3所需要资源。
S2、S3一开始都是使用P来进行阻塞,之后各自完成之后再去释放相对应后续执行进程所需的指令。
对于S6那么在临界区之前则需要执行三重P操作,分别是P(e)、P(f)、P(g),只有这三个资源都获取到了才能够进入到S6的临界区中。
若是实现互斥,则信号量初始值设置为1,先P后V。
若是实现同步,则信号量初始值设置为0,先V后P
若是对于前驱关系,那么每一对之间都使用一个信号量来进行表示。
对于信号量机制容易出现的问题:编写程序困难,易出错。
例如下方我们写将(mutex)与P(empty)写反了,实际前者在后,后者在前,这样子会十分容易得出现死锁问题!!!
是否恶意设计一种机制,能够让程序员写程序时不需要再关注复杂的PV操作,让写代码更加轻松呢?
1973年,Brinch Hansen首次在程序设计语言(Pascal)中引入了"管程"成分:一种高级同步机制。
管程:是一种高级同步机制。本质上也是来实现进程的同步与互斥。
管理是一种特殊的软件模块,有这些部分组成:
1、局部于管程的共享数据结构说明。
2、对数据结构进行操作的一组过程。
3、对局部于管程的共享数据设置初始值的语句。
4、管程有一个名字。
管程的基本特征:
1、局部于管程的数据只能被局部于管程的过程所访问。
2、一个进程只有通过调用管程内的过程才能够进入管程访问共享数据。
3、每次仅允许一个进程在管程内执行某个内部过程。
介绍:使用特殊的方法来定义管程:monitor、end monitor,管程的内容就是这个关键字中间的部分::
管程提供的互斥、同步功能相关体现:
实际在编译的时候,会由编译器负责实现各个进程互斥的进入管程当中的过程!
例1:两个生产者进程并发执行,依次调用了insert过程。【互斥例子】
过程:当第一个进程先进入到insert函数时,若是没有执行完,此时第二个进程就尝试着调用insert函数,由于编译器实现的功能,此时会暂时阻止第二个进程进入insert函数,此时第二个进程会阻塞insert函数后,只有第一个insert执行完才能够停止阻塞,继续执行。
例2:两个消费者进程先执行,生产者进程后执行。【同步例子】
过程:①默认count=0,由于两个消费者先执行,此时都会因为count==0都被阻塞住,首先consumer1进入等待队列,接着consumer2再次进入等待队列;②此时生产进程执行,会先执行count++为1,当=1是就会唤醒消费者进程,此时首先出队的测试consumer2,也就是消费者2进程,此时会执行count–,并且会调用remove_item()返回一个产品。
可以使用synchronized
关键字来表示管程,这里的话实际上是实现了一个函数互斥使用的过程。
特定的"入口"指的是函数。
使用PV操作来进行解决,对于一般PV操作题目分析步骤如下:
1、关系分析。找到题目中描述的各个进程,分析它们之间的同步、互斥关系。
2、整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
3、设置信号量,并根据题目条件确定信号量初值(互斥信号量初值一般为1,同步信号量的初值要看对应资源的初始值是多少)。
生产者-消费者问题描述:系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(这里的产品可以理解为某种数据)。
开始问题分析,按照上面给出的分析步骤来进行:
步骤一:关系分析
首先确定出两个一前一后的关系:
①缓冲区没满的时候,生产者才能生产。【缓冲区没满—>生产者生产】
②缓冲区不空的时候,消费者才能消费。【缓冲区不空—>消费者消费】
明确互斥规则:缓冲区是临界资源,各进程必须互斥的访问缓冲区。【互斥关系】
互斥访问的原因?若是生产者进程有多个此时进行并发访问:可能会出现数据覆盖的问题。
举例:此时两个生产者进程同时并发访问缓冲区,可能会出现向同一个位置去写数据的情况,此时就会出现数据覆盖问题。
2、整理思路,确定各个进程的操作流程并确认P、V操作的大致顺序
3、设置信号量
根据下图中的前后关系,我们来确认出同步信号量:
semaphore full = 0;//full用来表示 缓冲区不空—>消费者消费 之间的信号量,那么对于消费者消费的是产品数量,那么所对应的资源应该是产品的数量(【非空缓冲区的数量】),根据题目初始条件,一开始产品的数量为0,所以这里full设置为0
semaphore empty = n;//empty用来表示 缓冲区没满—>生产者生产 之间的信号量 对于生产者能够生产的条件则是是否还有空间能够去给生产,那么这里对应的应该是【空闲缓冲区数量】,根据题目初始条件初始产品数量为0,所以对应的空闲缓冲区数量为n,此时empty设置为n
实际在并发过程中,生产者在生产时可能会对同一个位置进行数据覆盖,对于消费者也有可能取出的产品也是同一个,主要原因就是各自临界区中的步骤不是一气呵成的,此时我们来使用一个互斥信号量来表示之间的互斥关系:
semaphore mutex = 1;//互斥信号量 表示对缓冲区的互斥访问
最终结果:
根据两个前后关系,我们来添加PV指令:
针对于生产产品、取出产品的过程中我们都使用信号量互斥锁住,最终同步、互斥的结果如下图所示:
想法1:改变相邻的P(mutex)与P(empty)这两个P操作是否有无问题?
结论:造成死锁。两个进程都无法推进下去。
实际例子1:我们将P(mutex)与P(empty)两个P操作进行互换,接着来尝试执行①②③④顺序。
当前条件:缓冲区内放满了产品,empty=0,full=n。
流程:生产者进程执行①使mutex变为0,再执行②,由于empty为0此时表示当前产品已满,此时生产者会进入到阻塞状态;接着切换到消费者进程执行③由于mutex为0,生产者目前并没有释放锁,此时消费者也被阻塞。
结果:生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现"死锁"问题。
实际例子2:若是缓冲区中没有产品,full=0,empty=n,按照③④①的顺序执行下去依旧会发生死锁。
强调必做:实现互斥的P操作一定要在实现同步的P操作之后,若是颠倒,那么就会出现死锁问题。
想法2:改变相邻的V(mutex) 与 V(full)是否有无问题?
结论:没有问题。因为两个V操作时不会导致进程阻塞的问题,那么两个V操作顺序是可以调换的。
对于生产者生产一个产品以及消费者使用一个产品是否可以访问PV操作之间呢?
结论:可以的,逻辑上来看放到PV操作之间是没有问题的,但是放入进去之后会影响系统的效率。
一种情况描述:若是当前盘子里只有一个苹果时,那么老妈无法再向盘子放橘子,儿子也无法从盘子中取水果,实际若是苹果在盘子上时,生产者进程2、消费者进程2若是执行了则都会进入到阻塞过程:
梳理条件:
盘子只能放一个水果
生产者:老爸、老妈
老爸生产苹果、老妈生产橘子
消费者:儿子、女儿
儿子消费苹果、女儿消费橘子
步骤一:关系分析。找到题目中描述的各个进程,分析它们之间的同步、互斥关系。
与生产者-消费者问题不同的是:在这里的多生产者-多消费者问题,每个生产者生产的东西以及对应消费者消费的东西是不一样的。
步骤二:整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
步骤三:设置信号量。
①父亲生产—>女儿取出:条件应该为盘子中的数量(苹果数)-1才能够取出,表示苹果数量,此时根据初始条件应该表示的是apple=0.
②母亲生产—>男孩取出:条件应该为盘子中的数量(橘子数)-1才能够取出,表示橘子数量,此时根据初始条件应该表示的是orange=0.
③男孩、女孩取出 —> 盘子为空:条件应该是盘子目前可存放的数量(空数量),表示空位置数量,此时根据初始条件没有水果(此时有一个空位置)是plate=1。
最终我们来实际代码添加同步与互斥代码:
首先是同步代码:对应的箭头指的是通过这个原语操作则会唤醒对应的进程
接着是互斥代码:
实际我们在本题中由于缓冲区只为1,无需来为每一个操作添加互斥PV的,具体原因见下面思考解释!!!
分析如下:
结论:可以,即使不设置专门的互斥变量mutex,也不会出现多个进程同时访问盘子的现象。
结论:不可以,此时可能导致两个进程写入缓冲区的数据相互覆盖的情况!:
分析如下:
结论:若是缓冲区太小>1,那么就必须设置一个互斥信号量mutex来保证互斥访问缓冲区。若是缓冲区大小为1,那么有可能不需要设置互斥信号量就可以实现互斥访问缓冲区的功能,这并不是绝对的需要具体功能具体分析。
解决多生产者-多消费者问题的关键:
关于进程同步互斥问题,下面是问题描述:
解决的问题:可以生产多个产品的单生产者在并发中实现同步、互斥。
梳理条件:
生产者 生产组合1 -> 吸烟者1号
生产者 生产组合2 -> 吸烟者2号
生产者 生产组合3 -> 吸烟者3号
吸烟者1号、吸烟者2号、吸烟者3号任意一者结束 —> 生产者生产下一组合
步骤一:关系分析。找到题目中描述的各个进程,分析它们之间的同步、互斥关系。
实际我们可以将桌子抽象为容量为1的缓冲区,这个缓冲区需要互斥访问。
生产者每次生产的两种材料实际是有三种情况,一种情况我们看做一个组合,此时就有三个组合了。对于吸烟者三位各自若是完成了则会发出通知信号让生产者准备下一组烟:
步骤二:整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
步骤三:设置信号量。
由于本题缓冲区只能够放一个组合,此时我们就暂时无需考虑互斥的问题了!
主要的初始信号值如下:
对于offer1、2、3实际就是吸烟者进行消费,初始默认没有放材料此时设置为0。
对于finsh则是表示是否抽烟已经完成,由于一开始没有放材料,那么默认也没有抽烟完成,此时我们设置finish为0。
最终我们来实际代码添加同步与互斥代码:
由于我们需要循环去制作不同组合的材料,所以我们这里使用一个局部临时变量1来进行不断循环:
接着我们来实现相对应的同步关系:注意这里P(finish)放在最底下的原因是初始值为0
由于缓冲区为1,同一时刻,四个同步信号量中之多有一个为1。
问题描述:读者、写者进程都可能有多个。 这两个进程在系统中并发运行,读就是会访问同一个共享文件,写进程就是不断的写这个共享文件,由于读、写这两个操作,对文件的影响是不一样的。
对于导致数据不一致问题,要求:
①允许多个读者可以同时对文件读操作。
②只允许一个写者在文件中写信息。
③任一写者在完成写操作之前不允许其他读者或写者工作。
④写者执行写操作前,应让已有的读者、写者全部退出。
对于这些要求为什么要这么设置这些要求呢?
①原因:两个读进程在读文件的时候并不会更改文件数据信息,并且这里的读与之前的消费者不一样,这里的读并不会取走数据。
②③④原因:当一个写进程正在对文件进行写操作的时候,其他进程是不能访问这个文件的。同时若是一个写进程想要写共享文件的时候,就必须等到其他进程对这个文件的操作结束之后,才能往里面写数据。
疑问1:为什么写进程执行的时候不能读?
主要原因是若是读进程在读的这块数据刚好是写进程在写的,那么此时读到的数据可能就会是已经被覆盖掉了的,此时读到的就是原先旧的数据。由于写者进程会改变共享文件内容,所以写者进程是不可以与其他进程同时访问的!
疑问2:若是两个写者进程并发运行会发生的情况?
并行运行的时候可能会出现两个写者进程都向同一块数据写数据的操作,此时就会造成后面发生的写者会将之前写者写的数据覆盖掉。
尝试使用PV操作来解决这个问题。
分析问题步骤:
步骤一:关系分析。找到题目中描述的各个进程,分析它们之间的同步、互斥关系。
两类进程:写进程、读进程。
互斥关系:写进程—写进程、写进程—读进程这两个存在互斥关系。读进程—读进程之间不存在互斥关系。
步骤二:整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
步骤三:设置信号量。
接下来我们通过多种方案来慢慢去完善实现读-写者所需要实现的特性:读-读并发执行、读写互斥、写写互斥。
方案1:使用一个rw信号量,在读者进程、写者进程中分别上锁解锁。但是存在问题,也就是无法进行读者与读者并发访问。
方案2:在方案1的基础上增加一个count变量用于表示当前有几个读进程在访问文件
思路:可以看到我们增加了一个count判断,第一个读进程进来时count==0,此时就会加锁接着count++,此时若是又有一个读进程进来则会跳过P操作,执行向下的读操作了。此时若是写进程来则会由于已经上锁无法向下执行进入阻塞状态。
实现效果:可以实现读者—读者进程同时共享文件(但还是有问题)。
方案3:在方案2的基础之上增加了一个mutex用于保证对count变量的互斥访问
思路:我们将原先if判断以及count++中添加互斥的PW操作。此时若是第一个读进程进来时进入到if判断,此时第二个读进程再次进来时会被阻塞直到第一个读进程count++后第二个读进程才会mutex阻塞结束来进行if判断,此时就解决了方案2中出现的读-读进程不能够进行并发访问的问题。
潜在问题:只有有读进程还在读,那么写进程就会一直阻塞等待,可能导致会出现"饿死"的问题。在这种算法中,读进程是优先的。
方案4(最终方案):此时可以再设置一个互斥信号量w,来用于实现写优先。
实现效果:解决了之前提到的写者可能出现饥饿的问题。
对于方案4中的五种情况示例走一遍:
①读者1—>读者2:可以实现两个读者同时访问。
②写者1—>写者2:可以实现两个写者互斥访问。
③写者1—>读者1:可以实现写者写,读者阻塞,可以实现互斥访问。
④读者1—>写者1—>读者2:
⑤写者1—>读者1—>写者2:
总结:经过多种情况测试,对于我们想要实现的读-读并发执行、读写互斥、写写互斥都能够通过方案4实现,并且其是"读写公平法",对于在写-读-写时,由于读先进入阻塞,所以在读-写时会先进行读操作。
问题描述:
问题分析:
1、关系分析:系统中有5为哲学家进程,5位哲学家与左右邻居对其中间筷子的访问是互斥关系。
2、整理思路:这个问题中只有互斥关系,与之前互斥问题不太一样的是,每一位哲学家需要拿起两根筷子,也就是两个临界资源才能够正常吃饭。如何避免临界资源分配不当造成的死锁现象,是哲学将问题的精髓。
3、信号量设置:定义互斥信号量数组chopstick[5]={1,1,1,1,1}用于实现对5个筷子的互斥访问,并对哲学家按0-4编号,哲学家i左边的筷子的编号为i,右边的筷子编号为(i+1)%5。
方案:在吃饭之前与之后各自使用对应的筷子资源
出现问题:每位哲学家都会循环等待右边的人放下筷子(阻塞),就会发生死锁问题。
如何防止死锁的发生呢?
策略1:可以对哲学家进程施加一些限制条件,例如最多允许四个哲学家同时进餐,这样可以保证至少有一个哲学家是可以拿到左右两只筷子的。
策略2:要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其他一个可以拿起第一只筷子,另一个会直接阻塞,这就避免了占有一支后再等待另一只的情况。
策略3:仅仅当一个哲学家左右两只筷子都可用时,才允许它抓起筷子。
这里信号量设置为1,当一个哲学家A拿起筷子的时候,其他哲学家不能够拿任何筷子,哲学家A全部拿完之后此时其他哲学家才可以开始。
注意:使用这种方法并不能保证只有两边的筷子都可用时,才允许哲学家拿起筷子。
例子1:哲学家并发时每个人只拿一根筷子就会出现一个人都拿不到两根筷子的情况
哲学家进餐例子中并发会出现死锁问题,若是每个哲学家都拿一根筷子,那么就会导致进程无法继续向下推进,都会在不断的循环等待另一根筷子资源而阻塞,此时就会出现"死锁"。
例2:每个人都占有着另一个人的心,而真正喜欢那个人的心不在自己这里
在并发情况下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象,就是"死锁"。发生死锁后若无外力干涉,这些进程都无法向前推进。
死锁
:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
饥饿
:由于长期得不到想要的资源,某进程无法向前推进的现象,比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程一直得不到处理机,此时会发生长进程"饥饿"。
死循环
:某进程执行过程中一直跳不出某个循环的现象,有时是因为程序逻辑bug导致的,有时候是程序员故意设计的。
公共点:都是系统发生了异常,无法向下推进的这种现象。
不同点:
必须满足下面四个条件,若是一个不成立,死锁就不会发生:
1、互斥条件
:必须需要互斥使用的资源争抢才会导致死锁,若是可以允许多个进程使用的资源如内存、扬声器就不会导致死锁。
2、不可剥夺条件
:进程所使用资源必须是该进程主动释放,而不是其他进程强行夺走,若是能够被抢走,那么就不会发生死锁。
请求和保持条件:以哲学家例子每一个哲学家在保持一个资源不放的同时,还在请求另一个新的资源,而新的资源则又是其他进程所持有的,当满足了请求和保持的条件会发生死锁。每个哲学家都在等待右手边的哲学家放下资源,此时形成了一种资源的循环等待链。
注意:发生死锁的时候,一定会有循环等待资源的想想,若是发生了循环等待资源现象,未必一定发生死锁。
3、请求和保持条件
:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,而又对自己有的资源保持不放。
4、循环等待条件
:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
小结论:若是同类资源数>1,即使有循环等待,那么也未必发生死锁,但如果系统中有没类资源都只有一个,那循环等待就是死锁的充分必要条件了。发生循环等待时未必死锁(循环等待是死锁的必要不充分条件)
示例:即使发生了循环等待资源的现象,但若是还有别的进程,持有一个同类型可以替代的资源,那么死锁是不一定发生的,如下若是原本五个哲学家发生了循环等待资源的现象,此时有第六个哲学家拥有一个筷子资源,这个筷子资源是可以让三号哲学家右手拿起来使用的,此时三号在等待四号哲学将放下筷子同时也可以等待第六个哲学家放下这只筷子。只要第六个哲学家放下筷子,此时三号就可以拿起来吃饭了。:
1、对系统资源的竞争:各进程对不可剥夺的资源(如打印机)的竞争可能引起死锁,对可剥夺的资源(CPU)的竞争是不会发生死锁的。
2、进程推进顺序非法:请求和释放资源的顺序不当,也同样会导致死锁。例如,并发执行的进程P1、P2分别申请占有资源R1、R2,之后进程P1又紧接着申请资源R2,而进程P2又申请资源R1,两者会因为申请的资源被对方占有而阻塞,从而发生死锁。
3、信号量的使用不当也会造成死锁,如生产者-消费者问题,若是实现互斥的P操作在实现同步的P操作之前,就可能导致死锁。(可以将互斥信号量、同步信号量看做是一种抽象的系统资源)
总之:对于不可剥夺资源的不合理分配,就会导致死锁。
1、预防死锁:破坏死锁产生的四个条件中的一个或几个。
2、避免死锁:用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)。
3、死锁的检测与解除:允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种策略解除死锁。
对于静态策略:预防死锁我们只需要破坏其中的一个条件即可。
互斥条件
:只有对必须互斥使用的资源的正确才会导致死锁。
那么我们是否可以去破坏互斥条件从而不会导致死锁呢?
举例:若是进程直接向同一个打印机发出打印命令,那么一定会出现进程1首先访问临界区使用打印机,接着进程2也想要使用此时会被阻塞住,只有等待进程1释放资源后,才可以使用。
此时我们实际可以去破坏它们之间的互斥性,此时引入SPOOLing技术,操作系统可以采用SPOOLing技术将独占设备在罗i就上改成共享设备,这里将打印机改造为共享设备,通过实现了一个输出进程,每一个进程来访问打印机时直接将任务放置到输出进程即可。
效果:使用了SPOOLing技术后,在各进程看来,自己对打印机资源的使用请i去立即就会被接收处理了,不需要再阻塞等待。
缺点:并不是所有的资源都可以改造成共享使用的资源,并且为了系统安全,很多地方必须保护这样的互斥性。因此,很多时候都无法破坏互斥条件。
不剥夺条件
:进程所获得的资源再未使用完之前,不能够由其他进程强行夺走,只能主动释放。
破坏不剥夺条件方案:
策略缺点:
1、实现起来比较复杂。
2、释放已经获得的资源可能会造成前一阶段工作的失效,这种方法只适用于易保存和恢复状态的资源,如CPU。
3、反复地申请和释放资源只会增加系统开销,降低系统吞吐量。
4、若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源都需要放弃,以后再重新申请,如果一直发生这样的情况,就会导致进程饥饿。
请求和保持条件
:进程已保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
可以采用静态分配法策略:进程再运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入使用,一旦运行后,这些资源就一直归它所有,该进程不会来请求别的任何资源。
该策略实现起来简单,有明显的缺点:
有些资源可能只需要很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,都会造成严重的资源浪费,资源利用率极低,另外,该策略也有可能导致某些进程饥饿。
举例如下:A类进程需要资源1,B类进程需要资源2 ,C类进程则需要两个资源。此时若是资源1被释放了,接着A类进程又来请求资源,此时可能又会分配给A进程,之后可能操作C类进程阻塞,该策略也有可能导致某些进程饥饿
循环等待条件
:存在一种进程资源的循环等待链,链中的每一个进程都已获得的资源同时被下一个进程所请求。
可采用顺序资源分配法:首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。
原理分析:一个进程只有占有小编号的资源时才能够又资格申请更大编号的资源,按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号地资源,从而就不会产生循环等待地现象。
举例子:我们来为所有的输出设备进行编号,下面P1进程所占用的是1号、3号设备;P2进程是2号、4号设备;P3进程则是5号、7号设备。
对于P3进程,实际可以畅通无阻的不断执行,因为之后所使用到的资源肯定都是>7号的,那么是可以顺利的执行完的。
该策略的缺点:
1、不方便增加新的设备:因为可能需要重新分配所有的编号。若是有新的设备添加,此时可能需要修改所有的设备编号。
2、进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费。
3、必须按照规定次序申请资源,用户编程麻烦。
实际银行案例如下:
首先会给你每个公司最大想要借的数,接着给出每个公司已借的钱,还有就是银行家手上的总金额钱。
根据给出的初始条件,目前银行家手里还有40亿,能够分配一个合适的借钱序列最终借给所有的公司呢?
情况1:B公司此时先问你借30亿,是否敢借?
结论:不敢借,因为一旦借出去后B公司还最多会借20亿,其他分别是30、20,那么剩余的10亿无法满足任何一家公司要借的钱款,此时就会进入不安全状态。
情况2:A公司问题借20亿,是否敢借。
结论:可以借。借钱可以有两种方案:
安全序列
:如果系统按照这种序列分配资源,则每个进程都能够顺序完成,只要能找出一个安全序列,系统就是安全状态,安全序列可能有多个。
我们是否能够提前判断系统是否是安全呢?
银行家算法
:是荷兰学者Dijkstra为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁。
核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态,如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待。
此时来举出一个实际在计算机系统中的例子:
题目自带给出每个进程的最大需求分配、已分配数量以及每个进程对于每个资源的初始数量。有了这三个条件之后我们可以通过银行家算法来实现预测系统是否是安全状态。
此时思考一个问题:BAT例子中,只有一种类型的资源"钱",在计算机系统中会有多种多样的资源,应该怎么把算法扩展为多种资源的情况呢?
思路:可以将单维的数字扩展为多维的向量。比如:系统中有5个进程P0~P4,3种资源R0-R2(使用一个数字来表示每个进程对于所有任意一个资源的需求数量),初始数量为(10, 5, 7),此时某一时刻的情况可表示如下:
通过安全性算法来进行检验是否安全,循环一步步算,下面的例子是能够找到一组安全序列的:
首先依次来检查每个进程最多还需要的资源是否是<=我们的剩余资源,此时首先我们找到了P1进程【(1,2,2) < (3,3,2)】,此时我们可以优先将资源分配给P1,P1一定是可以顺序执行结束的。
此时P1结束后,原本的资源数会增加(3,3,2) + (2,0,0) = (5,3,2),此时继续向下匹配是否有能够找到<剩余资源的也就是P3,此时借给他之后再次返回, (5,3,2)+(2,1,1) = (7,4,3)
实际我们再拿(7,4,3)对比一下剩余的所有进程,可以发现全部都能够满足,那么将所有的进程添加到安全序列中。
此时安全序列可以为(P1、P3、P0、P2、P4)。
接着来举例一个找不到安全序列例子:
通过使用(3,3,2)来去匹配每个进程最多需要的,可以找到有P1,此时借给P1之后,P1归还金额,银行家剩余(3,3,2)+(2,0,0) = (5,3,2)。
接着又可以匹配到P3,此时借给P3后,P3再次归还此时银行家手里则是(5,3,2)+(2,1,1) = (7,4,3)
进入到这个阶段的时候,我们拿(7,4,3)找不到任何一个进程最多还需要的资源是<的,此时我们找不到一组安全的序列,此时说明系统处于不安全状态,可能会发生死锁问题。
注意:若是系统进入了不安全状态,那么就会将数据回退到之前那一步,不答应这个进程的请求,让进程暂时阻塞等待,这就是银行家算法的流程。
前提条件:若是系统中有n个进程,m种资源,每个进程再运行前都需要提供三个条件:
目标:某进程Pi向系统申请资源,可用一个长度m的一维数组Requesti来表示本次申请的各种资源量。
实现流程:
可以使用银行家算法预判本次分配是否会导致系统进入不安全状态:
若是系统进入了不安全状态,那么就会将数据回退到之前那一步,不答应这个进程的请求,让进程暂时阻塞等待,这就是银行家算法的流程。
系统处于不安全状态未必死锁,但死锁时一定处于不安全状态,系统处于安全状态一定不会死锁。
若是系统中既不采取预防死锁的措施,也不采取避免死锁的措施,系统就很可能发生死锁。在这种情况下,系统应当提供两个算法。
①死锁检测算法:用于检测系统状态,以确定系统中是否发生了死锁。
②死锁解除算法:当认定系统中已经发生了死锁,利用该算法可将系统从死锁状态中解脱出来。
实现死锁的检测思路:实现一种数据结构配合算法来检测系统是否已经进入死锁状态
①用某种数据结构来保存资源的请求和分配信息。
②提供一种算法,利用上述信息来检测系统是否已进入死锁状态。
节点介绍:
边介绍:
下面是一个节点、资源图的示例:
案例1:不存在死锁情况
首先我们来对这个数据结构图开始检测是否存在死锁问题:
首先P1进程向R2资源请求一个资源,R2资源给P2进程提供了一个资源,此时R2还有一个资源暂时没有分配,那么对应P1进程的请求是可以满足的,那么此时P1进程发起请求以及R1资源分配的两条边此时可以消除:
此时P2进程向R1请求一个资源,而R1种目前还有两个资源是空闲中的(其中一个是给了P2进程),那么对于P2的请求R1是能够满足它的,并且R2资源将一个资源分配给P2同样也是满足的:
按照上面的过程分析,我们可以看到最终可以消除所有的边,那么表示这个图是可以完全简化的,此时一定不会发生死锁(可以找到一个安全序列)。
案例2:检测出系统出现死锁
首先我们来看P2进程,P2进程向R1资源请求了一个资源,此时R1已经分配出去了三个资源(所有),那么此时P2进程就会进入到阻塞状态。
接着看P1进程,P1进程向R2资源请求了两个资源,此时R2已经将两个资源分配给P3和P2了,那么此时P1也进入了阻塞状态,那么我们就可以对P3进程进行下手了。
由于P3只占用了R2资源,并没有向其他资源发起请求,此时它的边即可消除:
此时我们看看是否还能够消除边,看P1进程,此时R2资源被解除了一个,那么目前有1个资源是空闲的,而又P1进程是向R2资源发送两个请求的,此时P1依旧是在阻塞中,对于P2进程也是在阻塞中。
此时无法再消除其他的边,那么就发生了死锁,最终还连着边的就是处于死锁状态的进程。
死锁定理
:
检测死锁步骤
:
能够检测出来死锁,那么就可以去想办法解除死锁。
补充:并不是系统中所有的进程都是死锁状态,用死锁检测算法化简资源分配图后,还连着边的那些进程就是死锁进程。
解除死锁的主要方法有:
1、资源剥夺法
。挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程,但是应防止被挂起的进程长时间得不到资源而饥饿。
示例:我们将下面处于死锁状态的系统,剥夺P1进程(或者其他进程)的资源,将释放的资源给其他进程使用。
2、撤销进程法(或称终止进程法)
:强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。这种方式的优点是实现简单,但所付出的代价可能会很大。因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,还需要从头再来。
3、进程回退法
:让一个或多个死锁进程回退到足以避免死锁的地步,要求系统要记录进程的历史信息,设置还原点。
举例:暂时将P2的一个占用资源的代码步骤回退掉。此时P1进程就能够执行完毕,等待P1进程释放资源后再回来执行P2进程。
如何决定"对谁动手"的指标:
1、进程优先级。
2、已执行多长时间。
3、还要多久能完成。
4、进程已经使用了多少资源。
5、进程是交互式的还是批处理式的。
整理者:长路 时间:2023.7.5-12
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。