CPU シークレットのロックを解除する:CPUID 命令を使用してシステム情報を取得する
ブートローダー/カーネルを開発する場合、パフォーマンスとソフトウェアとハードウェア間の互換性を最適化するには、基礎となるアーキテクチャを理解することが重要です。
エンジニアがシステム情報をクエリして取得するために利用できる重要でありながら、時には見落とされがちなツールの 1 つは、CPUID 命令です。
CPUID 命令とは何ですか?
CPUID 命令は、最新の x86 および x86-64 プロセッサの中心部にある低レベルの命令で、ソフトウェアがプロセッサとそのサポートされる機能に関する情報を CPU にクエリできるようにします。
この命令を呼び出すことで、プロセッサのモデル、ファミリー、内部キャッシュ サイズ、SIMD やハードウェア仮想化などのサポートされている機能などの情報を収集できます。これは、パフォーマンスを最適化し、サポートされている機能を動的に有効または無効にするのに役立ちます。
ブートローダーまたはカーネルの開発者は、プロセッサがサポートする機能 (ハードウェア仮想化、キャッシュ サイズ、SIMD 命令など) を理解することで、システムが効率的に実行され、作成したコードが異なる CPU 間で互換性があることを確認できます。 CPUID 命令を利用すると、カーネルが実行されている特定のプロセッサに基づいてカーネルの動作を動的に調整できます。
この記事では、CPUID 命令がシステムで利用可能かどうかを確認する方法、CPUID 命令がどのように機能するか、そしてそれを使用することでどのような情報が得られるのかを学びます。
前提条件
-
アセンブリ言語に関するある程度の知識 (この例では FASM を使用します)
-
オペレーティング システム/カーネルに関するある程度の知識
-
低レベルのデバッグ ツール (GDB など) または QEMU などのハードウェア エミュレータにアクセスして、さまざまなプラットフォームでブートローダー/カーネルをテストします。
ステップ 1:CPUID が利用可能かどうかを確認する
すべての CPU がこの機能を備えていることが保証されているわけではないため、CPUID 命令を実行する前に、プロセッサが CPUID 命令をサポートしているかどうかを判断することが重要です。次のコードは、EFLAGS レジスタの ID ビット (ビット 21) を変更してテストすることにより、CPUID 命令が使用できるかどうかをチェックします。
これは、EFLAGS レジスタの各ビットを示す wiki.osdev.org の図です。

プロセッサがこのビットの切り替えを許可している場合、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 をスタックにプッシュします。
pop eax :変更された EFLAGS (ID ビットは反転される場合とされない場合があります) を EAX レジスタに置きます。
xor eax, [esp] :XOR 演算の後、EAX には変更されたビットが含まれます。
popfd :元の EFLAGS を復元します。
and eax, 0x00200000 :and この操作は、他のすべてのビットをマスクすることによって 21 番目のビット (ID ビット) を分離します。この操作の後、EAX レジスタには 0x00200000 (21 ビットが変更された場合、つまり CPUID がサポートされている場合) または 0x00 (21 ビットが変更されていない、CPUID がサポートされていない場合) が含まれます。
cmp eax, 0x00 :CMP 命令は、前の操作の結果をチェックします。 EAX が 0x00 に等しい場合、ID ビットは変更できず、プロセッサが CPUID 命令をサポートしていないことを意味します。ゼロでない場合は、ID ビットが反転され、プロセッサが CPUID 命令をサポートしていることを意味します。
CPU 機能を取得する
CPUID 命令は、EAX レジスタ内の異なる値を持つ異なる情報を返します。
mov eax, 0x1
cpuid
EAX を 1 に設定すると、CPUID は EDX のビットフィールドを返します。これには次の値が含まれます。ブランドが異なると、これらに異なる意味が与えられる場合があります (出典 https://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:乗算と加算を融合し、浮動小数点計算のパフォーマンスを向上させます。 -
RDRAND:乱数生成器。 -
X2APIC:マルチプロセッサ システムにおける高度な割り込み処理。 -
PCID:コンテキスト切り替え中のメモリ管理を最適化します。 -
FPU:演算処理を高速化するためのハードウェア浮動小数点ユニット。 -
PAE:物理アドレス拡張。4 GB を超えるメモリのアドレス指定が可能です。 -
HTT:単一の CPU コアが複数のスレッドを処理できるようにします。 -
PAT, PGE:キャッシュとページ マッピングを制御するためのメモリ管理機能。 -
MMX, SSE, SSE2:マルチメディア処理用の古い SIMD 命令セット。
CPU ベンダー文字列の取得
CPU ベンダー文字列を取得したい場合は、CPUID 命令を呼び出す前に EAX を 0×0 に設定する必要があります。
mov eax, 0x0
cpuid
ベンダー文字列は、AMD や Intel などの CPU ベンダーが使用する一意の識別子です。例としては、AuthenticIntel (Intel プロセッサ用) または AuthenticAMD (AMD プロセッサ用) があります。基本的には CPU の製造元を指定します。
ベンダー文字列を使用すると、カーネルは CPU の製造元を識別できるようになります。これは、製造元によって特定の機能の実装方法が異なるため、非常に役立ちます。また、ソフトウェアまたはドライバーは、互換性を確保するために、CPU メーカーに応じて異なる方法で相互作用することができます。
このように使用すると、ベンダー ID 文字列が EBX、EDX、ECX レジスタに返されます。それらをバッファに書き込んで、完全な 12 文字の文字列を取得できます。
コード例:
ステップ 1:バッファ
12 バイトを保持できるバッファを作成します。
buffer: db 12 dup(0), 0xA, 0xD, 0
ステップ 2:バッファを印刷する
まず、文字列出力関数を作成します。
このアセンブリ コードは文字列を 1 文字ずつ読み取り、BIOS 割り込み 0x10 を使用して画面に出力します。 print 関数は文字列をループし、lodsb を使用します。 al 内の各文字をロードする命令 登録します。
次に、print_char この関数は割り込み 0x10 を使用して画面に表示します。コードが文字列の終わり (null ターミネータ) に達すると、ループが終了します。
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 を使用して現在の状態を保存した後、 命令と cpuid の呼び出し EAX レジスタに 0x0 を渡すと、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
私の YouTube チャンネルのビデオでは、上記のコードを実装して詳しく説明しています
EAX レジスタに渡された値に従って CPUID 命令がどのような情報を提供できるかについての詳細は、https://gitlab.com/x86-cpuid.org/x86-cpuid-db
を参照してください。エピローグ
CPUID 命令を理解して使用することで、ブートローダー/カーネルを幅広いプロセッサにさらに適応させることができます。命令の可用性を検出し、CPU 機能、キャッシュ サイズ、サポートされているテクノロジーなどの重要なシステム情報を取得する方法を知ることで、パフォーマンスと互換性を大幅に向上させることができます。
この記事を読んだ後は、CPUID 命令とそれを独自のプロジェクトで使用する方法を検討し始めるためのツールと知識が得られるはずです。
コーディングを楽しんでください!
無料でコーディングを学びましょう。 freeCodeCamp のオープンソース カリキュラムは、40,000 人以上の人々が開発者としての職に就くのに役立ちました。始めましょう
-
PC の CPU 温度を監視する方法:簡単な手順と最適なツール
(画像クレジット:Shutterstock) TL;DR PC の CPU 温度を確認する方法 すべての CPU には温度センサーが内蔵されており、CPU の状態を定期的に監視するために使用できます。 CoreTemp や NZXT の CAM などのツールは、センサーにグラフィカル インターフェースを提供し、ユーザーが一目で温度を確認できるようにします。 CPU 温度を監視するその他のツールには、AIDA64、HWiINFO、HWMonitor などがあります。 各 CPU には、動作する安全な温度範囲があります。 アイドル時の CPU は約 50℃ で動作しますが、負荷が高くなると CP
-
プロセッサを提供する方法とその価値がある理由
ああ、その古い恐ろしい手順は、通常、エリートにのみ推奨されます。初心者の場合、デリッドとは、統合ヒートスプレッダ(IHS)をプロセッサの上部から分離し、製造元が使用したストックサーマルペーストをもう少しプレミアムなもの(通常は液体金属またはより優れたサーマル)に置き換えることです。貼り付けます。 かつては、あらゆる種類のかみそりの刃とクランプ、そしてプロセッサをIHSから慎重に切り離すための無数の技術を含む、非常に恐ろしいプロセスでした。 。 長い間、それが価値があることはめったにありませんでした。ハイエンド部品の温度は、アフターマーケットクーラーが最も弱い場合でも、摂氏70度を確実に