为什么MESI协议在x86上没有使用LOCK前缀无法保证CMPXCHG的原子性?

6

我知道MESI协议成功地保证了不同核心的内存(缓存)具有相同的视图。我的问题源于这样一个事实:在写入期间,MESI保证缓存专属于CPU,并且原子CMPXCHG只是在原子地比较和交换值。那么既然我们已经从MESI协议中得到了这个保证,为什么还需要使用LOCK指令来锁定缓存行呢?


7
由于缓存被访问两次(读取和写回结果),您不希望其他核心在这些操作之间进行干扰。 - LWimsey
4
如果你要原子地修改一个值,首先CPU需要将当前值从缓存加载到寄存器中,然后在寄存器内部进行修改,最后将其写回缓存。从外部看起来是原子的,但实际上并不是,因此需要加锁。 - LWimsey
所以从多线程视图,原子加是一个单一操作,在你提到的这些步骤之间不能有该线程的上下文切换,但是从MESI协议的角度来看,有三个操作,并且独占式缓存访问仅在需要写入时的最后一步才是必需的,因此我们需要在这三个步骤期间使用LOCK前缀锁定该缓存行(但从汇编的角度来看只有一个操作),这样对吗? - shota silagadze
4
可以说RMW是一次单独的操作,只要这个操作没有完成,就不会发生上下文切换。 但由于缓存被访问了两次并进行了修改,执行核心A需要对缓存行进行读写(MESI:“修改”或“独占”)。 如果没有锁定,另一个核心B上的RMW可能会请求相同的缓存行,然后MESI会将核心A上的缓存行标记为“无效”。 核心A可以(并且将)重新获取缓存行,但此时核心B可能已经对其进行了修改... - LWimsey
4
我不确定你从哪里得到了涉及3个MESI操作的信息,但不要过度思考MESI。它是一种确保内存视图一致性的协议。 锁是必需的,因为MESI在比RMW操作更低的层面上运行,并且MESI不知道核心需要两次不间断访问。在RMW操作期间(当锁处于活动状态时),MESI无法更改缓存行的状态。 - LWimsey
显示剩余2条评论
1个回答

7

原子CMPXCHG仅在比较和交换值时具有原子性

不,缓存访问硬件并没有将CMPXCHG实现为单周期固有原子操作。它是由多个uop合成的,这些uop分别进行加载和存储。

如果常规的CMPXCHG是这样工作的,那么您的推理就是正确的。但是对于其他核心上的观察者来说,常规的CMPXCHG是原子的。


lock cmpxchg 解码成多个 uops,从加载到存储保持缓存行“锁定”,将其转换为单个原子事务,就其他观察者而言。在系统中可以看到。(即延迟响应对该行的 MESI 无效或共享请求,直到存储提交)。它还使其成为完整的内存屏障。


没有加锁的情况下,CMPXCHG解码成多个uops,用于加载、检查相等性等操作,然后根据比较结果存储新值或不存储新值。 就原子性而言,它与add [mem], edx相同,后者在load和store uops之间使用ALU进行加法运算。即它不是原子性的,除了在同一个核心中针对中断(因为中断只能发生在指令边界处)。 加载和存储各自是独立的原子性操作,但它们不是单一的原子RMW事务。如果另一个核心使我们的缓存行失效,并在我们的加载和存储之间存储新值,则我们的存储将覆盖其他存储。而且那个其他存储将出现在我们的加载和存储之间的全局操作顺序中,违反了“原子”的定义=不可分割。

“因为中断只能在指令边界发生”,即使是针对“字符串”指令也是如此吗? - curiousguy
1
@curiousguy:我过于简化了,糟糕。每次rep-string指令的迭代都计为一个单独的可中断指令。聚集加载和分散存储也是可中断的,进度记录在掩码向量(或AVX512掩码寄存器)中。它们在实践中可能不会以这种方式实现,除了自动生成的异常之外。我认为没有其他的,上次讨论我们只能想到这些。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接