缓存一致性协议如何实现原子性?

时间:2014-08-17 01:07:39

标签: multithreading assembly x86 cpu atomic

我理解通过依赖缓存一致性协议(MESI / MESIF),可以在xsub()等操作上保证原子性,而不使用LOCK前缀。

1)缓存一致性协议如何做到这一点?

它让我想知道缓存一致性协议是否可以强制执行原子性,为什么我们需要特殊的原子类型/指令等?

2)如果MOSI跨多核系统实现原子指令,那么LOCK的目的是什么?遗留?

3)如果MOSI实现原子指令并且MOSI用于所有指令 - 那么为什么原子指令成本太高?当然,性能应该与普通指令相同。

2 个答案:

答案 0 :(得分:5)

x86中没有xsub指令,但有一个xadd;)

您应该阅读指令集参考中有关LOCK前缀的部分,以及 Software Developer&中的 8.1 LOCKED ATOMIC OPERATIONS 部分#39;手册第3A卷:系统编程指南,第1部分

单CPU 现在指的是单核,具有自己的缓存。当您有多个内核用于多个内核(物理上位于相同或不同的cpu芯片中)时,它们会使用一些缓存一致性协议。在MESI的情况下,执行原子指令的核心将首先确保它拥有包含操作数的高速缓存行,并将其标记为modified,另外将其锁定。如果另一个核心需要缓存行,它将执行读取操作,所有者核心将监听并延迟答案,直到原子操作完成。

在单CPU单核系统上,大多数指令在线程方面都是原子的,除了使用REP前缀的字符串指令,因为调度中断因此上下文切换只发生在指令边界上。但是,硬件设备可以观察到非原子行为。

答案 1 :(得分:4)

原子性和内存排序

对于一个原子操作,它必须看起来是对任何观察者的一个不可分割的操作。该观察者可以是任何可以看到操作效果的东西,无论是线程执行操作,同一处理器上的不同线程,不同处理器上的线程,还是系统中的某个组件或设备。无法看到操作效果的观察者,无论是相同的线程,不同的线程还是设备,都不会影响操作是否是原子操作。

(请注意,处理器我的意思是英特尔的文档称之为逻辑处理器。一个带有两个CPU插槽的系统,每个插槽都装有一个四核CPU,每个核心有两个逻辑处理器,总共16个处理器。)

一个相关但不同的概念是记忆排序。如果存储器访问对于观察者来说是按程序中出现的顺序发生的,则它们只是顺序一致的。当观察者与执行操作的线程相同时,此保证始终适用。其他更有限的内存排序保证是可能的。一个强大但不是顺序一致的排序可能保证相互之间订购许多种操作,但不是全部。弱内存排序无法保证访问对其他线程的访问方式。

编译器和原子

当您用C或其他更高级语言编写程序时,某些操作似乎是原子的并且按顺序排序,但编译器通常只在从执行这些操作的同一线程查看时才能保证这一点。但是,从编译器的角度来看,当线程异步中断时运行的任何代码都会发生在不同的执行线程中,即使该代码在同一个OS线程中运行也是如此。这意味着在信号处理程序或结构化异常处理程序中运行的代码不能保证在同一个线程中的处理程序之外执行的操作是原子的或顺序一致的。

由于有限的一般保证,编译器可以自由地执行诸如使用多个汇编器指令实现看起来像原子操作的事情,使得它们对其他观察者来说看起来是非原子的。编译器还可以重新排序内存访问,甚至完全删除明显冗余的访问。它可以在单个不间断的线程情况下做它想要的任何优化,程序仍然表现得好像它按程序顺序执行所有这些操作。

在多线程情况下,或者信号或异常处理程序存在的情况下,需要采取特殊步骤通知编译器您需要它的位置,以提供更广泛的原子性和内存排序保证。这就是特殊原子类型和功能的目的。即使CPU保证每条指令都是原子的,并且每个内存访问都是顺序地与所有其他线程保持一致,编译器也不会。

Intel CPU和Atomicity

Intel CPU使编译器可以轻松提供这些保证。除了一些奇怪的情况,指令是不间断的。任何导致执行指令的事件都会在指令完全完成后发生,或者允许指令恢复,就好像它从未被执行过一样。这意味着在机器代码级别,每个操作都是原子操作,并且每个内存操作都是顺序一致的,因为它似乎是在同一个处理器上运行的代码。在单处理器的情况下,除了需要对处理器以外的设备可见之外,不需要提供这些保证。在这种情况下,必须使用LOCK前缀与未缓存的内存区域相结合,以保证读取/修改/写入指令是原子的,并且内存访问按顺序与其他设备一致。

在访问高速缓存内存的多处理器情况下,高速缓存一致性协议提供大多数指令和强内存排序的原子性保证,但不提供顺序一致的排序。确切的机制是什么,这并不重要,只有保证是给出的。任何只访问单个内存位置的指令对其他处理器来说都是原子的。这里的排序保证太长了,英特尔用16个子弹点来描述它们,但它们显然是C和C ++提供的获取和释放内存顺序的保证。当指定了该级别的内存排序时,C / C ++原子操作可以使用普通的解锁指令。

当需要比缓存一致性协议提供更强的保证时,需要LOCK前缀以及隐含LOCK前缀的那些指令。如果您需要将read / modifiy / write指令设置为原子,则需要使用LOCK前缀。如果您需要按顺序一致的排序,则需要使用LOCK前缀。

LOCK前缀是原子操作的高成本来源。它使处理器等待所有先前的加载和存储操作完成。即使在访问高速缓存的内存时,LOCK前缀完全在高速缓存中处理而不断言LOCK#,处理器仍需要等待以确保操作按顺序与其他处理器一致。

摘要

总而言之,您的问题的答案是:

  1. 高速缓存一致性协议只能在从其他处理器查看时强制执行某些机器代码指令的原子性。它无法确保编译器为您希望成为原子的操作生成单个指令。它也不能保证指令看起来对系统上的非处理器设备是原子的。
  2. LOCK前缀用于机器代码指令
    • 执行多个内存访问,并且需要对其他处理器看起来是原子的
    • 需要与其他处理器顺序一致
    • 需要与其他非处理器设备原子和/或顺序一致。
  3. 如果可以在不使用LOCK前缀的情况下获得必要的原子性和内存排序保证,则使用的指令与普通指令相同,因此成本相同。如果需要LOCK前缀来提供必要的保证,则指令的成本会远高于正常指令。
相关问题