Note that the complete solution and download link is at the bottom. Modern Intel CPU’s have features such as Turbo Boost, which will vary the frequency of your CPU. Please note that (and I have seen this on multiple machines I own) you should disable the Hyper-V feature in Windows before trying out the code below, as Hyper-V seems to stop clock-variations from taking place on Intel CPU’s. In other words, it seems to stop Turbo Boost. To disable Hyper-V, go to Control Panel -> Uninstall a program -> Turn Windows features on or off (from the menu to the left of the window), and then deselect the Hyper-V check-box, and hit OK.
Before the introduction of Turbo Boost, functions such as QueryPerformanceFrequency in conjunction with some other methods provided an effective means of detecting a CPU’s clock speed. This is no longer the case. QueryPerformanceFrequency only seems to return the maximum frequency before clock-varying technologies start to kick in. However, this function is still going to be important (and still going to be used in our calculations). The actual formula for calculating CPU frequency is the maximum CPU frequency before clock-varying technologies multiplied by two Intel-specific registers which give you the ratio of actual performance over maximum performance (actualFrequency = maximumFrequencyWithoutClockSpeedVariations * APERF / MPERF). There registers are known as APERF and MPERF, and the ratio of APERF / MPERF is what we need to fetch.
To obtain the values of model-specific registers such as APERF or MPERF, we need to generate the rdmsr instruction using an Intrinsic Function known as __readmsr. Unfortunately, calling this function is not as simple as just, well…calling it. As the remark for it on MSDN goes, “This function is only available in kernel mode, and the routine is only available as an intrinsic.” When you write an application in Windows, like a WPF application or a Console Application, it runs as a user-mode application, meaning that it has some security restrictions in what you can do with it and that it runs in a ring of security called Ring 3, the least privileged security level. However, a kernel-mode application runs in Ring 0, the most privileged security level. Wikipedia has a great article on Protection Rings if you’d like to read more about them. As far as I know, the only way to get into kernel-mode is to write a kernel-mode driver. What this all means is that we need to have a user-mode application that talks to a kernel-mode driver in order to fetch the values of APERF and MPERF.
I started by downloading the Windows Driver Kit, which you will also need, so go ahead and grab it. I spent a lot of time trying to find examples of kernel-mode drivers that communicated with user-mode applications, and I came across a nice example on CodeProject. I noticed that the example in question uses driver source code that is only for WDM-based drivers, whereas most driver templates in the Windows Driver Kit are WDF-based. In fact, the latest Windows Driver Kit has no WDM driver with source code in it (only a blank template with absolutely no code). You can read about the differences between WDF and WDM on MSDN. WDM is clearly what we are after, as this article states, “The WDM model is closely tied to the operating system. Drivers interact directly with the operating system by calling system service routines and manipulating operating system structures. Because WDM drivers are trusted kernel-mode components, the system provides limited checks on driver input.” After spending some time looking through the Windows Driver Kit samples, I found the IOCTL sample driver which is a perfect sample skeleton driver that we can alter to fit our needs. Its description reads, “This sample driver is not a Plug and Play driver. This is a minimal driver meant to demonstrate a feature of the operating system. Neither this driver nor its sample programs are intended for use in a production environment. Instead, they are intended for educational purposes and as a skeleton driver.” I began modifying that driver to fit my actual needs. I also learned a few things along the way. For one, kernel drivers don’t like floating point arithmetic. I’m not even entirely sure that they will compile if you try to make use of a double or float type unless you find a means of explicitly including that type definition inside of your driver. Also, you’re limited because you cannot use the Windows header, and must use a special kernel-mode header known as the Ntddk header. I’ve modified the IOCTL sample skeleton as follows:
driver.c
// // Include files. // #include <ntddk.h> // various NT definitions #include <string.h> #include <intrin.h> #include "driver.h" #define NT_DEVICE_NAME L"\\Device\\KernelModeDriver" #define DOS_DEVICE_NAME L"\\DosDevices\\KernelModeDriver" #if DBG #define DRIVER_PRINT(_x_) \ DbgPrint("KernelModeDriver.sys: ");\ DbgPrint _x_; #else #define DRIVER_PRINT(_x_) #endif // // Device driver routine declarations. // DRIVER_INITIALIZE DriverEntry; _Dispatch_type_(IRP_MJ_CREATE) _Dispatch_type_(IRP_MJ_CLOSE) DRIVER_DISPATCH DriverCreateClose; _Dispatch_type_(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH DriverDeviceControl; DRIVER_UNLOAD DriverUnloadDriver; VOID PrintIrpInfo( PIRP Irp ); VOID PrintChars( _In_reads_(CountChars) PCHAR BufferAddress, _In_ size_t CountChars ); #ifdef ALLOC_PRAGMA #pragma alloc_text( INIT, DriverEntry ) #pragma alloc_text( PAGE, DriverCreateClose) #pragma alloc_text( PAGE, DriverDeviceControl) #pragma alloc_text( PAGE, DriverUnloadDriver) #pragma alloc_text( PAGE, PrintIrpInfo) #pragma alloc_text( PAGE, PrintChars) #endif // ALLOC_PRAGMA NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) /*++ Routine Description: This routine is called by the Operating System to initialize the driver. It creates the device object, fills in the dispatch entry points and completes the initialization. Arguments: DriverObject - a pointer to the object that represents this device driver. RegistryPath - a pointer to our Services key in the registry. Return Value: STATUS_SUCCESS if initialized; an error otherwise. --*/ { NTSTATUS ntStatus; UNICODE_STRING ntUnicodeString; // NT Device Name "\Device\KernelModeDriver" UNICODE_STRING ntWin32NameString; // Win32 Name "\DosDevices\KernelModeDriver" PDEVICE_OBJECT deviceObject = NULL; // ptr to device object UNREFERENCED_PARAMETER(RegistryPath); RtlInitUnicodeString( &ntUnicodeString, NT_DEVICE_NAME ); ntStatus = IoCreateDevice( DriverObject, // Our Driver Object 0, // We don't use a device extension &ntUnicodeString, // Device name "\Device\KernelModeDriver" FILE_DEVICE_UNKNOWN, // Device type FILE_DEVICE_SECURE_OPEN, // Device characteristics FALSE, // Not an exclusive device &deviceObject ); // Returned ptr to Device Object if ( !NT_SUCCESS( ntStatus ) ) { DRIVER_PRINT(("Couldn't create the device object\n")); return ntStatus; } // // Initialize the driver object with this driver's entry points. // DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverCreateClose; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverCreateClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl; DriverObject->DriverUnload = DriverUnloadDriver; // // Initialize a Unicode String containing the Win32 name // for our device. // RtlInitUnicodeString( &ntWin32NameString, DOS_DEVICE_NAME ); // // Create a symbolic link between our device name and the Win32 name // ntStatus = IoCreateSymbolicLink( &ntWin32NameString, &ntUnicodeString ); if ( !NT_SUCCESS( ntStatus ) ) { // // Delete everything that this routine has allocated. // DRIVER_PRINT(("Couldn't create symbolic link\n")); IoDeleteDevice( deviceObject ); } return ntStatus; } NTSTATUS DriverCreateClose( PDEVICE_OBJECT DeviceObject, PIRP Irp ) /*++ Routine Description: This routine is called by the I/O system when the KernelModeDriver is opened or closed. No action is performed other than completing the request successfully. Arguments: DeviceObject - a pointer to the object that represents the device that I/O is to be done on. Irp - a pointer to the I/O Request Packet for this request. Return Value: NT status code --*/ { UNREFERENCED_PARAMETER(DeviceObject); PAGED_CODE(); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest( Irp, IO_NO_INCREMENT ); return STATUS_SUCCESS; } VOID DriverUnloadDriver( _In_ PDRIVER_OBJECT DriverObject ) /*++ Routine Description: This routine is called by the I/O system to unload the driver. Any resources previously allocated must be freed. Arguments: DriverObject - a pointer to the object that represents our driver. Return Value: None --*/ { PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject; UNICODE_STRING uniWin32NameString; PAGED_CODE(); // // Create counted string version of our Win32 device name. // RtlInitUnicodeString( &uniWin32NameString, DOS_DEVICE_NAME ); // // Delete the link from our device name to a name in the Win32 namespace. // IoDeleteSymbolicLink( &uniWin32NameString ); if ( deviceObject != NULL ) { IoDeleteDevice( deviceObject ); } } NTSTATUS DriverDeviceControl( PDEVICE_OBJECT DeviceObject, PIRP Irp ) /*++ Routine Description: This routine is called by the I/O system to perform a device I/O control function. Arguments: DeviceObject - a pointer to the object that represents the device that I/O is to be done on. Irp - a pointer to the I/O Request Packet for this request. Return Value: NT status code --*/ { PIO_STACK_LOCATION irpSp;// Pointer to current stack location NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success ULONG inBufLength; // Input buffer length ULONG outBufLength; // Output buffer length void *inBuf; // pointer to input buffer unsigned __int64 *outBuf; // pointer to the output buffer UNREFERENCED_PARAMETER(DeviceObject); PAGED_CODE(); irpSp = IoGetCurrentIrpStackLocation( Irp ); inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength; outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength; if (!inBufLength || !outBufLength || outBufLength != sizeof(unsigned __int64)*2) { ntStatus = STATUS_INVALID_PARAMETER; goto End; } // // Determine which I/O control code was specified. // switch ( irpSp->Parameters.DeviceIoControl.IoControlCode ) { case IOCTL_SIOCTL_METHOD_BUFFERED: // // In this method the I/O manager allocates a buffer large enough to // to accommodate larger of the user input buffer and output buffer, // assigns the address to Irp->AssociatedIrp.SystemBuffer, and // copies the content of the user input buffer into this SystemBuffer // DRIVER_PRINT(("Called IOCTL_SIOCTL_METHOD_BUFFERED\n")); PrintIrpInfo(Irp); // // Input buffer and output buffer is same in this case, read the // content of the buffer before writing to it // inBuf = (void *)Irp->AssociatedIrp.SystemBuffer; outBuf = (unsigned __int64 *)Irp->AssociatedIrp.SystemBuffer; // // Read the data from the buffer // DRIVER_PRINT(("\tData from User :")); // // We are using the following function to print characters instead // DebugPrint with %s format because we string we get may or // may not be null terminated. // PrintChars(inBuf, inBufLength); // // Write to the buffer // unsigned __int64 data[sizeof(unsigned __int64) * 2]; data[0] = __readmsr(232); data[1] = __readmsr(231); DRIVER_PRINT(("data[0]: %d", data[0])); DRIVER_PRINT(("data[1]: %d", data[1])); RtlCopyBytes(outBuf, data, outBufLength); // // Assign the length of the data copied to IoStatus.Information // of the Irp and complete the Irp. // Irp->IoStatus.Information = sizeof(unsigned __int64)*2; // // When the Irp is completed the content of the SystemBuffer // is copied to the User output buffer and the SystemBuffer is // is freed. // break; default: // // The specified I/O control code is unrecognized by this driver. // ntStatus = STATUS_INVALID_DEVICE_REQUEST; DRIVER_PRINT(("ERROR: unrecognized IOCTL %x\n", irpSp->Parameters.DeviceIoControl.IoControlCode)); break; } End: // // Finish the I/O operation by simply completing the packet and returning // the same status as in the packet itself. // Irp->IoStatus.Status = ntStatus; IoCompleteRequest( Irp, IO_NO_INCREMENT ); return ntStatus; } VOID PrintIrpInfo( PIRP Irp) { PIO_STACK_LOCATION irpSp; irpSp = IoGetCurrentIrpStackLocation( Irp ); PAGED_CODE(); DRIVER_PRINT(("\tIrp->AssociatedIrp.SystemBuffer = 0x%p\n", Irp->AssociatedIrp.SystemBuffer)); DRIVER_PRINT(("\tIrp->UserBuffer = 0x%p\n", Irp->UserBuffer)); DRIVER_PRINT(("\tirpSp->Parameters.DeviceIoControl.Type3InputBuffer = 0x%p\n", irpSp->Parameters.DeviceIoControl.Type3InputBuffer)); DRIVER_PRINT(("\tirpSp->Parameters.DeviceIoControl.InputBufferLength = %d\n", irpSp->Parameters.DeviceIoControl.InputBufferLength)); DRIVER_PRINT(("\tirpSp->Parameters.DeviceIoControl.OutputBufferLength = %d\n", irpSp->Parameters.DeviceIoControl.OutputBufferLength )); return; } VOID PrintChars( _In_reads_(CountChars) PCHAR BufferAddress, _In_ size_t CountChars ) { PAGED_CODE(); if (CountChars) { while (CountChars--) { if (*BufferAddress > 31 && *BufferAddress != 127) { KdPrint (( "%c", *BufferAddress) ); } else { KdPrint(( ".") ); } BufferAddress++; } KdPrint (("\n")); } return; }
And finally, the user-mode Win32 Console Application that loads and also runs this driver:
FrequencyCalculator.cpp
#include "stdafx.h" #include <iostream> #include <windows.h> #include <winioctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strsafe.h> #include <process.h> #include "..\KernelModeDriver\driver.h" using namespace std; BOOLEAN ManageDriver( _In_ LPCTSTR DriverName, _In_ LPCTSTR ServiceName, _In_ USHORT Function ); HANDLE hDevice; TCHAR driverLocation[MAX_PATH]; void InstallDriver() { DWORD errNum = 0; GetCurrentDirectory(MAX_PATH, driverLocation); _tcscat_s(driverLocation, _T("\\KernelModeDriver.sys")); std::wcout << "Trying to install driver at " << driverLocation << std::endl; // // open the device // if ((hDevice = CreateFile(_T("\\\\.\\KernelModeDriver"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { errNum = GetLastError(); if (errNum != ERROR_FILE_NOT_FOUND) { printf("CreateFile failed! ERROR_FILE_NOT_FOUND = %d\n", errNum); return; } // // The driver is not started yet so let us the install the driver. // First setup full path to driver name. // if (!ManageDriver(_T(DRIVER_NAME), driverLocation, DRIVER_FUNC_INSTALL )) { printf("Unable to install driver. \n"); // // Error - remove driver. // ManageDriver(_T(DRIVER_NAME), driverLocation, DRIVER_FUNC_REMOVE ); return; } hDevice = CreateFile(_T("\\\\.\\KernelModeDriver"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hDevice == INVALID_HANDLE_VALUE){ printf("Error: CreatFile Failed : %d\n", GetLastError()); return; } } } void UninstallDriver() { // // close the handle to the device. // CloseHandle(hDevice); // // Unload the driver. Ignore any errors. // ManageDriver(_T(DRIVER_NAME), driverLocation, DRIVER_FUNC_REMOVE ); } double GetPerformanceRatio() { BOOL bRc; ULONG bytesReturned; int input = 0; unsigned __int64 output[2]; memset(output, 0, sizeof(unsigned __int64) * 2); //printf("InputBuffer Pointer = %p, BufLength = %d\n", &input, sizeof(&input)); //printf("OutputBuffer Pointer = %p BufLength = %d\n", &output, sizeof(&output)); // // Performing METHOD_BUFFERED // //printf("\nCalling DeviceIoControl METHOD_BUFFERED:\n"); bRc = DeviceIoControl(hDevice, (DWORD)IOCTL_SIOCTL_METHOD_BUFFERED, &input, sizeof(&input), output, sizeof(unsigned __int64)*2, &bytesReturned, NULL ); if (!bRc) { //printf("Error in DeviceIoControl : %d", GetLastError()); return 0; } //printf(" OutBuffer (%d): %d\n", bytesReturned, output); if (output[1] == 0) { return 0; } else { return (float)output[0] / (float)output[1]; } } struct Core { int CoreNumber; }; int GetNumberOfProcessorCores() { SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); return sysinfo.dwNumberOfProcessors; } float GetCoreFrequency() { // __rdtsc: Returns the processor time stamp which records the number of clock cycles since the last reset. // QueryPerformanceCounter: Returns a high resolution time stamp that can be used for time-interval measurements. // Get the frequency which defines the step size of the QueryPerformanceCounter method. LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); // Get the number of cycles before we start. ULONG cyclesBefore = __rdtsc(); // Get the Intel performance ratio at the start. float ratioBefore = GetPerformanceRatio(); // Get the start time. LARGE_INTEGER startTime; QueryPerformanceCounter(&startTime); // Give the CPU cores enough time to repopulate their __rdtsc and QueryPerformanceCounter registers. Sleep(1000); ULONG cyclesAfter = __rdtsc(); // Get the Intel performance ratio at the end. float ratioAfter = GetPerformanceRatio(); // Get the end time. LARGE_INTEGER endTime; QueryPerformanceCounter(&endTime); // Return the number of MHz. Multiply the core's frequency by the mean MSR (model-specific register) ratio (the APERF register's value divided by the MPERF register's value) between the two timestamps. return ((ratioAfter + ratioBefore) / 2)*(cyclesAfter - cyclesBefore)*pow(10, -6) / ((endTime.QuadPart - startTime.QuadPart) / frequency.QuadPart); } struct CoreResults { int CoreNumber; float CoreFrequency; }; CRITICAL_SECTION printLock; static void printResult(void *param) { EnterCriticalSection(&printLock); CoreResults coreResults = *((CoreResults *)param); std::cout << "Core " << coreResults.CoreNumber << " has a speed of " << coreResults.CoreFrequency << " MHz" << std::endl; delete param; LeaveCriticalSection(&printLock); } bool closed = false; static void startMonitoringCoreSpeeds(void *param) { Core core = *((Core *)param); SetThreadAffinityMask(GetCurrentThread(), 1 << core.CoreNumber); while (!closed) { CoreResults *coreResults = new CoreResults(); coreResults->CoreNumber = core.CoreNumber; coreResults->CoreFrequency = GetCoreFrequency(); _beginthread(printResult, 0, coreResults); } delete param; } int _tmain(int argc, _TCHAR* argv[]) { InitializeCriticalSection(&printLock); InstallDriver(); for (int i = 0; i < GetNumberOfProcessorCores(); i++) { Core *core = new Core{ 0 }; core->CoreNumber = i; _beginthread(startMonitoringCoreSpeeds, 0, core); } std::cin.get(); closed = true; UninstallDriver(); DeleteCriticalSection(&printLock); }
To have the user-mode application actually install this driver, you need to either:
- Sign the driver with a Code Signing certificate you’ve obtained from some third party issuer. To sign the driver, right click the driver’s project file, go to Configuration Properties -> Driver Signing -> General, and change the Sign Mode, setting the Production Certificate to the Code Signing certificate you have installed into your machine from the third party issuer.
- Do not sign the driver and either disable Driver Signature Verification from the advanced Windows start-up boot options or disable Secure Boot from your BIOS and enable test signing, and generate a new test certificate from the drop-down menu under the driver’s Configuration Properties -> Driver Signing -> General -> Test Certificate.
If you’ve completed the above steps, the code should run just fine (if you run it under Administrator credentials). Finally, you can download the complete source code of my implementation. Note that, you use this code at your own risk and are entirely liable for any problems that may arise out of it, whatsoever. I do ask that if you use my driver, you give me a little credit. Just throw my name in a comment somewhere. Here is the download link: FrequencyCalculator.zip.