与高级语言不同的 CPUID 使用
Posted
技术标签:
【中文标题】与高级语言不同的 CPUID 使用【英文标题】:Differing CPUID usage from high-level languages 【发布时间】:2016-07-20 15:24:31 【问题描述】:我正在尝试使用需要特定处理器架构的 x86 ASM 功能。我了解调用“CPUID 标准函数01H”后需要检查特定位。以下是来自CPUID Wikipedia 页面的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 \n\t" /* save %rbx */
#else
"pushl %%ebx \n\t" /* save %ebx */
#endif
"cpuid \n\t"
"movl %%ebx ,%[ebx] \n\t" /* write the result into output var */
#if defined(__x86_64__) || defined(_M_AMD64) || defined (_M_X64)
"popq %%rbx \n\t"
#else
"popl %%ebx \n\t"
#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 kernel 使用下面的函数:
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));
哪个更好?其他它们本质上是等价的?
【问题讨论】:
内联 asm 已经是 gcc 特定的,因此您不妨改用 compiler builtin。其他编译器也有类似的内置函数。 @Jester 哇,太棒了。谢谢!! 我不确定 'builtin' 在这里是不是正确的术语,因为它通常是指编译器本身的内置函数。 cpuid.h 只是创建一个定义,它是内联汇编。此外,与上面的native_cpuid
例程不同,__cpuid
定义不允许您输入 ecx。
【参考方案1】:
正如 Jester 所说,在 GNU C 中,cpuid.h 包装器内部函数可能是您最好的选择。
还有__builtin_cpu_supports("popcnt")
或"avx"
之类的,在您调用__builtin_cpu_init()
后有效。但是,仅支持真正的主要功能位。例如,文档没有提到 rdrand 的功能位,所以 __builtin_cpu_supports("rdrand")
可能不起作用。
自定义inline-assembly 版本:
Linux 的实现可以内联,不会浪费指令,而且看起来写得很好,所以没有理由使用其他任何东西。您很可能会收到关于无法满足"=b"
约束的投诉;如果是这样,请参阅下面的 clang 的 cpuid.h 的作用。 (但我认为这是没有必要的,而且是文档错误的结果)。
它实际上并不需要volatile
,但是,如果您将它用于产生的值而不是对管道的序列化效果:使用相同的输入运行 CPUID 将给出相同的结果,所以我们可以让优化器移动它或将它提升出循环。 (所以它运行的次数更少)。这可能没什么用,因为普通代码一开始不会在循环中使用它。
The source for clang's implementation of 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's mailing list post about it。
最重要的是,问题中的第一个版本(在main()
内)已损坏,因为它是clobbers the red-zone with push
。
要修复它,只需告诉编译器结果将在 ebx 中(带有"=b"
),并让它担心在函数的开始/结束时保存/恢复 ebx/rbx。
【讨论】:
非常感谢,Cordes 先生。我已经将汇编用于 atmega128,当然 C 用于 Linux 操作系统,但我从未真正将两者结合起来——这有点吓人哈哈我对cpuid.h
的担忧包括__get_cpuid()
不是便携,如果我理解正确的话。所以我一直在寻找更可靠的东西。非常感谢您的回答涉及我发布的两个解决方案;我学到了很多
@8protons:我认为cpuid.h
与 GNU C inline-asm 语法一样可移植。任何 GNU C 编译器(gcc、clang、icc、其他一些)都支持这两种编译器,否则不支持。实际上cpuid.h
可能是更新的,可能并不总是受支持,所以 IDK。
CPUID 显然不会是 portable,@8protons,因为它是特定于 x86 的。您的“可移植性”和“可靠性”究竟是什么?
@CodyGray 基本上我想使用 RDRAND() 但并非所有 CPU 都支持它。要检查当前 CPU 是否存在,请查看在调用 CPU 标准函数时是否设置了 ECX 中的第 30 位。 (如果未设置该位,我将使用 RDRAND 的替代方法)以上是关于与高级语言不同的 CPUID 使用的主要内容,如果未能解决你的问题,请参考以下文章
阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第2节 线程实现方式_13_Thread和Runnable的区别