在同步硬件中,我们使用从硬件到应用程序程序员可用的基于软件的API的技术探索针对临界区问题的更多解决方案。这些解决方案基于锁定的前提;但是,这种锁的设计可能非常复杂。
这些硬件功能可以使任何编程任务更加轻松,并提高系统效率。在这里,我们介绍了一些可在许多系统上使用的简单硬件说明,并说明了如何有效地使用它们来解决临界区问题。如果我们可以防止在修改共享变量时发生中断。临界区问题可以简单地在单处理器环境中解决。以这种方式,我们将确保当前的指令序列将被允许按顺序执行而无需抢占。将不会运行其他指令,因此不会对共享变量进行意外修改。这是非抢先内核采用的方法。但是不幸的是,这种解决方案在多处理器环境中并不可行。
当此消息传递延迟进入每个关键部分时,系统效率会降低。如果时钟通过中断保持更新,那么也会考虑对系统时钟的影响。因此,许多现代计算机系统提供了特殊的硬件指令,这些指令使我们能够测试和修改单词的内容,或者原子地交换两个单词的内容,即作为一个不间断的单元。我们可以使用这些特殊说明以相对简单的方式解决关键部分的问题。现在,我们抽象出这些类型的指令背后的主要概念。 TestAndSet()
指令可以被定义为示于下面的代码。
boolean test and set(boolean *target){ boolean rv = *target; *target = true; return rv; }
测试和set()
说明的定义。
基本特征是该指令是原子执行的。因此,如果同时执行两条TestAndSet C)指令(每条指令在不同的CPU上),它们将以任意顺序顺序执行。如果机器支持TestAndSet()指令,我们可以通过声明一个初始化为false的布尔变量锁来实现互斥。过程P的结构如下所示。
do { while (test and set(&lock)) ; /* do nothing */ /* critical section */ lock = false; /* remainder section */ } while (true);
使用test和的互斥实现set()
。
与TestAndSet0指令相反,SwapO指令对两个字的内容进行操作;它是原子执行的。如果机器支持SwapO指令,则可以按以下方式提供互斥。在此,声明了全局布尔变量锁并将其初始化为false。此外,每个进程都有一个本地布尔变量键。过程P的结构如下图所示。
int compare_and_swap(int *val, int expected, int new val){ int temp = *val; if (*val == expected) *val = new val; return temp; }
比较和swap()
指令的定义。
do{ while (compare_and_swap(&lock, 0, 1) != 0) ; /* do nothing */ /* critical section */ lock = 0; /* remainder section */ } while (true);
具有比较和swap()
指令的互斥实现。
由于这些算法满足互斥要求,因此不满足有界等待条件。在下面的代码中,我们使用TestAndSet()
满足所有关键部分要求的指令介绍了另一种算法。
do{ waiting[i] = true; key = true; while (waiting[i] && key) key = test and set(&lock); waiting[i] = false; /* critical section */ j = (i + 1) % n; while ((j != i) && !waiting[j]) j = (j + 1) % n; if (j == i) lock = false; else waiting[j] = false; /* remainder section */ } while (true);
与test和的有界等待互斥set()
。
相同的数据结构是boolean wait [n]; 布尔锁 这些数据结构被初始化为false。为了证明满足互斥要求的观点,我们注意到过程P;仅当waiting [i] == false或key-false时,才能进入其关键部分。仅当TestAndSet()
执行键时,键的值才能变为false 。第一个执行TestAndSet()
will的过程将找到key == false; 其他所有人必须等待。仅当另一个进程离开其临界区时,变量waiting [i]才可能变为false。只有一个等待[i]设置为false,才能满足互斥要求。
为了证明满足进度要求这一点,我们注意到为相互排斥而提出的论点在这里也适用,因为退出关键部分的进程将锁设置为false或将waiting [j]设置为false。它们都允许等待进入其关键部分的过程继续进行。为了证明满足有界等待条件这一点,我们注意到,当进程离开其临界区时,它将以循环顺序(z'+ 1,i + 2,...,n- 1,0,...,i — 1)。它按此顺序将进入部分(等待[j] =-true)中的第一个过程指定为进入关键部分的下一个过程。
任何等待进入其临界区的过程都将在n-1圈内完成。不幸的是,对于硬件设计人员而言,TestAndSet()
在多处理器上实现原子指令并不是一件容易的事。