发布时间:2025-12-10 11:45:44 浏览次数:6
在计算机操作系统中,进程是资源分配的基本单位,也是独立运行的基本单位。
前趋图是一个有向无循环图,每个结点可以表示一条语句、一个程序段或一个进程,结点间的有向边表示两个结点之间存在的偏序或前趋关系
→={(Pi,Pj)|Pi必须在Pj开始执行之前完成}
一个程序通常由若干程序段组成,它们必须按照某种先后次序执行,仅当前一个操作执行完后才能执行后续操作,这类计算过程就是程序的顺序执行过程。例如,在处理一个作业时,总是先输入用户的程序和数据,然后再进行计算,最后将所得结果打印出来。
程序的并发执行是指若干个程序(或序同时在系统中运行,这些(序段)的执行在时间上是重叠的,即一个程序(或程序段)的执行尚未结束,另一个程序(或程序段)行已开始。
程序的并发执行虽然提高了系统的处理能力和资源利用率,但也带来了一些新问题,产生了一些与顺序执行时不同的特征:
间断性。程序在并发执行时,由于它们共享资源或为完成同一项任务而相互合作,致使并发程序之间形成了相互制约的关系。在图2-1 中, 若C1未完成,则不能进行 P1,致使作业1的打印操作暂停运行,这是由相互合作完成同一项任务而产生的直接制约关系:若I1未完成,则不能进行 I2,致使作业 2的输入操作停运,这是共享资源而产的间接制约关系。这种相互制约关系将导致并发程序具有“执行一暂停执行一执行”这种间断性的活动规律。
失去封闭性。程序在并发执行时,多个程序共享系统中的各种资源,因而这些资源的状态将由多个程序来改变,致使程序的运行失去封闭性。这样一个程序在执行时,必然会受到其他程序影响,例如,当处理器被某程序占用时,其他程序必须等待。
不可再现性。程序并发执行时,由于失去了封闭性,也将导致失去其运行结果的可再现性,例如,有两个循环程序A和 B,它们共享一个变量 N。程序A 每执行一次,都要执行 N=N+1 的操作;程序 B 每执行一次,都要执行print(N)操作,然后执行 N=0。由于程序A和程序B的执行都以各自独立的速度向前推进,因此程序A的N=N+1操作可以发在 B 的print(N)作和N=0 操作之前,也可以发生在其后或中间。假设某时刻N的值为n,对于N=N+1出现在B的两个操作之前和之后两种情况(见图 2-2),执行完一个循环后打印出来的N值分别为 n+1和n。
程序并发执行的条件
程序并发执行时具有结果不可再现的特征,这并不是使用者希望看到的结果。为此,要求程序在并发执行时必须保持封闭性和可再现性。由于并发执行失去封闭性是共享资源的影响,因此现在要做的工作是消除这种影响。
若两个程序p1和 p2能满足下述3 个条件,它们便可以并发执行且其结果具有可再现性。
在多道程序环境下,程序的并发执行破坏了程序的封闭性和可再现性,使得程序和计算不再一一对应,程序活动不再处于一个封闭系统中,程序的运行出现了许多新的特征。在这种情况下,程序这种静态概念已经不能如实地反映程序活动的这些特征,为此引入了一个新的概念——进程。
由程序段、相关数据段和 PCB 三部分构成了进程映像,也叫进程实体。进程映像是静态的,进程是动态的,进程是进程实体的运行过程。
作业是用户需要计算机完成某项任务而要求计算机所做工作的集合。一个作业的完成要经过作业提交、作业收容、作业执行和作业完成4个阶段。而程是已提交完的作业的执行过程,是资源分配的基本单位。
PCB是进程存在的唯一标志。
PCB包括的内容:
在一个系统中,通常存在着很多进程,有的处于就绪状态,有的处于阻塞状态,且阻塞的原因各不相同。为了方便进程的调度和管理,需要将各进程的PCB 用适当的方法组织起来。目前常用的组织方式有链接方式和索引方式。
PCB 的作用是为了保证程序的并发执行。创建进程,实质上是创建进程的PCB;而撤销进程,实质上是撤销进程的PCB。
为什么PCB是进程存在的唯一标志?
在进程的整个生命周期中,系统总是通过PCB 对进程进行控制,亦即系统根据进程的 PCB 感知该进程的存在所以,PCB 是进程存在的唯一标志。
在做题时,要特别注意区分就绪状态与阻塞状态,区分两者的关键在于当分配给该进程处理器时,是否能立即执行,若能立即执行,则处于就绪状态,反之,则为阻塞状态。
由上可得出结论
进程控制的职责是对系统中的所有进程实施有效的管理,其功能包括进程的创建、进程的撤销、进程的阻塞与唤醒等。这些功能一般是由操作系统的内核来实现的。
创建原语
进程的创建原语实现,其主要操作过程如下:
一个进程在完成其任务后应予以撤销,以便及时释放它所占用的各类资源。撤销原语可采用两种策略:一种是只撤销一个具有指定标识符的进程,另一种是撤销指定进程及其所有子孙进程。导致进程撤销的事件有进程正常结束、进程异常结束及外界干预等。
撤销原语的功能是撤销一个进程,其主要操作过程如下:
阻塞原语(P 原语)的功能是将进程由执行状态转为阻塞状态,而唤醒原语 (V 原语)的功能则是将进程由阻塞状态变为就绪状态。
阻塞原语的主要操作过程如下:
唤醒原语的主要操作过程如下:
进程切换是指处理器从一个进程的运行转到另一个进程的运行,在这个过程中,进程的运行环境产生了实质性的变化。
进程切换的过程如下:
进程切换一定会产生中断,进行处理器模式切换,即从用户态进入内核态,之后又回到用户态;但处理器模式切换不一定产生进程切换,如系统调用同样会从用户态进入内核态,之后回到用户态,但在逻辑上,仍然是同一进程占用处理器执行。
进程通信是指进程之间的信息交换。进程的互斥与同步就是一种进程间的通信方式。由于进程互斥与同步交换的信息量较少且效率较低,因此称这两种进程通信方式为低级进程通信方式。
相应地,也可以将P、V原语称为两条低级进程通信原语。
高级进程通信方式可以分为3 大类:共享存储器系统、消息传递系统和管道通信系统。
为了传输大量数据,在存储器中划出一块共享存储区,多个进程可以通过对共享存储区进行读写来实现通信。在通信前,进程向系统申请建立一个共享存储区,并指定该共享存储区的关键字。若该共享存储区已经建立,则将该共享存储区的描述符返回给申请者。然后,申请者把获得的共享存储区附接到进程上。这样,进程便可以像读写普通存储器一样读写共享存储区了。
在消息传递系统中,进程间以消息为单位交换数据,用户直接利用系统提供的一组通信命令(原语)来实现通信。操作系统隐藏了通信的实现细节,简化了通信程序,得到了广泛应用。
根据实现方式的不同,消息传递系统可以分为以下两类:
管道是用于连接读进程和写进程以实现它们之间通信的共享文件。向管道提供输入的发送进程(即写进程)以字符流形式将大量的数据送入管道,而接收管道输出的进程(即读进程)可以从管道中接收数据。
线程是近年来操作系统领域出现的一种非常重要的技术,其重要程度丝毫不亚于进程。线程的引入提高了程序并发执行的程度,从而进一步提高了系统吞吐量。
线程的引入:如果说在操作系统中引入进程的目的是使多个程序并发执行,以改善资源利用率及提高系统吞吐量,那么,在操作系统中再引入线程,则是为了减少程序并发执行时所付出的时空开销,使操作系统具有更好的并发性。
线程的定义
综上所述,不妨将线程定义为:线程是进程内一个相对独立的、可调度的执行单元。线程自己基本上不拥有资源,只拥有一点在运行时必不可少的资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程拥有的全部资源。
线程的实现
在操作系统中有多种方式可实现对线程的支持。最自然的方法是由操作系统内核提供线程的控制机制。在只有进程概念的操作系统中,可由用户程序利用函数库提供线程的控制机制。还有一种做法是同时在操作系统内核和用户程序两个层次上提供线程的控制机制。
线程锁
线程锁有:互斥锁、条件锁、自旋锁、读写锁。一般而言,锁的功能越强大,性能就越低。
有些系统同时支持用户级线程和内核级线程,因此根据用户级线程和内核级线程连接方式的不同产生了3种不同的多线程模型。
调度是操作系统的一个基本功能,几乎所有的资源在使用前都需要调度。由于 CPU 是计算机的首要资源,因此调度设计均围绕如何能够高效利用 CPU 展开。
在多道程序环境下,一个作业从提交到执行,通常都要经历多级调度,如高级调度、中级调度和低级调度。而系统的运行性能在很大程度上都取决于调度,因此调度便成为多道程序的关键。
高级调度又称为宏观调度、作业调度或者长程调度,其主要任务是按照一定的原则从外存上处于后备状态的作业中选择一个或者多个,给它们分配内存输入/输出设备等必要资源,并建立相应的进程,以使该作业具有获得竞争处理器的权利(作业是用户在一次运算过程或一次事务处理中要求计算机所做工作的总和)。作业调度的运行频率较低,通常为几分钟一次。
调度程序必须决定操作系统可以接纳多少个作业?
作业调度每次要接纳多少个作业进入内存取决于多道程序的并发程度,即允许有多少个作业同时在内存中运行。当内存中可以同时运行的作业太多时,可能会影响到系统的服务质量,如导致周转时间太长。而当内存中同时运行的作业太少时,又会导致系统资源利用率和吞吐量下降。因此,多道程序的并发程度应根据系统的规模和运行速度来确定。
调度程序必须决定接纳哪些作业?
应将哪些作业从外存调入内存取决于所采取的调度算法。最简单的调度算法是先来先服务调度算法,它将最早进入外存的作业最先调入内存:较常用的一种调度算法是短作业优先调度算法,它将外存上执行时间最短的作业最先调入内存,此外还有其他调度算法。
中级调度又称为中程调度或者交换调度,引入中级调度是为了提高内存利用率和系统吞吐量,其主要任务是按照给定的原则和策略,将处于外存对换区中的具备运行条件的进程调入内存,并将其状态修改为就绪状态,挂在就绪队列上等待,或者将处于内存中的暂时不能运行的进程交换到外存对换区,此时的进程状态称为挂起状态。中级调度主要涉及内存管理与扩充(其实中级调度可以理解为在换页时将页面在外与内之间调度)。
低级调度又称为微观调度、进程调度或者短程调度,其主要任务是按照某种策略和方法从就绪队列中选取一个进程,将处理器分配给它。进程调度的运行频率很高,一般隔几十毫秒要运行一次。
高级调度与低级调度的区别:
不同调度算法有不同的调度策略,这也决定了调度算法对不同类型的作业影响不同。在选择调度算法时,我们必须考虑不同算法的特性。为了衡量调度算法的性能,人们提出了一些评价标准。
CPU 是系统最重要、也是最昂贵的资源,其利用率是评价调度算法的重要指标。在批处理以及实时系统中,一般要求 CPU 的利用率要达到比较高的水平,不过对于 PC 和某些不强调利用率的系统来说,CPU 利用率并不是最主要的。
系统吞吐量表示单位时间内 CPU 完成作业的数量。对长作业来说,由于它要占用较长的CPU处理时间,因此会导致系统吞吐量下降;而对短作业来说,则相反。
相对于系统吞吐量和 CPU 利用率来说,响应时间主要是面向用户的。在交互系统中,尤其在多用户系统中,多个用户同时对系统进行操作,都要求在一定时间内得到响应,不能使某些用户的进程长期得不到调用。因此,从用户角度看,调度策略要保证尽量短的响应时间,使响应时间在用户的接受范围内。
从每个作业的角度来看,完成该作业的时间是至关重要的,通常用周转时间或者带权周转时间来衡量。
周转时间:指作业从提交至完成的时间间隔,包括等待时间和执行时间。周转时间Ti;用公式表示为作业i的周转时间Ti=作业i的完成时间-作业i的提交时间
平均周转时间:平均周转时间是指多个作业(如n个作业)周转时间的平均值。平均周转时间T用公式表示为T=(T1+T22+…+Tn) /n。
带权周转时间:带权周转时间是指作业周转时间与运行时间的比。作业i的带权周转时间Wi用公式表示Wi=作业i的周转时间/作业i的运行时间。
平均带权周转时间:与平均周转时间类似,平均带权周转时间是多个作业的带权周转时间的平均值。
在多道程序系统中,用户进程数往往多于处理器数,这将导致用户进程争夺处理器。此外,系统进程同样需要使用处理器。因此,系统需要按照一定的策略动态地把处理器分配给就绪队列中的某个进程,以便使之执行。处理器分配的任务由进程调度程序完成。
进程调度方式是指当某一个进程正在处理器上执行时,若有某个更为重要或紧迫的进程需要进行处理(即有优先级更高的进程进入就绪队列),此时应如何分配处理器。
先来先服务调度算法(FCFS)是一种最简单的调度算法,可以用于作业调度与进程调度。其==基本思想是按照进程进入就绪队列的先后次序来分配处理器==。先来先服务调度算法采用非抢占的调度方式,即一旦一个进程(或作业)占有处理器,它就一直运行下去,直到该进程(或作业)完成其工作或因等待某一事件而不能继续执行时才释放处理器。
从表面上看,先来先服务调度算法对于所有进程(或作业)是公平的,即按照它们到来的先后次序进行服务。但假设有等数量的长进程(10t) 和短进程(t),因为数量相等,所以谁先到的概率也相等。当长进程先来时,短进程的等待时间为 10t,而当短进程先来时,长进程的等待时间仅为t。所以先来先服务调度算法有利于长进程(作业),不利于短进程(作业)。
现在,先来先服务调度算法已经很少作为主要的调度策略,尤其是不能作为分时系统和实时系统的主要调度策略,但它常被结合在其他调度策略中使用。例如,在使用优先级作为调度策略的系统中,往往对多个具有相同优先级的进程或作业按照先来先服务原则进行处理。
短作业优先(SJF)调度算法用于进程调度时被称为短进程优先调度算法,该算法既可以用于作业调度,也可以用于进程调度。
短作业(或进程)优先调度算法的基本思想就是把处理器分配给最快完成的作业(或进程)。在作业调度中,短作业优先调度算法每次从后备作业队列中选择估计运行时间最短的一个或几个作业调入内存,分配资源,创建进程并放入就绪队列。在进程调度中,短进程优先调度算法每次从就绪队列中选择估计运行时间最短的进程将处理器分配给它,使该进程运行并直到完成或因某种原因阻塞才释放处理器。
在所有作业同时到达时,SJF 调度算法是**算法,平均周转时间最短(如果短进程先执行,长进程等待时间较长进程先执行的情况要短很多,因此平均等待时间最短,而进程运行时间是确定不变的)。但该算法很显然对长作业不利,当有很多短作业不断进入就绪队列时,长作业会因长期得不到调度而产生“饥饿”现象(“饥饿”现象是指在一段时间内进程得不到调度执行或得不到所需资源)。
优先级调度算法是一种常用的进程调度算法,既可用于作业调度,也可用于进程调度。其基本思想是把处理器分配给优先级最高的进程。该算法的核心问题是如何确定进程的优先级。进程的优先级用于表示进程的重要性,即运行的优先性。
进程的优先级通常分为两种静态优先级和动态优先级。
基于优先级的调度算法还可以按调度方式的不同分为非抢占优先级调度算法和抢占优先级调度算法。
进程调度通常采用时间片轮转调度算法。在时间片轮转调度算法中,系统将所有的就绪进程按到达时间的先后次序排成一个队列,进程调度程序总是选择队列中的第一个进程执行,并规定执行一定时间,称为时间片(如100ms)。当该进程用完这一时间片时(即使进程并未执行结束),系统将它送至就绪队列队尾,再把处理器分配给下一个就绪进程。这样,处于就绪队列中的进程就可以依次轮流获得一个时间片的处理时间,然后重新回到队列尾部排队等待执行,如此不断循环,直至完成。
如果时间片设置得太大,所有进程都能在一个时间片内执行完毕,那么时间片轮转调度算法就退化为先来先服务调度算法;如果时间片设置得太小,那么处理器将在进程之间频繁切换,处理器真正用于运行用户进程的时间将减少。因此,时间片的大小应设置适当。
时间片的大小通畅由以下因素确定:
系统的响应时间。分时系统必须满足系统对响应时间的要求,系统响应时间与时间片的关系可以表示为
T=Nxq
其中,T 为系统的响应时间, 为时间片的大小,N 为就绪队列中的进程数。根据这个关系可以得知,若系统中的进程数一定,时间片的大小与系统响应时间成正比。
就绪队列中的进程数目。在响应时间固定的情况下,就绪队列中的进程数与时间片的大小成反比。
系统的处理能力。通常要求用户键入的常用命令能够在一个时间片内处理完毕。因此,计算机的速度越快,单位时间内可处理的命令就越多,时间片就可以越小。
高响应比优先调度算法综合了先来先服务与短作业优先两种调度算法的特点,即考虑了作业的等待时间和作业的运行时间两个因素,弥补了之前两种调度算法只考虑其中一个因素的不足。
高响应比优先调度算法主要用于作业调度。其基本思想是每次进行作业调度时,先计算就绪队列中的每个作业的响应比,挑选响应比最高的作业投入运行。
响应比=作业响应时间/估计运行时间
即响应比=(作业等待时间+估计运行时间)/估计运行时间
该算法有利于短作业(作业等待时间相同时,估计运行时间越短,响应比越高),同时考虑长作业(只要作业等待时间足够长,响应比就会变为最高。该算法对于短作业和长作业都有考虑,但由于要计算每个后备作业的响应比,因此增加了系统开销。
多级队列调度算法的基本思想是根据进程的性质或类型,将就绪队列划分为若干个独立的队列,每个进程固定地分属于一个队列。每个队列采用一种调度算法,不同的队列可以采用不同的调度算法。例如,为交互型任务设置一个就绪队列,该队列采用时间片轮转调度算法:再如,为批处理任务另外设置一个就绪队列,该队列采用先来先服务调度算法。
多级反馈队列调度算法是时间片轮转调度算法和优先级调度算法的综合与发展。通过动态调整进程优先级和时间片的大小,多级反馈队列调度算法可兼顾多方面的系统目标。
首先,应设置多个就绪队列,并为每个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列的优先级次之,其余队列的优先级逐次降低。
其次,每个队列中的进程执行时间片的大小也各不相同,进程所在队列的优先级越高其相应的时间片就越短。通常,第 i+1 队列的时间片是第i队列时间片的两倍。
当一个新进程进入系统时,应先将其放入第一个队列末尾,按先来先服务的原则排队等待调度。当轮到该进程执行时,如能在此时间片内完成,便可准备撤离系统:如果该进程在一个时间片结束时尚未完成,调度程序便将该进程转入第二个队列的末尾,再同样按照先来先服务的原则等待调度执行:如果该进程在第二个队列中运行一个时间片后仍未完成再以同样方法转入第三个队列。如此下去,最后一个队列中使用时间片轮转调度算法。
最后,仅当第一个队列空闲时,调度程序才调度第二个队列中的进程运行;仅当第一个至第-1个队列均为空时,才会调度第个队列中的进程运行。当处理器正在为第i个队列中的某进程服务时,若又有新进程进入优先级较高的队列中,则此时新进程将抢占正在运行进程的处理器,即由调度程序把正在执行的进程放回第 个队列末尾,并重新将处理器分配给新进程。
间接相互制约关系(互斥):若某一进程要求使用某种资源,而该资源正被另一进程使用,并且该资源不允许两个进程同时使用,那么该进程只好等待已占用资源的进程释放资源后再使用。这种制约关系的基本形式是“进程一资源一进程”。
这种制约关系源于多个同种进程需要互斥地共享某种系统资源(如打印机),互斥是设置在同种进程之间以达到互斥地访问资源的目的(如在生产者-消费者问题中,生产者与生产者之间需要互斥地访问缓冲池)。
直接相制约关系(同步)
某一进程若收不到另一进程给它提供的必要信息就不能继续运行下去,这种情况表明了两个进程之间在某些点上要交换信息,相互交流运行情况。这种制约关系的基本形式是“进程-进程”。
这种制约主要源于进程间的合作,同步设置在不同进程之间以达到多种进程间的同步(如在生产者-消费者问题中,生产者可以生产产品并放入缓冲池,消费者从缓冲池取走产品进行消费,若生产者没有生产产品,则消费者无法进行消费)。
只要是同类进程即为互斥关系,不同类进程即为同步关系,例如,消费者与消费者就是互斥关系,消费者和生产者就是同步关系。
进程在运行过程中,一般会与其他进程共享资源,而有些资源的使用具有排他性。把同时仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机、绘图机等。
临界资源的访问可以分为4部分:
简单来说,临界资源是一种系统资源,需要不同进程互斥访问,而临界区则是每个进程中访问临界资源的一段代码,是属于对应进程的,临界区的前后需要设置进入区和退出区以进行检查和恢复。临界区和临界资源是不同的,临界资源是必须互斥访问的资源,这种资源同时只能被一个进程所使用,但需要这种资源的进程不止一个,因此需要对使用临界资源的进程进行管理,这也就产生了临界区的概念。
每个进程的临界区代码可以不相同。进程对临界资源进怎样的操作,这和临界资源及互斥同步管理是无关的。
根据互斥的定义,当一个进程进入临界区使用临界资源时,另一个进程必须等待,直到占用该临界资源的进程退出临界区后,才允许新的进程访问该临界资源。
为了禁止两个进程同时进入临界区,软件算法或同步机构都应遵循以下准则:
一般来说,一个进程相对另一个进程的运行速度是不确定的。也就是说,进程之间是在异步环境下运行的。但是相互合作的进程需要在某些关键点上协调它们的工作。所谓进程同步,是指多个相互合作的进程在一些关键点上可能需要互相等待或互相交换信息,这种相互制约关系称为进程同步。可以用信号量实现同步。
互斥既可以用软件方法来实现,也可以用硬件方法来实现。
算法1:设置一个公用整型变量 turn,用来表示允许进入临界区的进程标识。若 turn 为0则允许进程P0进入临界区;否则循环检查该变量,直到turn 变为本进程标识;在退出区,修改允许进入进程的标识 turn 为1。进程P1的算法与此类似。两个进程的程序结构如下:
此方法可以保证互斥访问临界资源,但存在的问题是强制两个进程以交替次序进入临界区,很容易造成资源利用不充分。
例如,当进程P0退出临界区后将 turn 置为1,以便允许进程P1进入临界区,但如果进程P1 暂时并未要求访问该临界资源,而P0又想再次访问临界资源,则它将无法进入临界区。可见,此算法不能保证实现“空闲让进”准则。
算法2:设置标志数组flag表示进程是否在临界区中执行,初值均为假。在每个进程访问该临界资源之前,先检查另一个进程是否在临界区中,若不在,则修改本进程的临界区标志为真并进入临界区,在退出区修改本进程临界区标志为假。两进程的程序结构如下:
此算法解决了“空闲让进”的问题,但又出现了新问题,即当两个进程都未进入临界区时,它们各自的访问标志都为 false,若此时刚好两个进程同时都想进入临界区,并且都发现对方的标志值为 false(当两进程交替执行了检查语句后,都满足 flag[]=false 的条件),于是两个进程同时进入了各自的临界区,这就违背了临界区的访问规则“忙则等待”。
算法3:本算法仍然设置标志数组 flag,但标志用来表示进程是否希望进入临界区,每个进程在访问临界资源之前,先将自己的标志设置为真,表示希望进入临界区,然后检查另一个进程的标志。若另一个进程的标志为真,则进程等待;反之,则进入临界区。两进程的程序结构如下:
此算法可以有效防止两进程同时进入临界区,但存在两个进程都进不了临界区的问题,即当两个进程同时想进入临界区时,它们分别将自己的标志位设置为 true,并且同时去检查对方的状态,发现对方也要进入临界区,于是都阻塞自己,结果导致两者都无法进入临界区,造成“死等”现象,这就违背了“有限等待”准则。
算法 4:本算法的思想是算法 3 和算法1的结合。标志数组flag[]表示进程是否希望进入临界区或是否在临界区中执行。此外,还设置了一个 turn 变量,用于表示允许进入临界区的进程标识。两进程的程序结构如下:
至此,算法4可以完全正常工作,利用 flag[]解决临界资源的互斥访问,而利用 tumn 解决“饥饿”现象。
完全利用软件方法实现进程互斥有很大的局限性,现在已经很少单独采用软件方法。硬件方法的主要思想是用一条指令完成标志的检查和修改这两个操作,因而保证了检查操作与修改操作不被打断;或通过中断屏蔽的方式来保证检查和修改作为一个整体执行。
硬件方法主要有两种:一种是中断屏蔽;另一种是硬件指令。
硬件方法的优点:
硬件方法有诸多优点,但也有一些自身无法克服的缺点。这些缺点主要包括进程在等待进入临界区时要耗费处理器时间,不能实现“让权等待”(需要软件配合进行判断);进入临界区的进程的选择算法用硬件实现有一些缺陷,可能会使一些进程一直选不上,从而导致“饥饿”现象。
虽然前面讲解的软件及硬件方法都可以解决互斥问题,但它们都存在缺点。软件方法的算法太复杂,效率不高、不直观,而且存在“忙等”现象(在进入区时会持续检测标志变量)。就硬件方法而言,对于用户进程,中断屏蔽方法不是一种合适的互斥机制;硬件指令方法有不能实现“让权等待”等缺点。
信号量是一个确定的二元组 (s,q),其中 s 是一个具有非负初值的整型变量,q 是一个初始状态为空的队列。整型变量 s 表示系统中某类资源的数目,当其值大于0时,表示系统中当前可用资源的数目:当其值小于0 时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目。除信号量的初值外,信号量的值仅能由P操作(又称为 wait 操作)和V操作(又称为signal操作)改变。操作系统利用它的状态对进程和资源进行管理。
一个信号量的建立必须经过说明,即应该准确说明 s 的意义和初值(注:这个初值不是一个负值)。每个信号量都有相应的一个队列,在建立信号量时队列为空。
设s 为一个信号量,
P(s)执行时主要完成以下动作:先执行 s=s-1;若s>=0,则该进程继续运行;若 s<0,则阻塞该进程,并将它插入该信号量的等待队列中。
V(s)执行时主要完成下述动作:先执行 s-s+1;若 s > 0,则该进程继续执行:若s<=0,则从该信号量等待队列中移出第一个进程,使其变为就绪状态并插入就绪队列,然后再返回原进程继续执行。
P、V 操作均为不可分的原子操作,这保证了对信号量进行操作的过程不会被打断或阻塞。P 操作相当于申请资源,V 操作相当于释放资源。P 操作和V 操作在系统中一定是成对出现的,但未必在一个进程中,可以分布在不同进程中。
整型信号量:整型信号量是一个整型量 s,除初始化外,仅能通过标准的原子操作 P 和V 来访问。整型信号量引入了 P、V 操作,但是在进行 P 操作时,若无可用资源,则进程持续对该信号量进行测试,存在“忙等”现象,未遵循“让权等待”准则。
记录型信号量(资源信号量):为了解决整型信号量存在的“忙等”问题,添加了链表结构,用于链接所有等待该资源的进程,记录型信号量正是因采用了记录型的数据结构而得名。
当进程对信号量进行P操作时,若此时无剩余资源可用,则进程自我阻塞,放弃处理器并插入到等待链表中。可见,该机制遵循“让权等待”准则。当进程对信号量进行V操作时若链表中仍有等待该资源的进程,则唤醒链表中的第一个等待进程。
如果信号量初值为1,表示该资源为同时只允许一个进程访问的临界资源。
实现进程的同步
假设存在并发进程 P1和P2。P1中有一条语 S1,P2中有一条语 S2,要S1必须S2之前执行。这种同步问题使用信号量就能很好解决。
实现进程互斥
假设有进程 P1和 P2,两者有各自的临界区,但系统要求同时只能有一个进程进入自己的临界区。这里使用信号量可以很方便地解决临界区的互斥进入。设置信号量N,初值为1(即可用资源数为1),只需要将临界区放在 P(N)和 V(N)之间即可实现两进程的斥进入。
若有两个或者多个进程需要互斥访问某资源,可以设置一个初值为 1的信号量,在这些进程的访问资源的代码前后分别对该信号量进行 P 操作和 V 操作,即可保证进程对该资源的互斥访问。
生产者-消费者问题是著名的进程同步问题。它描述的是一组生产者向一组消费者提**品,他们共享一个有界缓冲区,生产者向其中投入产品,消费者从中取走产品。这个问题是许多相互合作进程的一种抽象。例如,在输入时,输入进程是生产者,计算进程是消费者:在输出时,计算进程是生产者,打印进程是消费者。
为解决这一问题,应当设置两个同步信号量:一个说明空缓冲区数目,用empty 表示,初值为有界缓冲区大小n;另一个说明满缓冲区数目(即产品数目),用full表示,初值为0。此外,还需要设置一个互斥信号量 mutex,初值为1,以保证多个生产者或者多个消费者互斥地访问缓冲池。
P(full)/P(empty)与P(mutex)的顺序不可颠倒,必须先对资源信号量进行P操作再对互斥信号量进行P操作,否则会导致死锁。
在有多个信号量同时存在的情况下,P操作往往是不能颠倒顺序的,必须先对资源信号量进行 P操作,再对互斥信号量进行 P 操作,这样可以在占有信号量访问权时保证有资源可以使用,否则会产生占用使用权而无资源可用的“死等”现象。
只要有多个同类进程,就一定需要互斥信号量。
在读者-写者问题中,有一个许多进程共享的数据区,这个数据区可以是一文件或者主存的一块空间,有一些只读取这个数据区的进程(读者)和一些只往数据区写数据的进程(写者)。此外还需要满足以下条件:
读者优先算法
一个读者试图进行读操作时,如果这时正有其他读者在进行读操作,他可以直接开始读操作,而不需要等待。由于只要有读者在进行读操作,写者就不能够写,但后续读者可以直接进行读操作,因此只要读者陆续到来,读者一到就能够开始读操作,而写者进程只能等待所有读者都退出才能够进行写操作,这就是读者优先。
要解决此问题,需要设置如下几个信号量:设置记录读者数量的整型变量 readcount,初值为0,当其值大于0时,表明有读者存在,写者不能进行写操作:设置互斥信号量rmutex初值为1,用于保证多个读者进程对于 readcount 的互斥访问:设置互斥信号量 mutex,初值为1,用于控制写者进程对于数据区的互斥访问。算法如下:
公平情况算法
进程的执行顺序完全按照到达顺序,即一个读者试图进行读操作时,如果有写者正等待进行写操作或正在进行写操作,后续读者要等待先到达的写者完成写操作后才能开始读操作。
要解决此问题,与读者优先算法相比,需要增设一个信号量 wmutex,其初值为1,用于表示是否存在正在写或者等待的写者,若存在,则禁止新读者进入。算法如下:
写者优先算法
有的书把公平情况算法也叫作写者优先,但并不是真正意义上的写者优先,只是按照到达顺序进行读写操作而已。若要实现真正的写者优先(即当写者和读者同时等待时,后续写者到达时可以插队到等待的读者之前,只要等待队列中有写者,不管何时到达,都优先于读者被唤醒),则需要增设额外的信号量进行控制。
为了达到这一目的,需要增设额外的一个信号量 readable,用于控制写者到达时可以优先于读者进入临界区,当有写者到达时,只需要等待前面的写者写完就可以直接进入临界区,而不论读者是在该写者之前还是之后到达。另外,需要增设一个整数 writeount 用于统计写者的数量。与之前的算法相比,wmutex 的作用有所变化,现在是用于控制写者互斥访问writecount。算法如下:
银行家算法的描述:
安全性算法描述如下:
资源分配图
一个系统资源分配图(System Resource Allocation Graph)可定义为一个二元组,即SRAG=(V,E),其中V是顶点集合,而E 是有向边集合。顶点集合可分为两部分:P=(P1,P2··,Pn)是由系统内的所有进程组成的集合,每一个P代表一个进程。R=(r1,r2,··,rm)
是系统内所有资源组成的集合,每一个r代表一类资源。
有向边集合 E中的每一条边是一个有序对表示请求资源或者分配资源,<Pi,ri>是请求资源,<ri,Pi>是分配资源。
在SRAG中,用圆圈代表进程,用方框表示每类资源。每一类资源n可能有多个,可用方框中的圆圈表示各个资源。申请边是从进程到资源的有向边,表示进程申请一个资源,但当前该进程尚未得到该资源。分配边是从资源到进程的有向边,表示有一个资源分配给进程。一条申请边仅指向代表资源类r的方框,表示申请时不指定哪一个具体资源。当进程 Pi申请资源类ri的一个资源时,将一条申请边加入资源分配图,若这个申请是可以满足的,则该申请边立即转换成分配边;当进程随后释放了某个资源时,则删除分配边。
死锁定理
可以用简化资源分配图的方法来检测系统状态S是否是死锁状态。
可以证明的是,不同的简化顺序将得到相同的不可简化图。系统状态 S 为死锁状态的条件是:当且仅当S 状态的资源分配图是不可完全简化的,该定理称为死锁定理。
检测死锁算法的基本思想是:获得某时刻t系统中各类可利用资源的数目向量 available(t),对于系统中的一组进程{P1,P2,··Pn},找出那些对各类资源请求数目均小于系统现有的各类可利用资源数目的进程。这样的进程可以获得它们所需要的全部资源并运行结束,当运行结束后,它们会释放所占有的全部资源,从而使可用资源数目增加,将这样的进程加入到可运行结束的进程序列中,然后对剩下的进程再进行上述考查。如果一组进程中有几个不属于该序列,那么它们会发生死锁。
旦检测出系统中出现了死锁,就应使陷入死锁的进程从死锁状态中解脱出来,即死锁解除。
常用的解除死锁的方法有以下 3 种:
即使系统没有发生死锁,某些进程也可能会长时间等待。当等待时间给进程推进和响应带来明显影响时,称此时发生了进程饥饿,当饥饿到一定程度,进程所赋予的任务即使完成也不再具有实际意义时,则称该进程被饿死。
死锁与饿死的区别:
饥饿和饿死与资源分配策略有关,因而可从公平性方面考虑防止饥饿与饿死,以确保所有进程不被忽视,如多级反馈队列调度算法。