目录
一、进程同步
为保证多个进程能有条不紊地运行,在多道程序系统中,必须引入进程同步机制。单处理机系统中的进程同步机制有:硬件同步机制、信号量机制、管程机制等,利用它们可以保证程序执行的可再现性。// 保证程序执行结果的确定性
1、进程同步的基本概念
进程同步机制的主要任务,是对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按照一定的规则共享系统资源,并能很好地相互合作,从而使程序的执行具有可再现性。
(1)两种形式的制约关系
在多道程序环境下,对于同处于一个系统中的多个进程,由于它们共享系统中的资源或为完成某一任务而相互合作,它们之间可能存在着以下两种形式的制约关系:
1 - 间接相互制约关系
多个程序在并发执行时,由于共享系统资源,如 CPU、I/O 设备等,致使在这些并发执行的程序之间形成相互制约的关系。对于像打印机、磁带机这样的临界资源,必须保证多个进程对之只能互斥地访问,由此,在这些进程间形成了源于对该类资源共享的所谓间接相互制约关系。为了保证这些进程能有序地运行,对于系统中的这类资源,必须由系统实施统一分配,即用户在要使用之前,应先提出申请,而不允许用户进程直接使用。// 线程间无协作关系
2 - 直接相互制约关系
某些应用程序,为了完成某任务而建立了两个或多个进程。这些进程将为完成同一项任务而相互合作。进程间的直接制约关系就是源于它们之间的相互合作。例如,有两个相互合作的进程一输入进程 A 和计算进程 B,它们之间共享一个缓冲区。进程A 通过缓冲向进程 B 提供数据。进程 B 从缓冲中取出数据,并对数据进行处理。但如果该缓冲空时计算进程因不能获得所需数据而被阻塞。一旦进程 A 把数据输入缓冲区后便将进程 B 唤醒。反之,当缓冲区已满时,进程 A 因不能再向缓冲区投放数据而被阻塞,当进程 B 将缓冲区数据取走后便可唤醒 A。// 线程间相互协作
(2)临界资源(Critical Resouce)
许多硬件资源如打印机、 磁带机等,都属于临界资源,诸进程间应采取互斥方式,实现对这种资源的共享。// 即共享资源或者共享变量
(3)临界区(critical section)
不论是硬件临界资源还是软件临界资源,多个进程必须互斥地对它进行访问。我们把在每个进程中访问临界资源的那段代码称为临界区(critical section)。
显然,若能保证诸进程互斥地进入自己的临界区,便可实现诸进程对临界资源的互斥访问。为此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看它是否正被访问。如果此刻临界资源未被访问,进程便可进入临界区对该资源进行访问,并设置它正被访问的标志;如果此刻该临界资源正被某进程访问,则本进程不能进入临界区。
因此,必须在临界区前面增加一段用于进行上述检查的代码,把这段代码称为进入区(entry section)。相应地,在临界区后面也要加上一段称为退出区(exit section)的代码,用于将临界区正被访问的标志恢复为未被访问的标志。进程中除上述进入区、临界区及退出区之外的其它部分的代码在这里都称为剩余区。这样,可把一个访问临界资源的循环进程描述如下:
while(TURE)
{
进入区
临界区
退出区
剩余区
}
(4)同步机制应遵循的规则
为实现进程互斥地进入自己的临界区,可用软件方法,更多的是在系统中设置专门的同步机构来协调各进程间的运行。所有同步机制都应遵循下述四条准则:// 准则和重要,即标准
- 空闲让进。当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效地利用临界资源。
- 忙则等待。当已有进程进入临界区时,表明临界资源正在被访问,因而其它试图进入临界区的进程必须等待,以保证对临界资源的互斥访问。
- 有限等待。对要求访问临界资源的进程,应保证在有限时间内能进入自己的临界区,以免陷入“死等”状态。
- 让权等待。当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态。
// 其实就三个条件:空闲让进、忙则等待、有限等待
2、硬件同步机制
许多计算机提供了一些特殊的硬件指令,允许对一个字中的内容进行检测和修正,或者是对两个字的内容进行交换等。可利用这些特殊的指令来解决临界区问题。// 使用硬件指令
实际上,在对临界区进行管理时,可以将标志看做一个锁,“锁开”进入,“锁关”等待,初始时锁是打开的。每个要进入临界区的进程必须先对锁进行测试,当锁未开时,则必须等待,直至锁被打开。反之,当锁是打开的时候,则应立即把其锁上,以阻止其它进程进入临界区。显然,为防止多个进程同时测试到锁为打开的情况,测试和关锁操作必须是连续的,不允许分开进行。
(1)关中断
关中断是实现互斥的最简单的方法之一。在进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。这样,进程在临界区执行期间,计算机系统不响应中断,从而不会引发调度,也就不会发生进程或线程切换。由此,保证了对锁的测试和关锁操作的连续性和完整性,有效地保证了互斥。但是,关中断的方法存在许多缺点:// 局限性很大
- 滥用关中断权力可能导致严重后果。
- 关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力。
- 关中断方法也不适用于多CPU 系统,因为在一个处理器上关中断并不能防止进程在其它处理器上执行相同的临界段代码。
(2)利用 Test-and-Set 指令实现互斥
借助一条硬件指令:“测试并建立”指令 TS(Test-and-Set) 实现互斥。在许多计算机中都提供了这种指令。TS 指令的一般性描述如下:
boolean TS(boolean lock){
Boolean old;
old = lock;
lock = TRUE;
return old;
}
这条指令可以看作为一个函数过程,其执行过程是不可分割的,即是一条原语。其中,lock 有两种状态:当 lock = FALSE 时,表示该资源空闲;当 lock = TRUE 时,表示该资源正在被使用。
用TS 指令管理临界区时,为每个临界资源设置一个布尔变量 lock,由于变量 lock 代表了该资源的状态,故可把它看成一把锁。lock 初值为 FALSE,表示该临界资源空闲。进程在进入临界区之前,首先用 TS 指令测试 lock,如果其值为 FALSE,则表示没有进程在临界区内,可以进入,并将 TRUE 值赋予 lock,这等效于关闭了临界资源,使任何进程都不能进入临界区,否则必须循环测试直到 TS(s) 为 TRUE。// 其实就是让检测和设置 lock 的值成为一条原语
利用 TS 指实现斥的循环进程结构可描述如下:
do(
...
while TS(&lock); // 进入
critical section; // 临界区
lock = FALSE; // 退出
remainder section; // 剩余区
while(TRUE);
(3)利用 Swap 指令实现进程互斥
该指令称为对换指令,在 Intel 80x86 中又称为 XCHG 指令,用于交换两个字的内容。其处理过程描述如下:
void swap(boolean a, boolean b){
boolean temp;
temp = a;
a = b;
b = temp;
}
用对换指令可以简单有效地实现互斥,方法是为每个临界资源设置一个全局的布尔变量 lock,其初值为 false,在每个进程中再利用一个局部布尔变量 key。利用 Swap 指令实现进程互斥的循环进程可描述如下:// 交换过程是原子的,所以 key 的值不会在同一时刻不同进程出现不同的值
do {
key = TRUE;
do {
swap(&lock, &key);
} while (key != FALSE);
临界区操作;
lock = FALSE;
...
} while(TRUE);
利用上述硬件指令能有效地实现进程互斥,但当临界资源忙碌时,其它访问进程必须不断地进行测试,处于一种 “忙等” 状态,不符合 “让权等待” 的原则,造成处理机时间的浪费,同时也很难将它们用于解决复杂的进程同步问题。// 不能避免忙等问题
3、信号量机制
信号量机制已被广泛地应用于单处理机和多处理机系统以及计算机网络中。
(1)整型信号量
整型信号量为一个用于表示资源数目的整型量 S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation)wait(S) 和 signal(S) 来访问(即 P、V 操作)。wait 和 signal 操作可描述如下:// 等待和唤醒
wait(S){
while(S<=0); // 循环等待
S--;
}
signal(S){
S++;
}
wait(S) 和 signal(S) 是两个原子操作,它们在执行时是不可中断的。
(2)记录型信号量
在整型信号量机制中的 wait 操作,只要是信号量 S <= 0,就会不断地测试。因此,该机制并未遵循 “让权等待” 的准则,而是使进程处于 “忙等” 的状态。
记录型信号量机制则是一种不存在 “忙等” 现象的进程同步机制。但在采取了 “让权等待” 的策略后,又会出现多个进程等待访问同一临界资源的情况。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量 value 外,还应增加一个进程链表指针 list,用于链接上述的所有等待进程。记录型信号量是由于它采用了记录型的数据结构而得名的。// 信号量 + 阻塞队列
它所包含的上述两个数据项可描述如下:
typedef struct {
int value; // 信号量
struct 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++;
i