CPUの物理CPU数・ソケット数等を取得(32/64bit)・プログラムソースの概要

icon 項目のみ表示/展開表示の切り替え

プログラムソースの概要

cpuidview.cpp

CPUクラスのオブジェクトを割り当て・初期化します。

_tWinMain

ダイアログボックスを表示します。

DlgProc

ダイアログボックスプロシージャーです。
WM_INITDIALOG
ListViewHeader関数によりリストビューのヘッダーを表示します。
cpu_status関数によりリストビューアイテムの表示およびパッケージ情報を表示する。
WM_CTLCOLORSTATIC
エミュレーション時にスタティックコントロールの色を赤にします。
WM_COMMAND
IDOK
OKボタンをクリックすると発生するメッセージです。
CPUID一覧を表示するダイアログボックスを呼び出します。
IDCANCEL
CPU_PKGクラスで動的に確保したメモリを解放します。
EndDialog APIによりダイアログボックスを終了させます。
IDC_VIRTUALBOX_BUTTON
VirtualBoxの仮想マシーンの一覧から仮想マシーン名を取得するダイアログボックスを実行します。
仮想マシーン名を取得できた場合、cpuid_virtualbox_putメンバー関数によりVirtualBoxでCPUIDを設定するバッチファイルを作成します。
IDC_CSV_BUTTON
CPUID値の一覧をcpuid_csv_putメンバー関数でファイルに出力します。
出力結果はインポートすることによりCPUIDの値を閲覧等ができます。
IDC_INPORT_BUTTON
GetFileName関数によりCSVファイル名を取得するコモンダイアログボックスを表示します。
clearメンバー関数でCPU_PKGクラスで動的に確保したメモリを解放します。
clearメンバー関数でCPUクラスで動的に確保したメモリを解放します。
cpuid_csv_getメンバー関数でCSVファイルからCPUID値を呼び出しCPU_PKGクラスを初期化します。
addメンバー関数によりCPUクラスにCPU_PKGクラスを登録します。
initメンバー関数により論理CPU数の取得およびトポロジーを解析します。
parseメンバー関数によりトポロジーを登録します。
getCpuNameメンバー関数によりブランドストリングが無いCPUに対してブランドID及びシグネチャからCPU名を解析し登録します。
LVM_DELETEALLITEMSメッセージをリストビューに送りリストビューのアイテムを消去します。
cpu_status関数によりリストビューアイテムの表示およびパッケージ情報を表示する。
IDC_EXPORT_BUTTON
cpuid_put,cpu.cpuid_info_putメンバー関数によりCPUIDの解析結果をテキストファイルに保存します。
cpuid_cpp_putメンバー関数によりインポート用のヘッダーファイルを作成します。
cpuid_summaryメンバー関数によりCPUIDの解析結果の概要をテキストファイルに保存します。
IDC_CPU_NAME_EDIT
エクスポート名のエディットボックスからのメッセージが届きます。
EN_UPDATEメッセージの場合、エディットボックスのテキストが変更されていることを示しています。
GetWindowText APIでエディットボックスのテキストを取得します。
shortCpuName関数によりテキストからファイル名や関数名に使用できない文字の変換および不要な空白等を削除した短縮名を作成します。
この短縮名によりエクスポートファイル名やクラス名が設定されます。
短縮名をSetWindowText APIによりエディットボックス下のラベルに表示します。
WM_PAINT
ダイアログボックスで再描画が必要な時に呼び出されます。

ListViewHeader

リストビューのヘッダーを表示します。
ソケット数に応じて列を増やします。

cpu_status

putListView関数によりリストビューのアイテムを設定します。
getEmuAPICBit,getEmuX2apicBitメンバー関数によりパッケージ・コア・スレッドのビット幅を取得し、ラベルにSetWindowText APIによりビット配置を表示します。
Sがスレッド、Cがコア、Pがパッケージです。
LVSCW_AUTOSIZEメッセージをリストビューに送り、リストビューの列幅を最大文字数に合わせます。
CPUのパッケージを描画します。

putListView

リストビューに各ソケット毎に、コア数・スレッド数及びコア内のスレッドに対するWindowsが割り当てた論理CPU番号及びコア番号を表示します。

CPUID_DlgProc

CPUID値の一覧を表示するダイアログボックスのプロシージャーです。
WM_INITDIALOG
ListView_InsertColumnマクロによりリストビューのヘッダーを設定します。
LB_ADDSTRINGメッセージによりリストボックスにアイテム(ソケット番号)を設定します。
cpuid_put_ListViewメンバー関数によりリストビューアイテムを設定します。
WM_COMMAND
IDC_PKG_LISTBOX
リストボックスの選択状態が変化したときにLBN_SELCHANGEメッセージが発生します。
LB_GETCURSELメッセージによりリストボックスの選択されているアイテム番号を取得します。
アイテム番号はパッケージ番号と同じです。
LVM_DELETEALLITEMSメッセージによりリストビューのアイテムを全部削除します。
cpuid_put_ListViewメンバー関数により選択されているパッケージ番号のリストビューアイテムを設定します。
IDCANCEL
閉じるボタンのクリックおよびESCキーを押したときに呼び出されます。
EndDialog APIによりダイアログボックスを終了させます。

_sjis_ftprintf

_tfprintfと似ていますが、ファイルへの出力は絶えずSJISで行います。
入力はTCHAR*型、出力はchar*で文字コードはSJISとなります。
コンパイル時に_UNICODEマクロが定義されているとWideCharToMultiByte APIを用いてUNICODEからSJISに変換します。
_tfprintfの様に可変長の引数を扱うためにstdarg.hをインクルードし、va_listを用いています。
va_startマクロにより書式設定の引数formの次のバイトのアドレスを算出しva_list型に格納します。
後は、va_listを引数とするv系の関数を用いれば可変長引数をサポートできます。
最後にva_endマクロを実行します。

GetVmDlgProc

仮想マシーン名を取得するダイアログボックスプロシージャーです。
WM_INITDIALOG
このダイアログを表示するときに使用した、DialogBoxParam APIで指定したLPARAMを取り出してスタティック変数vm_nameに保存しておきます。vm_nameに取得された仮想マシーン名が保存されます。
SHGetSpecialFolderPath APIによりユーザープロフィールのフォルダー名を取得します。
このAPIはWindows 2000以上で使用可能です。Windows 2000で使用できる実行ファイルはVisual C++ 2005で作成可能なので、Visual C++ 97でビルドした場合は、Windows 9x系の実行ファイルをビルドしているとみなし、このAPIを無視するようにします。
VirtualBoxの仮想マシンの設定ファイルは、ユーザープロフィールの中のVirtualBox VMsフォルダーに仮想マシンごとにフォルダーが作成されています。
FindFirstFile,FindNextFile APIによりVirtualBox VMsフォルダーの中のフォルダーを検索します。自分自身のフォルダーを示す.と親フォルダーを示す..を無視するようにします。
見つかったフォルダー名は、CB_INSERTSTRINGメッセージによりコンボボックスに登録します。
コンボボックスのスタイルには、CBS_DROPDOWNを指定していますので、コンボボックスに登録されていない場合、直接編集して作成することが可能です。
リストボックスにアイテムが登録されている場合は、CB_SETCURSELメッセージにより0個目のアイテムを選択状態にします。
WM_COMMAND
IDOK
GetWindowText APIにより選択されているアイテムまたは、編集されたアイテムをスタティック変数vm_nameに取得します。 EndDialog APIによりダイアログボックスを終了させます。
IDCANCEL
閉じるボタンのクリックおよびESCキーを押したときに呼び出されます。
EndDialog APIによりダイアログボックスを終了させます。

cpu.h cpu.cpp

CPUIDを管理するクラス及びCPUIDを解析するための共用体を定義しています。
エミュレーション時は、このクラスが保持する値はエミュレーション中の値となります。

CPU_PKG クラス

CPUの物理パッケージを管理します。
DWORD coreMax;
コア数
DWORD threadMax;
スレッド数
CACHE_CODE cache[8];
キャッシュの情報を保存しています。
DWORD cache_max;
キャッシュ情報の個数
int* threadvec;
CPUのスレッド番号を配列の添え字 → Windows上の論理CPU番号
int* corevec;
CPUのスレッド番号→コア番号
cpuid?????????_tbl;
cpuidの実行結果のテーブル(エミュレーション時は継承側のクラスで定義する)

REG32* cpuid_tbl;//     eax=0~指定されるcpuid
REG32* cpuid80000000_tbl;//     eax=0x80000000~指定されるcpuid
REG32* cpuid2_tbl;//    eax=2で指定されるcpuid
REG32* cpuid4_tbl;//    eax=4 ,ecx指定されるcpuid
REG32* cpuid7_tbl;//    eax=7 ,ecx指定されるcpuid
REG32* cpuid11_tbl;//   eax=11,ecx指定されるcpuid
REG32* cpuid13_tbl;//   eax=13,ecx指定されるcpuid
    ~
REG32* cpuid20_tbl;//   eax=20,ecx指定されるcpuid

DWORD cpuid????????_max;
cpuid????????_tblの要素数

DWORD cpuid_max;        //      cpuid_tblの要素数
DWORD cpuid80000000_max;//      cpuid80000000_tblの要素数
DWORD cpuid2_max;       //      cpuid2_tblの要素数
DWORD cpuid4_max;       //      cpuid4_tblの要素数
DWORD cpuid7_max;       //      cpuid7_tblの要素数
DWORD cpuid11_max;      //      cpuid11_tblの要素数
DWORD cpuid13_max;      //      cpuid13_tblの要素数
    ~
DWORD cpuid20_max;      //      cpuid20_tblの要素数
TCHAR name;
CPUの名前
void cache_set(void);
cpuid EAX=2とcpuid EAX=4によりキャッシュ諸元を設定cache[n]に設定します。
基本的にはcpuid EAX=4の情報のみで行けますが、Pentium4のトレスキャッシュのみcpuid EAX=2にしか情報がありませんので、cpuid EAX=2でキャッシュ情報を一括して取得後、cpuid EAX=4で重複をチェックしながらcache[n]に登録します。
cpuid EAX=2をサポートしていないCPUの場合、プロセッサシグネッチャからキャッシュ情報を設定します。(80486~MMX Pentium)
CACHE_CODE* cpuid2_cache_find(DWORD code);
cpuid EAX=2の実行結果の各1byteを引数に渡して呼び出すと該当するキャッシュサイズ等を示す構造体のポインタを返します。
void cpuid2_cache_set(DWORD c);
cpuid EAX=2の実行結果の各1byteを引数に渡して呼び出すと該当するキャッシュサイズ等を示す構造体を検索して該当するものがあればcahche[n]に登録し、cache_maxを1加算します。
void getCPUID(void);
現システムの指定される物理パッケージのCPUIDを取得します。
cpuid EAX=2 EAX=11は、コア及びスレッドにより異なったCPUIDの結果を返すので、SetThreadAffinityMask APIにより実行時のCPUを固定してから__cpuid及び__cpuidexを実行しています。
物理パッケージ内の全スレッドに対して順番に実行します。
実行結果はcpuid?????????_tbl,cpuid????????_maxに保存されます。
bool emuIsX2apic(void);
CPUがx2apicをサポートしているかをcpuid?????????_tblによって判断しサポートしている場合trueを返します。直接__cpuid及び__cpuidexを呼び出していないのでエミュレーション時を対象とします。
基本的には、cpuidでeax=11をサポートしていればx2apicをサポートしています。ただし、Core2Duoの一部は、eax=11をサポートしているが、返す値は無効な値なので、x2apicをサポートしていない。
bool isX2apic(void);
CPUがx2apicをサポートしているかを直接__cpuid及び__cpuidexを呼び出しすことにより判断しサポートしている場合trueを返します。
DWORD getTopologyBit(DWORD* CorePlus_Mask_Width, DWORD* SMT_Mask_Width);
CPUトポロジーのビット位置を直接__cpuidexを呼び出しすことにより取得します。
x2apicが使える場合はx2apic上の位置とx2apicの値を返します。
x2apicが使えない場合はapic上の位置とapicの値を返します。
CPUトポロジーのビット位置とはスレッドのビット幅(CorePlus_Mask_Width)とコアのビット幅(SMT_Mask_Width)を意味します。
例えばCorePlus_Mask_Width=3,SMT_Mask_Width=1で返されたapicまたはx2apicの値が0x1fの場合、
SMT_Mask_Widthが1のなのでビット幅1となり、スレッド番号は0ビット目に含まれ SMT=1
CorePlus_Mask_Widthが3のなので、ビット幅は(CorePlus_Mask_Width-SMT_Mask_Width=2)となり、コア番号は1ビット目から3ビット目に含まれ3となります。
パッケージ番号は、CorePlus_Mask_Widthの上位ビットなので、4ビット目から上位ビットに向かって含まれ、1となります。
void SetTopology(DWORD* apic, DWORD pkg, DWORD core, DWORD smt, DWORD CorePlus_Mask_Width, DWORD SMT_Mask_Width);
指定されたCPUトポロジーのビット位置にパッケージ番号及びコア番号及びスレッド番号を格納します。
void getEmuAPICBit(DWORD* CorePlus_Mask_Width, DWORD* SMT_Mask_Width);
apicよりCPUトポロジーのビット位置をcpuid?????????_tblより取得します。
bool getEmuX2apicBit(DWORD* CorePlus_Mask_Width, DWORD* SMT_Mask_Width);
x2apicよりCPUトポロジーのビット位置をcpuid?????????_tblより取得します。
CPU拡張トポロジーをサポートしているときはtrueを返す
void cpuid(int thread, int* info, int eax);
cpuid?????????_tblよりcpuidをエミュレーションします。
void cpuidex(int thread, int* info, int eax, int ecx);
cpuid?????????_tblよりcpuidexをエミュレーションします。
TCHAR* ext_inst_str(TCHAR* buf, int sz);
サポートされている拡張命令を文字列化します。
例えばMMX,SSEをサポートしているCPUの場合、文字列MMX SSEをbufにコピーします。
void cpuid_summary(TCHAR* str,int sz);
CPUIDの結果の概要をカンマで区切って文字列に出力します。 例えばPentium 4の場合、以下のような文字列が作成されます。
Intel(R) Pentium(R) 4 CPU 3.20GHz,1,2,Trace Cache 12kμOPs 8way*1個,16kByte*1個 8way,2048kByte*1個 8way,,f,6,2,f,6,MMX SSE SSE2 SSE3 EM64T NX/XD-bit 
void cpuid????????_info_put(FILE* fp);

void cache_put(FILE* fp);       //      キャッシュ情報の出力
void DrawOwn(HDC hdc, int sx, int sy, int cpuNum);      // 1個の物理CPUを描画
void Draw(HDC hdc, int sx, int sy, int* ex, int* ey);   // 1個のパッケージを描画
void cpuid2_info_put(FILE* fp); //      cpuid EAX=2の実行結果を出力
void cpuid4_info_put(FILE *fp); //      cpuid EAX=4の実行結果を出力
void cpuid7_info_put(FILE* fp); //      cpuid EAX=7の実行結果を出力
void cpuid10_info_put(FILE *fp);        //      cpuid EAX=10の実行結果を出力
void cpuid11_info_put(FILE *fp);        //      cpuid EAX=11の実行結果を出力
void cpuid11_small_put(FILE* fp);       //      cpuid EAX=11の実行結果を出力(簡易)
void cpuid80000008_info_put(FILE *fp);  //      cpuid EAX=0x80000008の実行結果を出力
void cpuid_info_put(FILE *fp);  //      cpuidの実行結果を全部出力
void cpuid_cpp_put(FILE* fp, TCHAR* fname);     //      物理パッケージのCPUIDをC++のクラスとしてソースをファイルに出力
void cpuid_put(FILE* fp);       //      取得されたcpuidをファイルに出力
void cpuid_put_ListView(HWND hList);    //      cpuidをリストビューに出力
x2APIC
各スレッド数のパッケージ・コア・スレッド番号を保存しています。
APIC
各スレッド数のパッケージ・コア・スレッド番号を保存しています。

CPU クラス

CPUのパッケージを複数個まとめて管理します。
DWORD lCpuMax;
システムの論理CPU数を返します。
void cpuid_cpp_put(void);
物理パッケージのCPUIDをC++のクラスとしてソースをファイルに出力
void cpuid_put(FILE* fpv);
取得されたcpuidをファイルに出力
void cpuid_info_put(FILE* fpv);
cpuidの実行結果を全部出力
CPU();
エミュレーションしない場合のコンストラクタ
CPU(int package);
エミュレーションする場合のコンストラクタ 物理パッケージ数(ソケット数)を指定
TCHAR* getCpuName(void);
CPUの名称を返す。
CPU_PKG** cpu_pkg;
物理パッケージ(*CPU_PKG)を保存
DWORD pkgMax;
物理パッケージ数
DWORD* smt, *core, *pkg;
Windowsの論理CPU番号を添え字としスレッド番号、コア番号、パッケージ番号を保存
UINT64 mask;
CPUIDをエミュレーションしている時、実CPU数とエミュレーションCPU数が一致しない時があるので、CPUのプロセッサ・アフィニティ・マスクを仮想化します。仮想化した値を保持しており、emuSetThreadAffinityMasメンバ関数の引数が保存されます。
UINT64 sysmask;
エミュレーションしているCPU数全部にスレッドを割り当てるCPUのプロセッサ・アフィニティ・マスク値を保存しています。
int* emu_cpu_num;
仮想CPU番号を添え字としWindowsの論理CPU番号を保存しています。
emuSetThreadAffinityMasメンバ関数により割り当てCPUを変更すると実CPUへの割り当ても変更します。
void make_cpuid_para(int* cpun, int* pkgn, int* threadn);
仮想CPUのプロセッサ・アフィニティ・マスク値より一番わかりCPU番号等を取得します。
void alloc_cpu(int* v, int vcpumax);
仮想CPU番号を添え字としWindowsの論理CPU番号を格納する配列を初期化します。
仮想論理CPU<=実論理CPU/2の場合、ハイパースレッドのCPUを考慮して実論理CPUを2個飛ばしに割り振ります。

仮想論理CPU 2個
実論理CPU 8個
emu_cpu_num[0] -> 0
emu_cpu_num[1] -> 2
仮想論理CPU>実論理CPU/2の場合、実論理CPUを1ずつ加算して割り振ります。

仮想論理CPU 4個
実論理CPU 2個
emu_cpu_num[0] -> 0
emu_cpu_num[1] -> 1
emu_cpu_num[2] -> 0
emu_cpu_num[3] -> 1
void init(void);
エミュレーション時にCPUトポロジーよりパッケージ・コア・スレッドを取得します。
本関数を呼び出す前に、まず本オブジェクトの作成のコンストラクタに物理パッケージ数を指定します。CPU_PKGから派生されたオブジェクトを作成し、addメンバ関数でCPU_PKGから派生されたクラスを追加する必要があります。その後、parseメンバー関数を呼び出すと、エミュレーションが使用可能となりemucpuid,emucpuidex,emuGetSystemInfo,emuSetThreadAffinityMaskメンバ関数が使用できます。
void getCPUID(void);
現システムから各cpu_pkgオブジェクトのgetCPUIDを呼び出し、CPUIDの実行結果を抽出します。
int bit_num(UINT64 bit);
引数に含まれる1のビット数を返します。
DWORD_PTR emuSetThreadAffinityMask(HANDLE h, UINT64 vmask);
実行CPUを固定します。指定するCPUのプロセッサ・アフィニティ・マスクの値は仮想化したものを指定します。詳しくはUINT64 sysmask;を参照してください。
BOOL emuGetProcessAffinityMask(HANDLE h, UINT64* vmask, UINT64* vsysmask);
仮想化されたCPUのプロセッサ・アフィニティ・マスクの値を返します。
void emuGetSystemInfo(SYSTEM_INFO* sys);
GetSystemInfo APIを呼び出し、戻り値のうち論理CPU数を仮想化された論理CPU数に書き換えて返します。
void cpuid(int* info, int eax);
仮想化されたCPUのプロセッサ・アフィニティ・マスクの値より実行中の仮想論理CPU番号を算定し、取得されたcpuidによりエミュレーションします。
void cpuidex(int* info, int eax, int ecx);
仮想化されたCPUのプロセッサ・アフィニティ・マスクの値より実行中の仮想論理CPU番号を算定し、取得されたcpuidによりエミュレーションします。
void get(void);
現システムのCPUトポロジー等を解析します。
void parse(void);
現システムまたはエミュレーションされた結果より詳細のCPUトポロジー等を解析します。
void add(int package, CPU_PKG* cpu);
エミュレーションするCPUを追加します。
int getPackageMax(void);
物理パッケージ数(ソケット)を取得

undervc2008.h

Visual C++ 2013未満でビルドする場合に足りない関数等を定義しています。
Visual C++ 97で使用できないセキュリティー強化版の関数群を従来関数で定義。ただしセキュリティー強化はなされていない。
Visual C++ 2013では16進文字列を32bit整数に変換するのにstrtoll関数(64bit整数を返す)を使用している。32bit整数を返すstrtolの場合、大きな数値を示す文字列を渡すと誤変換する場合があるから64bit版を使用している。
Visual C++ 2010以下では自分で作成した strtoDWORDに置き換える。
Visual C++ 97で使用できないGetClassLongPtr APIをGetClassLongに置き換える。またGCLP_HBRBACKGROUNDをGCL_HBRBACKGROUNDに置き換える。