Я пытаюсь использовать RDTSC, но кажется, что мой подход может быть неправильным для получения скорости ядра:
#include "stdafx.h"#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
struct Core
{
int CoreNumber;
};
static void startMonitoringCoreSpeeds(void *param)
{
Core core = *((Core *)param);
SetThreadAffinityMask(GetCurrentThread(), 1 << core.CoreNumber);
while (true)
{
DWORD64 first = __rdtsc();
Sleep(1000);
DWORD64 second = __rdtsc();
cout << "Core " << core.CoreNumber << " has frequency " << ((second - first)*pow(10, -6)) << " MHz" << endl;
}
}
int GetNumberOfProcessorCores()
{
DWORD process, system;
if (GetProcessAffinityMask(GetCurrentProcess(), &process, &system))
{
int count = 0;
for (int i = 0; i < 32; i++)
{
if (system & (1 << i))
{
count++;
}
}
return count;
}
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
}
int _tmain(int argc, _TCHAR* argv[])
{
for (int i = 0; i < GetNumberOfProcessorCores(); i++)
{
Core *core = new Core {0};
core->CoreNumber = i;
_beginthread(startMonitoringCoreSpeeds, 0, core);
}
cin.get();
}
Он всегда выводит значения около 3,3 ГГц, что неправильно, потому что такие вещи, как Turbo Boost, время от времени включаются, и мои ядра наверняка переходят на 4,3 ГГц. Позвольте мне дать перекрестную ссылку на некоторые статьи, стоящие за этой идеей.
Во-первых (http://users.utcluj.ro/~ancapop/labscs/SCS2.pdf): «TSC на ядрах процессора не синхронизированы. Поэтому не уверен, что если процесс мигрирует во время
выполнение от одного ядра к другому, измерение не будет затронуто. Чтобы избежать этой проблемы, сродство измеряемого процесса должно быть установлено только на одно ядро, чтобы предотвратить миграцию процесса ». Это говорит мне о том, что RDTSC должен возвращать другое значение на ядро, в котором находится мой поток, используя маску сродства, которую я установил, что очень хорошо ,
Во-вторых, и, пожалуйста, проверьте эту статью (http://randomascii.wordpress.com/2011/07/29/rdtsc-in-the-age-of-sandybridge/): «Если вам нужен согласованный таймер, который работает на всех ядрах и может использоваться для измерения времени, то это хорошая новость. Если вы хотите измерить фактические тактовые частоты процессора, вам не повезло. Если вы хотите согласованности в широком диапазоне семейства процессоров — это отстой, чтобы быть вами. Обновление: раздел 16.11 Руководства по системному программированию Intel документирует это поведение счетчика меток времени. Грубо говоря, это говорит о том, что на старых процессорах тактовая частота изменяется, но на более новых процессорах она остается одинаковой В завершение говорится о Constant TSC: «Это архитектурное поведение, движущееся вперед». Хорошо, это говорит мне о том, что RDTSC остается согласованным, что делает мои результаты вышеописанными, так как ядра моего процессора имеют стандартную частоту 3,3 ГГц …
Что ДЕЙСТВИТЕЛЬНО напрашивается на вопрос: как такие приложения, как Intel Turbo Boost Technology Monitor и Piriform Speccy и CPUID CPU-Z, измеряют тактовую частоту процессора в режиме реального времени в режиме турбо-усиления?
Полное решение следует. Я адаптировал Пример драйвера IOCTL в MSDN сделать это. Обратите внимание, что образец IOCTL единственный относительный образец скелетного драйвера WDM, который я смог найти, а также самое близкое, что я мог найти к шаблону WDM потому что большинство шаблонов режима ядра из коробки в WDK являются драйверами на основе WDF (любой шаблон драйвера WDM на самом деле пустой, без исходного кода), но пока единственный пример логики, которую я видел, чтобы сделать этот ввод / вывод, был через драйвер на основе WDM. Кроме того, я узнал несколько забавных фактов: драйверам ядра не нравится плавающая арифметика, и вы не можете использовать «windows.h», который действительно ограничивает вас до «ntddk.h», специального заголовка режима ядра. Это также означает, что я не могу выполнять все свои вычисления в режиме ядра, потому что я не могу вызывать такие функции, как QueryPerformanceFrequency, поэтому мне пришлось получить среднее соотношение производительности между временными метками и вернуть их обратно в пользовательский режим для некоторых вычислений ( без QueryPerformanceFrequency значения, которые вы получаете из регистров ЦП, в которых хранятся галочки, наподобие того, что использует QueryPerformanceCounter, бесполезны, потому что вы не знаете размер шага; возможно, есть обходной путь, но я решил просто использовать среднее, так как он работает довольно хорошо) , Кроме того, в соответствии со сном в одну секунду, причина, которую я использовал, заключается в том, что в противном случае вы почти дерьмово вычисляете дерьмо на нескольких потоках, что действительно портит ваши вычисления, потому что ваши частоты будут расти на ядро, постоянно проверяя результаты из QueryPerformanceCounter (вы поднимите свои ядра, поскольку вы делаете больше вычислений) — НЕ НАМЕРЕНО — это соотношение … так что время дельта не так важно, так как его циклы за время …вы всегда можете увеличить дельту, она все равно должна дать вам такое же соотношение относительно размера шага. Кроме того, это настолько минималистично, насколько я мог это себе представить. Удачи, сделав его намного меньше или короче этого. Кроме того, если вы хотите установить драйвер, вы есть два варианта если вы не хотите купить сертификат подписи кода у какой-либо третьей стороны, оба сосут, так что выбирайте один и поглощайте его. Начнем с драйвера:
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_PRAGMANTSTATUS
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;
}
driver.h:
//
// Device type -- in the "User Defined" range."//
#define SIOCTL_TYPE 40000
//
// The IOCTL function codes from 0x800 to 0xFFF are for customer use.
//
#define IOCTL_SIOCTL_METHOD_IN_DIRECT \
CTL_CODE( SIOCTL_TYPE, 0x900, METHOD_IN_DIRECT, FILE_ANY_ACCESS )
#define IOCTL_SIOCTL_METHOD_OUT_DIRECT \
CTL_CODE( SIOCTL_TYPE, 0x901, METHOD_OUT_DIRECT , FILE_ANY_ACCESS )
#define IOCTL_SIOCTL_METHOD_BUFFERED \
CTL_CODE( SIOCTL_TYPE, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define IOCTL_SIOCTL_METHOD_NEITHER \
CTL_CODE( SIOCTL_TYPE, 0x903, METHOD_NEITHER , FILE_ANY_ACCESS )#define DRIVER_FUNC_INSTALL 0x01
#define DRIVER_FUNC_REMOVE 0x02
#define DRIVER_NAME "ReadMSRDriver"
Теперь вот приложение, которое загружается и использует драйвер (консольное приложение Win32):
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);
Sleep(1000);
}
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);
}
Он использует install.cpp, который вы можете получить из примера IOCTL. Я опубликую работающее, полностью рабочее и готовое решение (с кодом, очевидно) на мой блог в течение следующих нескольких дней, если не сегодня вечером.
Редактировать: Blogged это в http://www.dima.to/blog/?p=101 (полный исходный код доступен там) …