cpuid で CPU の詳細な情報を得る

eax に適当な値を入れて cpuid することで、ebx, ecx, edx に CPU の詳細な情報がセットされる。


まずは一体いくつまで cpuid できるかを調べる。
これは eax = 0 として cpuid した後の eax を見ればわかるようだ。
このとき、ebx, ecx, edx には、ebx:edx:ecx の順に VendorID が ASCII 文字列としてセットされている。
いちいちインラインアセンブラを書くのは面倒だから、以下のような関数を用意しておくと便利。

typedef struct {
  uint32_t a, b, c, d;
} Regs;

void cpuid(uint32_t eax, Regs *r)
{
  asm volatile (
      "pushl %%ebx\n\t"
      "cpuid\n\t"
      "movl %%ebx, %%esi\n\t"
      "popl %%ebx"
      : "=a"(r->a), "=S"(r->b), "=c"(r->c), "=d"(r->d)
      : "a"(eax)
  );
}

ホントは ebx をそのまま使おうとしたんだけど、

can't find a register in class `BREG' while reloading `asm'

と言われたので、ebx は保存しておいて適当に esi を使っておいた。何故使えないのかよくわからない。


そうしてから、

Regs r;
char buf[13];
cpuid(0, &r);
printf("Largest standard function number supported: %d\n", r.a);
memcpy(buf, &r.b, 4);
memcpy(buf+4, &r.d, 4);
memcpy(buf+8, &r.c, 4);
buf[12] = '\0';
printf("VendorID: %s\n", buf);

のように使う。
俺の環境での結果は

Largest standard function number supported: 10
VendorID: GenuineIntel

だった。VendorID はベンダによって当然異なる。例えば AMD なら AuthenticAMD といった具合に。


standard function の他に、extended function がある。
eax にセットする番号は 0x80000000 から始まっている。
これは、元々は AMD が始めたため、AMDIntel と番号が被らないようにしたかららしい。
まずはいくつまで使えるか調べる。

Regs r;
cpuid(0x80000000, &r);
printf("Largest extended function number supported: %d\n, r.a - 0x80000000);

俺の環境での結果は

Largest extended function number supported: 8

だった。


eax = 0x80000002, 0x80000003, 0x80000004 として cpuid をすると プロセッサ名が eax:ebx:ecx:edx の順に ASCII 文字列で返ってくる。
一番最後の 48byte 目は NUL('\0') であることが保証されている。

Regs r;
char buf[48];
cpuid(0x80000002, &r);
memcpy(buf, &r, 16);
cpuid(0x80000003, &r);
memcpy(buf+16, &r, 16);
cpuid(0x80000004, &r, 16);
memcpy(buf+32, &r, 16);
puts(buf);

memcpy の部分が環境依存というか処理系依存な気もするけど、きっと動く。
うまく動かなかったら地道に 4byte ずつ memcpy すればいいさ。
俺の環境での結果は

Intel(R) Core(TM)2 Duo CPU     T8300  @ 2.40GHz

だった。
ま、これくらいの情報なら、システムプロファイラから見ることができるから、そんなに意味はないね。


もうちょっと意味ありそうなものとして、自分の CPU がどれくらいまでの SIMD を使えるか調べるコードを一応載せておく。

Regs r;
cpuid(1, &r);
printf("MMX: %s\n", r.d & 1<<23 ? "OK" : "NONE");
printf("SSE: %s\n", r.d & 1<<25 ? "OK" : "NONE");
printf("SSE2: %s\n", r.d & 1<<26 ? "OK" : "NONE");
printf("SSE3: %s\n", r.c & 1 ? "OK" : "NONE");
printf("SSE4.1: %s\n", r.c & 1<<19 ? "OK" : "NONE");
printf("SSE4.2: %s\n", r.c & 1<<20 ? "OK" : "NONE");

俺の環境での結果は

MMX: OK
SSE: OK
SSE2: OK
SSE3: OK
SSE4.1: OK
SSE4.2: NONE

だった。


cpuid で得られる情報はこんなもんじゃないので、詳しくは Intel のホームページとかを見て下さい。

参考

日本語の PDF。下の英語のと比べて多少新しい気がする。例えば eax = 0x80000001 のときの edx の 29bit目(Intel® 64 Instruction Set Architecture) とか。
AP-485 インテル® プロセッサーの識別と CPUID 命令
英語の PDF Intel(R) Processor Idientification and the CPUID Instruction
わかりやすく書いてあったので載せておきます。 CPUID (命令) - 通信用語の基礎知識