山本ワールド
Windowsプログラミング
アルゴリズム Vitual C++ 2008/2013によるWin32/Win64 APIレベルのプログラム 基礎 Vitual C++ 2008/2013によるAPIレベルのプログラム(32/64bit) Wix3でインストーラーを作る Visual C++ 2008 Standard Editonによるフォームアプリケーションのプログラム(32/64bit) Vitual C++ 2008 Standard EditonによるAPIレベルのプログラム(32/64bit) Windows 7対応 Visual C++ 2008 ExpressによるAPIレベルのプログラム Visual C++ 2005 ExpressによるAPIレベルのプログラム Visual C++ Versiosn 5 BORLAND C++ Windowsプログラム全般 Excel VBA その他MSRレジスタを読み込むデバイスドライバ(32bit)を作成しIntel CPUのコア温度を表示する
概要
MSRレジスタを呼び出すrdmsr命令は特権命令なのでカーネルモードでしか動作しません。
MSRレジスタを呼び出す32bitデバイスドライバ(rdmsrdrv.sys)をインストールした状態で、別のプログラム(cputemp2.exe)からデバイスドライバを制御し各スレッドごとにCPUのジャンクション温度を取得します。
ジャンクション温度の最大値が取得できるCPU(Nehalem以上)は、各スレッドのジャンクション温度を表示します。
ジャンクション温度の最大値が取得できないCPU(Core 2 世代)は、最大ジャンクション温度までの現在の許容値を表示します。
ジャンクション温度が取得できないCPU(Pentium 4以下)は、プログラムを終了します。
一口にCPUの温度といってもジャンクション温度(半導体内部の温度)とケース温度(パッケージ中央の温度)の違いがあります。通常、明示されているのはケース温度の方です。
使用するには、まずデバイスドライバをインストールし、その後cputemp2.exeを実行します。
下図は、Core i3 M370のWindows Vista上で実行したケースです。
2コアでハイパースレッドが使用できるので4スレッド実行可能です。
tjmaxがジャンクション温度の許容温度、温度はジャンクション温度を表しています。
CPU0の場合、許容温度まで43度ということです。
MSRレジスタを呼び出す32bitデバイスドライバ(rdmsrdrv.sys)をインストールした状態で、別のプログラム(cputemp2.exe)からデバイスドライバを制御し各スレッドごとにCPUのジャンクション温度を取得します。
ジャンクション温度の最大値が取得できるCPU(Nehalem以上)は、各スレッドのジャンクション温度を表示します。
ジャンクション温度の最大値が取得できないCPU(Core 2 世代)は、最大ジャンクション温度までの現在の許容値を表示します。
ジャンクション温度が取得できないCPU(Pentium 4以下)は、プログラムを終了します。
一口にCPUの温度といってもジャンクション温度(半導体内部の温度)とケース温度(パッケージ中央の温度)の違いがあります。通常、明示されているのはケース温度の方です。
使用するには、まずデバイスドライバをインストールし、その後cputemp2.exeを実行します。
下図は、Core i3 M370のWindows Vista上で実行したケースです。
2コアでハイパースレッドが使用できるので4スレッド実行可能です。
tjmaxがジャンクション温度の許容温度、温度はジャンクション温度を表しています。
CPU0の場合、許容温度まで43度ということです。
テスト環境
コンパイラ
ドライバの開発 WDK7ドライバを制御するソフトの開発 Visual C++ 2013 Express
実行環境
Windows 7 Professional Service Pack 1 32bitWindows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit
ドライバソースファイル等のフォルダー構成
d:¥rdmsrdrv │ └─rdmsrdrv rdmsrdrv.cpp makefile sources
ドライバ開発環境の構築とビルドの方法
デバイスドライバの開発にWDK7を使用します。以下のサイトよりWDK 7.1.0をダウンロードし、インストールします。
Windows Driver Kit (WDK)https://msdn.microsoft.com/ja-jp/library/windows/hardware/ff557573(v=vs.85).aspx
WDK7にはコンパイラも含まれています。(Visual C++ 2008の英語版相当)ただし、コマンドラインでしか使用できません。
文法チェックやインテリセンス機能を使用したいので、Visual Studioでソースコードの編集を行いWDK7でデバイスドライバを開発することとします。
ここではd:¥ドライブにrdmsrdrvプロジェクトを作成しました。
プロジェクトのプロパティを表示し、インクルードの検索パスにDDKのインクルードファイルが格納されているフォルダー名を指定します。(ディフォルトではC:¥WinDDK¥7600.16385.1¥inc¥ddk)
これによりVisual Studioのエディタ上で関数名の色が変わったり、インテリセンスが使えるようになり便利です。
makefileとsourcesファイル名には拡張子はついていないので注意してください。
SOURCESでソースファイル名を指定します。複数のソースファイルがある場合はスペースで区切ります。
メニューで選択するOSは開発しているパソコンのOSではなく、ドライバを実行するOSです。XPを選択すればXP,Vista,7で使用可能です。
コマンドプロンプトのウィンドウが開くので、CDコマンドを使用してソースファイルが存在するフォルダーに移動します。
buildコマンドでコンパイルできます。
Windows Driver Kit (WDK)https://msdn.microsoft.com/ja-jp/library/windows/hardware/ff557573(v=vs.85).aspx
WDK7にはコンパイラも含まれています。(Visual C++ 2008の英語版相当)ただし、コマンドラインでしか使用できません。
文法チェックやインテリセンス機能を使用したいので、Visual Studioでソースコードの編集を行いWDK7でデバイスドライバを開発することとします。
Visual Studio C++でプロジェクトを作成
Visual Studioで新規プロジェクトを作成を開始し、テンプレートはメイクファイルプロジェクトを選択し後は適当に操作して完了させます。ここではd:¥ドライブにrdmsrdrvプロジェクトを作成しました。
プロジェクトのプロパティを表示し、インクルードの検索パスにDDKのインクルードファイルが格納されているフォルダー名を指定します。(ディフォルトではC:¥WinDDK¥7600.16385.1¥inc¥ddk)
これによりVisual Studioのエディタ上で関数名の色が変わったり、インテリセンスが使えるようになり便利です。
makefileとsourcesファイルの作成
上記のファイルを作成します。保存先は、d:¥rdmsrdrv¥rdmsrdrvフォルダとします。本プログラムに必要なファイルは本ページの最後のダウンロードのリンク先に含まれています。makefileとsourcesファイル名には拡張子はついていないので注意してください。
makefile
!INCLUDE $(NTMAKEENV)¥makefile.def
sources
TARGETNAMEで作成するデバイスドライバのファイル名を指定します。本例ではbeeptest.sysとなります。SOURCESでソースファイル名を指定します。複数のソースファイルがある場合はスペースで区切ります。
TARGETNAME=rdmsrdrv TARGETTYPE=DRIVER TARGETPATH=obj SOURCES=rdmsrdrv.cpp
WDK7によるデバイスドライバの作成
スタートメニューからWindows Driver Kits・WDK 7600.16385.1・Build Environments・Windows XP¥x86 Free Build Environmentを実行します。メニューで選択するOSは開発しているパソコンのOSではなく、ドライバを実行するOSです。XPを選択すればXP,Vista,7で使用可能です。
コマンドプロンプトのウィンドウが開くので、CDコマンドを使用してソースファイルが存在するフォルダーに移動します。
buildコマンドでコンパイルできます。
cd d:¥rdmsrdrv¥rdmsrdrv buildデバイスドライバは、d:¥rdmsrdrv¥rdmsrdrv¥objfre_wxp_x86¥i386 に作成されます。
デバイスドライバのインストール
ドライバのインストールは、ドライバのインストール/アンインストールするプログラムを参照指定ください。ドライバ ソースの概要
rdmsrdrv.cpp
各関数名が勝手に修飾されないように、extern "C"で定義します。各関数は、コンパイル時にOACRにより警告が出ないように独特の型でプロトタイプ宣言を行います。
#pragma alloc_textにより各関数をメモリ上の扱いを定義します。この定義を行った場合、各関数の入口にPAGED_CODE();を記述する必要があります。
INITを指定した場合、ページアウトしません。PAGEをした場合、ページアウト可能です。
MSRを読み込むためにインラインアセンブラでrsmsr命令を使用しています。
DriverEntry関数
デバイスドライバが起動されたときの1度だけ実行される関数です。デバイスドライバの名称の定義と、 デバイスドライバを使用するプログラムで実行されるCreateFile関数・CloseFile関数・DeviceIoControl関数に対する処理先等を定義します。
以後、使用する側から呼び出される場合は、定義した処理先が呼び出されます。
DeviceControlFunction関数
DriverEntry関数で登録されたデバイスドライバを使用する上のDeviceIoControl関数から呼び出されるプログラムで実行される関数である。直接呼び出されるのではなく、カーネルがバッファ処理等を行ってから呼び出されます。
引数より入力・出力バッファのアドレス・サイズ等が含まれる構造体をIoGetCurrentIrpStackLocation関数により呼び出しますす。
アクセスIDを調べ該当する処理に振り分けます。本ドライバはIOCTL_RDMSR_CODEのみサポートしています。
入力バッファより、アドレス値を取得しecxレジスタにコピーします。
ecxレジスタにMSRレジスタのアドレスを格納して、rdmsr命令を呼び出すとedx:eaxレジスタに結果が保存されます。
存在しないMSRレジスタのアドレスの指定した場合、例外が発生しますのでプログラムが終了しないように、__try{ }__exceptで例外を捕捉します。
デバイスドライバを呼び出したプログラムからの例外の有無の確認は、出力バッファのサイズが0の場合、例外が発生したことを示します。
出力バッファにedx:eaxレジスタの値を格納します。
IoCompleteRequestにより処理したコマンドを消去します。
UnloadDriverFunction関数
ドライバがアンロードされるときに呼び出されます。IoDeleteDevice関数によりドライバを消去します。
ドライバ(rdmsrdrv.sys) ソースコード
rdmsrdrv.cpp
// WDMドライバサンプル(MSRの読み込み)
// Windows XP Vista 7 32bit OS限定
#ifdef __cplusplus
extern "C" {
#endif
#include <wdm.h>
#define IOCTL_RDMSR_CODE 0x800
// エントリーポイント
DRIVER_INITIALIZE DriverEntry;
// Create
__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH CreateFunction;
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp);
// Close
__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH CloseFunction;
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
// DeviceControl
__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH DeviceControlFunction;
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
// AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet);
// UnloadDriver
DRIVER_UNLOAD UnloadDriverFunction;
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, DriverEntry )
#pragma alloc_text( PAGE, CreateFunction)
#pragma alloc_text( PAGE, CloseFunction)
#pragma alloc_text( PAGE, DeviceControlFunction)
#pragma alloc_text( PAGE, AddDeviceFunction)
#pragma alloc_text( PAGE, UnloadDriverFunction)
#endif
#ifdef __cplusplus
}
#endif
// ドライバーがロードされたときに呼び出される関数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath){
UNICODE_STRING usDeviceName,usDosDeviceName;
PAGED_CODE();
RtlInitUnicodeString( &usDeviceName, L"\\Device\\msrtest" );
RtlInitUnicodeString(&usDosDeviceName, L"\\DosDevices\\msrtest");
PDEVICE_OBJECT pDeviceObject = NULL;
NTSTATUS NtStatus=IoCreateDevice( pDriverObject, 0, &usDeviceName, FILE_DEVICE_SECURE_OPEN, 0, FALSE, &pDeviceObject);
if( NtStatus != STATUS_SUCCESS){
return NtStatus;
}
pDriverObject->MajorFunction[IRP_MJ_CREATE]=CreateFunction; // CreateFile呼び出し時の処理を登録
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlFunction; // DeviceControl呼び出し時の処理を登録
pDriverObject->MajorFunction[IRP_MJ_CLOSE]=CloseFunction; // CloseFile呼び出し時の処理を登録
pDriverObject->DriverUnload = UnloadDriverFunction; // ドライバがアンロードされる時の処理を登録
NTSTATUS status = IoCreateSymbolicLink(&usDosDeviceName, &usDeviceName);
return STATUS_SUCCESS;
}
// Create
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
PAGED_CODE();
return STATUS_SUCCESS;
}
// Close
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
PAGED_CODE();
return STATUS_SUCCESS;
}
// DeviceControl 周波数,on/off
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){
PAGED_CODE();
int dwDataRead = 0, dwDataWritten = 0;
NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
PIO_STACK_LOCATION pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
if (pIoStackIrp){
// アクセスIDを調べる
if (pIoStackIrp->Parameters.DeviceIoControl.IoControlCode == IOCTL_RDMSR_CODE){
// バッファポインタ取得
PCHAR pInputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
PCHAR pOutputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
// バッファサイズ取得
int InSize = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
int OutSize = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
DWORD32 ads = *(DWORD32*)pInputBuffer;
DWORD32* d32 = (DWORD32*)pOutputBuffer;
DWORD32 l, h;
__try{
__asm{
mov ecx, ads
rdmsr
mov l, eax
mov h, edx
}
d32[0] = l;
d32[1] = h;
Irp->IoStatus.Information = 8;
NtStatus = STATUS_SUCCESS;
}
__except (EXCEPTION_EXECUTE_HANDLER){
Irp->IoStatus.Information = 0;
NtStatus = STATUS_NOT_SUPPORTED;
}
}
}
Irp->IoStatus.Status = NtStatus;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet){
PAGED_CODE();
return STATUS_SUCCESS;
}
// UnloadDriver
VOID UnloadDriverFunction(IN PDRIVER_OBJECT DriverObject){
PAGED_CODE();
IoDeleteDevice(DriverObject->DeviceObject);
}
制御ソフト ソースの概要
cputemp2.cpp
WinMain関数
最初に呼び出される関数です。ダイアログボックスを表示します。
DlgProc関数
ダイアログボックスのプロシージャです。WM_INITDIALOG
ダイアログボックスの初期化時に呼び出されます。GetCpuMax関数により論理CPU数を取得します。
CreateFile APIによりデバイスドライバをオープンします。
デバイスドライバが見つからない場合は、エラーメッセージを出力しプログラムを終了します。
SetTimer APIにより1秒置きに、WM_TIMERメッセージを発生します。
WM_TIMER
1秒置きに呼び出されます。GetCurrentThread APIにより自身のスレッドハンドルを取得します。
各論理CPUごとの処理
SetThreadAffinityMask APIにより実行する論理CPUを固定します。RDMSR関数によりMSRのアドレスIA32_THERM_STATUS(0x019c)の値を取得します。取得値の22~16ビット目は、ジャンクション許容値-現在値を示します。
RDMSR関数によりMSRのアドレスIA32_TEMPERATURE_TARGET(0x01a2)の値を取得します。取得値の23~16ビット目は、ジャンクション許容値を示します。
RDMSR関数の戻り値がTRUEの場合は、指定したアドレスが存在することを示します。
IA32_THERM_STATUSとIA32_TEMPERATURE_TARGETの両方が取得できた場合は、ジャンクション許容値と現在値をダイアログに表示します。
IA32_THERM_STATUSのみ取得できた場合は、許容温度まで残り値を表示します。
IA32_THERM_STATUSとIA32_TEMPERATURE_TARGETの両方とも取得できなかった場合は、KillTimer APIによりタイマーを解放し、CloseHandle APIによりドライバを閉じて、EndDialog APIによりダイアログボックスを終了させます。
WM_COMMAND
IDCANCEL
KillTimer APIによりタイマーを解放し、CloseHandle APIによりドライバを閉じて、EndDialog APIによりダイアログボックスを終了させます。RDMSR関数
第1引数がドライバのハンドル、第2引数がMSRのアドレス、第3引数が取得MSRの値を保存するポインタです。DeviceIoControl経由でデバイスドライバのDeviceControlFunction関数を呼び出します。
DeviceIoControl APIの戻り値のバイト数をチェックし0バイトの場合はFALSEを返します。
GetCPUMax関数
GetSystemInfo APIにより論理CPU数を取得します。制御ソフト(cputemp2) ソースコード
cputemp2.cpp
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <tchar.h>
#include "resource.h"
#define TIMER_ID 200
#define IOCTL_RDMSR_CODE 0x800
#define IA32_TEMPERATURE_TARGET 0x01a2
#define IA32_THERM_STATUS 0x019c
#pragma comment(lib,"comctl32.lib")
LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam); // ダイアログボックスプロシージャー
BOOL RDMSR(HANDLE h, DWORD32 adr,DWORD64* data);
int GetCPUMax(void); // 論理CPU数を取得する
HINSTANCE hInst;
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow){
::hInst = hCurInst;
DialogBox(hCurInst, TEXT("DLG1"), 0, (DLGPROC)DlgProc);
return (int)0;
}
// ダイアログボックスプロシージャー
LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
static int CPUMAX;
static UINT_PTR tid;
static HANDLE hFile;
TCHAR buf[1024];
switch (msg) {
case WM_INITDIALOG:{
CPUMAX = GetCPUMax();
hFile = CreateFile(_TEXT("\\\\.\\msrtest"),
GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE){
MessageBox(hDlg, _TEXT("CreateFileが失敗しました"), _TEXT("エラー"), MB_OK);
EndDialog(hDlg, TRUE);
}
tid = SetTimer(hDlg, TIMER_ID, 1000, NULL);
return TRUE;
}
case WM_TIMER:{
buf[0] = _T('\0');
int n;
int error=0;
HANDLE h = GetCurrentThread();
for (n = 0; n < CPUMAX; n++){
TCHAR s[32];
SetThreadAffinityMask(h, 1 << n);
int t,tjmax;
DWORD64 temp;
if (RDMSR(hFile, IA32_THERM_STATUS, &temp)==TRUE){
t = (temp >> 16) & 0x7f;
if (RDMSR(hFile, IA32_TEMPERATURE_TARGET, &temp)==TRUE){
tjmax = (temp >> 16) & 0xff;
_stprintf_s(s, sizeof(s) / sizeof(TCHAR), _TEXT("CPU%.2d:tjmax %d℃ 温度 %d℃\r\n"), n, tjmax,tjmax-t);
_tcscat_s(buf, sizeof(buf) / sizeof(TCHAR), s);
}else{
_stprintf_s(s, sizeof(s) / sizeof(TCHAR), _TEXT("CPU%.2d:許容温度まで残り%d℃\r\n"), n, t);
_tcscat_s(buf, sizeof(buf) / sizeof(TCHAR), s);
}
}else{
error=-1;
break;
}
}
if(error){
KillTimer(hDlg, tid);
CloseHandle(hFile);
MessageBox(hDlg,_TEXT("このシステムはCPU温度を取得できません。"),_TEXT("エラー"),MB_OK);
EndDialog(hDlg, TRUE);
}
SetWindowText(GetDlgItem(hDlg, IDC_EDIT100), buf);
break;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDCANCEL: // ダイアログの終了
KillTimer(hDlg, tid);
CloseHandle(hFile);
EndDialog(hDlg, TRUE);
return TRUE;
default:
return FALSE;
}
default:
return FALSE;
}
return FALSE;
}
BOOL RDMSR(HANDLE h, DWORD32 adr,DWORD64* data){
DWORD dwReturn;
*data = adr;
DeviceIoControl(h, IOCTL_RDMSR_CODE, data, sizeof(*data),
data, sizeof(*data), &dwReturn, NULL);
if (dwReturn == sizeof(DWORD64))
return TRUE;
else
return FALSE;
}
// 論理CPU数を取得する
int GetCPUMax(void){
SYSTEM_INFO sys;
GetSystemInfo(&sys);
return sys.dwNumberOfProcessors;
}
resource.h
#define IDC_EDIT100 100
resource.rc
#include <windows.h>
#include "resource.h"
DLG1 DIALOG DISCARDABLE 0, 0, 142, 142
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | DS_SETFONT
CAPTION "CPU温度"
FONT 9, "MS 明朝"
{
CONTROL "", IDC_EDIT100, "EDIT", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_MULTILINE | ES_READONLY, 7, 7, 128, 128
}
実行ファイルとソースファイルのダウンロード
実行ファイルとソースファイルのダウンロード rdmsrdrv.zipの内容は以下のとおりです。
rdmsrdrv ├─rdmsrdrv │ └─rdmsrdrv │ │ rdmsrdrv.cpp │ │ makefile │ │ sources │ │ │ └─objfre_wxp_x86 │ └─i386 │ rdmsrdrv.sys │ └─cputemp2 cputemp2.cpp cputemp2.exe
Copyright (C) 2012 山本ワールド All Rights Reserved.