[gcc]CPUID命令を使用して、CPUの情報を取得する

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

関連記事

コメントを残す

メールアドレスが公開されることはありません。