我无法找到关于我应该如何编写内联asm的简洁解释,以及同时使用包含{foo
函数可能产生的问题是什么? 1}}代码。
我看到的问题是asm
中的寄存器是唯一命名的,因此1个名称严格依赖于cpu的一个非常精确的部分,如果你正在编写1个应该同时运行的代码,因为你不能简单地使用相同名称的额外寄存器。
另一个问题是asm
并没有真正使用调用约定,只是调用寄存器和/或值,有时调用寄存器意味着在另一个寄存器上执行 silent 操作甚至没有在你的代码中明确显示;所以我甚至不能指望我的C / C ++函数asm
将被打包并密封在它自己的堆栈中,如果它包含foo
代码。
现在使用asm
调用extended asm
我基本上可以声明输入和输出的位置,因此每个函数都可以使用自己的参数“as registers”,以及模式如下
gcc
假设我现在的主要目标是数学运算,并且我的函数只能提供某种功能并执行一些计算(没有内部锁定),那么扩展asm对并发性有好处吗?我应该如何设计一个应该由并发应用程序使用的asm?
现在我正在使用 asm ( assembler template
: output
: input
: registers
);
,但我想要一个关于我应该给这种代码片段的一般gcc
设计的通用答案。
答案 0 :(得分:2)
您似乎误解了线程实际上是什么。我们先考虑一个单处理器系统。线程不会实际并发运行,因为只有一个单元可以成功解码并执行它们。您的操作系统只是通过在其中使用调度来创建运行多个线程(以及进程)的错觉:每个线程或进程被分配一定的时间来在处理器上执行。
这就是为什么在执行线程时,它们不会覆盖彼此的寄存器。当切换当前执行的线程或进程时,操作系统要求处理器执行称为上下文切换的操作。简而言之,处理器在执行上一个任务/线程/进程时将其状态保存到某个由OS控制的内存区域。新任务/线程/进程从先前存储的状态恢复其上下文并继续执行。当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
自动别名为asm
变量。您可以对在输入/输出列表中指定的每个变量执行此操作。正如您所见,它们从零开始编号。
我真的不记得使用未编码为操作数的寄存器的指令,所以我现在不能给你一个例子。在这种情况下,您需要将它们放在clobber列表(最后一个)上。我非常确定您可以参考this了解更多信息。
我也无法回答有关“一般{{1}}设计”的任何内容,因为这是一个非标准的扩展,因此在编译器之间有所不同。例如,64位Visual Studio编译器根本不支持它。