IntelのCPUでは、CPUIDというアセンブラの命令を使用することで、CPUの情報を入手する事が出来ます。
ここでいうCPU情報というのは、CPUのシリーズや、対応している命令セット、キャッシュ容量、プロセッサシリアルNoなどが含まれます。
このCPUID命令、C言語で普通にプログラムしていてはコールする事が出来ませんが、インラインアセンブラを使用することで利用する事が可能です。
今回は、gccのインラインアセンブラである”__asm__”命令を使用して、CPUID情報を取得する方法を説明します。
C言語からCPUID命令をコールする
まずは、CPUID取得のベースになるインラインアセンブラ処理です。
void getCpuId( int param, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx ) { /* cpuid命令はeax(param)で指定した値に応じた情報を返す */ __asm__( "cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (param) ); } |
__asm__を使用して、cpuid命令をコールしています。
cpuid命令は、eaxレジスタにパラメータを渡すと、そのパラメータに応じた情報をeax~edxレジスタにセットしてくれるという仕組みです。
今回は、C言語の関数であるgetCpuId()関数の第1引数にパラメータをセットすると、第2~5引数にレジスタ値が返されるようにしました。
ちなみにこのコード、gccによるアセンブラ出力は以下のようになりました。
※アセンブラ出力はgccに-Sオプションを指定する事で確認できます。
インラインアセンブラの記述は、ちゃんと効いているみたいですね。
.globl _getCpuId .def _getCpuId; .scl 2; .type 32; .endef _getCpuId: pushl %ebp movl %esp, %ebp pushl %esi pushl %ebx movl 8(%ebp), %eax /APP # 74 "cpuid.c" 1 cpuid # 0 "" 2 /NO_APP movl 12(%ebp), %esi movl %eax, (%esi) movl 16(%ebp), %eax movl %ebx, (%eax) movl 20(%ebp), %eax movl %ecx, (%eax) movl 24(%ebp), %eax movl %edx, (%eax) popl %ebx popl %esi popl %ebp ret |
はじめて読むPentium マシン語入門編
CPUID命令に渡すパラメータ
次は、CPUID命令がサポートしているパラメータの情報のセットについてです。
これは、Intelのサイトで公開されている、アセンブラの命令セット・リファレンスに載っています。
IA-32 インテルR アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル、中巻 A: 命令セット・リファレンス A-M

以下にドキュメントよりCPUIDのパラメータ情報を抜粋したものを引用します(pdfのp170辺りです)
※画像はクリックで拡大します



表の左端にある値がパラメータです。
CPUID命令を呼び出してみる
沢山の情報が入手できますが、今回は比較的確認が容易な”プロセッサブランドストリング”を取得してみる事にします。
プロセッサブランドストリングは、eaxレジスタに,80000001H~80000004Hの値を順に設定していく事で取得可能です。但し、全てのプロセッサでこの情報が取得可能とは限らないので、始めに80000000Hパラメータで、対応状況を見る必要があります。
この処理を、先ほど作った関数を利用してC言語でコーディングすると、以下のようになります。
void getProsessorBrandStr( char *outStr ) { unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; char buff[64]; memset( buff, 0x00, sizeof( buff ) ); getCpuId( 0x80000000, &eax, &ebx, &ecx, &edx ); if ( eax < 0x80000004 ) { // cpuidによるプロセッサ名の取得に未対応 *outStr = '\0'; return; } getCpuId( 0x80000002, &eax, &ebx, &ecx, &edx ); sprintf( buff, "%4.4s%4.4s%4.4s%4.4s", &eax, &ebx, &ecx, &edx ); strcat( outStr, buff ); getCpuId( 0x80000003, &eax, &ebx, &ecx, &edx ); sprintf( buff, "%4.4s%4.4s%4.4s%4.4s", &eax, &ebx, &ecx, &edx ); strcat( outStr, buff ); getCpuId( 0x80000004, &eax, &ebx, &ecx, &edx ); sprintf( buff, "%4.4s%4.4s%4.4s%4.4s", &eax, &ebx, &ecx, &edx ); strcat( outStr, buff ); } |
この関数の呼び元は、以下のような感じになります。
int main() { char outStr[64]; // initialize memset( outStr, 0x00, sizeof( outStr ) ); // CPUIDでCPUのブランド文字列を取得する getProsessorBrandStr( outStr ); printf( "-- brand string --\n" ); printf( "%s\n", outStr ); } |
手元の環境で実行してみたところ、以下のような出力になりました。
どうやら、上手くいったようです。
-- brand string -- Intel(R) Core(TM) i7 CPU L 640 @ 2.13 GHz |
以下が、今回作成したプログラムの全文です。
#include <stdio.h> #include <string.h> void getCpuId( int param, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx ); void getProsessorBrandStr( char *outStr ); int main() { char outStr[64]; unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; // initialize memset( outStr, 0x00, sizeof( outStr ) ); // CPUIDのサポートレベルを取得する getCpuId( 0x80000000, &eax, &ebx, &ecx, &edx ); printf( "cpuidサポートレベル: eax=%x\n", eax ); // CPUIDでCPUのブランド文字列を取得する getProsessorBrandStr( outStr ); printf( "-- brand string --\n" ); printf( "%s\n", outStr ); } void getProsessorBrandStr( char *outStr ) { unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; char buff[64]; memset( buff, 0x00, sizeof( buff ) ); getCpuId( 0x80000000, &eax, &ebx, &ecx, &edx ); if ( eax < 0x80000004 ) { // cpuidによるプロセッサ名の取得に未対応 *outStr = '\0'; return; } getCpuId( 0x80000002, &eax, &ebx, &ecx, &edx ); sprintf( buff, "%4.4s%4.4s%4.4s%4.4s", &eax, &ebx, &ecx, &edx ); strcat( outStr, buff ); getCpuId( 0x80000003, &eax, &ebx, &ecx, &edx ); sprintf( buff, "%4.4s%4.4s%4.4s%4.4s", &eax, &ebx, &ecx, &edx ); strcat( outStr, buff ); getCpuId( 0x80000004, &eax, &ebx, &ecx, &edx ); sprintf( buff, "%4.4s%4.4s%4.4s%4.4s", &eax, &ebx, &ecx, &edx ); strcat( outStr, buff ); } void getCpuId( int param, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx ) { /* cpuid命令はeax(param)で指定した値に応じた情報を返す */ __asm__( "cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (param) ); } |
今回は、gccでインラインアセンブラを使用する事により、CPUID命令を発行する事で、プロセッサの情報を取得する方ほうを説明しました。例で取得したのはプロセッサのブランド名称だけでしたが、他にも命令セットの情報や、自分のPCがx64に対応しているか、プロセッサのシリアル値など様々な情報が入手可能です。
恋するCUPID
関連記事
コメントを残す