Calculating the Core Frequencies of a Modern Intel CPU with Clock-Varying Features in Visual C++ on a Windows Machine

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.

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle

"It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward

"Science does not know its debt to imagination." - Ralph Waldo Emerson

"Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump

"All our dreams can come true, if we have the courage to pursue them." - Walt Disney

"Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

I'm not entirely sure what makes me successful in general programming or development, but to any newcomers to this blood-sport, my best guess would be that success in programming comes from some strange combination of interest, persistence, patience, instincts (for example, someone might tell you that something can't be done, or that it can't be done a certain way, but you just know that can't be true, or you look at a piece of code and know something doesn't seem right with it at first glance, but you can't quite put your finger on it until you think it through some more), fearlessness of tinkering, and an ability to take advice because you should be humble. Its okay to be wrong or to have a bad approach, realize it, and try to find a better one, and even better to be wrong and find a better approach to solve something than to have had a bad approach to begin with. I hope that whatever fragments of information I sprinkle across here help those who hit the same roadblocks.

4 thoughts to “Calculating the Core Frequencies of a Modern Intel CPU with Clock-Varying Features in Visual C++ on a Windows Machine”

  1. Nice article.
    You can also use the Intel Performance Monitoring Counters for a precise reading.
    As a UNIX programmer, you can browse source code of my Turbo monitoring program
    XFreq

Leave a Reply

Your email address will not be published. Required fields are marked *