35

只是想知道哪些 CPU 架构支持比较和交换原子原语?

4

10 回答 10

12

Powerpc 有更强大的可用原语:“lwarx”和“stwcx”

lwarx 从内存中加载一个值,但会记住该位置。接触该位置的任何其他线程或 cpu 都会导致条件存储指令“stwcx”失败。

所以 lwarx /stwcx 组合允许您实现原子增量/减量、比较和交换,以及更强大的原子操作,如“原子增量循环缓冲区索引”

于 2008-09-30T05:15:13.557 回答
10

很抱歉写了很多信。:(

x86 ISA 中的几乎所有指令(除了所谓的字符串指令,可能还有少数其他指令),包括 CMPXCHG,在单核 CPU 的上下文中都是原子的。这是因为根据 x86 架构,CPU 在每条指令执行完成后检查到达的中断,而不是在中间。结果,可以检测到中断请求,并且仅在两个连续指令执行之间的边界处启动它的处理。因此,CPU 在执行单条指令期间采用的所有内存引用都是隔离的,并且不能被任何其他活动交错。这种行为在单核和多核 CPU 中很常见。但是如果在单核 CPU 的上下文中只有一个系统单元执行对内存的访问,在多核 CPU 的上下文中,系统中有多个单元同时执行对内存的访问。在这种环境下,指令隔离不足以保持一致性,因为不同 CPU 在同一时间进行的内存访问可以相互交错。由于这个额外的保护层必须应用于数据更改协议。对于 x86,这一层是锁定前缀,它在系统总线上启动原子事务。

在此处输入图像描述

总结:如果你保证这个指令访问的数据只能被一个内核访问,那么使用没有锁前缀的同步指令,如CMPXCHG、XADD、BTS等是安全且成本更低的。如果您对此不放心,请应用锁定前缀以通过权衡性能来提供安全性。

CPU 支持硬件同步有两种主要方法:

  1. 基于原子事务。
  2. 基于缓存一致性协议。

没有人是银弹。两种方法都有其优点和缺点。

基于原子事务的方法依赖于对内存总线上特殊类型事务的支持。在此类事务期间,只有一个连接到总线的代理(CPU 内核)有资格访问内存。结果,一方面,总线所有者在原子事务期间所做的所有内存引用都被确保作为单个不间断事务进行。另一方面,所有其他总线代理(CPU 内核)将被强制等待原子事务完成,以恢复访问内存的能力。他们想要访问哪些内存单元并不重要,即使他们想要访问在原子事务期间总线所有者未引用的内存区域也是如此。因此,大量使用以锁定为前缀的指令会显着降低系统速度。另一方面,由于总线仲裁器根据循环调度为每个总线代理授予对总线的访问权,因此可以保证每个总线代理对内存的访问相对公平,并且所有代理都能够取得进展和以同样的速度做到了。此外,在原子事务的情况下,ABA 问题也会发挥作用,因为原子事务本质上是非常短的(单个指令进行的内存引用很少),并且在事务期间对内存执行的所有操作仅依赖于内存区域的值,没有考虑到,内存区域是在两个事务之间被其他人访问的。基于原子事务的同步支持的一个很好的例子是 x86 架构,其中锁定前缀指令强制 CPU 在原子事务中执行它们。

基于缓存一致性协议的方法依赖于这样一个事实,即内存行只能在一个瞬间缓存在一个 L1 缓存中。缓存一致性系统中的内存访问协议类似于下一个动作序列:

  1. CPU A 将内存线 X 存储在 L1 高速缓存中。同时 CPU B 希望访问内存行 X。(X --> CPU A L1)
  2. CPU B 在总线上发出内存线 X 访问事务。(X --> CPU A L1)
  3. 所有总线代理(CPU 内核)都有一个所谓的监听代理,它监听总线上的所有事务并检查事务请求的内存线访问是否存储在其所有者 CPU L1 缓存中。因此,CPU A snooping agent 检测到 CPU A 拥有 CPU B 请求的内存线。 (X --> CPU A L1)
  4. CPU A 暂停 CPU B 发出的内存访问事务。 (X --> CPU A L1)
  5. CPU A 从其 L1 高速缓存中刷新 B 请求的内存行。(X --> 内存)
  6. CPU A 恢复先前暂停的事务。(X --> 内存)
  7. CPU B 从内存中取出内存行 X。(X --> CPU B L1)

由于该协议,CPU 内核始终访问内存中的实际数据,并且对内存的访问是按严格的顺序序列化的,一次访问是及时的。基于缓存一致性协议的同步支持依赖于这样一个事实,即 CPU 可以轻松检测到特定内存行在两个时间点之间被访问。在对必须打开事务的行 X 的第一次内存访问期间,CPU 可以标记 L1 缓存中的该内存行必须由 snooping agent 控制。反过来,侦听代理可以在缓存行刷新期间执行检查以识别该行是否被标记为控制,如果受控行被刷新,则引发内部标志。结果,如果 CPU 在关闭事务的内存访问期间检查内部标志,它将知道受控内存线是否能够被其他人更改并得出结论是事务必须成功完成或必须被视为失败。这就是 LL\SC 指令类的实现方式。这种方法比原子事务更简单,并且在同步方面提供了更大的灵活性,因为与原子事务方法相比,可以在其基础上构建更多数量的不同同步原语。这种方法更具可扩展性和效率,因为它不会阻止系统所有其他部分对内存的访问。正如你所看到的,它解决了 ABA 问题,因为它基于内存区域访问检测的事实,而不是内存区域变化检测的值。对参与正在进行的事务的内存区域的任何访问都将被视为事务失败。这可能是好是坏,因为特定算法可能只对内存区域的值感兴趣并且不考虑该位置被中间人访问过,直到该访问更改内存. 在这种情况下,读取中间的内存值将导致错误的否定交易失败。此外,这种方法会导致位于同一内存线上的控制流的性能大幅下降,因为它们能够不断地相互连接内存线,从而阻止彼此成功完成事务。这是一个非常重要的问题,因为在终端情况下它可以使系统处于活锁状态。基于缓存一致性协议的同步支持通常用于 RISC CPU,因为它简单且灵活。但必须注意的是,英特尔也决定在 x86 架构中支持这种方法来支持同步。去年,英特尔宣布了 x86 架构的事务同步扩展,将在 Haswell 一代英特尔处理器中实现。结果,看起来 x86 将拥有最强大的同步支持,并允许系统开发人员利用这两种方法的优势。去年,英特尔宣布了 x86 架构的事务同步扩展,将在 Haswell 一代英特尔处理器中实现。结果,看起来 x86 将拥有最强大的同步支持,并允许系统开发人员利用这两种方法的优势。去年,英特尔宣布了 x86 架构的事务同步扩展,将在 Haswell 一代英特尔处理器中实现。结果,看起来 x86 将拥有最强大的同步支持,并允许系统开发人员利用这两种方法的优势。

于 2013-04-29T15:59:24.033 回答
8

回答这个问题的另一种更简单的方法可能是列出不支持比较和交换的多处理器平台(或可用于编写的加载链接/存储条件)。

我知道的唯一一个是 PARISC,它只有一个原子明文指令。这可用于构造互斥体(前提是在 16 字节边界上对齐字)。此架构上没有 CAS(与 x86、ia64、ppc、sparc、mips、s390 不同,...)

于 2009-11-26T15:32:39.680 回答
7

一些人评论/询问了 cmpxchg 在 x86/x64 上是否需要“锁定”前缀。对于多核机器,答案是肯定的。对于没有锁的单核机器,该指令是完全原子的。

自从我深入研究这些东西以来已经有一段时间了,但我似乎记得该指令在技术上是可重新启动的 - 它可以在飞行中中止指令(如果它还没有任何副作用)以避免延迟中断处理太长。

于 2011-09-24T11:11:04.467 回答
5

Intel x86 有这种支持。IBM 在它的Solaris to Linux Porting Guide中给出了这个例子:

bool_t My_CompareAndSwap(IN int *ptr, IN int old, IN int new)
{
        unsigned char ret;

        /* Note that sete sets a 'byte' not the word */
        __asm__ __volatile__ (
                "  lock\n"
                "  cmpxchgl %2,%1\n"
                "  sete %0\n"
                : "=q" (ret), "=m" (*ptr)
                : "r" (new), "m" (*ptr), "a" (old)
                : "memory");

        return ret;
}
于 2008-09-30T04:51:58.967 回答
5

从 ARMv6 架构开始,ARM 具有可用于实现原子比较交换操作的 LDREX/STREX 指令。

于 2008-09-30T05:48:14.587 回答
4

只是为了完成列表,MIPS 具有加载链接 (ll) 和条件存储 (sc) 指令,它们从内存中加载一个值,然后如果没有其他 CPU 访问该位置,则有条件地存储。确实,您可以使用这些指令来执行交换、递增和其他操作。然而,缺点是大量 CPU 非常频繁地使用锁,你会陷入活锁:条件存储会经常失败,需要另一个循环再试一次,这会失败,等等。

如果这些情况被认为足够重要,需要担心的话,软件 mutex_lock 的实现可能会变得非常复杂,试图实现指数退避。在我使用 128 个内核工作的一个系统中,它们是。

于 2008-10-01T04:00:41.810 回答
3

x86 和 Itanium 有 CMPXCHG(比较和交换)

于 2008-09-30T04:49:46.100 回答
3

比较和交换于 1973 年被添加到 IBM 大型机中。它(以及比较双精度和交换)仍然在 IBM 大型机上(以及最近的多处理器功能,如 PLO - 执行锁定操作)。

于 2010-08-17T00:53:10.467 回答
2

Sparc v9 有一个 cas 指令。SPARC v9体系结构手册讨论了附录 J 中 CAS 指令的使用,具体参见示例 J.11 和 J.12。

我相信指令的名称实际上是“casa”,因为它可以访问当前地址空间或备用地址空间。“cas”是访问当前 ASI 的汇编器宏。

developer.sun.com上还有一篇文章讨论了 Sparc 处理器多年来实现的各种原子指令,包括 cas。

于 2008-09-30T04:46:22.613 回答