完成図
はじめに
この記事ではインテル系のCPUに実装されているCPUID
命令がどのように働いているかを考察します。
今回、デモプログラム(littleCPUID)を作成しました。このプログラムは実際に使用しているシステムからCPUID
命令を実行し、その結果を出力します。
対象読者
- CPUの働き、特に
CPUID
命令がどのように働くのか興味のある方 - Visual C++によるインラインアセンブラの実装に興味がある方
必要な環境
Visual C++ version 6 SP6(MFC)で開発を行っています。
実行環境としてはWindows 2000/XPを想定しています。現行ではWindows 9x系では実行出来ないはずです。これは2000/XPのみで使用されるAPIを使用しているためで、それらを明示的にLoadlibrary
すれば対応出来るのですが、面倒なのでやっていません。
CPUID(CPU Identification)
システムのプロパティ表示の不思議
下の [システムのプロパティ]画面を見てください。
コンピュータのCPU名が表示されているのですが、CPU名の頭に余計な空白が入っています。この現象はインテル社のPentium 4以降のCPUでのみで発生しており、AMD社のCPUでは発生していません。
実は、この空白の原因はCPUID
命令の動作に起因した現象と考えています。
CPUID(CPU Identification)命令とは
CPUID
命令(プロセッサ識別)は、CPUの基本的な命令コードであるMOV
命令(データ移動)やADD
命令(加算)、IN/OUT
命令(外部入出力)と同じくインテル系のCPUには必ず実装されている命令コードです。
CPUID
命令は主に以下の目的で使用されます。
- 新たに実装された新機能命令の有無
- CPUベンダーの判別
- プロセッサ・シリアル・ナンバー(PSN, Processor Serial Number)
CPUID
は、OSやソフトウェアがあらかじめ新機能が実装されているかを確認するために使用されます。CPUID
は各CPUベンダーのブランドを判別するために使用されます。CPUID
に署名やブランド名を実装することで、自社のブランドを誇示しています。近年、AMD社やインテル社は機能以外にも自社のCPUに独自のブランド番号(AMD社はモデルナンバー、インテル社はプロセッサ・ナンバ)を使用しています。CPUID
は正確なブランド番号を取得出来る仕組みが実装されています。CPUID
はかつてプロセッサ・シリアル・ナンバー(PSNと略)というCPU固有シリアル番号を返却するために使用されていました。CPUID命令の動作
インテルのサイトで配布されている日本語技術資料の「IA-32 インテル アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル 中巻」に以下の記述があります。
オペコード | 命令 | 説明 |
0F A2 | CPUID | EAX レジスタに最初に入力された値に応じて、プロセッサの識別情報と機能情報をEAX 、EBX 、ECX 、EDX の各レジスタに返す。 |
レジスタEAX
に所定のインデックス値を入れCPUID
命令を実行することでEAX
、EBX
、ECX
、EDX
に結果を出力します。
インデックス値に対して以下の値が返却されます。以下の説明でCPUID(xxx) と表現されていますが、これはEAX
=xxxを設定しCPUID
命令を実行した結果を指します。
CPUID情報
CPUID(2)以降は、Intelがほぼ占有状態のようです。
CPUID入力 | 出力 | 詳細 |
CPUID(0) | EAX | CPUIDの有効最大値 |
EBX :EDX :ECX | Vendor ID | |
CPUID(1) | EAX | プロセッサ・シグネチャ(Family/Model/Stepping...) |
EBX ,ECX ,EDX | Function Flags | |
CPUID(2) | EAX ,EBX ,ECX ,EDX | CPUキャッシュ情報 |
CPUID(3) | EAX ,EBX ,ECX ,EDX | PSN |
一般的に「Vendor ID」で製造CPU会社を判断し、「プロセッサ・シグネチャ」でPentium III/Pentium 4/AthlonなどのCPUの種類を判断します。CPUの種類でCPUの機能を判断してはならず(例えば「Pentium IIIなら全てMMXをサポートしているはずだ」という決め打ち)、「Function Flags」にてCPUの機能を検出し判断します。例えばCPUID(1)のEDX
の値の23bit目が立っている場合、MMXをサポートしていると判断します。
拡張CPUID情報
拡張CPUID
はAMD社が活用している領域です。Pentium 4よりIntelも活用し始めたようです。
CPUID入力 | 出力 | 詳細 |
CPUID(8000-0000h) | EAX | 拡張CPUIDの有効最大値 |
EBX :EDX :ECX | Reserved(Intel) | |
CPUID(8000-0001h) | EAX ,EBX ,ECX ,EDX | 拡張Function Flags |
CPUID(8000-0002h) | EAX ,EBX ,ECX ,EDX | プロセッサ・ブランド・ストリング |
CPUID(8000-0003h) | EAX ,EBX ,ECX ,EDX | プロセッサ・ブランド・ストリング(続き) |
CPUID(8000-0004h) | EAX ,EBX ,ECX ,EDX | プロセッサ・ブランド・ストリング(続き) |
CPUID命令の実際
実際にCPUID
命令を実行してみましょう。
実際に実行するにあたっては、Windowsに標準搭載されているdebug.exe互換である EXDEB というソフトウェアを使用します。コマンドプロンプトよりEXDEBを実行し、以下の手順にて動作確認をします。下記の例ではWindows 2000のコマンドプロンプト上で実行しましたが、Windows XPでも同様の結果が得られるはずです。
- r1コマンドの実行
- aコマンドの実行
- 「mov eax, 0[Enter]」(
EAX
レジスタに0を挿入します) - 「cpuid[Enter]」(
CPUID
命令を実行します) - 「int 3[Enter]」(実行を中断します)
- 「[Enter]」
- uコマンドの実行
- gコマンドの実行
- qコマンドの実行
EAX
, EBX
, ECX
, EDX
が変化していることが分かります。以下が上記の実行例になります。
D:\>exdeb[Enter] Extended Debugger Ver1.85 94,97/07 by Sey_ju_row CPU [Pentium II] FPU [Present] Machine [PC-98 DOS/V] -r1[Enter] EAX=000F:0000 EBX=0000:0000 ECX=0000:0000 EDX=6974:0000 ESP=0000:0000 EBP=0000:0000 ESI=0000:0000 EDI=0000:0000 DS=5F51 ES=0000 SS=5F51 FS=0000 GS=0000 CS=5F51 IP=0100 -N11--I--------- 5F51:0100 66B800000000 MOV EAX,00000000 -a[Enter] 5F51:0100 mov eax, 0[Enter] 5F51:0106 cpuid[Enter] 5F51:0108 int 3[Enter] 5F51:0109 [Enter] -u[Enter] 5F51:0100 66B800000000 MOV EAX,00000000 5F51:0106 0FA2 CPUID 5F51:0108 CC INT 3 5F51:0109 0000 ADD [BX+SI],AL 5F51:010B 0000 ADD [BX+SI],AL 5F51:010D 0000 ADD [BX+SI],AL 5F51:010F 0000 ADD [BX+SI],AL 5F51:0111 0000 ADD [BX+SI],AL -g[Enter] EAX=0000:0001 EBX=6874:7541 ECX=444D:4163 EDX=6974:6E65 ESP=0000:0000 EBP=0000:0000 ESI=0000:0000 EDI=0000:0000 DS=5F51 ES=0000 SS=5F51 FS=0000 GS=0000 CS=5F51 IP=0108 -N11--I--------- 5F51:0108 CC INT 3 -q[Enter] D:\>
[-gコマンド]の下に出力されているレジスタ値がCPUID(0) の出力結果となります。まとめると以下の通り。
EAX
= 0x1EBX
= 0x68747541 ("htuA")ECX
= 0x444D4163 ("DMAc")EDX
= 0x69746E65 ("itne")
EBX
:EDX
:ECX
をリトルエンディアンの順に並べると ASCII で「AuthenticAMD」となります。この文字列は、このCPUがAMD社にて開発されたことを示します。
一般的なソフトウェア開発では、エンディアンを意識する場面は少ないと思いますが、ハードウェアを直接制御する場合やネットワークを意識したプログラミングを行う場合には、エンディアンに対する意識が必要となります。
またCPUID(0)のEAX
が1であるということはCPUID(0)、CPUID(1)のみが有効であることを示しています。これは昔からAMD社のCPUは0x80000000以上のExtended CPUIDの値を中心に情報を返却する方針を取っているからのようです。余談ですが、後にインテル社もPentium 4辺りからExtended CPUIDをサポートすることになります。
以下は、同様にIntel社製のCPU(Pentium 4)で上記のプログラムを実行した結果です。
EAX
= 0x2EBX
= 0x756E6547 ("uneG")ECX
= 0x6C65746E ("letn")EDX
= 0x49656E69 ("Ieni")
EBX
:EDX
:ECX
をリトルエンディアンの順に並べるとASCIIで「GenuineIntel」となります。この文字列は、このCPUがインテル社にて開発されたことを示します。
またCPUID(0) のEAX
が2であるということはCPUID(0)、CPUID(1)、CPUID(2) が有効であることを示しています。
EAX
レジスタを0にするために今回は「mov EAX
,0」と表記しました。実はこの方法は人の目からは分かりやすいのですが「即値入力」と言いCPUからすると最も効率の悪い手法とされています。x86系CPUに限らず、最も効率の良くパフォーマンスが良いとされている方法はレジスタ同士の計算です。上記の場合「mov
EAX
,0(命令バイトで表すと66B800000000)」より「xor EAX
,EAX
(6633C0)」や「sub EAX
,EAX
(662BC0)」の方が効率的(命令バイト数が少ない)で一命令当たりの処理時間が少ないです。ちなみにxorは XOR演算でsubは引き算です。
EAX
同士のXOR演算はEAX
にどのような数値が入っていようとも0になりますし、EAX
同士の引き算は当然、どのような数値が入っていようとも0になります。トランジスタ数的にはXOR演算の方が分があるかもしれませんが、これも誤差範囲でしょう。今回は誤差の範囲ですので分かり易い「mov
EAX
,0」を選択しましたが、組み込み機器で4KBしかコードが組めない場面などで意識する必要があるかもしれません。