CPUの隠れた力を引き出す:CPUID命令でシステム情報を取得する方法
ブートローダーやカーネルを開発する際、ハードウェアとソフトウェアの互換性を確保し、パフォーマンスを最大限に引き出すためには、基盤となるアーキテクチャを深く理解することが不可欠です。
その中でも、システム情報を照会・取得するためにエンジニアが利用できる強力なツールの一つがCPUID命令です。本記事では、CPUID命令の基本概念から、実際のコードによる実装方法までを段階的に解説します。
CPUID命令とは?
CPUID命令は、現代のx86およびx86-64プロセッサの心臓部に組み込まれている低レベル命令です。ソフトウェアからプロセッサ自身や、そのサポート機能に関する情報を直接照会できるようにするものです。
この命令を呼び出すことで、プロセッサのモデルやファミリー、内部キャッシュのサイズ、SIMDやハードウェア仮想化などの対応機能といった情報を収集できます。これにより、パフォーマンスの最適化や、サポート状況に応じた機能の動的な有効化・無効化が可能になります。
ブートローダーやカーネルの開発者にとって、プロセッサがどのような機能を備えているか(ハードウェア仮想化、キャッシュサイズ、SIMD命令など)を事前に把握することは、システムを効率的に動作させ、異なるCPU間でのコード互換性を確保するうえで極めて重要です。CPUID命令を活用すれば、実行環境のプロセッサに応じてカーネルの挙動を動的に調整できます。
この記事では、CPUID命令がシステムで利用可能かどうかを確認する方法、その仕組み、そして呼び出しによって得られる情報について学びます。
前提知識と必要な環境
- アセンブリ言語の基礎知識(本記事の例ではFASMを使用)
- オペレーティングシステム/カーネルに関する基礎知識
- GDBなどの低レベルデバッグツール、またはQEMUなどのハードウェアエミュレータを使い、さまざまなプラットフォームでブートローダー/カーネルをテストできる環境
ステップ1:CPUID命令の利用可否を確認する
CPUID命令を実行する前に、プロセッサがその命令をサポートしているかどうかを確認することが重要です。すべてのCPUがこの機能を持つとは限らないからです。以下のコードは、EFLAGSレジスタのIDビット(ビット21)を変更してテストすることで、CPUID命令の利用可否を判定します。
以下はwiki.osdev.orgより引用した、EFLAGSレジスタの各ビットを示した図です。

プロセッサがこのビットの切り替えを許可していればCPUIDはサポートされており、許可しなければサポートされていません。検出の仕組みは以下の通りです。
※多くの方は「リアルモードでは32ビットレジスタにアクセスできない」と考えていますが、それは誤りです。実際にはすべての32ビットレジスタが使用可能です。
cpuid_check:
pusha ; save state
pushfd ; Save EFLAGS
pushfd ; Store EFLAGS
xor dword [esp],0x00200000 ; Invert the ID bit in stored EFLAGS
popfd ; Load stored EFLAGS (with ID bit inverted)
pushfd ; Store EFLAGS again (ID bit may or may not be inverted)
pop eax ; eax = modified EFLAGS (ID bit may or may not be inverted)
xor eax,[esp] ; eax = whichever bits were changed
popfd ; Restore original EFLAGS
and eax,0x00200000 ; eax = zero if ID bit can't be changed, else non-zero
cmp eax,0x00
je .cpuid_instruction_not_is_available
.cpuid_instruction_is_available:
;handle CPUID exists
.cpuid_instruction_not_is_available:
;handle CPUID isn't supported
.cpuid_check_end:
popa ; restore state
ret
各行の命令が果たす役割を順に見ていきましょう。
pusha:すべての汎用レジスタを保存し、処理終了時に元の状態へ復元できるようにします。pushfd:現在のEFLAGSレジスタの状態を保存します。pushfd:EFLAGSのコピーをさらにスタックに退避します。xor dword [esp], 0x00200000:XOR演算により、退避したEFLAGSのIDビット(21)を反転します。popfd:IDビットを反転した状態のEFLAGSを読み込みます。pushfd:その状態のEFLAGSを再度スタックに退避します(IDビットが反転できたかどうかはこの時点では未確定)。pop eax:退避しておいたEFLAGSの値をEAXレジスタに取り出します。xor eax, [esp]:XOR演算の結果、EAXには「変化があったビット」だけが残ります。popfd:元のEFLAGSを復元します。and eax, 0x00200000:AND演算で他のビットをマスクし、21番目のビット(IDビット)のみを抽出します。この結果、EAXには0x00200000(IDビットが変化した=CPUID対応)または0x00(変化なし=非対応)のいずれかが入ります。cmp eax, 0x00:CMP命令で直前の結果を判定します。EAXが0x00ならIDビットを変更できなかったことになり、プロセッサはCPUID命令をサポートしていません。ゼロ以外であればIDビットの反転に成功しており、CPUID命令が利用可能です。
CPUの機能フラグを取得する
CPUID命令は、EAXレジスタに設定する値によって異なる情報を返します。
mov eax, 0x1
cpuid
EAXに1を設定して呼び出すと、CPUIDはEDX(およびECX)にビットフィールド形式で機能フラグを返します。以下は主な定義の一覧です。なお、CPUブランドによって各ビットの意味が異なる場合があります(出典:wiki.osdev.org/CPUID)。
enum {
CPUID_FEAT_ECX_SSE3 = 1 << 0,
CPUID_FEAT_ECX_PCLMUL = 1 << 1,
CPUID_FEAT_ECX_DTES64 = 1 << 2,
CPUID_FEAT_ECX_MONITOR = 1 << 3,
CPUID_FEAT_ECX_DS_CPL = 1 << 4,
CPUID_FEAT_ECX_VMX = 1 << 5,
CPUID_FEAT_ECX_SMX = 1 << 6,
CPUID_FEAT_ECX_EST = 1 << 7,
CPUID_FEAT_ECX_TM2 = 1 << 8,
CPUID_FEAT_ECX_SSSE3 = 1 << 9,
CPUID_FEAT_ECX_CID = 1 << 10,
CPUID_FEAT_ECX_SDBG = 1 << 11,
CPUID_FEAT_ECX_FMA = 1 << 12,
CPUID_FEAT_ECX_CX16 = 1 << 13,
CPUID_FEAT_ECX_XTPR = 1 << 14,
CPUID_FEAT_ECX_PDCM = 1 << 15,
CPUID_FEAT_ECX_PCID = 1 << 17,
CPUID_FEAT_ECX_DCA = 1 << 18,
CPUID_FEAT_ECX_SSE4_1 = 1 << 19,
CPUID_FEAT_ECX_SSE4_2 = 1 << 20,
CPUID_FEAT_ECX_X2APIC = 1 << 21,
CPUID_FEAT_ECX_MOVBE = 1 << 22,
CPUID_FEAT_ECX_POPCNT = 1 << 23,
CPUID_FEAT_ECX_TSC = 1 << 24,
CPUID_FEAT_ECX_AES = 1 << 25,
CPUID_FEAT_ECX_XSAVE = 1 << 26,
CPUID_FEAT_ECX_OSXSAVE = 1 << 27,
CPUID_FEAT_ECX_AVX = 1 << 28,
CPUID_FEAT_ECX_F16C = 1 << 29,
CPUID_FEAT_ECX_RDRAND = 1 << 30,
CPUID_FEAT_ECX_HYPERVISOR = 1 << 31,
CPUID_FEAT_EDX_FPU = 1 << 0,
CPUID_FEAT_EDX_VME = 1 << 1,
CPUID_FEAT_EDX_DE = 1 << 2,
CPUID_FEAT_EDX_PSE = 1 << 3,
CPUID_FEAT_EDX_TSC = 1 << 4,
CPUID_FEAT_EDX_MSR = 1 << 5,
CPUID_FEAT_EDX_PAE = 1 << 6,
CPUID_FEAT_EDX_MCE = 1 << 7,
CPUID_FEAT_EDX_CX8 = 1 << 8,
CPUID_FEAT_EDX_APIC = 1 << 9,
CPUID_FEAT_EDX_SEP = 1 << 11,
CPUID_FEAT_EDX_MTRR = 1 << 12,
CPUID_FEAT_EDX_PGE = 1 << 13,
CPUID_FEAT_EDX_MCA = 1 << 14,
CPUID_FEAT_EDX_CMOV = 1 << 15,
CPUID_FEAT_EDX_PAT = 1 << 16,
CPUID_FEAT_EDX_PSE36 = 1 << 17,
CPUID_FEAT_EDX_PSN = 1 << 18,
CPUID_FEAT_EDX_CLFLUSH = 1 << 19,
CPUID_FEAT_EDX_DS = 1 << 21,
CPUID_FEAT_EDX_ACPI = 1 << 22,
CPUID_FEAT_EDX_MMX = 1 << 23,
CPUID_FEAT_EDX_FXSR = 1 << 24,
CPUID_FEAT_EDX_SSE = 1 << 25,
CPUID_FEAT_EDX_SSE2 = 1 << 26,
CPUID_FEAT_EDX_SS = 1 << 27,
CPUID_FEAT_EDX_HTT = 1 << 28,
CPUID_FEAT_EDX_TM = 1 << 29,
CPUID_FEAT_EDX_IA64 = 1 << 30,
CPUID_FEAT_EDX_PBE = 1 << 31
};
上記の主要なCPU機能について、簡単に解説します。
- PCLMUL、AES:高速な暗号化・復号化を実現する暗号関連の命令セット
- VMX、SMX:仮想マシンの実行を支えるハードウェア仮想化サポート
- SSE3、SSSE3、SSE4.1、SSE4.2、AVX:マルチメディア処理・数値計算・ベクトル演算を高速化するSIMD命令セット
- FMA:融合積和演算(Fused Multiply-Add)。浮動小数点演算の性能を向上させる
- RDRAND:ハードウェア乱数生成器
- X2APIC:マルチプロセッサ環境における高度な割り込み処理
- PCID:コンテキストスイッチ時のメモリ管理を最適化する
- FPU:数学演算を高速化するハードウェア浮動小数点ユニット
- PAE:Physical Address Extension。4GBを超えるメモリのアドレッシングを可能にする
- HTT:単一のCPUコアで複数スレッドを処理できるようにする(ハイパースレッディング)
- PAT、PGE:キャッシュ制御やページマッピングを管理するメモリ管理機能
- MMX、SSE、SSE2:マルチメディア処理向けの旧世代SIMD命令セット
CPUベンダー文字列を取得する
CPUのベンダー文字列(製造元を示す識別子)を取得したい場合は、CPUID命令を呼び出す前にEAXを0x0に設定します。
mov eax, 0x0
cpuid
ベンダー文字列は、IntelやAMDといったCPUベンダーが固有に持つ識別子です。代表的な例としては、Intelプロセッサなら「GenuineIntel」、AMDプロセッサなら「AuthenticAMD」があります。これは基本的にCPUの製造元を表すものです。
ベンダー文字列を取得できると、カーネルはCPUの製造元を特定できます。これは非常に有用です。というのも、同じ機能でも製造元によって実装の詳細が異なるケースがあるからです。また、ソフトウェアやドライバ側でも、CPUメーカーに応じて挙動を切り替えることで互換性を確保しやすくなります。
この使い方では、ベンダーID文字列はEBX、EDX、ECXの3つのレジスタに分割して返されます。これらをバッファへ順に書き込めば、全12文字の文字列を組み立てられます。
以下、具体的なサンプルコードを見ていきましょう。
ステップ1:バッファを用意する
まず、12バイト以上を格納できるバッファを作成します。
buffer: db 12 dup(0), 0xA, 0xD, 0
ステップ2:文字列出力関数を作成する
次に、文字列を画面へ出力するための関数を用意します。
このアセンブリコードは、文字列を1文字ずつ読み込み、BIOS割り込み0x10を使って画面に表示します。print関数は文字列を先頭から順に走査し、lodsb命令で1文字ずつALレジスタへロードします。
続いてprint_char関数が割り込み0x10を呼び出して文字を描画します。文字列の終端(ヌル終端文字)に到達すると、ループが終了します。
print_string:
call print
ret
print:
.loop:
lodsb ;read character to al and then increment
cmp al ,0 ;check if we reached the end
je .done ;we reached null terminator, finish
call print_char ;print character
jmp .loop ;jump back into the loop
.done:
ret
print_char:
mov ah, 0eh
int 0x10
ret
ステップ3:バッファに格納して表示する
最後に、pusha命令で現在のレジスタ状態を保存し、EAXに0x0を設定してcpuidを呼び出した後、EBX、EDX、ECXの内容をバッファへ書き込みます。その後、print_stringを呼び出せば、ベンダー文字列が画面に表示されます。
get_cpu_vendor:
pusha
mov eax, 0x0
cpuid
mov [buffer], ebx
mov [buffer + 4], edx
mov [buffer + 8], ecx
mov si, buffer
call print_string
popa
ret
CPUID命令がEAXレジスタに渡された値に応じてどんな情報を返せるのか、さらに詳しく知りたい方は、以下のデータベースが参考になります。
x86-cpuid-db(GitLab)
まとめ
CPUID命令を理解し活用することで、幅広い種類のプロセッサに柔軟に対応できるブートローダーやカーネルを構築できます。命令の利用可否を正しく検出し、CPUの機能フラグ、キャッシュサイズ、サポートされる技術といった重要なシステム情報を取得できれば、パフォーマンスと互換性の両面で大きなメリットが得られます。
この記事を通じて、CPUID命令を自分のプロジェクトで試してみるための基礎知識とツールが揃ったはずです。ぜひ実際にコードを書いて、CPUの世界を深く掘り下げてみてください。
それでは、楽しいコーディングライフを!
-
CPU温度を監視する方法|適正温度の目安と高温時の対処法
PCのCPU温度をチェックすることは、車のオイル点検に似ています。毎日行う必要はありませんが、数ヶ月に一度は目を通しておきたい項目です。特に、ゲーミング向けの高性能CPUなどで日常的に高負荷をかけている方は、定期的な温度管理が重要になります。幸い、CPU温度の確認はとても簡単です。PCケースを開けて温度計を差し込む必要はありません。すべてのCPUにはデジタル温度センサーが内蔵されており、専用ソフトでその数値を読み取るだけで済みます。この記事では、CPUにとって健康的な温度範囲、温度の確認方法、そして温度が高すぎる場合の対処法について詳しく解説します。CPUの適正温度はどのくらい?CPUがアイド
-
Intel K付きCPUと非Kモデルの違いを徹底解説!あなたのPC構築に最適なプロセッサーはどっち?
(画像クレジット:Intel)はじめにPCを自作・アップグレードする際、プロセッサー選びは性能を左右する最重要ポイントです。IntelのCPUラインナップでは、「K」付きモデルと「K」なしモデルのどちらを選ぶかが大きな分かれ目となります。この2つのカテゴリーは、特にオーバークロックへの対応可否といった性能面で大きく異なり、カジュアルなユーザーからエンスージアスト、ゲーマーまで、ユーザーのタイプによって適した選択肢が変わってきます。本記事では、IntelのK付きCPUと非Kモデルの違いを詳しく解説し、あなたのニーズに最適なCPUを選ぶための判断材料を提供します。Intel K付きCPUと非Kモデ