2.1 进程与线程
2.1.1 进程的概念与特征
1.进程的概念:
多个程序并发运行,它们失去封闭性,且具有间断性和不可再现性的特征,所以引入进程(Process)的概念,描述和控制程序的并发执行,实现了操作系统的并发性和共享性。
配置专门的数据结构—进程控制块(Process Control Block,PCB)保证并发的程序独立地运行,PCB描述进程的基本情况和运行状态,以此来进行控制和管理进程。由程序段、相关数据段和PCB三部分构成了进程映像(进程实体)。进程的创建与撤销的实质是创建与撤销进程映像中的PCB,但进程映像是静态的,进程是动态的,而PCB是进程唯一存在的标志。
进程有多种定义方式,比较典型的是:
1)进程是程序的一次执行过程;
2)进程是一个程序及其数据在处理机上顺序执行时所发生的活动;
3)进程时具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
系统资源是指处理机、存储器和其他设备服务于某个进程的“时间片”,以进程为单位将这些时间片分配给不同进程。
2.进程的特征
进程与程序是两个不同的概念,基本特征有:
1)动态性:是程序的一次执行,有创建、活动、暂停、中止等过程,有一定的生命周期,是最基本的特征。
2)并发性:多个进程实体同时存于内存中,在一段时间内同时运行,引入进程的目的就是使程序并发执行,提高资源利用率。
3)独立性:进程实体是一个能独立运行、独立获取资源和接受独立接受调度的基本单位,未建立PCB的程序,不能作为一个独立的单位参与运行。
4)异步性:进程按个自独立的、不可预知的速度向前推进,异步性导致结果的不可再现性,在操作系统中应配置相应的进程同步机制。
5)结构性:每个进程都配置一个PCB对其进行描述,进程实体由程序段、数据段和进程控制块三部分组成。
2.1.2 进程的状态与转换
进程的5个状态:
1)运行态:进程在处理机上运行,在单处理机环境下,每个时刻最多只有一个进程处于运行状态。
2)就绪态:进程获取了除处理机外的一切所需资源,一旦处理机就绪,便可立即运行,多个处于就绪态的进程可以形成一个就绪队列。
3)阻塞态:又称等待态,进程正在等待某资源可用或输入/输出完成而暂停运行。
4)创建态:正在被创建,尚未转到就绪态。创建的过程先申请一个PCB,向PCB中写入控制和管理进程的信息,然后为其分配资源,最后转入就绪态。
5)结束态:进程因为正常结束或中断而退出运行,系统先置进程为结束态,然后释放和回收资源。
就绪态和等待态的区别:就绪态是只等待处理机,而等待态是等待除处理机外的其他资源,两个完全不同。
5个状态的转换过程:
就绪转运行:就绪的进程获得处理机资源后转为运行态。
运行转就绪:运行的进程在将处理机时间片用完之后,让出处理机,转为就绪态。
运行转阻塞:进程请求某一资源的使用或等待某一事件的发生,从运行转换为阻塞态。
阻塞转就绪:进程请求的资源得到分配时或事件完成时,中断处理程序必须把相应进程的状态转换为就绪态。
运行变阻塞是主动的行为,阻塞变成就绪是被动行为。
2.1.3 进程控制
进程控制主要是对进程实施有效的管理,具有创建新进程、撤销已有进程、实现进程状态转换等功能,进程控制用的程序段称为原语,它在执行期间不允许中断,是一个不可分割的单位。
1.进程的创建:
运行一个进程(父进程)创建另一个进程(子进程),子进程可以继承父进程所拥有的资源,当子进程被撤销时,应将资源全部还给父进程,而撤销父进程,必须撤销子进程。
操作系统创建新进程的过程(创建原语):
1)分配进程识别号和申请PCB;
2)分配资源,若资源不够进程处于阻塞状态,等待资源;
3)初始化PCB,主要包括初始化标志信息,初始化处理机状态信息、初始化处理机控制信息、设置优先级;
4)进程进入就绪队列等待被调度运行。
2.进程的终止:
引起进程终止的原因:正常结束,进程运行完毕正常退出;异常结束,发生了某种异常事件导致进程不能继续运行,如存储区越界、非法指令、运行超时等;外界干预,进程应外界的请求而终止运行,如操作员或操作系统的干预、父进程请求和父进程终止。
终止进程过程(撤销原语):
1)根据被终止进程的标示符,检索PCB,从中读出该进程的状态;
2)若进程处于执行状态,立即终止,将资源重新分配;
3)若其还有子孙进程,终止子孙进程;
4)将子孙进程的资源归还父进程或操作系统;
5)将PCB删除。
3.进程的阻塞和唤醒:
在执行过程中,若期待的事件未发生,系统执行阻塞原语(block),转为阻塞态,属于主动行为,阻塞原语执行过程如下:
1)根据进程标示号找到PCB;
2)若为运行态,保护现场,转为阻塞态,停止运行;
3)把该PCB插入相应事件的等待队列,将处理机资源交给其他就绪程序。
当所期待的事情发生时,调用唤醒原语(wakeup),等待进程唤醒,唤醒过程:
1)在该事件的等待队列中找到PCB;
2)从等待队列移出,转换为就绪态;
3)把PCB插入就绪队列,等待调度执行。
block和wakeup必须成对使用,前者是自我调用,后者是被其他调用。
4.进程切换
进程是在操作系统内核的支持下完成的,与内核紧密相关。进程切换是指处理机从一个进程的运行转到另一个进程上运行,在此过程中进程运行的环境产生了实质性的变化,过程如下:
1)保存处理机上下文,包括程序计数器和其他寄存器;
2)更新PCB信息;
3)把进程的PCB移入相应的队列,如就绪队列或阻塞队列;
4)选择另一个进程执行,并更新PCB;
5)更新内存管理的数据结构;
6)恢复处理机上下文。
注意:进程切换与处理机模式切换是不同的,模式切换时,处理机逻辑上可能还在同一进程中运行。若进程因中断或异常进入核心态运行,执行完又回到用户态继续执行该进程,操作系统只需恢复进程进入内核前所保存的CPU现场,无需改变当前进程的环境信息;但切换进程,运行环境信息也需要改变。
调度与切换的区别:调度指决定资源分配给哪个进程的行为,是一种决策行为;切换是指实际分配的行为,是执行行为。
2.1.4 进程的组织
进程是一个独立的运行单位,也是资源分配和调度的基本单位。由三部分组成,最核心的是PCB。
1.进程控制块
创建时,新建PCB,存储进程运行状态和优先级,常驻内存,任意时刻都可存取,并在进程结束时删除,PCB是进程实体的一部分,是进程存在的唯一标志,系统通过PCB表来管理和控制进程。
当操作系统欲调度某进程运行时,从该进程的PCB中查出运行状态和优先级;在调度到某进程之后,根据其PCB中所保存的处理机状态信息,设置恢复现场,并根据PCB中存储的程序和数据的地址,找到程序和数据;在运行过程中,需要和与之相关的进程进行同步、通信或访问文件时,也需访问PCB;当进程由于某种原因而暂停运行时,又需将其断点的处理机环境保存在PCB中。在整个进程运行的生命周期中,系统唯有经过PCB才能感知该进程的存在。
下图是一个PCB实例:
1)进程描述信息。进程标识符:各个进程的唯一标志。用户标识符:标示进程所属的用户,为共享和保护服务。
2)进程控制和管理信息。进程当前状态:描述进程的状态信息,作为处理机分配调度的依据。进程优先级:描述抢占处理机的优先级。
3)资源分配清单,说明有关内存地址空间或虚拟地址空间的状况,所打开文件的列表和所使用的输入/输出设备信息。
4)处理机相关信息,主要指处理机中各寄存器的值,当进程被切换时,处理机状态信息都必须保存在相应的PCB中,方便重新执行。
为了方便进程的调度和管理,需要将各进程的PCB用适当的方法组织起来,常用的组织方式有链接方式和索引方式,链式是将同一状态的PCB链接成一个队列,不同状态对应不同队列,也可把阻塞原因不同的队列根据其原因分成若干个。索引方式是将同一状态的进程组织在一个索引表中,索引表的表项指向相应的PCB,不同状态对应不同索引表。
2.程序段
能被进程调度程序调度到CPU执行的程序代码段,程序可被多个进程共享。
3.数据段
一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行后产生的中间或最终结果数据。
2.1.5 进程的通信
通信指进程之间信息的交换,PV是低级通信方式,高级通信方式是以较高的效率传输大量数据的通信方式,有以下三类:
1.共享存储:进程之间存在一块可直接访问的共享空间,对这个空间的读/写操作实现进程之间的信息交换,在读/写操作时,需要用同步互斥(P/V操作)进行控制。共享存储又分两种:低级方式的共享基于数据结构的共享;高级方式的共享基于存储区的共享,操作系统只提供空间和工具,交换由用户自己安排完成。
注意,用户进程空间一般是独立的,运行期间一般不能访问其他进程空间,要想让两个用户进程共享空间,必须通过特殊的系统调用实现,而进程内的线程是自然共享进程空间的。
2.消息传递
在消息传递系统中,进程间的数据交换以格式化的消息(Message)为单位的。若通信的进程之间不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信。进程通过系统提供的发送消息原语与接收消息原语进行数据交换。
1)直接通信方式。发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息,如下图。
2)间接通信方式。发送进程把消息发送到某个中间实体,接收进程从中间实体取得消息。中间实体一般称为信箱,这种通信方式又称为信箱通信方式。该通信方式广泛应用于计算机网络中,称为电子邮件系统。
3.管道通信
管道通信是消息传递的一种特殊方式,管道指用于连接一个读进程和一个写进程以实现它们通信的一个共享文件(pipe文件)。向pipe提供输入的发送进程(写进程),以字符流形式将大量的数据送入写管道;接收管道输出的接收进程(读进程)则从管道中接收(读)数据。为了协调双方的通信,管道机制必须提供互斥、同步、确定对方存在的协调能力。
管道只能采用半双工通信——某一时刻只能单向传输,要实现父子进程双向互动,要定义两个管道。它通过限制管道大小和规定读进程比写进程快克服使用文件进行通信的缺点。
管道可以理解为共享存储的优化和发展。在共享存储中,某进程要访问共享存储空间,必须没有其他进程在该共享存储空间进行读/写操作。否则会被阻塞。在管道通信中存储空间进化成缓冲区,允许一边写入,另一边读出,只要缓冲区中有数据,数据就能被进程读出,不用担心其他进程正在读数据而阻塞。
2.1.6 线程概念和多线程模型
1.线程的基本概念
引入进程是为了使多道程序并发执行,提高资源利用率和系统吞吐量;引入线程为了减少程序在并发执行时所付出的时空开销,提高并发执行的性能。
线程是一个轻量级进程,是一个基本的CPU执行单元,也是程序执行流的最小单元,它由线程ID、程序计数器、寄存器集合和堆栈组成。线城是进程中的一个实体,是被系统独立调度和分派的基本单位,线程可与同属一个进程的其它线程共享资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程可以并发执行。线程之间有相互制约,所以线程运行会呈现间断性。线程的状态也有就绪、阻塞、运行。
进程只作为除CPU外的系统资源的分配单元,线程作为处理机的分配单元,一个进程内部有多个线程,线程的切换位于同一个进程内,需要的时空开销很小。
2.线程和进程的比较
1)调度:无线程时,进程是拥有资源和独立调度的基本单位,有线程时,线程是独立调度的基本单位,但进程依旧是拥有资源的基本单位。同一进程中,线程的切换不会引起进程的切换,在不同进程中进行线程切换,则会引起进程切换。
2)拥有资源:进程拥有资源,线程不拥有资源,但线程可以访问其奴隶进程的系统资源,若线程也是拥有资源的单位,则切换线程时需要较大的时空开销。
3)并发性:进程之间可以并发执行,多个线程之间也可并发执行。
4)系统开销:创建撤销进程时所需开销,比对线程进行同样操作时所需的开销大。
5)地址空间和其他资源(如打开的文件):进程的地址空间相互独立,同一进程的线程共享同一地址空间。
6)通信方面:进程间的通信(IPC)需要进程同步和互斥手段的辅助,保证数据一致,线程间的通信通过直接读/写进程数据段实现。
3.线程的属性
1)轻型实体:不拥有系统资源,有标识符、线程控制块,线程控制块记录了线程执行的寄存器和栈等现场状况。
2)同一个服务被不同用户调用时,操作系统把它们创建成不同的线程。
3)进程中的线程共享此进程的资源。
4)线程是处理机的调度单位,线程可并发。
5)线程被创建之后,开始它的生命周期,会经历阻塞态、就绪态和运行态的变化。
4.线程的实现方式
线程分为用户级线程(User-Level Thread,ULT)和内核级线程(Kernel-Level Thread,KLT)(内核支持的线程)。
用户级线程中,线程管理由应用程序完成,内核意识不到线程的存在。应用程序通过使用线程库设计成多线程程序,从单线程开始,在该线程中开始运行,在其运行的任何时刻,通过调用线程库中的派生例程创建新线程。如下图(a)。
内核级线程中,管理由内核完成,应用程序没有进行线程管理的代码,只有一个内核级线程的编程接口。内核为其进程及内部的每个线程维护上下文信息,调度也在内核基于线程架构的基础上完成。如下图(b)。
一些系统使用组合方式实现。创建在用户空间中完成,调度和同步在应用程序中进行。应用程序中的多个用户线程被映射到内核级线程上。如下图(c)
5.多线程模型
1)多对一:多个用户级线程映射到一个内核级线程,线程管理在用户空间内完成,用户线程对操作系统不透明。这样做的优点是:线程管理在用户空间内进行,效率高。缺点是:线程在使用内核服务时被阻塞,整个进程都会被阻塞;多个线程不能并行地运行在多处理机上。
2)一对一:每个用户级线程映射到相应单独的内核级线程上。这样做的优点是:当一个线程被阻塞时,允许另一个线程继续执行,并发能力强。缺点是:每创建一个用户级线程都需要创建一个内核级线程,开销大。
3)多对多:n个用户级线程映射到m个内核级线程上(m≤n)。是上两个模型的折中,集两者所长。
2.2 处理机调度
2.2.1 调度的概念
1.基本概念
处理机调度是对处理机进行分配,即从就绪队列中按照一定的算法,选择一个进程,将处理机分配给它使用,实现进程并发执行。是操作系统的基础,是操作系统设计的核心问题。
2.调度的层次
三级调度:
1)作业调度(高级调度):按照一定原则从外存上处于后备状态的作业中挑选作业,给他们分配资源,建立进程,使它们获得竞争处理机的权利。多道批处理系统中大多配置,其它操作系统不需配置,执行频率低,几分钟一次。
2)终极调度(内存调度):提高内存利用率和吞吐量,将暂时不运行的进程调至外存,成为挂起态,当具备运行条件时,从外存调度到内存,成为就绪态。
3)进程调度(低级调度):按照算法从就绪队列中选取进程,分配给其处理机,操作系统都配置,且调度频率高,几十毫秒一次。
3.三级调度的联系
作业调度为进程活动做准备,进程调度使进程正常活动,中级调度挂起暂时不能运行的进程,处于之间;三者调度频率依次上升;进程调度最基本,不可或缺。
2.2.2 调度的时机、切换与过程
现代操作系统中,不能进行进程的调度与切换的情况有:
1)处理中断的过程中:处理中断的过程复杂,难以做到进程切换,在中断执行时,处理器资源不应被剥夺。
2)进程在操作系统内核程序的临界区中:进入临界区后需要独占式的访问共享数据,必须加锁。
3)其它需要完全屏蔽中断的原子操作过程:如加锁、解锁、中断现场保护、恢复等。
若在上述过程中发生了引起调度的条件,不能马上进行调度与切换,应置系统的请求调度标志,上述过程完成后再进行。
应进行调度与切换的情况:
1)发生引起调度条件且当前进程无法继续运行下去,立即调度和切换。若操作系统只在这种情况下进行调度,属于非剥夺调度。
2)中断处理结束或自陷处理结束后,返回被中断进程的用户态程序执行现场前,若置上请求调度标志,则可马上进行调度与切换。若操作系统支持这种状态下的运行调度程序,则实现了剥夺方式调度。
进程切换在调度完成后立刻发生,要求保存原进程当前切换点的现场信息,恢复被调度进程的现场信息。现场切换时,操作系统内核将原进程的现场信息推入当前进程的内核堆栈来保存它们,并更新堆栈指针。内核完成从新进程的内核栈中装入新进程的现场信息、更新当前运行进程空间指针、重设PC寄存器等相关工作之后,开始运行新的进程。
2.2.3 进程调度方式
当某个进程正在处理机上执行时,若有某个更为重要或紧迫的进程需要处理,进程调度的方式,分两种:
1)非剥夺调度方式(非抢占方式):等待正在执行的进程执行完毕或进入阻塞状态时,把处理机分配给更为重要的进程。优点是实现简单、系统开销少,适用于大多数批处理系统;缺点是不能用于分时系统和大多数的实时系统。
2)剥夺调度方式(抢占方式):立即暂停正在执行的进程,将处理机分配给优先级更高的进程。优点是提高系统吞吐和响应效率;缺点是对资源的剥夺要设置更合理的规则。
2.2.4 调度的基本准则
不同调度算法有不同的特性,为了比较算法性能,人们提出了评价准则。
1)CPU利用率:要保持CPU始终处于利用率高的状态。
2)系统吞吐量:表示单位时间内CPU完成作业的数量。
3)周转时间:从作业提交到作业完成所经历的时间,是作业等待、在就绪队列中排队、在处理机上运行、输入/输出操作所花费的时间的总和。
周转时间公式:周转时间=作业完成时间—作业提交时间
平均周转时间(多个作业周转时间的平均值):
平均周转时间=(作业1的周转时间+……+作业n的周转时间)/n
带权周转时间是指作业周转时间与实际作业时间的比值:
带权周转时间=(作业周转时间)/(作业实际运行时间)
平均带权周转时间是指多个作业带权周转时间的平均值:
平均带权周转时间=(作业1的带权周转时间+……+作业n带权周转时间)/n
4)等待时间:指进程处于等处理机状态的时间之和,等待时间越长,用户满意度越低。处理机调度算法实际上并不影响作业执行或输入/输出操作的时间,只影响作业在就绪队列中等待所花的时间。因此,衡量一个调度算法的优劣,常常只需考察等待时间。
5)响应时间:从用户提交请求到系统首次产生响应所用的时间。在交互式系统,用响应时间作为衡量调度算法的重要准则之一。
2.2.5 典型的调度算法
1.先来先服务(FCFS)调度算法
最简单,即可用于作业调度,又可用于进程调度。在作业调度中,算法按照在队列里的顺序,调入内存,分配资源,创建进程并放入就绪队列。
属于不可剥夺算法,表面上看公平,但有时作业等待时间过长,因此它不能作为分时系统和实时系统的主要调度策略。但可与其它策略联合使用,如在以优先级为调度策略中,优先级相同,以先到先服务为策略。
FCFS调度算法的特点是简单,但效率低;对长作业比较有利,对短作业不利;有利于繁忙型作业,而不利于I/O繁忙型作业。
2.短作业优先(SJF)调度算法
从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行;短进程优先(SPF)调度算法从就绪队列中选择一个估计运行时间最短的进程,为其分配处理机,使之立即执行,直到进程完成或阻塞,之后释放处理机。
SJF调度算法也存在缺点:
1)对长作业不利:长作业的周转时间会增加,甚至有些长作业不会被处理,因为可能一直有后到的短作业优先被处理。
2)该算法完全未考虑作业的紧迫程度,不能保证急迫的任务被优先处理。
3)由于作业的长短是根据用户所提供的估计执行时间而定,而用户会缩短作业的估计运行时间,可能不能真正做到短作业优先调度。SJF的平均等待时间、平均周转时间最少。
3.优先级调度算法
又称优先权调度算法,即可用于作业调度,又可用于进程调度。优先级用于描述作业运行的紧迫程度。
在作业调度中,每次从作业后备队列中选取优先级最高的一个或几个,调入内存,分配资源,创建进程并放入就绪队列。在进程调度中,每次从就绪队列中选取优先级最高的进程,分配处理机,执行。
根据能否抢占正在执行的进程,分为非剥夺式优先级调度算法,先等待正在运行的作业运行完毕或这阻塞,之后再执行;剥夺式优先级调度算法,暂停正在执行的任务,执行优先级高的任务。
根据优先级是否可改,分为静态和动态优先级,静态是优先级一直不变,动态是优先级在任务执行过程中,根据动态情况调整优先级。
优先级高低比较:
1)系统>用户。
2)交互>非交互。
3)I/O(频繁用I/O)型>计算(频繁用CPU)型。
4.高响应比优先调度算法
主要用于作业调度,是对FCFS和SJF的一种综合平衡,同时考虑了每个作业的等待时间和估计的运行时间,每次进行调度时,先计算响应比,找出最高的,优先运行。
响应比R=(等待时间+要求服务时间)/要求服务时间
根据公式:
1)作业的等待时间相同时,要求服务时间越短,响应比越高,越有利于短作业。
2)要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越长,其响应比越高,因而它实现的是先来先服务。
3)对于长作业,作业的响应比可以随等待时间的增加而提高,等待时间足够长时,其响应比便可升到很高,从而也可获得处理机,因此,克服了饥饿状态,兼顾了长作业。
5.时间片轮转调度算法
适用于分时系统,就绪进程按到达时间排队,依次给每个进程分配一个时间片,轮流进行执行。时间片的长短由响应时间、进程数目、处理能力等因素决定。
6.多级反馈队列调度算法(融合算法)
是时间片轮转调度算法和优先级调度算法的综合与发展。
算法实现思想如下:
1)设置多个就绪队列,并为各个队列赋予不同的优先级,优先级依次降低。
2)给不同优先级队列分配的时间片不同。优先级越高,时间片越小。
3)新进程进入内存后,首先将它放入第一季队列(优先级最高)的末尾,按FCFS原则调度。当轮到该进程执行时,若它能在该时间片内完成,可撤离系统;若未完成,该进程转入第二次队列末尾,以此类推。
4)仅当第一级队列为空时,调度程序才调度第2级队列中的进程运行,以此类推。若处理机正在执行第i队列中的某一进程,此时有优先级更高的进程进入队列,则此时新进程抢占处理机,而正在运行的进程则被进程调度程序放在第i队列的末尾。
优势有如下几点:
1)终端型作业用户:短作业优先。
2)短批处理作业用户:周转时间较短。
3)长批处理作业用户:经过前面几个队列得到部分执行,不会长期得不到处理。
2.3 进程同步
2.3.1 进程同步的基本概念
为了协调不同进程之间的相互制约关系,引入进程同步。进程同步简单理解就是进程的执行需要先后执行,保证结果的正确。
1.临界资源
一次仅允许一个进程使用的资源称为临界资源,如打印机,变量或数据等,具有当前进程的独占性,必须互斥的访问。在进程中访问临界资源的代码称为临界区。临界资源的访问分4个过程。
1)进入区:为了进入临界区使用临界资源,进入时要检查是否可进,若能进,需设置正在访问临界区的标志,防止其它进程进入临界区。
2)临界区:访问临界资源的代码,又称临界段。
3)退出区:将正在访问临界区的标志清除。
4)剩余区:代码中的其余部分。
2.同步
也称直接制约关系,为完成某个任务创建多个进程,这些进程执行有先后顺序,才能保证结果的正确性。制约关系源于它们之间相互合作。
3.互斥
也称间接制约关系,一个进程进入临界区时,此时临界资源被当前进程独占,其它进程只能等待。
为禁止多个进程同时进入临界区,同步机制应遵循以下准则:
1)空闲让进:临界区空闲时,可以允许一个请求的进程立即进入临界区。
2)忙则等待:临界区内有进程时,其它必须等待。
3)有限等待:对请求访问的进程,保证其在有限时间内进入临界区。
4)让权等待:进程不能进入临界区时,立即释放处理器。
2.3.2 实现临界区互斥的基本方法
1.软件实现方法
在进入区设置并检查一些标志来标明是否有进程在临界区中,若已有,在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。
1)算法一:单标志法。设置一个公用整型变量turn,指示被允许进入临界区的进程编号,turn=n,允许进程n进入。该算法可以保证一次一个进程进入临界区,但两个进程必须交替进入,若某个不再进,另一个也无法进,造成资源浪费。
2)算法二:双标志法先检查。基本思想是进程进入临界区之前,检查临界区是否被占用(先检查对方的进程状态标志,在置自己的标志)。需设置一个数据flag[i],若为False,表示i进程未进入临界区,True,进入。
优点:不用交替进入,可连续使用;缺点:进程i和进程j可能同时进入临界区。在检查对方的flag后和切换自己的flag前有一段时间,都通过检查,导致同时进入。
3)算法三:双标志法后检查。先将自己的标志设为True,再检测对方的状态标志,若对方为True,则进程等待,否则进入临界区。这样会导致两个进程同时想进入临界区,但发现对方也想进,两个进程互相谦让,导致谁也进不了。
4)算法四:Peterson‘s Algorithm。为防止两个进程为进入临界区而无限期等待,设置变量turn,每个进程先设置自己的标志后再设置turn标志,再同时检测另一个进程状态标志和不允许进入标志,保证只允许一个进程进入临界区。
是算法一和算法三的结合,用flag解决互斥访问,turn解决饥饿现象。
2.硬件实现方法
计算机提供了特殊的硬件指令,允许对一个字中的内容进行检测与修正,通过硬件实现称为低级方法(元方法)
(1)中断屏蔽方法
当一个进程正在使用处理机执行临界区代码时,禁止一切中断是防止其它进程进入临界区的最好方法,我们称这种行为为屏蔽中断、关中断。限制了处理机交替执行程序的能力,效率会降低。对内核来说,在执行指令期间,关中断很方便,但不能将关中断的权力交给用户。
(2)硬件指令方法
TestAndSet指令:是原子操作,功能是读出指定标志后把标志设置为真。可以为每个临界资源设置共享布尔变量lock,表示资源的两种状态,true表示正在被占用,初值为false。
应为每个临界资源设置一个共享变量lock,初值为false;每一个进程中设置一个局部变量key,用于与lock交换信息。进入临界区前,先利用Swap指令交换lock与key的内容,然后检查key的状态;有进程在临界区时,重复交换和检查过程。
硬件方法的优点:适用于任意数目的进程;简单、易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区设置一个布尔变量。
硬件方法的缺点:等待进入临界区时耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,但可能有进程一直不能被选上,导致饥饿现象。
2.3.3 信号量
信号量机制是一种功能较强的机制,可用来解决互斥与同步问题,只能被两个标准的原语wait(S)和signal(S)访问,也可记为“P操作(申请资源)”与“V操作(释放资源)”。
1.整型信号量
被定义为一个用于表示资源数目的整型量S,wait操作中,信号量S≤0,就会不断地测试,因此该机制使进程处于“忙等的状态”。
2.记录型信号量,不存在“忙等”现象,除一个需要一个整型变量之外,再增加一个进程链表L,用于链接所有等待该资源的进程。可描述为:wait操作,S.value表示进程请求一个该类资源,当S.value<0时,表示资源已经被分完,因此调用block原语,进行自我阻塞,放弃处理机,并将进程插入到该类资源的等待序列,该机制遵循了“让权等待”的原则。
signal操作,表示进程释放一个资源,使系统中可分配的该资源数增1,若加1后S.value(可用的资源数)≤0,表示等待该资源的进程被阻塞,调用wakeup原语进行唤醒。
3.利用信号量实现同步
信号量机制能用于解决进程间的各种同步问题。设S为进程1与进程2的公共信号量,初值为0。进程2中要使用进程1运行的结果,若进程2先执行,进程2会被阻塞,当进程1执行完毕后,进程2会被置就绪态,继续执行。
4.利用信号量实现进程互斥
信号量机制也能很方便解决互斥问题。设S为进程1与进程2互斥的信号量,S的初值为1。把临界区置于P(S)和V(S)之间。当临界区空闲时,进入临界区的进程执行P操作置S为0;再有进程执行P操作会被阻塞。
总的来说,同步问题中,用前要P,用后要V,两者之间紧夹动作。
5.利用信号量实现前驱关系
信号量也可用来描述程序之间或语句之间的前驱关系,有几对先后顺序,就应设置几个信号量,初值都为0,再通过判断信号量的值,实现先后关系的确定。
6.分析进程同步和互斥问题的方法步骤
1)关系分析:找出问题中的进程数,分析同步与互斥关系。
2)整理思路:找出解决问题的关键点,根据流程确定P、V操作的顺序。
3)设置信号量:根据上面两步,设置需要的信号量,确定初值,完善整理。
2.3.4 管程
在信号量机制中,大量的P、V操作容易导致系统死锁。于是,引入新的进程同步工具—管程,它的特性保证了进程互斥,无需程序员自己实现互斥,降低死锁发生的可能性。
1.管程的定义
用数据结构描述硬件和软件资源,忽略它们内部结构和实现细节,把对该数据结构实施的操作定义为一组过程,进程对资源的申请和释放通过这组过程实现,这组过程可以通过机制保证进程对资源的互斥访问,数据结构与过程组成的资源管理程序称为管程(monitor)。
管程由四部分组成:名称;局部于管程内部的共享结构数据说明;对该数据结构进行操作的一组过程(或函数);对局部于管程内部的共享数据设置初始值的语句。
管程很像一个类:
1)管程把对共享资源的操作封装起来,管程内的共享数据结构只能被管程内的过程所访问。一个进程只有通过调用管程内的过程才能进入管程访问共享资源。对于上例,外部进程只能通过调用take_away()过程来申请一个资源;归还资源也一样。
2)每次只允许一个进程进入管程,实现互斥。多个进程同时调用take_away(),give_back(),则只有某个进程运行完它调用的过程后,下个进程才能开始它自己的调用过程。
2.条件变量
当一个进程进入管程后被阻塞,直到阻塞的原因解除时,在此期间,如果该进程不释放管程,其它进程无法进入管程。所以将阻塞原因定义为条件变量(condition)。原因有多个,在管程中设置多个条件变量。每个条件变量保存了一个等待队列,用于记录因该条件变量而阻塞的所有进程,对条件变量只能进行两种操作,wait和signal。
x.wait:当x对应的条件不满足时,正在调用管程的进程调用x.wait将自己插入x条件的等待队列,并释放管程,此时其它进程可以使用该管程。
x.signal:x对应的条件发生了变化,则调用x.signal,唤醒一个因x条件而阻塞的进程。
条件变量和信号量的比较:
相似点:条件变量的wait/signal操作类似于信号量的P/V操作,可以实现进程的阻塞/唤醒。
不同点:条件变量是“没有值”的,仅实现了“排队等待”功能;而信号量是“有值”的,信号量的值反映了剩余资源数,在管程中,剩余资源数用共享数据结构记录。
2.3.5 经典同步问题
1.生产者-消费者问题
问题描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没满时,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。
问题分析:
1)关系分析:生产者和消费者对缓冲区互斥访问,但两者又是相互协作关系,生产之后才能消费,也是同步关系。
2)整理思路:两个进程同时存在互斥和同步关系,需要解决两者的PV操作问题。
3)信号量设置:mutex为互斥信号量,用于控制互斥访问缓冲池,初始为1;full记录当前缓冲池中状态为满的缓冲区数,初始为0。empty记录缓冲区为空的缓冲区数,初始为n。
seamphore mutex=1; //临界区互斥信号量
seamphore empty=n; //空闲缓冲区
seamphore full=0; //缓冲区初始化为空
producer(){ //生产者进程
while(1){
produce an item in nextp; // 生产数据
P(empty);(用什么,p一下) // 获取空缓冲区单元
P(mutex);(互斥夹紧) // 进入临界区
add nextp to buffer; (行为)//将数据放入缓冲区
V(mutex);(互斥夹紧) // 离开临界区,释放互斥信号量
V(full);(提供什么,V一下) //满缓冲区数加1
}
}
consumer(){ //消费者进程
while(1){
P(full); //获取满缓冲区单元
P(mutex); //进入临界区
remove an item from buffer; // 从缓冲区取出数据
V(mutex); // 离开临界区,释放互斥信号量
V(empty); // 空缓冲区数加 1
consume the item; //消费数据
}
}
该类问题要注意对缓冲区大小为n的处理,当缓冲区中有空时,便可对empty变量执行P操作,取走产品后,执行V操作释放空闲区。对empty和full的P必须放在对mutex的P之前。释放信号量时,mutex、full先释放哪一个无所谓。
2.复杂的生产者-消费者问题
问题描述:有一盘子,每次只能向其中放入一个水果,爸爸专放苹果,妈妈专放橘子,儿子专吃橘子,女儿专吃苹果,只有当盘子空时才放,当盘中仅有自己吃的水果时,才取。
问题分析:
1)关系分析:每次只能放一种水果,爸爸妈妈是互斥的关系,爸爸和女儿,妈妈和儿子是同步的关系。
2)思路整理:两个生产者和两个消费者连接到大小为1的缓冲区上。
3)信号量设置:将plate设置成互斥信号量,初值为1,表示允许放入。apple表示盘子中是否有苹果,初值为0,orange表示盘子中是否有橘子,初值为0。
semapore plate=1,apple=0,orange=0;
dad(){
while(1){
prepare an apple;
P(plate); //互斥向盘中取、放水果
put the apple on the plate; //向盘中放苹果
V(apple); // 允许取苹果
}
}
mom(){
while(1){
prepare an orange;
P(plate);
put the orange on the plate;
V(orange);
}
}
son(){
while(1){
P(orange); //互斥从盘中取橘子
take an orange from the plate;
V(plate); //允许向盘中放、取水果
eat the orange;
}
}
daughter(){
while(1){
P(apple);
take an aplle from the plate;
V(plate);
eat the apple;
}
}
3.读者-写者问题
问题描述:有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程同时访问共享数据时则可能导致数据不一致的错误,因此:
1) 允许多个读者可以同时对文件执行读操作;
2) 只允许一个写者往文件中写信息;
3) 任一写者在完成写操作之前不允许其他读者进程或写者进程工作;
4) 写者执行写操作前,应让已有的读者和写者全部退出。
问题分析:
1)关系分析:读者、写者互斥,写者、写者互斥,读者、读者不互斥。
2)思路整理:写者和任何进程互斥,用P、V操作解决;读者需实现与写者互斥,与其它读者同步,需要用到计数器,判断当前是否有读者读文件。不同读者对计数器的访问也是互斥的。
3)信号量设置:count为计数器,记录当前读者数量,初值为0;mutex为互斥信号量,保护更新count变量时的互斥;设置互斥信号量rw,用于保护读者和写者的互斥访问。
int count=0;
semaphore mutex=1;
semaphore rw=1;
writer(){
while(1){
P(rw); // 互斥访问共享文件
writing
V(rw); // 释放共享文件
}
}
reader(){
while(1){
P(mutex); // 互斥访问 count 变量
if(count==0) // 当第一个读进程读共享文件时
P(rw) // 阻止写进程
count++;
V(mutex); // 释放互斥变量 count
reading;
P(mutex);
count--;
if(count==0) // 当最后一个读进程读完共享文件
V(rw); // 允许写进程写
V(mutex);
}
}
此种方式下,可能导致写进程长时间等待甚至出现“饿死”的情况。改变上面这种读进程优先,让写进程优先,需要再增加一个信号量,并在上面的 writer() 和 reader() 函数中各增加一对PV操作。如下:
int count=0;
semaphore mutex=1;
semaphore rw=1;
semaphore w=1; // 实现写者优先
writer(){
while(1){
P(w); // 在无写进程请求时进入
P(rw); // 互斥访问共享文件
writing
V(rw); // 释放共享文件
V(w); // 恢复对共享文件的访问
}
}
reader(){
while(1){
P(w);
P(mutex); // 互斥访问 count 变量
if(count==0) // 当第一个读进程读共享文件时
P(rw) // 阻止写进程
count++;
V(mutex); // 释放互斥变量 count
V(w);
reading;
P(mutex);
count--;
if(count==0) // 当最后一个读进程读完共享文件
V(rw); // 允许写进程写
V(mutex);
}
}
此种算法又叫做读写公平法。
4.哲学家进餐问题
问题描述:一张圆桌上坐着5名哲学家,每两名哲学家之间的桌子上摆着一根筷子,两根筷子之间是一碗米饭。哲学家倾注毕生精力于思考和进餐,哲学家思考时不影响其他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子——一根一根地拿起。若筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿到了两根筷子才能开始进餐,进餐完毕,放下筷子继续思考。
问题分析:
1)关系分析:左右邻座对其中间的筷子的访问是互斥关系。
2)整理思路:哲学家拿到左右两根筷子不造成死锁或饥饿现象,解决方法有两个:第一个同时让他们拿两根筷子,第二个对每个哲学家的动作制定规则。
3)信号量设定:互斥信号量数组chopstick[5]={1,1,1,1,1},哲学家按顺序编号0-4,哲学家i左边筷子编号为i,哲学家右边筷子编号为(i+1)%5.
semaphore chopstick[5]={1,1,1,1,1};
Pi(){
do{
P(chopstick[i]); //取左边筷子
P(chopstick[(i+1)%5]);// 取右边筷子
eat;
V(chopstick[i]); //放回左边筷子
V(chopstick[(i+1)%5]);// 放回右边筷子
think;
}while(1);
}
此算法存在的问题就是,当5名哲学家都想要进餐并分别拿起左边的筷子时,所有的筷子将被拿光,等到他们再想拿起右边的筷子时,就会发生全被阻塞,出现死锁。若要避免此种情况,可以增加限制条件,如至多允许4名哲学家同时进餐;仅当一名哲学家左右两边筷子都可以用时,才允许他抓起筷子;对哲学家顺序编号,奇数号哲学家先拿起左边筷子,然后拿起右边的,而偶数哲学家相反。
用正确的规则如下:假设采用第二种方法,当一名哲学家左右两边的筷子都可用时,才允许他抓起筷子。
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex=1;
Pi(){
do{
P(mutex); // 在取筷子前获得互斥量
P(chopstick[i]); //取左边筷子
P(chopstick[(i+1)%5]);// 取右边筷子
V(mutex); // 释放取筷子的信号量
eat;
V(chopstick[i]); //放回左边筷子
V(chopstick[(i+1)%5]);// 放回右边筷子
think;
}while(1);
}
5.吸烟者问题
问题描述:假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但要卷起一支烟,抽烟者需要三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉已完成,此时供应者就会将另外两种材料放到桌子上,循环反复如此。
问题分析:
1)关系分析:供应者与三个抽烟者是同步关系。三个抽烟者互斥。
2)整理思路:供应者作为生产者向三个抽烟者提供材料。
3)信号量设置:offer1、offer2、offer3分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源,finish用于互斥进行抽烟动作。
int random; // 存储随机数
semaphore offer1=0;
semaphore offer2=0;
semaphore offer3=0;
semaphore finish=0;
process P1(){//供应者
while(1){
random=a random num;
random=random%3;
if(random==0)
V(offer1); // 提供烟草和纸
else if(random==1)
V(offer2); // 提供烟草和胶水
else
V(offer3); //提供纸和胶水
put on ; // 将材料放在桌子上
P(finish);
}
}
process P2(){//拥有烟草者
while(1){
P(offer3);
working; // 拿起纸和胶水,卷成烟,抽掉
V(finish);
}
}
process P3(){//拥有纸者
while(1){
P(offer2);
working; // 拿起烟草和胶水,卷成烟,抽掉
V(finish);
}
}
process P4(){//拥有胶水者
while(1){
P(offer1);
working; // 拿起纸和烟草,卷成烟,抽掉
V(finish);
}
}
2.4 死锁
2.4.1 死锁的概念
1.死锁的定义
多道程序系统中,虽然提高了系统的处理能力,但多个进程竞争同一资源会造成僵局,我们称这种僵局为死锁。
2.死锁产生的原因
(1)系统资源的竞争
不可剥夺的资源数量小于进程数,此时对其的竞争可能会引起死锁,但对可剥夺资源的竞争是不会引起死锁的。
(2)进程推进顺序非法
进程在运行过程中,请求和释放资源的顺序不当,当两个进程拥有不同资源时,这两个进程互相申请对方拥有资源时,产生死锁。
信号量使用不当也会造成死锁,此时进程间彼此相互等待对方发来的消息,也会引起死锁。
(3)死锁产生的必要条件
有四个,任意一个不成立,死锁就不会发生:
(1)互斥条件:进程要求所分配资源进行排他性控制,若有进程申请此资源,则只能等待。
(2)不剥夺条件:资源未被使用完时,不会被夺走,只能由该进程释放。
(3)请求并保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,该资源等待,同时不释放已经保持的资源。
(4)循环等待条件:存在一种进程资源的循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。
2.4.2 死锁的处理策略
必须破坏上述四个条件之一,或允许死锁产生,且能检测出死锁,并有能力实现恢复。
1.预防死锁:设置某些限制条件,破坏产生死锁产生的必要条件。
2.避免死锁:在资源分配过程中,用某种方法防止系统进入不安全状态,从而避免死锁。
3.死锁的检测及解除:允许发生死锁,但及时检测发现死锁,并采取某个措施解除死锁。
预防死锁和避免死锁都属于事先预防策略,预防的限制条件较严格,实现简单,但导致效率低;避免死锁的限制条件相对宽松,资源分配后需要通过算法来判断是否进入不安全状态,实现复杂。
各种资源的比较:
2.4.3 预防死锁
防止死锁的发生只需破坏死锁产生的4个必要条件之一即可。
1.破坏互斥条件:若允许系统资源都能共享使用,系统不会死锁,但有些资源不能同时访问,有些场合需要保护某种资源的互斥性。
2.破坏不剥夺条件:当某个进程保持了某个资源,它申请新的资源得不到满足时,它必须释放已经保持的所有资源,以后需要时再申请。这种策略会增加系统开销,降低系统吞吐量,所以这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源。
3.破环请求并保持条件:采用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在资源未满足前,不把它投入运行。一旦投入运行,资源一直归它所有,不再提出其它资源请求,保证不死锁。此策略会导致系统资源被浪费,并且由于个别资源长期被某个进程占用,导致饥饿现象。
4.破坏循环等待条件:采用顺序资源分配法,先给资源编号,规定进程必须按照编号申请资源,同类资源一次申请完成,例如,某个进程申请5号资源,它以后只能申请5号之后的资源。这种策略的问题是编号必须相对稳定,限制了新类型设备的增加,且会经常发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源浪费。
2.4.4 避免死锁
在资源动态分配过程中,防止系统进入不安全的状态,避免死锁的发生,限制弱,系统性能高。
1.系统安全状态
安全状态是指系统能按照某种进程推进顺序为每个进程分配所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序完成,反之则称为不安全状态。系统进入不安全状态时,可能导致死锁。
2.银行家算法
思想是把操作系统视为银行家,资源相当于资金,请求分配资源相当于申请贷款。进程运行之前先声明对各种资源的最大需求量,当进程在执行中继续申请资源时,先测试该进程已占用的资源数与本次申请的资源数之和是否超过该进程声明的最大需求量,若能满足则按当前的申请量分配资源,否则要推迟分配。
(1)数据结构描述
可利用资源向量Available:含有m个元素的数组,其中每个元素代表一类可用的资源数目。
最大需求矩阵Max:n*m矩阵,定义系统中n个进程中的每个进程对m类资源的最大需求。简单来说,一行代表一个进程,一列代表一类资源。
分配矩阵:Allocation:n*m矩阵,定义系统中每类资源当前分配给每个进程的资源数。
需求矩阵:Need:n*m矩阵,定义每个进程接下来还需多少资源。
三个矩阵关系:Need=Max-Allocation
一般情况下,Max和Allocation是已知条件,求Need是第一步。
(2)银行家算法描述:
先Request,若Request的量≤Need的量成立(不成立,则认为出错。),再判断Request的量≤Available的量成立(若不成立,该进程必须等待),系统尝试将资源分配给进程,并修改下面数据结构的数值,其中:
Available=Available-Request;Allocation=Allocation+Request;Need=Need-Request。
最后,系统执行安全性算法,检测此次资源分配后,系统是否处于安全状态,若安全,则正式分配,不安全,放弃分配,让进程等待。
(3)安全性算法
设置工作向量Work,有m个元素,表示剩余的可用资源数,安全性算法开始时,work=available。
步骤一:初始时安全序列为空;
步骤二:从Need矩阵中找出符合下面条件的行:该行对应的进程不在安全序列中,而且该行小于等于Work向量,找到后,把对应进程加入安全序列,若找不到,执行步骤四;
步骤三:进程进入安全序列后,可顺利执行,直至完成,并释放分配给它的资源,执行Work=Work+Allocation,返回步骤二;
步骤四:若此时安全序列中已有所有进程,则系统处于安全状态,否则系统处于不安全状态。
2.4.5 死锁检测和解除
系统在分配资源时不采取任何措施,应会提供死锁检测和解除的手段。
1.资源分配图
圆圈代表一个进程,框代表一类资源。由于一种类型的资源可能有多个,框中的一个圆圈代表一类资源中的一个资源。从进程到资源的有向边称为请求边,表示申请资源;从资源到进程的边称为分配边,表示资源分配给进程。
2.死锁定理
简化资源分配图可检测系统状态S是否为死锁状态。简化方法如下:
1)在资源分配图中,找出既不阻塞,又不孤点的进程(找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有的空闲资源数量),若所有连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的资源。之后消去它所有的请求边和分配边,使之称为孤立的点。总结为找圈消边。
2)进程所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来阻塞进程可能变为非阻塞进程。根据1)中的方法进行简化,若能消除所有边,称该图是可完全可以简化的。S为死锁的条件是当且仅当S状态的资源分配图是不可完全简化的,称为死锁条件。
3.死锁解除
1)资源剥夺法:挂起某些死锁的进程,并抢占它的资源,将这些资源分配给其它的死锁进程。但应防止被挂起的进程长时间得不到资源而处于资源匮乏的状态。
2)撤销进程法:强制撤销部分甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。
3)进程回退法:让若干个进程回退到足以回避死锁的地步,进程回退时自愿放弃资源而非被剥夺。要求系统保持进程的历史信息,设置还原点。