>我正在尝试利用需要某些处理器架构的x86 ASM函数。我知道在调用">CPUID 标准函数 01H"后需要检查特定位。下面是来自 CPUID 维基百科页面的 C 实现,用于调用 CPUID:
#include <stdio.h>
int main() {
int i;
unsigned int index = 0;
unsigned int regs[4];
int sum;
__asm__ __volatile__(
#if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64)
"pushq %%rbx nt" /* save %rbx */
#else
"pushl %%ebx nt" /* save %ebx */
#endif
"cpuid nt"
"movl %%ebx ,%[ebx] nt" /* write the result into output var */
#if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64)
"popq %%rbx nt"
#else
"popl %%ebx nt"
#endif
: "=a"(regs[0]), [ebx] "=r"(regs[1]), "=c"(regs[2]), "=d"(regs[3])
: "a"(index));
for (i=4; i<8; i++) {
printf("%c" ,((char *)regs)[i]);
}
for (i=12; i<16; i++) {
printf("%c" ,((char *)regs)[i]);
}
for (i=8; i<12; i++) {
printf("%c" ,((char *)regs)[i]);
}
printf("n");
}
虽然Linux内核使用以下功能:
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
/* ecx is often an input as well as an output. */
asm volatile("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax), "2" (*ecx));
}
哪一个更好?其他他们在情感上等同?
正如Jester所说,在GNU C中,cpuid.h包装器内部可能是你最好的选择。
<小时 />还有__builtin_cpu_supports("popcnt")
或"avx"
或其他什么,在您调用__builtin_cpu_init()
后有效。 不过,仅支持真正主要的功能位。 例如,文档没有提到 rdrand 的功能位,因此__builtin_cpu_supports("rdrand")
可能不起作用。
定制内联装配版本:
Linux 的实现可以内联,没有浪费的指令,而且看起来写得很好,所以没有理由使用其他任何东西。 您可能会收到有关无法满足"=b"
约束的投诉;如果是这样,请参阅下面的Clang的CPUID.h功能。 (但我认为这从来都不是必需的,并且是文档错误的结果(。
但是,如果您将其用于生成的值而不是对管道的序列化效果,它实际上并不需要volatile
:使用相同的输入运行 CPUID 将给出相同的结果,因此我们可以让优化器移动它或将其提升出循环。 (所以它运行次数更少(。 这可能没有帮助,因为普通代码首先不会在循环中使用它。
clang 实现 cpuid.h
的源代码做了一些奇怪的事情,比如保留%rbx
,因为显然某些 x86-64 环境可能无法满足使用 %rbx
作为输出操作数的约束? 评论很/* x86-64 uses %rbx as the base register, so preserve it. */
,但我不知道他们在说什么。 如果 SysV ABI 中的任何 x86-32 PIC 代码将 %ebx
用于固定目的(作为指向 GOT 的指针(,但我不知道 x86-64 有类似的东西。 也许该代码是由 ABI 文档中的错误引起的? 请参阅HJ Lu的邮件列表帖子。
最重要的是,问题中的第一个版本(main()
内部(被破坏了,因为它用push
破坏了红色区域。
要修复它,只需告诉编译器结果将在 ebx 中(带有 "=b"
(,并让它担心在函数的开始/结束时保存/恢复 ebx/rbx。