硬件同步


在硬件同步中,我们探索了更多解决临界区问题的方案,这些方案使用了从基于硬件到基于软件的API等多种技术,这些API可供应用程序程序员使用。这些解决方案基于锁定的前提;但是,此类锁的设计可能非常复杂。

这些硬件特性可以简化任何编程任务并提高系统效率。在这里,我们介绍一些许多系统上都可用的简单硬件指令,并展示如何有效地使用它们来解决临界区问题。如果我们能够防止在修改共享变量时发生中断,则可以在单处理器环境中简单地解决临界区问题。通过这种方式,我们可以确保当前指令序列能够按顺序执行而不会被抢占。不会执行其他指令,因此不会对共享变量进行意外修改。非抢占式内核采用的就是这种方法。但不幸的是,这种解决方案在多处理器环境中并不那么可行。由于消息被传递到所有处理器,因此在多处理器上禁用中断可能非常耗时。

当此消息传递延迟进入每个临界区时,系统效率会降低。如果时钟由中断保持更新,则还会考虑对系统时钟的影响。因此,许多现代计算机系统都提供特殊的硬件指令,允许我们以原子方式测试和修改字的内容或交换两个字的内容——也就是说,作为一个不可中断的单元。我们可以使用这些特殊指令以相对简单的方式解决临界区问题。现在我们抽象出这些指令背后的主要概念。TestAndSet() 指令可以定义如下所示。

boolean test and set(boolean *target){
   boolean rv = *target;
   *target = true;
   return rv;
}

TestAndSet() 指令的定义。

其基本特征是此指令以原子方式执行。因此,如果同时执行两个 TestAndSet() 指令(每个指令在不同的 CPU 上),则它们将按某种任意顺序顺序执行。如果机器支持 TestAndSet() 指令,我们可以通过声明一个初始化为 false 的布尔变量 lock 来实现互斥。进程 P 的结构如下所示。

示例

do {
   while (test and set(&lock)) ;
   /* do nothing */
   /* critical section */
   lock = false;
   /* remainder section */
}
while (true);

使用 TestAndSet() 实现互斥。

与 TestAndSet() 指令相反,Swap() 指令操作于两个字的内容;它以原子方式执行。如果机器支持 Swap() 指令,则可以如下提供互斥。这里,声明一个全局布尔变量 lock 并将其初始化为 false。此外,每个进程都有一个局部布尔变量 key。进程 P 的结构如下图所示。

int compare_and_swap(int *val, int expected, int new val){
   int temp = *val;
   if (*val == expected)
   *val = new val;
   return temp;
}

Compare and Swap() 指令的定义。

do{
   while (compare_and_swap(&lock, 0, 1) != 0) ;
   /* do nothing */
   /* critical section */
   lock = 0;
   /* remainder section */
}
while (true);

使用 Compare and 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);

使用 TestAndSet() 实现有界等待互斥。

相同的数据结构是 boolean waiting[n]; boolean lock; 这些数据结构初始化为 false。为了证明满足互斥要求,我们注意到进程 P; 只有当 waiting[i] == false 或 key == false 时才能进入其临界区。只有当执行 TestAndSet() 时,key 的值才能变为 false。第一个执行 TestAndSet() 的进程将发现 key == false;所有其他进程都必须等待。只有当另一个进程离开其临界区时,变量 waiting[i] 才能变为 false;只有一个 waiting[i] 设置为 false,从而保持互斥要求。

为了证明满足进展要求,我们注意到为互斥提出的论点也适用于此,因为进程退出临界区要么将 lock 设置为 false,要么将 waiting[j] 设置为 false。两者都允许等待进入其临界区的进程继续执行。为了证明满足有界等待要求,我们注意到,当一个进程离开其临界区时,它会以循环顺序 (i + 1, i + 2,..., n - 1, 0, ..., i - 1) 扫描数组 waiting。它将此顺序中第一个处于入口段 (waiting[j] == true) 的进程指定为下一个进入临界区的进程。

任何等待进入其临界区的进程都将在 n - 1 个轮次内这样做。不幸的是,对于硬件设计人员而言,在多处理器上实现原子 TestAndSet() 指令并非易事。

更新于:2019年10月17日

14K+ 次浏览

启动您的职业生涯

完成课程获得认证

开始学习
广告