进程同步

在OS中引入进程后,一方面可以使系统中的多道程序并发执行,这不仅能有效地改善资源利用率,还可以显著地提高系统的吞吐量,但另一方面却使系统变得更加复杂。如果不能采取有效的措施,对多个进程的运行进行妥善的管理,必然会因为这些进程对系统资源的无序争夺给系统造成混乱。致使每次处理的结果存在不确定性,即显现出其不可在现性。。

为保证多个进程能有条不紊地运行,在多道程序系统中,必须引入进程同步机制。接下来将详细介绍单处理机系统中的进程同步机制-硬件同步机制、信号量同步机制、管程机制等,利用它们来保证程序执行的可再现性。

一、进程同步的基本概念

进程同步机制的主要任务,是对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按照一定的规则(或时序)共享系统资源,并能很好地相互合作,从而使程序的执行具有可在现性。

1.1 两种形式的制约关系

在多道程序下,处于同一个系统中的多个进程,由于它们共享系统中的资源,或为完成某一任务而互相合作,它们之间可能存在着以下两种形式的制约关系。

1.1.1 间接相互制约关系

多个程序在并发执行时,由于共享系统资源,如CPU、I/O设备等,致使在这些并发执行的程序之间形成相互制约的关系。对于像打印机、磁带机这样的临界资源,必须保证多个进程对之只能互斥地访问,由此,在这些进程间形成了原语对该类资源共享的所谓间接相互制约关系。为了保证这些进程能有序地运行,对于系统中的这类资源,必须由系统实施同一分配,即用户在要使用之前,应先提出申请,而不允许用户进程直接使用。

1.1.2 直接相互制约关系

某些应用程序,为了完成某任务而建立了两个或多个进程。这些进程将为完成同一项任务而相互合作。进程间的直接制约关系就是源于它们之间的相互合作。例如,有两个相互合作的进程
待补充(59)

三、信号量机制

3.1 整型信号量

最初由Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作wait(S)和signal(S)来访问。很长时间来,这两个操作一直被分别称为P、V操作。wait和signal操作可描述如下:
在这里插入图片描述
这两个操作都是原子操作,因此,它们在执行过程中是不可中断的。即:当一个进程在修改某信号量时,没有其他进程可同时对该信号量进行修改。此外,在wait操作中,对S值的测试和做S:=S-1操作时都不可中断。

3.2 记录型信号量

在整型信号量机制中的wait操作,只要是信号量 S ≤ 0 S\le 0 S0 ,就会不断地测试,因此,该机制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。记录型信号量则是一种不存在“忙等”现象的进程同步机制。但在采用了“让权等待”的策略后,又会出现多个进程等待访问同一临界资源的情况。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针list,用于链接上述的所有等待进程。记录型信号量是由于它采用了记录型的数据结构而得名的。它所包含的上述两个数据项可描述如下:

typedef struct{
	int value;
	sruct process_control_block *list;
}semaphore;

相应地,wait(S)和signal(S)操作可描述如下:

wait(semaphore *S){
	S->value--;
	if(S-value<0) block(S-list);
}
signal(semaphore *S){
	S->value++;
	if(S->value<=0) wakeup(S->list);
}

在记录型信号量机制中,S->value的初值表示系统中某类资源的数目,因而又称为资源信号量,对它的每次wait操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源数减少一个,因此描述为S->value–;当S->value<0时,表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞,放弃处理机,并插入到信号量链表S->list中。可见,该机制遵循了“让权等待”的准则。此时S->value的绝对值表示在该信号量链表中已阻塞进程的数目。对信号量的每次signal操作表示执行进程释放一个单位资源,使系统中可供分配的该类资源数增加一个,故S->value++操作表示资源数目加1。若加1后仍是S->value ≤ 0 \le 0 0,则表示在该信号量链表中仍有等待该资源的进程被阻塞,故还应调用wakeup原语,将S->list链表中的第一个等待进程唤醒。如果S->value的初值为1,表示只允许一个进程访问临界资源,此时的信号量可以转化为互斥信号量,用于进程互斥。

3.3 AND型信号量

在这里插入图片描述
在这里插入图片描述

3.4 信号量集

在前面所述的记录型信号量机制中,wait(S)或signal(S)操作仅能对信号量施以加1或减1操作,意味着每次只能对某类临界资源进行一个单位的申请或释放。当一次需要N个单位时,便要进行N次wait(S)操作,这显然是低效的,甚至会增加死锁的概率。此外,在有些情况下,为确保系统的安全性,当所申请的资源数量低于某一下限值时,还必须进行管制,不予以分配。因此,当进程申请某类临界资源时,在每次分配之前,都必须测试资源的数量,判断是否大于可分配的下限值,决定是否予以分配。

基于上述两点,可以对AND信号量机制加以扩充,对进程所申请的所有资源以及每类资源不同的资源需求量,在一次P、V原语操作中完成申请或释放。进程对信号量 S 1 S_1 S1的测试值不再是1,而是该资源的分配下限值 t i t_i ti,否则不予分配,一旦允许分配,该进程对该资源的需求值为 d i d_i di,即表示资源占用量,进行 S i : S i − d i S_i:S_i-d_i Si:Sidi操作,而不是简单的 S i = S i − 1 S_i=S_i-1 Si=Si1。由此形成一般化的“信号量集”机制。对应的Swait和Ssignal格式为:

S w a i t ( S 1 , T 1 , d 1 , . . . , S n , t n , d n ) S_{wait}(S_1,T_1,d_1,...,S_n,t_n,d_n) Swait(S1,T1,d1,...,Sn,tn,dn)
S s i g n a l ( S 1 , d 1 , . . . , S n , d n ) S_{signal}(S_1,d_1,...,S_n,d_n) Ssignal(S1,d1,...,Sn,dn)

一般“信号量集”还有下面几种特殊情况:

  1. S w a i t ( S , d , d ) S_{wait}(S,d,d) Swait(S,d,d):此时在信号量集中只有一个信号量S,但允许它每次申请d个资源,当现有资源数少于d时,不予分配
  2. S w a i t ( S , 1 , 1 ) S_{wait}(S,1,1) Swait(S,1,1):此时的信号量集已蜕化为一般的记录型信号量( S > 1 S>1 S>1时)或互斥信号量( S = 1 S=1 S=1时)
  3. S w a i t ( S , 1 , 0 ) S_{wait}(S,1,0) Swait(S,1,0):这是一种很特殊且很有用的信号量操作。当 S ≥ 1 S\ge 1 S1时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。

四、信号量的应用

4.1 利用信号量实现进程互斥

为使多个进程能互斥地访问某临界资源,只需为该资源设置一互斥信号量mutex,并设其初始值为1,然后将各进程访问该资源的临界区CS置于wait(mutex)和signal(mutex)操作之间即可。这样,每个欲访问该临界资源的进程在进入临界区之前,都要先对mutex执行wait操作,若该资源此刻未被访问,本次wait操作必然成功,进程便可进入自己的临界区,此时若再有其他进程也欲进入自己的临界区,由于对mutex执行wait操作定会失败,因而此时该进程阻塞,从而保证了该临界资源能被互斥地访问。当访问临界资源的进程退出临界区后,又应对mutex执行signal操作,以便释放该临界资源。利用信号量实现两个进行互斥的描述如下:

4.1.1 语言描述

设mutex为互斥信号量,其初值为1,取值范围为(-1,0,1)。当mutex=1时,表示两进程皆未进入需要互斥的临界区;当mutex=0时,表示有一个进程进入临界区运行,另外一个必须等待,挂入阻塞队列;当mutext=-1时,表示有一个进程正在临界区运行,另外一个进程因等待而阻塞在信号量队列总,需要被当前已在临界区运行的进程退出时唤醒。

4.1.2 代码描述

在这里插入图片描述
在利用信号量机制实现进程互斥时应该注意,wait(mutex)和signal(mutex)必须成对地出现。缺少wait(mutex)将会导致系统混乱,不能保证对临界资源的互斥访问;而缺少signal(mutex)将会使临界资源永远不被释放,从而使因等待该资源而阻塞的进程不能被唤醒。

4.2 利用信号量实现前趋关系

待补充/67

五、管程机制

虽然信号量机制是一种既方便、又有效的进程同步机制,但每个要访问临界资源的进程都必须自备同步操作wait(S)和signal(S)。这就使大量的同步操作分散在各个进程中。这不仅给系统的管理带来了麻烦,而且还会因同步操作的使用不当而导致系统死锁。这样,在解决上述问题的过程中,便产生了一种新的进程同步工具-管程(Monitors)

5.1 管程的定义

系统中的各种硬件资源和软件资源均可用数据结构抽象地描述其资源特性,即用少量信息和对该资源所执行的操作来表征该资源,而忽略它们的内部结构和实现细节。因此,可以利用共享数据结构抽象地表示系统中的共享资源,并且将对该共享数据结构实施的特定操作定义为一组过程。进程对共享资源的申请、释放和其他操作必须通过这组过程,间接地对共享数据结构实现操作。对于请求访问共享资源的诸多并发进程,可以根据资源的情况接收或阻塞,确保每次仅有一个进程进入管程,执行这组过程,使用共享资源,达到对共享资源所有访问的同一管理,有效地实现进程互斥。

代表共享资源的数据结构以及对该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块,我们称之为管程。管程被请求和释放资源的进程所调用。Hansan为管程所下的定义是:“一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据”

由上述的定义可知,管程由四部分组成:

  • 管程的名称
  • 局部于管程的共享数据结构说明
  • 对该数据结构进行操作的一组过程
  • 对局部于管程的共享数据设置初始值的语句

下图是一个管程的示意图:

在这里插入图片描述
管程的语法描述如下:
在这里插入图片描述

实际上,管程中包含了面向对象的思想,它将表征共享资源的数据结构以及对数据结构操作的一组过程,包括同步机制,都集中并封装在一个对象内部,隐藏了实现细节,封装于管程内部的数据结构仅能被封装于管程内部的过程所访问,任何管程外的过程都不能访问它;反之,封装于管程内部的过程也仅能访问管程内的数据结构。所有进程要访问临界资源时,都只能通过管程间接访问,而管程每次只准许一个进程进入管程,执行管程内的过程,从而实现了进程互斥。

管程是一种程序设计语言的结构成份,它和信号量有同等的表达能力,从语言的角度来看,管程主要有以下特性:

  1. 模块化:即管程是一个基本程序单位,可以单独编译
  2. 抽象数据类型:指管程中不仅有数据,而且有对数据的操作
  3. 信息掩蔽:指管程中的数据结构只能被管程中的过程访问,这些过程也是在管程内部定义的,供管程外的进程调用,而管程中的数据结构以及过程(函数)的具体实现外部不可见

管程和进程不同:

  1. 虽然二者都定义了数据结构,但进程定义的是私有数据结构PCB,管程定义的是公共数据结构,如消息队列等
  2. 二者都存在对各自数据结构上的操作,但进程是由顺序程序执行有关操作,而管程主要是进行同步操作和初始化操作
  3. 设置进程的目的是在于实现系统的并发性,而管程的设置则是解决共享资源的互斥使用问题
  4. 进程通过调用管程中的过程对共享数据结构实行操作,该过程就如通常的子程序一样被调用,因而管程为被动工作方式,进程则为主动工作方式
  5. 进程之间能并发执行,而管程则不能与其调用者并发
  6. 进程具有动态性,由“创建”而诞生,由“撤销”而消亡,而管程则是操作系统中的一个资源管理模块,供进程调用

5.2 条件变量

在利用管程实现进程同步时,必须设置同步工具,如两个操作原语wait和signal。当某进程通过管程获得临界资源而未能满足时,管程便调用wait原语使该进程等待,并将其排在等待队列上,并将其排在等待队列上。仅当另一进程访问完并释放该资源之后,管程才又调用signal原语,唤醒等待队列上的队首进程。

但是仅仅有上述的同步工具是不够的,考虑下面这种情况:
当一个进程调用了管程,在管程中时被阻塞或挂起,直到阻塞或挂起的原因解除,而在此期间,如果该进程不释放管程,则其他进程无法进入管程,被迫长时间的等待。为了解决这个问题,引入了条件变量condition。通常,一个进程被阻塞或挂起的条件(原因)可有多个,因此在管程中设置了多个条件变量,对这些条件变量的访问只能在管程中进行。

管程中对每个条件变量都予以说明,其形式为:
condition x, y
对条件变量的操作仅仅是wait和signal,因此条件变量也是一种抽象数据类型,每个条件变量保存了一个李娜表,用于记录因该条件变量而阻塞的所有进程,同时提供的两个操作即可表示为x.wait和x.signal,其含义为:

  1. x.wait:正在调用管程的进程因x条件需要被阻塞或挂起,则调用x.wait将自己插入到x条件的等待队列上,并释放管程,直到x条件变化。此时其他进程可以使用该管程
  2. x.signal:正在调用管程的进程发现x条件发生了变化,则调用x.signal,重新启动一个因x条件而阻塞或挂起的进程,如果存在多个这样的进程,则选择其中的一个,如果没有,继续执行原进程,而不产生任何结果。这与信号量机制中的signal操作不同。因为,后者总是要执行s:=s+1,操作,因而总会改变信号量的状态。

如果有进程Q因x条件处于阻塞状态,当正在调用管程的进程p执行了x.signal操作后,进程Q被重新启动,此时两个进程P和Q,如何确定哪个执行哪个等待,可采用下述两种方式之一进行处理:

  1. P等待,直至Q离开管程或等待另一条件
  2. Q等待,直至P离开管程或等待另一条件

Hansan选择了两者的折中,他规定管程中的过程所执行的signal操作是过程体的最后一个操作,于是,进程P执行signal操作后立即退出管程,因而,进程Q马上被恢复执行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值