操作系统--进程同步(一)

协作进程是可以与在系统内执行的其他进程互相影响的进程。互相协作的进程可以直接共享逻辑地址空间(包括代码和数据),或者值通过文件共享数据。 前一种情况可通过轻量级进程或线程的使用来实现。共享数据的开发访问会导致产生数据不一致。在这里,我们讨论各种确保共享同一逻辑地址空间的协作进程能有序执行并维护数据一致性的机制。

背景

开发了一种系统模型,它包括若干 协作顺序进程(cooperating sequential processes),这些进程异步执行且可能共享数据,采用有限缓冲方案(在操作系统中较为典型)说明了这种模型。
再次回到有限缓冲问题的共享内存解决方案。正如曾经指出的,解决方案允许在缓冲内同时最多只有 BUFFER_SIZE-1 项。假定要修改这一算法 以弥补这个缺陷。一种可能的方法是增加一个整数变量 counter,初始化为0。每次向缓冲增加一个新的项时,counter就递增,每次从缓冲中移去一项时,counter就递减。生产者进程代码可修改如下:

while(1){
	/*produce an item in nextProduced*/
	while(counter == BUFFER_SIZE);
	/*do nothing*/
	buffer[in] = nextProduced;
	in = (in + 1)% BUFFER_SIZE;
	counter++;
}

消费者进程代码如下:

while1{
	while(counter == 0);
	/*do nothing*/
	nextConsumed = buffer[out];
	out = (out + 1) % BUFFER_SIZE;
	counter --;
	/*consume the item in nextConsumed*/
}

虽然生产者和消费者程序各自都正确,但是当并发执行时,它们可能并不能正确执行。为了方便说明,假设变量counter 的当前值为5,且生产者进程和消费者进程并发执行语句“counter++”和“counter–”。根据这两句语句的执行,变量counter的值可以是 4、5或6! 唯一正确的是counter == 5,如果生产者和消费者独立执行,那么会生成此正确结果。
可说明按如下方式counter 的值就可能不正确。注意语句“counter ++”可按如下方式以机器语言(在一个典型机器上)实现:
register1 = counter
register1 = register1 + 1
counter = register1
其中, register1 为本地CPU寄存器值。类似地,语句“counter–”可按如下方式来实现:
register1 = counter
register2 = register2 - 1
counter = register2

其中,register2位本地CPU寄存器值。虽然register1 和 register2 可以是同一物理寄存器(如累加器),但是请记住中断处理程序会保存和回复寄存器(如累加器),但是请记住中断处理程序会保存和恢复该集群器的值。

并发执行“counter++” 和 “counter–”相当于 按任意顺序来交替执行上面所表示的较低级别语句(每条高级语句内的顺序被保持)。一种交叉的形式如下:
To: producer execute register1 = counter {register1 = 5}
T1:producer execute register1 = register1 + 1 {register1 = 6}
T2:consumer execute register2 = counter {register2 = 5}
T3:consumer execute register2 = register2 - 1 {register2 = 4}
T4:producer execute counter = register1 {register = 6}
T5:consumer execute counter = register2 {counter = 4}

注意这里得到了表示有4个满缓冲的不正确的状态“counter == 4”,而事实上有5个满缓冲。如果交换 T4 和 T5的语义顺序,那么会得到不正确的状态 “counter == 6”。
之所以得到了 不正确的状态,是因为允许两个进程并发操作变量counter。像这样的情况,即多个进程并发访问和操作同一数据并执行结果和访问发生的特定顺序有关,称为竞争条件(race condition)。为了防止上述竞争条件,需要确保一段时间内只有一个进程能操作变量counter。为了实现这种保证,要求一定形式的进程间同步。这种情况经常出现在操作系统中,因为系统的不同部分操纵资源而人们需要这些变化之间不会互相影响。本章的主要部分是关于 进程同步(process synchronization)和协调(coordination)的问题。

7.2 临界区域问题

考虑一个系统有n个进程{P0, P1, …, Pn-1},每个进程有一个代码段称为 临界区(critical section),在该区中进程可能改变共同变量,更新一个表、写一个文件等。这种系统的重要特征是当一个进程的临界区内执行,没用其他进程被允许在临界区内执行。因此, 进程临界区的执行在时间上互斥。临界区问题是设计一个进程能用来协作的协议。每个进程必须请求允许进入其临界区。实现这一请求的代码段称为 进入区(entry section)。临界区之后可有退出区(exit section)。其他代码为剩余区(remainder section)。
临界区问题的解答必须满足如下三项要求:

  1. 互斥:如果进程Pi在其临界区内执行,那么其他进程都不能再起临界区内执行。
  2. 有空让进:如果没有进程在其临界区内执行且有进程希望进入临界区执行且有进程希望进入临界区,那么只有那些不在剩余区内执行的进程能参加决策,以选择谁下一个进入临界区,且这种选择不能无限推迟。
  3. 有限等待:在一个进程做出进入其临界区的请求到该请求被允许期间,其他进程被允许进入其临界区的次数存在上限。

假定每个进程的执行速度不为0。然而,不能对n个进程的相对速度作为任何假设。
在下面的小节中,会给出满足这三项要求的临界区问题的解答。这些解答不需要对任何硬件指令或硬件支持的处理器的数量做假设。然而,假定基本机器语言指令(如load、store和test)是原子执行的。即如果两个这样的指令并发执行,那么结果等同于它们按任何顺序来顺序执行。在这种情况下,如果load和store并发执行,那么load会得到旧值或新值 但却不能为两者的组合。
在描述算法时,只定义用于同步目的的变量,并只描述一种典型进程Pi,它具有图7.1 所示的通用结构。进入区和退出区被框起来以突出这些代码段的重要性。
在这里插入图片描述
图 7.1 典型进程Pi的通用结构

7.2.1 两进程解法

在这里,我们讨论同一时间用于两个进程的算法。两个进程标为P0 和P1。为了方便,当使用Pi时,用Pj来表示另一个进程;即j == 1 - i。

1. 算法1
第一种方法是让两个进程共享一个普通整数变量turn,其初值为0 (或 1)。如果turn == i,那么进程 Pi允许 在其临界区内执行。进程Pi 的结构如图7.2 所示。
在这里插入图片描述
图 7.2 算法1 中的进程Pi 的结构
这一解答确保了每个时刻只有一个进程处于临界区域。然而, 它并不满足前进要求,这是因为 它要求,这是因为它要求进程在临界区中执行时要严格交替。例如,如果turn = 0 且 P1 就绪要进入其临界区,那么尽管 P0 可能在其剩余区段, P1并不能这么做。
2. 算法 2
算法1 的问题是它没有保留每个进程状态的足够信息;它只记住了哪个进程能进入其临界区。为了解决这一问题,可以用下面的数组来替换变量turn:
boolean flag[2];
数组元素初始化 为false。如果 flag[i] 为true,那么该值表示Pi准备进入临界区。进程Pi的结构如图7.3 所示。
在这里插入图片描述
图 7.3 算法2 中的进程Pi的结构

在该算法中,进程Pi首先设置flag [i] 为真,以表示进程它准备进入其临界区。接着,Pi检查并验证进程Pj 没有准备进入其临界区。如果Pj 也已就绪,那么Pi会等待直到Pj表示它不再需要在其临界区内(即,直到flag[j]为假)。这时,Pi会进入临界区。在退出临界区时, Pi会设置flag[i]为假,以允许其他进程(如果等待)进入临界区。
这一解答满足了互斥要求,但遗憾的是没有满足前进要求。为了说明这个问题,考虑下面执行顺序:
T0 :P0 置 flag[0] = true
T1 :P1 置flag[1] = true
现在P0和P1 在各自的while内无穷循环。
该算法与两进程的精确时序有非常密切的关系。这种顺序可出现如下情况:多个处理器并发执行,或者当执行T0 时马上产生了中断(如定时器中断)且CPU从一个进程切换到另一个进程。
注意切换设置flag[i] 与检查 flag[j]的值的语句顺序并不能解决问题。事实上,可能出现这样的情况:两个进程可能同时出现在其临界区内,违反了互斥要求。

3. 算法 3
通过结合算法1 和算法2 的关键思想,得到了临界区问题的一个正确解答,以满足三个要求。进程共享两个变量:
boolean falg[2];
int turn;
初时,flag[0] = flag[1] = false,turn的值无关紧要(但为0 或1)。进程Pi的结构如图7.4所示。
在这里插入图片描述
图7.4 算法3中的进程Pi的结构
为了进入临界区,进程Pi首先设置flag[i]的值为true,且设置turn的值为j,从而表面如果另一个进程希望进入临界区,那么它能进入。如果两个进程同时试图进入,那么turn会几乎在同时设置成 i 和 j。 只有一个赋值语句的结果会保持,另一个也会发生,但会立即被重写。最终turn 值决定了哪个进程能被允许先进入其临界区。
现在证明这一解答是正确的。需要证明

  1. 互斥成立。
  2. 前进要求满足。
  3. 有限等待要求满足。
    为了证明第一点,注意到只有当 flag[j] = false 或者 turn = i 时,进程Pi才进入其临界区。而且,注意到如果两个进程同时在其临界区内执行,那么flag[0] = flag[1] = true.这两点意味着 P0 和P1 不可能成功地同时执行它们的while 语句,因为turn 的值只能为 0 或 1,而不可能同时为两个值。因此, 只有一个进程如Pj,能成功地执行完while语句,而进程Pi至少 必须执行一个附加的语句(“turn == j”)。而且,由于只要Pj 在其临界区内,flag[j] == true 和 true == j 就同时成立。结果是:互斥被满足。

为了证明第2点 和第3点,注意到只要条件 flag[j] == true 和turn == j 成立,进程Pi陷入在 while的循环语句,那么 Pi就能被阻止进入临界区。如果Pj不准备进入临界区,那么flag[j] == false, Pi 能进入临界区。如果 Pj 已设置flag[j] 为 true且也 在其while语句 中执行, 那么 turn == i或 turn == j 。 如果 turn == i,那么Pi 进入临界区。如果 turn == j,那么 Pj 将进入临界区。然而, 当Pj退出临界区,它会设置 flag[j] 为 false,以允许Pi 进入临界区。如果Pj 重新设置 flag[j] 为 true,那么它也必须设置 turn 为i。因此,由于进程 Pi 执行while 语句时并不改变变量turn的值,所以Pi会进入临界区(前进),且Pi最多在Pj进入临界区一次后就能进入(有限等待)。

7.2.2 多进程解法

已经证明了算法3能解决两个进程的临界区问题。现在,研究一个解决n个进程的临界区问题的算法。该算法称为面包店算法,它的原型是一种用于面包店、冰淇淋点、熟食店、摩托车登记处等必须在混乱中找到顺序的场合中的调度算法。该算法为分布式环境而开发,但是这里只关心与集中式环境有关的算法特性。
在进入商店时,每个客户接收一个号码。具有最小号码的客户先得到服务。然而,面包店算法不能保证两个进程(客户)不会收到同样的号码。在出现相同号码时,具有最小名称的进程先得到服务。即,如果Pi和Pj 收到同样的号码,且 i < j, 那么Pi 先得到 服务。由于进程名称唯一且完全排序,所以这个算法是完全确定的。
共同的数据结构是
boolean choosing[n] ;
int number[n];
初时 ,这些数据结构分别初始化为 false 和 0。为了方便,定义如下符号:

  • 若 a< c, (a, b)<(c,d), 或若 a == c 和 b < d均成立时,情况亦然。
  • max(a0, …, an-1)是数k,从而k >= ai, 对i == 0, …, n - 1成立。
    采用面包店算法,进程Pi的结构如图7.5 所示:
    在这里插入图片描述
    为了证明面包店算法正确,需要首先证明:如果Pi在其临界区内,且 Pk(k!= i)已经选择了number[k] != 0, 那么(number[i], i) < (number[k], k)。
    有了这一结果,证明互斥满足就简单了。事实上, 考虑Pi在临界区内切Pk试图进入 Pk临界区。当进程Pk执行 第二条while 语句时, j ==i,它会发现
  • number[i] != 0
  • (number[i] ,i) < (number[k], k)
    因此, 它会继续在while语句内循环,知道Pi离开其临界区。
    如果希望证明前进和有限等待要求得以满足,且算法确保公平,那么只要证明进程按照FCFS 基本原则进入临界区就足够了。
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值