前趋图(Precedence Graph)是一个有向无循环图
,记为DAG(Directed Acyclic Graph),用于描述进程之间执行的前后关系。
图中的每个结点
可用于描述一个程序段或进程,乃至一条语句;结点间的有向边则用于表示两个结点之间存在的偏序(Partial Order)或前趋关系(Precedence Relation)。
进程(或程序)之间的前趋关系可用“→” 来表示,
→={(Pi, Pj)|Pi must complete before Pj may start}, 如果(Pi, Pj)∈→,可写成Pi→Pj
,称Pi是Pj的直接前趋
,而称Pj是Pi的直接后继
。
在前趋图中,把没有前趋的结点称为初始结点
(Initial Node),把没有后继的结点称为终止结点
(Final Node)。
每个结点还具有一个重量(Weight),用于表示该结点所含有的程序量或结点的执行时间。
对于图 2-2(a)所示的前趋图, 存在下述前趋关系:
应当注意,前趋图中必须不存在循环
,但在图2-2(b)中却有着下述的前趋关系:
S2→S3, S3→S2
即,S3运行之前,S2要运行完了,但S2运行之前,S3也需要运行完才可,这显然是不可能实现的。
Ii→Ci,Ii→Ii+1, Ci→Pi, Ci→Ci+1,Pi→Pi+1
而Ii+1和Ci及Pi-1是重叠的,亦即在Pi-1和Ci以及Ii+1之间,可以并发执行。
对于具有下述四条语句的程序段:
S1: a∶=x+2
S2: b∶=y+4
S3: c∶=a+b
S4: d∶=c+b
我们可以画出下面的前趋图
可以看出: S3必须在a和b被赋值后方能执行: S4必须在S3之后执行; 但S1和S2则可以并发执行, 因为它们彼此互不依赖。
间断性
相互制约将导致并发程序具有“执行——停止——执行” 这种间断性的活动规律。
失去封闭性
当系统中存在着多个可以并发执行的程序时,系统中的各种资源将为它们所共享,而这些资源的状态也由这些程序来改变,致使其中任一程序在运行时,其环境都必然会受到其它程序的影响。例如, 当处理机已被分配给某个进程运行时,其它程序必须等待。显然,程序的运行已失去了封闭性。
不可再现性
程序在并发执行时, 由于失去了封闭性, 也将导致其又失去可再现性。
例如,有两个循环程序A和B,它们共享一个变量N。
程序A每执行一次时,都要做N=N+1操作;
程序B每执行一次时, 都要执行Print(N)操作,然后再将N置成“0”。
程序A和B以不同的速度运行。
则结果可能有:
(1) N=N+1在Print(N)和N=0之前,此时得到的**N值**分别为n+1, n+1, 0。
(2) N=N+1在Print(N)和N=0之后,此时得到的**N值**分别为n, 0, 1。
(3) N=N+1在Print(N)和N=0之间,此时得到的**N值**分别为n, n+1, 0。
由上可以看出,**程序的不能随便执行并发控制,因为在多道程序环境下, 程序的执行属于并发执行, 此时它们将失去其封闭性, 并具有间断性, 以及其运行结果不可再现性的特征。 **
由此, 决定了通常的程序是不能参与并发执行的, 否则, 程序的运行也就失去了意义。 为了能使程序并发执行, 并且可以对并发执行的程序加以描述和控制, 人们引入了 “进程” 的概念。
进程是程序的一次执行
进程是一个程序及其数据在处理机上顺序执行
时所发生的活动
进程是程序在一个数据集合上运行的过程
,它是系统进行资源分配和调度的一个独立单位
。
为了使参与并发执行的每个程序(含数据)都能独立地运行, 在操作系统中必须为之配置一个专门的数据结构, 称为进程控制块(Process Control Block, PCB)。
系统利用PCB来描述进程的基本情况和活动过程, 进而控制和管理进程。 这样, 由程序段、 相关的数据段和PCB三部分便构成了进程实体(又称进程映像)
。
一般情况下, 我们把进程实体就简称为进程, 例如, 所谓创建进程, 实质上是创建进程实体中的PCB;而撤消进程, 实质上是撤消进程的PCB。
在引入了进程实体的概念后, 我们可以把传统OS中的进程定义为: “进程是进程实体的运行过程, 是系统进行资源分配和调度的一个独立单位。
”
进程的特征
进程和程序是两个截然不同的概念, 除了进程具有程序所没有的PCB结构外, 还具有下面一些特征:
进程的三种基本状态
创建态和终止态
创建状态:首先由进程申请一个空白PCB,并向PCB中填写用于控制和管理进程的信息;然后为该进程分配运行时所必须的资源: 最后, 把该进程转入就绪状态并插入就绪队列之中。
对于处于创建状态的进程, 当其获得了所需的资源以及对其PCB的初始化工作完成后, 便可由创建状态转入就绪状态
终止状态:
首先, 是等待操作系统进行善后处理, 最后将其PCB清零, 并将PCB空间返还系统。
当一个进程到达了自然结束点, 或是出现了无法克服的错误, 或是被操作系统所终结, 或是被其他有终止权的进程所终结, 它将进入终止状态。
进入终止态的进程以后不能再执行, 但在操作系统中依然保留一个记录, 其中保存状态码和一些计时统计数据, 供其他进程收集。
一旦其他进程完成了对其信息的提取之后, 操作系统将删除该进程, 即将其PCB清零, 并将该空白PCB返还系统。
挂起操作
引入挂起操作的原因
引入挂起原语操作后三个进程状态的转换
在引入挂起原语Suspend和激活原语Active后, 在它们的作用下, 进程将可能发生以下几种状态的转换:
活动就绪→静止就绪。
当进程处于未被挂起的就绪状态
时, 称此为活动就绪状态
,表示为Readya,此时进程可以接受调度
。 当用挂起原语Suspend将该进程挂起
后, 该进程便转变为静止就绪状态
, 表示为Readys,处于Readys状态的进程不再被调度执行
。
活动阻塞→静止阻塞。
当进程处于未被挂起的阻塞状态
时, 称它是处于活动阻塞状态
, 表示为Blockeda。 当用Suspend原语将它挂起后
, 进程便转变为静止阻塞状态
,表示为Blockeds。处于该状态的进程在其所期待的事件出现后, 它将从静止阻塞变为静止就绪Readys状态
静止就绪→活动就绪。
处于Readys状态的进程若用激活原语Active激活后, 该进程将转变为Readya状态。
静止阻塞→活动阻塞。
处于Blockeds状态的进程若用激活原语Active激活后,进程将转变为Blockeda状态
引入挂起操作后五个进程状态的转换
NULL-创建: 一个新进程产生时, 该进程处于创建状态。
创建→活动就绪: 在当前系统的性能和内存的容量均允许的情况下, 完成对进程创建的必要操作后, 相应的系统进程将进程的状态转换为活动就绪状态。
创建→静止就绪: 考虑到系统当前资源状况和性能的要求, 不分配给新建进程所需资源,主要是主存,相应的系统将进程状态转为静止就绪状态,被安置在外存,不参与调度, 此时进程创建工作尚未完成。
执行→终止: 当一个进程己完成任务时, 或是出现了无法克服的错误, 或是被OS或是被其他进程所终结, 此时将进程的状态转换为终止状态。
操作系统中用于管理控制的数据结构
在计算机系统中, 对于每个资源和每个进程都设置了一个数据结构, 用于表征其实体,我们称之为资源信息表
或进程信息表
, 其中包含了资源或进程的标识、 描述、 状态等信息以及一批指针。
通过这些指针, 可以将同类资源或进程的信息表, 或者同一进程所占用的资源信息表分类链接成不同的队列,便于操作系统进行查找。
OS管理的这些数据结构一般分为以下四类: 内存表、 设备表、 文件表和用于进程管理的进程表, 通常进程表又被称为进程控制块PCB
。
进程控制块的作用
进程控制块的作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位,一个能与其它进程并发执行的进程。
或者说,OS是根据PCB来对并发执行的进程进行控制和管理的。
进程控制块中的信息
进程标识符
进程标识符用于惟一地标识一个进程。
一个进程通常有两种标识符:
为了描述进程的家族关系, 还应设置父进程标识及子进程标识。
此外,还可设置用户标识,以指示拥有该进程的用户。
处理机状态
处理机状态信息主要是由处理机的各种寄存器中的内容组成的。
进程调度信息
在PCB中还存放一些与进程调度和进程对换有关的信息,包括:
进程控制信息
进程控制信息包括
线性方式
即将系统中所有的PCB都组织在一张线性表中, 将该表的首址存放在内存的一个专用区域中。
该方式实现简单、 开销小, 但每次查找时都需要扫描整张表, 因此只适合进程数目不多的系统。
PCB线性表示意图
链接方式
即把具有相同状态进程的PCB分别通过PCB中的链接字链接成一个队列。
这样, 可以形成就绪队列、 若干个阻塞队列和空白队列等。
索引方式
进程控制是进程管理中最基本的功能, 主要包括创建新进程、 终止已完成的进程、 将因发生异常情况而无法继续运行的进程置于阻塞状态、 负责进程运行中的状态转换等功能。
如当一个正在执行的进程因等待某事件而暂时不能继续执行时, 将其转变为阻塞状态,而在该进程所期待的事件出现后, 又将该进程转换为就绪状态等。 进程控制一般是由os的内核中的原语来实现的。
为了形象地描述一个进程的家族关系而引入了进程图(Process Graph)。 所谓进程图就是用于描述进程间关系的一棵有向树, 如图2-13所示。 图中的结点代表进程。
根结点A是这个进程家族的祖先。
引起进程终止(Termination of Process)的事件
正常结束
在任何计算机系统中,都应有一个用于表示进程已经运行完成的指示。
例如,在批处理系统中,通常在程序的最后安排一条Holt指令或终止的系统调用。当程序运行到Holt指令时,将产生一个中断,去通知OS本进程已经完成。 在分时系统中,用户可利用Logs off去表示进程运行完毕, 此时同样可产生一个中断,去通知OS进程已运行完毕。
异常结束
在进程运行期间,由于出现某些错误和故障而迫使进程终止。
这类异常事件很多,常见的有:
① 越界错误。这是指程序所访问的存储区,已越出该进程的区域;
② 保护错误。进程试图去访问一个不允许访问的资源或文件,或者以不适当的方式进行访问,例如,进程试图去写一个只读文件;
③ 非法指令。程序试图去执行一条不存在的指令。出现该错误的原因,可能是程序错误地转移到数据区,把数据当成了指令;
④ 特权指令错。用户进程试图去执行一条只允许OS执行的指令;
⑤ 运行超时。进程的执行时间超过了指定的最大值;
⑥ 等待超时。进程等待某事件的时间, 超过了规定的最大值;
⑦ 算术运算错。进程试图去执行一个被禁止的运算,例如,被0除;
⑧ I/O故障。这是指在I/O过程中发生了错误等。
外界干预
外界干预并非指在本进程运行中出现了异常事件,而是指进程应外界的请求而终止运行
。
这些干预有:
① 操作员或操作系统干预。 由于某种原因,例如,发生了死锁, 由操作员或操作系统终止该进程;
② 父进程请求。 由于父进程具有终止自己的任何子孙进程的权利, 因而当父进程提出请求时,系统将终止该进程;
③ 父进程终止。 当父进程终止时,OS也将他的所有子孙进程终止。
进程的终止过程
(1) 根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态
。
(2) 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真
,用于指示该进程被终止后应重新进行调度。
(3) 若该进程还有子孙进程,还应将其所有子孙进程予以终止
,以防他们成为不可控的进程。
(4) 将被终止进程所拥有的全部资源,或者归还给其父进程, 或者归还给系统
。
(5) 将被终止进程(它的PCB)从所在队列(或链表)中移出
, 等待其他程序来搜集信息。
进程阻塞过程
正在执行的进程,当发现上述某事件时,由于无法继续执行,于是进程便通过调用阻塞原语block把自己阻塞。
可见,进程的阻塞是进程自身的一种主动行为
。
进入block过程后,由于此时该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状态由“执行”改为阻塞,并将PCB插入阻塞队列。
如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列。
最后,转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换,亦即,保留被阻塞进程的处理机状态(在PCB中),再按新进程的PCB中的处理机状态设置CPU的环境。
进程唤醒过程
当被阻塞进程所期待的事件出现时,如I/O完成或其所期待的数据已经到达,则由有关进程(比如,用完并释放了该I/O设备的进程)调用唤醒原语wakeup( ),将等待该事件的进程唤醒。
唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。
进程的挂起
当出现了引起进程挂起的事件时,比如,用户进程请求将自己挂起,或父进程请求将自己的某个子进程挂起, 系统将利用挂起原语suspend( )将指定进程或处于阻塞状态的进程挂起。
挂起原语的执行过程是:
首先检查被挂起进程的状态,若处于活动就绪状态,便将其改为静止就绪;对于活动阻塞状态的进程,则将之改为静止阻塞。
为了方便用户或父进程考查该进程的运行情况而把该进程的PCB复制到某指定的内存区域。
最后,若被挂起的进程正在执行,则转向调度程序重新调度。
进程的激活过程
当发生激活进程的事件时,例如,父进程或用户进程请求激活指定进程,若该进程驻留在外存而内存中已有足够的空间时,则可将在外存上处于静止就绪状态的进程换入内存。
这时,系统将利用激活原语active( )将指定进程激活。
激活原语先将进程从外存调入内存,检查该进程的现行状态,若是静止就绪,便将之改为活动就绪;若为静止阻塞便将之改为活动阻塞。
假如采用的是抢占调度策略,则每当有新进程进入就绪队列时,应检查是否要进行重新调度,即由调度程序将被激活进程与当前进程进行优先级的比较,如果被激活进程的优先级更低,就不必重新调度;否则,立即剥夺当前进程的运行,把处理机分配给刚被激活的进程。
为保证多个进程能有条不紊地运行, 在多道程序系统中, 必须引入进程同步机制。
进程同步机制的主要任务, 是对多个相关进程在执行次序上进行协调, 使并发执行的诸进程之间能按照一定的规则(或时序)共享系统资源, 并能很好地相互合作, 从而使程序的执行具有可再现性。
间接相互制约关系
多个程序在并发执行时, 由于共享系统资源, 如CPU、 I/O设备等, 致使在这些并发执行的程序之间形成相互制约的关系。 对于像打印机、 磁带机这样的临界资源, 必须保证多个进程对之只能互斥地访问, 由此, 在这些进程间形成了源于对该类资源共享的所谓间接相互制约关系。
直接相互制约关系
某些应用程序, 为了完成某任务而建立了两个或多个进程。 这些进程将为完成同一项任务而相互合作。 进程间的直接制约关系就是源于它们之间的相互合作。
例如, 有两个相互合作的进程— —输入进程A和计算进程B, 它们之间共享一个缓冲区。 进程A通过缓冲向进程B提供数据。 进程B从缓冲中取出数据, 并对数据进行处理。 但如果该缓冲空时,计算进程因不能获得所需数据而被阻塞。 一旦进程A把数据输入缓冲区后便将进程B唤醒;反之, 当缓冲区己满时, 进程A因不能再向缓冲区投放数据而被阻塞, 当进程B将缓冲区数据取走后便可唤醒A。
在多道程序环境下, 由于存在着上述两类相互制约关系, 进程在运行过程中是否能获得处理机运行与以怎样的速度运行, 并不能由进程自身所控制, 此即进程的异步性
。 由此会产生对共享变量或数据结构等资源不正确的访问次序, 从而造成进程每次执行结果的不一致。 这种差错往往与时间有关, 故称为“与时间有关的错误” 。 为了杜绝这种差错, 必须对进程的执行次序进行协调, 保证诸进程能按序执行。
生产者-消费者(producer-consumer)问题
描述
有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中; 消费者进程可从一个缓冲区中取走产品去消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品;也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
我们可利用一个数组来表示上述的具有n个(0,1,…,n-1)缓冲区的缓冲池,用输入指针in来指示下一个可投放产品的缓冲区,每当生产者进程生产并投放一个产品后,输入指针加1;用一个输出指针out来指示下一个可从中获取产品的缓冲区,每当消费者进程取走一个产品后,输出指针加1。
由于这里的缓冲池是组织成循环缓冲的,故应把输入指针加1表示成 in=(in+1)%n;输出指针加1表示成out=(out+1)%n。当(in+1)%n=out时表示缓冲池满;而in=out则表示缓冲池空。此外,还引入了一个整型变量counter, 其初始值为0。每当生产者进程向缓冲池中投放一个产品后,使counter加1;反之,每当消费者进程从中取走一个产品时, 使counter减1。
生产者和消费者两进程共享下面的变量:
int in=0, out=0, count=0;
item buffer[n];
指针in和out初始化为0。
在生产者进程中使用一局部变量nextp,用于暂时存放每次刚生产出来的产品;而在消费者进程中,则使用一个局部变量nextc,用于存放每次要消费的产品。
void producer()
{
while(1){
produce an item in nextp;
...
while (counter==n)
;
buffer [in] = nextp;
in = (in+1) % n;
counter++;
}
};
void consumer()
{
while(1){
因篇幅问题不能全部显示,请点此查看更多更全内容