与高级语言不同的 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的区别

c语言的用途及与其他高级语言的异同?

阶段1 语言基础+高级_1-3-Java语言高级_02-继承与多态_第2节 抽象类_14-抽象的概念

学习shell第三天

python 第一天之高级语言与低级语言的爱恨情仇

C语言和其他高级语言的最大的区别是啥