概要

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度ということです。

テスト環境

コンパイラ

ドライバの開発 WDK7
ドライバを制御するソフトの開発 Visual C++ 2013 Express

実行環境

Windows 7 Professional Service Pack 1 32bit
Windows 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でデバイスドライバを開発することとします。

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