SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

CPUIDチュートリアル

CPUID命令によるCPUの性能・機能の把握

インテル系のCPUに実装されているCPUID命令の考察


  • X ポスト
  • このエントリーをはてなブックマークに追加

本稿ではインテル系のCPUに実装されているCPUID命令がどのように働いているかを考察します。CPUID命令を使用すると、CPUの性能や機能をソフトウェアから知ることができます。

  • X ポスト
  • このエントリーをはてなブックマークに追加

完成図

littleCPUID
littleCPUID

はじめに

 この記事ではインテル系の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命令は主に以下の目的で使用されます。

  1. 新たに実装された新機能命令の有無
  2. CPUIDは、OSやソフトウェアがあらかじめ新機能が実装されているかを確認するために使用されます。
    インテル系のCPUは、発展に伴い、多くの命令コードや機能が追加されました。例えばマルチメディア機能に特化したMMX/SSE命令、データ保護機能であるNX-bit/XD-bit機能、64-bit対応であるAMD64/EM64Tなどがそうです。OSやソフトウェアがこれらの新機能を使用する場合、CPUが新機能をサポートしているかどうか確認する必要があります。なぜなら新機能をサポートしていないCPUが新機能を使用しようとした場合、大概は一般保護エラー(未定義の実行コード)として扱い、その後の処理を中断してしまうからです。DOSのような保護されていないOS上で一般保護エラーを発生させた場合、最悪、元の制御に戻れない(ハングアップ?)ことも予想されます。あらかじめ新機能をサポートしてるかどうかを確認しておくことで、新機能を使用するか、従来の機能で乗り切るか、そもそも実行しないかの切り分けが可能になります。
  3. CPUベンダーの判別
  4. CPUIDは各CPUベンダーのブランドを判別するために使用されます。
    インテル系のCPUは、インテル社以外にもAMD社やVIA社など多くのベンダーが各々開発を行っています。各社はCPUIDに署名やブランド名を実装することで、自社のブランドを誇示しています。近年、AMD社やインテル社は機能以外にも自社のCPUに独自のブランド番号(AMD社はモデルナンバー、インテル社はプロセッサ・ナンバ)を使用しています。CPUIDは正確なブランド番号を取得出来る仕組みが実装されています。
  5. プロセッサ・シリアル・ナンバー(PSN, Processor Serial Number)
  6. CPUIDはかつてプロセッサ・シリアル・ナンバー(PSNと略)というCPU固有シリアル番号を返却するために使用されていました。
    CPUごとに割り振られ重なることのない番号を割り振ることでPC使用者を特定することが出来ます。PSNを利用したサービスとしてDRMなどが考えられていましたが、同時にプライバシー問題に関わるとして後にPSNは削除されました(PSNはインテルのPentium IIIで実装されPentium 4で削除されています)。

CPUID命令の動作

 インテルのサイトで配布されている日本語技術資料の「IA-32 インテル アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル 中巻」に以下の記述があります。

CPUIDのopcode
オペコード命令説明
0F A2CPUIDEAXレジスタに最初に入力された値に応じて、プロセッサの識別情報と機能情報をEAXEBXECXEDXの各レジスタに返す。

 レジスタEAXに所定のインデックス値を入れCPUID命令を実行することでEAXEBXECXEDXに結果を出力します。

 インデックス値に対して以下の値が返却されます。以下の説明でCPUID(xxx) と表現されていますが、これはEAX=xxxを設定しCPUID命令を実行した結果を指します。

CPUID情報

 CPUID(2)以降は、Intelがほぼ占有状態のようです。

CPUIDの返却値
CPUID入力出力詳細
CPUID(0)EAXCPUIDの有効最大値
EBX:EDX:ECXVendor ID
CPUID(1)EAXプロセッサ・シグネチャ(Family/Model/Stepping...)
EBX,ECX,EDXFunction Flags
CPUID(2)EAX,EBX,ECX,EDXCPUキャッシュ情報
CPUID(3)EAX,EBX,ECX,EDXPSN

 一般的に「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入力出力詳細
CPUID(8000-0000h)EAX拡張CPUIDの有効最大値
EBX:EDX:ECXReserved(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でも同様の結果が得られるはずです。

  1. r1コマンドの実行
  2. プログラムを実行する前のレジスタ情報を見ます。
  3. aコマンドの実行
  4. ハンドアセンブルによるプログラミングを行います。以下の命令を打ち込んでください。[Enter]はエンターキーを示します。
    • 「mov eax, 0[Enter]」(EAXレジスタに0を挿入します)
    • 「cpuid[Enter]」(CPUID命令を実行します)
    • 「int 3[Enter]」(実行を中断します)
    • 「[Enter]」
  5. uコマンドの実行
  6. 今までの打ち込んだ結果を逆アセンブルします。正しく打ち込まれているか確認できます。
  7. gコマンドの実行
  8. プログラムを実行します。100番地からint 3までプログラムを実行します。以下の実行結果よりEAX, EBX, ECX, EDXが変化していることが分かります。
  9. qコマンドの実行
  10. EXDEBから抜けます。

 以下が上記の実行例になります。

EXDEBの入力実行例
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) の出力結果となります。まとめると以下の通り。

AMD社製CPUのCPUID(0)の返却値
EAX = 0x1
EBX = 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)で上記のプログラムを実行した結果です。

Intel製CPUのCPUID(0)の返却値
EAX = 0x2
EBX = 0x756E6547 ("uneG")
ECX = 0x6C65746E ("letn")
EDX = 0x49656E69 ("Ieni")

 EBX:EDX:ECXをリトルエンディアンの順に並べるとASCIIで「GenuineIntel」となります。この文字列は、このCPUがインテル社にて開発されたことを示します。

 またCPUID(0) のEAXが2であるということはCPUID(0)、CPUID(1)、CPUID(2) が有効であることを示しています。

コラム:レジスタを0にする様々な手法
 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しかコードが組めない場面などで意識する必要があるかもしれません。

次のページ
littleCPUIDの実装

修正履歴

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
CPUIDチュートリアル連載記事一覧

もっと読む

この記事の著者

Mc.N(エムシイエヌ)

SyncHack 管理人。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/168 2006/01/06 16:08

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング