当前位置: 首页 > 知识库问答 >
问题:

c如何仅使用MOV在x86上实现发布和获取?

傅献
2023-03-14

这个问题是对此的后续/澄清:

MOV x86指令是否实现了C 11memory_order_release原子存储?

这表明MOV汇编指令足以在x86上执行acquire release语义。我们不需要锁、Geofence或xchg等。然而,我很难理解这是如何工作的。

英特尔文件第3A卷第8章规定:

https://software.intel.com/sites/default/files/managed/7c/f1/253668-sdm-vol-3a.pdf

在单处理器(核心)系统中......

  • 读取不与其他读取重新排序。
  • 写入不与旧读取重新排序
  • 写入内存不会与其他写入一起重新排序,但以下例外:

但这是一个单一的核心。多核部分似乎没有提到如何施加荷载:

在多处理器系统中,以下订购原则适用:

  • 单个处理器使用与单处理器系统中相同的排序原则

那么,单靠MOV如何才能促进acquire的发布呢?

共有2个答案

景宏朗
2023-03-14

刷新acquire和release的语义(引用cppreference而不是标准,因为这是我手头的内容-标准更…详细,这里):

memory_order_acquire:具有此内存顺序的加载操作对受影响的内存位置执行获取操作:在此加载之前,当前线程中的任何读取或写入都不能重新排序。释放相同原子变量的其他线程中的所有写入在当前线程中都是可见的

memory\u order\u release:具有此内存顺序的存储操作执行释放操作:在此存储之后,当前线程中的任何读取或写入都不能重新排序。在获取相同原子变量的其他线程中,当前线程中的所有写操作都是可见的

这给了我们四个保证:

  • 获取排序:“在此加载之前,当前线程中的任何读取或写入都不能重新排序”
  • 释放排序:“当前线程中的任何读取或写入都不能在此存储后重新排序”
  • 获取-发布同步:
    • "释放相同原子变量的其他线程中的所有写入在当前线程中可见"
    • "当前线程中的所有写入在获取相同原子变量的其他线程中可见"

    审查保证:

    • 读取不与其他读取重新排序。
    • 写入不与旧读取重新排序
    • 写入内存不会与其他写入一起重新排序[...]
    • 单个处理器使用与单处理器系统相同的排序原则。

    这足以满足订购保证。

    对于获取顺序,考虑已经发生了对原子的读取:对于该线程,很明显,之前任何稍后的读取或写入迁移都将分别违反第一个或第二个要点。

    对于发布顺序,请考虑已发生原子写入:对于该线程,显然之前的任何读取或写入迁移都将分别违反第二个或第三个要点。

    剩下的唯一一件事是确保如果一个线程读取了一个已发布的存储,那么它将看到writer线程在此之前生成的所有其他加载。这就是需要其他多处理器保证的地方。

    • 单个处理器的写入被所有处理器以相同的顺序观察。

    这足以满足获取-释放同步。

    我们已经确定,当发布写入发生时,之前的所有其他写入也会发生。然后,此要点确保如果另一个线程读取已发布的写操作,它将读取写入程序在该点之前生成的所有写操作。(如果没有,那么它将观察到单处理器的写入顺序与单处理器不同,违反了要点。)

卢作人
2023-03-14

但这是一个单一的核心。多核部分似乎没有提到如何施加荷载:

该部分的第一个要点是关键:单个处理器使用与单处理器系统相同的排序原则。该语句的隐含部分是......当从缓存相干共享内存加载/存储时。即多处理器系统不会引入重新排序的新方法,它们只是意味着可能的观察者现在包括其他内核上的代码,而不仅仅是DMA/IO设备。

共享内存访问的重新排序模型是单核模型,即程序顺序a存储缓冲区=基本上是acq\U rel。实际上比acq\U rel略强,这很好。

唯一发生的重新排序是本地的,在每个CPU内核中。一旦存储变得全局可见,它就会同时对所有其他内核可见,并且在此之前对任何内核都不可见。(除了执行存储的内核,通过存储转发。)这就是为什么只有本地屏障足以恢复SC存储缓冲区模型之上的顺序一致性。(对于x86,只需mo_seq_cst在SC存储之后只需要mford,以在执行任何进一步加载之前耗尽存储缓冲区。mfordlocked指令(也是完整的障碍)不必打扰其他内核,只需让这个等待)。

要理解的一个关键点是,所有处理器都共享一致的内存共享视图(通过一致的缓存)。英特尔SDM第8章的顶部定义了以下背景:

这些多处理机制具有以下特点:

  • 为了保持系统内存一致性—当两个或多个处理器试图同时访问系统内存中的同一地址时,必须有一些通信机制或内存访问协议来提高数据一致性,并且在某些情况下,允许一个处理器临时锁定内存位置

第11章讨论了Intel 64和IA-32处理器的缓存机制和缓存一致性。

(CPU使用MESI的一些变体;Intel实际使用MESIF,AMD实际使用MOESI。)

同一章还包括一些帮助说明/定义内存模型的试金石测试。您引用的部分并不是内存模型的严格正式定义。但是8.2.3.2加载和存储都不使用类似操作重新排序一节显示加载不会随加载重新排序。另一节还显示LoadStore重新排序是被禁止的。Acq_rel基本上阻止了除StoreLoad之外的所有重新排序,这就是x86所做的。(https://preshing.com/20120913/acquire-and-release-semantics/和https://preshing.com/20120930/weak-vs-strong-memory-models/)

相关:

  • 如何在微体系结构上实现屏障/Geofence和获取、释放语义
  • x86 mfence和C内存屏障-询问为什么acq\U rel不需要任何屏障,但从不同的角度来看(想知道数据是如何对其他内核可见的)
  • memory\u order\u seq\u cst和memory\u order\u acq\u rel有何不同?(seq\u cst要求刷新存储缓冲区)
  • C11原子获取/释放和x86\U 64缺乏加载/存储一致性
  • 全局不可见加载指令程序顺序存储缓冲区与acq\U rel不完全相同,尤其是当您考虑仅部分重叠最近存储的加载时
  • x86 TSO:针对x86多处理器的严格且可用的程序员模型-针对x86的正式内存模型

一般来说,大多数较弱的内存硬件模型也只允许本地重新排序,因此屏障仍然只在CPU核心内的本地,只是使该核心(部分)等待某个条件。(例如,在存储缓冲区耗尽之前,x86 mfence会阻止后续加载和存储的执行。对于x86在每次内存操作之间强制执行的内容,其他ISA也会受益于轻量级的效率屏障,例如阻止加载和加载存储重新排序。)。https://preshing.com/20120930/weak-vs-strong-memory-models/)

一些ISA(现在只有PowerPC)允许存储在对所有内核可见之前对其他内核可见,从而允许IRIW重新排序。请注意,C中的mo\U acq\U rel允许IRIW重新排序;只有cst禁止使用。大多数硬件内存型号略强于ISO C,因此无法实现,因此所有内核都同意全局存储顺序。

 类似资料:
  • 问题内容: 我想像gdb一样获得类似输出。但是我想直接通过这样做。我的平台是Linux,x86;以及后来的x86_64。 现在,我只想从堆栈中读取返回地址,而无需转换为符号名称。 因此,对于测试程序,通过以下方式进行编译: 我将启动一个程序,并从一开始就连接到测试程序。然后,我将执行PTRACE_CONT并等待信号。当测试程序会做一次自杀;信号将被传送到我的程序。这时我想读取返回地址,它们就像(因

  • 我正在阅读《实用恶意软件分析》一书,其中出现了以下示例代码: 作者接着说: 返回的COM对象将存储在堆栈中IDA Pro标记为ppv的变量中,如图所示。 我的问题是,这是为什么?既然我们做了一个mov eax,[esp 24h ppv],这难道不是将[esp 24h ppv]内部的数据移动到eax并覆盖返回值,而不是将返回值存储在变量中吗?我认为在Intel格式中,mov操作数1、操作数2总是将第

  • 我正在上尝试在发布模式下构建我的项目。 在调试模式下一切正常,但是在发布模式下会出现以下错误... 问题是异常消息不显示发生错误的类和方法的名称。 我在项目中使用的所有第三方库都是在中指定的。 如何解决此问题?

  • 我正在用winscard编写一个C代码。我注意到,如果我使用Scardtransmit发送命令,其中只发送数据或只接收数据,则没有问题。我可以发送数据或得到正确的回复。然而,当命令同时发送数据并期望响应时,我总是得到61xx。我知道错误代码61xx表示有一个xx字节的响应,其中Le不正确,并检查了所有可能的Le,包括返回值xx,但没有任何变化。例如,让Apdu的形式为CLA INS P1 P2 L

  • 我想使用亚马逊 SNS 服务向Android设备发送推送通知。 但正如亚马逊官方网站上列出的: 您可以使用以下受支持的推送通知服务之一向移动设备和台式机发送推送通知消息: 亚马逊设备消息(ADM) 适用于iOS和Mac OS X的Apple推送通知服务(APNS) 百度云推送(百度) 适用于Android的谷歌云消息传递(GCM) 适用于Windows Phone的Microsoft推送通知服务(

  • 我只是在学习ASM/x86,请耐心听我说。 我在我正在检查的程序中注意到以下内容,我认为它正在向被调用的函数传递一个参数: 据我所知,这似乎是将堆栈顶部的第二个字节设置为值。 这是否有效地将参数5传递给函数? 它是否类似于中的以下内容: 如果将单个参数5传递给函数,为什么将其设置为第二个字节(-04),而不是堆栈顶部?堆栈顶部是什么?我对这一切的解释都错了吗? 编辑函数顶部是设置ebp的位置: 这