2) 进程的同步与互斥:临界资源、临界区、进程同步与互斥问题、信号量机制以及 P、V 操作、管程机制。

2.3.1 进程同步的基本概念

1.两种形式的制约关系
  在多道程序环境下,当程序并发执行时,由于资源共享和进程合作,使同处于一个系统中的诸进程之间可能存在着以下两种形式的制约关系。
  (1) 间接相互制约关系。同处于一个系统中的进程,通常都共享着某种系统资源,如共享CPU、共享I/O设备等。所谓间接相互制约即源于这种资源共享,例如,有两个进程A和B,如果在A进程提出打印请求时,系统已将惟一的一台打印机分配给了进程B,则此时进程A只能阻塞;一旦进程B将打印机释放,则A进程才能由阻塞改为就绪状态。
  (2) 直接相互制约关系。这种制约主要源于进程间的合作。例如,有一输入进程A通过单缓冲向进程B提供数据。当该缓冲空时,计算进程因不能获得所需数据而阻塞,而当进程A把数据输入缓冲区后,便将进程B唤醒;反之,当缓冲区已满时,进程A因不能再向缓冲区投放数据而阻塞,当进程B将缓冲区数据取走后便可唤醒A。
   2. 临界资源
  在第一章中我们曾经介绍过,许多硬件资源如打印机、磁带机等,都属于临界资源(Critical Resouce),诸进程间应采取互斥方式,实现对这种资源的共享。下面我们将通过一个简单的例子来说明这一过程。
  生产者-消费者(producer-consumer)问题是一个著名的进程同步问题。它描述的是:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
  我们可利用一个数组来表示上述的具有n个(0,1,…,n-1)缓冲区的缓冲池。用输入指针in来指示下一个可投放产品的缓冲区,每当生产者进程生产并投放一个产品后,输入指针加1;用一个输出指针out来指示下一个可从中获取产品的缓冲区,每当消费者进程取走一个产品后,输出指针加1。由于这里的缓冲池是组织成循环缓冲的,故应把输入指针加1表示成 in:=(in+1)mod n; 输出指针加1表示成out:= (out+1) mod n。当 (in+1)mod n=out时表示缓冲池满;而in=out则表示缓冲池空。此外,还引入了一个整型变量counter,其初始值为0。每当生产者进程向缓冲池中投放一个产品后,使counter加1;反之,每当消费者进程从中取走一个产品时,使counter减1。生产者和消费者两进程共享下面的变量:
  Var n,integer;
  type item=…;
  var buffer: array[0,1,…,n-1] of item;
  in,out: 0,1,…,n-1;
  counter: 0,1,…,n;
  指针in和out初始化为1。在生产者和消费者进程的描述中,noop是一条空操作指令,while condition do no-op语句表示重复的测试条件(condication),重复测试应进行到该条件变为false(假),即到该条件不成立时为止。在生产者进程中使用一局部变量nextp,用于暂时存放每次刚生产出来的产品;而在消费者进程中,则使用一个局部变量nextc,用于存放每次要消费的产品。
  producer: repeat
  produce an item in nextp;
  while counter=n do no-op;
  buffer[in]:=nextp;
  in:=in+1 mod n;
  counter:=counter+1;
  until false;
  虽然上面的生产者程序和消费者程序在分别看时都是正确的,而且两者在顺序执行时其结果也会是正确的,但若并发执行时就会出现差错,问题就在于这两个进程共享变量counter。生产者对它做加1操作,消费者对它做减1操作,这两个操作在用机器语言实现时, 常可用下面的形式描述:
  register1:=counter;  register2:=counter;
  register1:=register1+1; register2:=register2-1;
  counter:=register1;  counter:=register2;
  假设counter的当前值是5。如果生产者进程先执行左列的三条机器语言语句,然后消费者进程再执行右列的三条语句,则最后共享变量counter的值仍为5; 反之,如果让消费者进程先执行右列的三条语句,然后再让生产者进程执行左列的三条语句,则counter值也还是5,但是,如果按下述顺序执行:
  register1:=counter; (register1=5)
  register1:=register1+1; (register1=6)
  register2:=counter; (register2=5)
  register2:=register2-1; (register2=4)
  counter:=register1; (counter=6)
  counter:=register2; (counter=4)
  正确的counter值应当是5,但现在是4。读者可以自己试试,倘若再将两段程序中各语句交叉执行的顺序改变,将可看到又可能得到counter=6的答案,这表明程序的执行已经失去了再现性。为了预防产生这种错误,解决此问题的关键是应把变量counter作为临界资源处理,亦即,令生产者进程和消费者进程互斥地访问变量counter。
  3 .临界区
  由前所述可知,不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。人们把在每个进程中访问临界资源的那段代码称为临界区(critical section)。显然,若能保证诸进程互斥地进入自己的临界区,便可实现诸进程对临界资源的互斥访问。为此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看它是否正被访问。如果此刻该临界资源未被访问,进程便可进入临界区对该资源进行访问,并设置它正被访问的标志;如果此刻该临界资源正被某进程访问,则本进程不能进入临界区。因此,必须在临界区前面增加一段用于进行上述检查的代码,把这段代码称为进入区(entry section)。相应地,在临界区后面也要加上一段称为退出区(exit section)的代码,用于将临界区正被访问的标志恢复为未被访问的标志。
  进程中除上述进入区、临界区及退出区之外的其它部分的代码,在这里都称为剩余区。这样,可把一个访问临界资源的循环进程描述如下:
  repeat
    entry section
    critical section;
    exit section
    remainder section;
  until false;

2.3.2 信号量机制

1 .整型信号量
  最初由Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation) wait(S)和signal(S)来访问。很长时间以来,这两个操作一直被分别称为P、V操作。Wait(S)和signal(S)操作可描述为:
  wait(S): while S<=0 do no-op;
         S:=S-1;
  signal(S): S:=S+1;
  
  wait(S)和signal(S)是两个原子操作,因此,它们在执行时是不可中断的。亦即,当一个进程在修改某信号量时,没有其他进程可同时对该信号量进行修改。此外,在wait操作中,对S值的测试和做S:=S-1操作时都不可中断。
  #2 .记录型信号量
  在整型信号量机制中的wait操作,只要是信号量S≤0,就会不断地测试。因此,该机制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。但在采取了“让权等待”的策略后,又会出现多个进程等待访问同一临界资源的情况。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针L,用于链接上述的所有等待进程。记录型信号量是由于它采用了记录型的数据结构而得名的。它所包含的上述两个数据项可描述为:

  type semaphore=recordvalue: integer;L: list of process;end相应地,wait(S)和signal(S)操作可描述为:procedure wait(S)var S:semaphore;beginS.value:=S.value-1;if S.value<0 then block(S.L);end
procedure signal(S)var S: semaphore;beginS.value:=S.value+1;if S.value<=0 then wakeup(S.L);end

在记录型信号量机制中,S.value的初值表示系统中某类资源的数目,因而又称为资源信号量。对它的每次wait操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源数减少一个,因此描述为S.value:=S.value-1;当S.value<0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入到信号量链表S.L中。可见,该机制遵循了“让权等待”准则。此时S.value的绝对值表示在该信号量链表中已阻塞进程的数目。对信号量的每次signal操作,表示执行进程释放一个单位资源,使系统中可供分配的该类资源数增加一个,故S.value:=S.value+1操作表示资源数目加1。若加1后仍是S.value≤0,则表示在该信号量链表中,仍有等待该资源的进程被阻塞,故还应调用wakeup原语,将S.L链表中的第一个等待进程唤醒。如果S.value的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量,用
于进程互斥。
#3 .AND 型信号量
  上述的进程互斥问题,是针对各进程之间只共享一个临界资源而言的。在有些应用场合,是一个进程需要先获得两个或更多的共享资源后方能执行其任务。假定现有两个进程A和B,他们都要求访问共享数据D和E。当然,共享数据都应作为临界资源。为此,可为这两个数据分别设置用于互斥的信号量Dmutex和Emutex,并令它们的初值都是1。相应地,在两个进程中都要包含两个对Dmutex和Emutex的操作,即
process A:    process B:
wait(Dmutex);  wait(Emutex);
wait(Emutex);  wait(Dmutex);

若进程A和B按下述次序交替执行wait操作:
  process A: wait(Dmutex); 于是Dmutex=0
  process B: wait(Emutex); 于是Emutex=0
  process A: wait(Emutex); 于是Emutex=-1 A阻塞
  process B: wait(Dmutex); 于是Dmutex=-1 B阻塞
  最后,进程A和B处于僵持状态。在无外力作用下,两者都将无法从僵持状态中解脱出来。我们称此时的进程A和B已进入死锁状态。显然,当进程同时要求的共享资源愈多时,发生进程死锁的可能性也就愈大。
  AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源也不分配给它。亦即,对若干个临界资源的分配,采取原子操作方式:要么把它所请求的资源全部分配到进程,要么一个也不分配。由死锁理论可知,这样就可避免上述死锁情况的发生。为此,在wait操作中,增加了一个“AND”条件,故称为AND同步,或称为同时wait操作,即Swait(Simultaneous wait)定义如下:

Swait(S 1 ,S 2 ,…,S n )if S i >=1 and … and S n >=1 thenfor i:=1 to n doS i :=S i -1;endforelseplace the process in the waiting queue associated with the
first S i  found with S i <1,and set the program count of this
process to the beginning of Swait operationendif
Ssignal(S 1 ,S 2 ,…,S n )
for i:=1 to n doS i :=Si+1;
Remove all the process waiting in the queue associated with Si
into the ready queue.
endfor;

4 .信号量集
  在记录型信号量机制中,wait(S)或signal(S)操作仅能对信号量施以加1或减1操作,意味着每次只能获得或释放一个单位的临界资源。而当一次需要N个某类临界资源时,便要进行N次wait(S)操作,显然这是低效的。此外,在有些情况下,当资源数量低于某一下限值时,便不予以分配。因而,在每次分配之前,都必须测试该资源的数量,看其是否大于其下限值。基于上述两点,可以对AND信号量机制加以扩充,形成一般化的“信号量集”机制。Swait操作可描述如下,其中S为信号量,d为需求值,而t为下限值。

Swait(S 1 ,t 1 ,d 1 ,…,S n ,t n ,d n )if S i >=t 1 and … and S n >=t n thenfor i:=1 to n doS i :=S i -d i ;endforelsePlace the executing process in the waiting queue of the
first Si with S i <t i and set its program counter to the beginning of
the Swait Operation.endiveSsignal(S 1 ,d 1 ,…,S n ,d n )for i:=1 to n doS i :=S i +d i ;Remove all the process waiting in the queue associated with
Si into the ready queueendfor;

下面我们讨论一般“信号量集”的几种特殊情况:
  (1) Swait(S,d,d)。此时在信号量集中只有一个信号量S,但允许它每次申请d个资源,当现有资源数少于d时,不予分配。
  (2) Swait(S,1,1)。此时的信号量集已蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)。
  (3) Swait(S,1,0)。这是一种很特殊且很有用的信号量操作。当S≥1时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。

2.3.4  管程机制

1 .管程的定义
  系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对该资源所执行的操作来表征该资源,而忽略了它们的内部结构和实现细节。例如,对一台电传机,可用与分配该资源有关的状态信息(busy或free)和对它执行请求与释放的操作,以及等待该资源的进程队列来描述。又如,一个FIFO队列,可用其队长、队首和队尾以及在该队列上执行的一组操作来描述。
  利用共享数据结构抽象地表示系统中的共享资源,而把对该共享数据结构实施的操作定义为一组过程,如资源的请求和释放过程request和release。进程对共享资源的申请、释放和其它操作,都是通过这组过程对共享数据结构的操作来实现的,这组过程还可以根据资源的情况,或接受或阻塞进程的访问,确保每次仅有一个进程使用共享资源,这样就可以统一管理对共享资源的所有访问,实现进程互斥。
  代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,共同构成了一个操作系统的资源管理模块,我们称之为管程。管程被请求和释放资源的进程所调用。Hansan为管程所下的定义是:“一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据”。
  由上述的定义可知,管程由四部分组成:① 管程的名称;② 局部于管程内部的共享数据结构说明;③ 对该数据结构进行操作的一组过程;④ 对局部于管程内部的共享数据设置初始值的语句。图2-13是一个管程的示意图。
  
  需要指出的是,局部于管程内部的数据结构,仅能被局部于管程内部的过程所访问,任何管程外的过程都不能访问它;反之,局部于管程内部的过程也仅能访问管程内的数据结构。由此可见,管程相当于围墙,它把共享变量和对它进行操作的若干过程围了起来,所有进程要访问临界资源时,都必须经过管程(相当于通过围墙的门)才能进入,而管程每次只准许一个进程进入管程,从而实现了进程互
斥。
  管程是一种程序设计语言结构成分,它和信号量有同等的表达能力,从语言的角度看,管程主要有以下特性:
  (1) 模块化。管程是一个基本程序单位,可以单独编译。
  (2) 抽象数据类型。管程中不仅有数据,而且有对数据的操作。
  (3) 信息掩蔽。管程中的数据结构只能被管程中的过程访问,这些过程也是在管程内部定义的,供管程外的进程调用,而管程中的数据结构以及过程(函数)的具体实现外部不可见。
  (4) 进程通过调用管程中的过程对共享数据结构实行操作,该过程就如通常的子程序一样被调用,因而管程为被动工作方式,进程则为主动工作方式;
  (5) 进程之间能并发执行,而管程则不能与其调用者并发;
  (6) 进程具有动态性,由“创建”而诞生,由“撤销”而消亡,而管程则是操作系统中的一个资源管理模块,供进程调用。

管程的语法描述如下:type monitor_name = MONITOR;
<共享变量说明>;
define <(能被其他模块引用的)过程名列表>;
use <(要调用的本模块外定义的)过程名列表>;
procedure <过程名>(<形式参数表>);
begin
end;
function <函数名>(<形式参数表>):值类型;
begin
end;
begin
<管程的局部数据初始化语句序列>;
end
… …

2 .条件变量
  在利用管程实现进程同步时,必须设置同步工具,如两个同步操作原语wait和signal。当某进程通过管程请求获得临界资源而未能满足时,管程便调用wait原语使该进程等待,并将其排在等待队列上,如图2-13 所示。仅当另一进程访问完成并释放该资源之后,管程才又调用signal原语,唤醒等待队列中的队首进程。
  但是仅仅有上述的同步工具是不够的。考虑一种情况:当一个进程调用了管程,在管程中时被阻塞或挂起,直到阻塞或挂起的原因解除,而在此期间,如果该进程不释放管程,则其它进程无法进入管程,被迫长时间地等待。为了解决这个问题,引入了条件变量condition。通常,一个进程被阻塞或挂起的条件(原因)可有多个,因此在管程中设置了多个条件变量,对这些条件变量的访问,只能在管程中进行。
  管程中对每个条件变量都须予以说明,其形式为:Var x,y:condition。对条件变量的操作仅仅是wait和signal,因此条件变量也是一种抽象数据类型,每个条件变量保存了一个链表,用于记录因该条件变量而阻塞的所有进程,同时提供的两个操作即可表示为x.wait和x.signal,其含义为:
  ① x.wait:正在调用管程的进程因x条件需要被阻塞或挂起,则调用x.wait将自己插入到x条件的等待队列上,并释放管程,直到x条件变化。此时其它进程可以使用该管程。
  ② x.signal:正在调用管程的进程发现x条件发生了变化,则调
用x.signal,重新启动一个因x条件而阻塞或挂起的进程。如果存在
多个这样的进程,则选择其中的一个,如果没有,则继续执行原进
程,而不产生任何结果。这与信号量机制中的signal操作不同,因
为后者总是要执行s:=s+1操作,因而总会改变信号量的状态。
  如果有进程Q因x条件处于阻塞状态,当正在调用管程的进程P
执行了x.signal操作后,进程Q 被重新启动,此时两个进程P和Q,
如何确定哪个执行,哪个等待,可采用下述两种方式之一进行处理:
(1) P等待,直至Q离开管程或等待另一条件。
(2) Q等待,直至P离开管程或等待另一条件。

操作系统 第二部分 进程管理(二)相关推荐

  1. (王道408考研操作系统)第二章进程管理-第三节8:经典同步问题之吸烟者问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 文章目录 一 ...

  2. (王道408考研操作系统)第二章进程管理-第三节10:经典同步问题之哲学家进餐问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 (王道408 ...

  3. (王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题

    注意:生产者与消费者问题Linux系统编程专栏有案例讲解 Linux系统编程39:多线程之基于阻塞队列生产者与消费者模型 Linux系统编程40:多线程之基于环形队列的生产者与消费者模型 本文接:(王 ...

  4. 笔记篇:操作系统第二章 进程管理

    笔记篇:操作系统第二章 进程管理 目录 笔记篇:操作系统第二章 进程管理 2.1 进程的基本概念 2.1.1 程序的顺序执行及其特征 2.1.2 前驱图 2.1.3 程序的并发执行及其特征 2.1.4 ...

  5. 【王道】操作系统OS第二章进程管理(二[1])

    本笔记结合<2023王道操作系统考研复习指导>食用 操作系统OS第二章进程管理 本笔记结合<2023王道操作系统考研复习指导>食用 1.进程 1.1.进程的组成 1.1.1.P ...

  6. 操作系统第二章 进程管理

    写在前面:本文参考王道论坛的 操作系统考研复习指导单科书 文章目录 第二章 进程管理 进程同步 读者写者问题 哲学家就餐问题 练习题 哲学家就餐:加碗(2019真题) 既是生产者又是消费者 和尚取水( ...

  7. 操作系统 第二章 进程管理

    2.1 进程与线程 第一节零碎知识比较多,关键在于进程状态的切换.进程线程的关系. 第一章中提到过的多道程序环境,由于程序的特点,不能让程序并发,所以引入了进程的概念,让进程来并发,从而实现了多道程序 ...

  8. (王道408考研操作系统)第二章进程管理-第一节4:进程通信(配合Linux)

    文章目录 一:什么是进程通信 二:如何实现进程间通信及其分类 三:通信方式1-共享存储(共享内存) (1)课本基础内容 (2)补充-Linux中的进程通信 四:通信方式2-管道 (1)管道是什么 (2 ...

  9. (王道408考研操作系统)第二章进程管理-第一节3:进程控制(配合Linux讲解)

    文章目录 一:如何实现进程控制 二:进程控制原语 (1)进程创建 A:概述 B:补充-Linux中的创建进程操作 ①:fork() ②:fork()相关问题 (2)进程终止 A:概述 B:补充-僵尸进 ...

  10. 3 操作系统第二章 进程管理 进程定义、特征、组织、状态与转换

    文章目录 1 进程的定义和特征 2 进程的组织 3 进程的状态与转换 3.1 进程的状态 3.2 进程状态转换 1 进程的定义和特征 引入进程的原因 为了使程序能够并发执行,并且可以对并发执行的程序加 ...

最新文章

  1. 浅谈几种区块链网络攻击以及防御方案之女巫攻击
  2. Opencv实现透视形变
  3. 爱立信将在加拿大建立人工智能实验室
  4. 【Elasticsearch】Elasticsearch如何实现 SQL语句中 Group By 和 Limit 的功能
  5. 【MySQL通过视图(或临时表)实现动态SQL(游标】
  6. Dev-C++下载和安装教程
  7. C语言停车场管理系统
  8. 微信收钱的盒子服务器老是断开,好哒微信、支付宝入账异常处理办法
  9. OpenCV.反阈值二值化
  10. python+图书管理系统
  11. broker指定ip
  12. C语言学生学号管理系统练习
  13. EAUML日拱一卒 历史状态
  14. 讲座报名 | 清华大学副教授刘知远:大模型十问
  15. 时间序列统计特征的详细解析
  16. 《被讨厌的勇气》- 让干涉你的人见鬼去吧
  17. chatGPT AI绘画
  18. 专业C/C++程序员的培养路线
  19. DIY技巧:微星B760主板13600K降压教程 CPU温度暴降25℃
  20. Educoder - Java入门 - Java循环与分支语句编程各关卡题目总结

热门文章

  1. Manitest: Are classifiers really invariant?论文解读
  2. 十二、I/O复用介绍
  3. RAW socket使用
  4. 弹性分布式数据集RDD
  5. 第三季-第7课-动态函数库设计
  6. 页面JS实现按钮点击增加输入框
  7. 使用openssl模拟CA和CA证书的签发
  8. [SDOI2011]染色 BZOJ2243 树链剖分+线段树
  9. 渗透测试为什么要重基础、重实践?
  10. MVP Open Day 2011