并发使用中内联 asm 的设计元素

Posted

技术标签:

【中文标题】并发使用中内联 asm 的设计元素【英文标题】:Design elements for inline asm in concurrent usage 【发布时间】:2014-01-20 21:04:28 【问题描述】:

我找不到关于我应该如何编写一段内联 asm 的简洁解释,以及同时使用包含 asm 代码的 foo 函数可能出现的问题是什么在里面。

我看到的问题是,在asm 中,寄存器是唯一命名的,因此 1 个名称与您的 CPU 的一个非常精确的部分严格相关,如果您正在编写一段代码,那就是一个大问题应该同时运行,因为您不能简单地使用相同名称的额外寄存器。

另一个问题是asm 并没有真正使用调用约定,您只需调用寄存器和/或值,有时调用寄存器意味着对另一个寄存器执行 silent 操作'甚至没有在你的代码中明确显示;所以我什至不能指望我的 C/C++ 函数 foo 会在它包含 asm 代码的情况下被打包并密封在自己的堆栈中。

现在gcc 调用extended asm 我基本上可以声明输入和输出的去向,所以每个函数都可以使用自己的参数 "as registers" ,模式是关注

   asm ( assembler template 
       : output               
       : input               
       : registers 
       );

假设我现在的主要目标是数学运算,并且我的函数只应该提供某种功能并执行一些计算(没有内部锁),扩展 asm 是否有利于并发?我应该如何设计一个应该由并发应用程序使用的 asm?

目前我使用的是gcc,但我想要一个关于我应该为这种代码sn-ps 提供的一般asm 设计的通用答案。

【问题讨论】:

"1 名称与您的 cpu 的一个非常精确的部分严格相关,如果您正在编写一段应该同时运行的代码,这是一个大问题,因为您不能简单地额外的寄存器同名。” - 好吧,这显然是错误的。在编写多线程代码之前,请确保您了解什么是线程。 @DanielKamilKozar 你能解释一下吗?如果我在我的asm 代码中写%eax,我认为除了eax 寄存器是或包含什么之外,我不可能得到其他东西...... 每个线程都有自己的一组寄存器。你不必分享它们。每个线程也有自己的堆栈,您也不共享它。您共享的所有其他记忆。 @Fozi 您是否假设使用sequentially consistent 内存顺序?就像在 C++11 cplusplus.com/reference/atomic/memory_order 默认情况下一样? Mu 【参考方案1】:

您似乎误解了线程实际上是什么。让我们首先考虑一个单处理器系统。这些线程实际上不会同时运行,因为只有一个单元可以成功解码和执行它们。您的操作系统只是通过在其中使用调度来创建运行多个线程(和进程)的错觉:每个线程或进程都被分配了一定的时间在处理器上执行。

这就是为什么当线程被执行时,它们不会覆盖彼此的寄存器。当切换当前执行的线程或进程时,操作系统会要求处理器执行称为上下文切换的操作。简而言之,处理器在执行前一个任务/线程/进程时将其状态保存到由操作系统控制的某个内存区域中。新任务/线程/进程从先前存储的状态恢复其上下文并继续执行。当 CPU 上的这个任务/线程/进程的时间片到时,调度程序决定接下来要恢复哪个任务/线程/进程。时间片通常非常小,这就是为什么您会产生多个代码流同时运行的错觉。请记住,这是一个非常非常简化的描述:有关详细信息,请参阅 CPU 手册或操作系统书籍。

这种情况在多处理器系统上是类似的:唯一的例外是,可以执行指令的单元不止一个。对于多核处理器也是如此:每个内核都有自己的一组寄存器。基本内容保持不变 - 操作系统中的调度程序决定正在执行的代码是否实际上由一个处理器中的多个内核同时执行。

因此,您在这种情况下的担忧是无效的。然而,他们被提出是有非常正当的理由的。请记住,线程共享的唯一内容是主内存:每个线程都有自己的寄存器和堆栈。

让我回到关于 gcc 的扩展内联汇编的实际问题。编译器本身无法确定您编写的程序集修改了哪些寄存器。这就是为什么你需要指定它。但是,一条指令在您无法控制它的情况下修改寄存器是非常罕见的,而且它只发生在少量指令中——假设我们谈论的是 x86。此外,当您想从程序集中引用 C/C++ 变量时,gcc 可以自己计算出目标/源操作数。事实上,这是首选方法,因为它为编译器留出了更多优化空间。

考虑这段代码:

unsigned int get_cr0(void)

    unsigned int rc;
    __asm__ (
        "movl %%cr0, %0\n"
        : "=r"(rc)
        :
        :
    );
    return rc;

这个函数的目的是返回控制寄存器cr0的内容。这是一条特权指令,因此当您在用户模式下运行程序时,程序将无法运行,但这并不重要。看看我是如何将%0 放入指令中,然后在输出列表中指定"=r"(rc)。这意味着 %0 将被编译器自动别名为您的 rc 变量。您可以对输入/输出列表中指定的每个变量执行此操作。如您所见,它们从零开始编号。

我真的不记得使用未编码为操作数的寄存器的指令,所以我现在不能给你一个例子。在这种情况下,您需要将它们放在clobber 列表中(最后一个)。我很确定您可以参考this 了解更多信息。

我也无法回答有关“一般asm 设计”的任何问题,因为这是一个非标准扩展,因此在编译器之间会有所不同。例如,64 位 Visual Studio 编译器根本不支持它。

【讨论】:

超线程的 CPU 怎么样?在这种情况下,1 个核心运行 2 个逻辑线程。是什么阻止了 cpu 制造商创建一个同时使用寄存器的 CPU 呢?一个真正的解释是所谓的“上下文切换”是关于在线程之间移动时对寄存器进行“备份”。 具有超线程的 CPU 被视为 2 个逻辑核心,因为每个核心都维护自己的“架构状态”,正如英特尔在其手册中所定义的那样。这包括寄存器状态。 “并发”寄存器是不可以的,因为它们需要锁定,并且寄存器旨在成为可能的最快存储。这并不意味着它不可能实现:我只是不知道有什么架构可以真正做到这一点。 我们在这里所说的对 ARM 和 MIPS 也有效? 不,只有 x86。我对任何其他架构都不够熟悉(关于“并发寄存器”-所有其他东西都保持不变)。 ***.com/questions/21244104/…

以上是关于并发使用中内联 asm 的设计元素的主要内容,如果未能解决你的问题,请参考以下文章

内联 ASM:使用 MMX 在计时器上返回 NaN 秒

内联 asm 到 x64 - 理解

在 C++ 内联 asm 中使用基指针寄存器

内联 asm 缓冲区循环

在C ++内联asm中使用基指针寄存器

使用内联 ASM c++ 显示 640x480 BMP 图像