| 04.02.2009 12:00 | |
Чак ДеСильва (Chuck DeSylva)
Данный документ описывает технику получения информации о питании от операционной системы. Данный метод, главным образом, имеет своей целью контроль питания в системах, основанных на технологии Intel® x86 и работающих с технологией Intel® SpeedStep®. Метод применяется также тогда, когда использование инструкции счетчика временной метки чтения варьируется в зависимости от изменения частот, вызванного технологией Intel Speedstep.
Вы должны знать основы управления питанием в операционной системе Microsoft Windows XP* и иметь начальные знания технологии Intel SpeedStep1, а также понимать принципы работы драйверов устройств в Microsoft Windows XP и потоковых прикладных программных интерфейсов Win32*.
Во всех процессорах, от самого первого Intel® 8088 с тактовой частотой 4,77 МГц до новейшего процессора Pentium® 4, существовали внутренние системные часы, работающие на частоте 14 318 160 Гц. По причинам, кроющимся в изначальной конструкции системы персонального компьютера, этот такт делится на 12, что составляет 1 193 180 Гц. Разделенный такт в дальнейшем вводится в 16-рязрядный счетчик, который обнуляется каждые 65 536 циклов; таким образом, 1 193 180 Гц делится на 65 536, а итоговая частота составляет 18,2 Гц. В системах с процессором 8088 с тактовой частотой 4,7 МГц эта частота была доступна в качестве аппаратного прерывания.
При помощи подсчета числа прерываний, вызванных обнулением (по 8 разрядов за один раз) 16-разрядного счетчика/таймера, получается довольно точный аппаратный счетчик. Это первоначальный метод считывания системных часов, однако он имеет свои особенности. Одной из существующих проблем данного способа считывания такта является то, что считать цикл прерывания и аппаратный делитель в одно и то же время невозможно, а при считывании числа прерываний (обнулений) нельзя точно сказать, было ли это число считано до следующего прерывания или после (по отношению к циклу в 16-разрядном делителе) появления возможности "синтезирования" аппаратного состояния внутри связанного счетчика, работающего на частоте 1,193180 МГц. Инвертирование этого показателя дает интервал счетчика 0,838 микросекунд, или 838 наносекунд. Дальнейший вызов системных часов с большей частотой приведет к ошибке и неточным результатам по причине более длинного интервала такта.
В процессорах Pentium появилась дополнительная инструкция счетчика временной метки чтения (RDTSC). Это предоставляет программе прямой доступ к числу циклов такта, произошедших в процессоре с момента последнего включения или перезагрузки. На современных тактовых частотах, например, 3,06 ГГц, период синхронизации составляет, таким образом, 0,326 наносекунд.
В отличные от старых часов с частотой 1,193180 Гц, скорость такта, определяемая RDTSC, полностью зависит от частоты ядра процессора. В связи с этим RDTSC меняет показания счетчика по мере изменения системной скорости во время работы Intel SpeedStep, что не может гарантировать последовательности считываемых ей данных. Целью настоящего документа является предоставление метода не только для получения последовательных измерений RDTSC на машинах с технологией Intel SpeedStep, но также для получения механизма, зависящего от событий, в то время как изменения в управлении питанием в операционной системе могут быть обнаружены и безопасно устранены в целях обеспечения последовательности.
Что касается данного документа, однопроцессорный уровень аппаратных абстракций ОС Windows 2000*/XP использует тот же счетчик с частотой 1,193180 МГц для прикладного программного интерфейса "Счетчик производительности запроса", что и предыдущие версии Windows. Однако многопроцессорный уровень аппаратных абстракций ОС Windows 2000 и XP использует собственную скорость такта системы через процессорную инструкцию RDTSC. Это вызывает проблемы в программах, в которых происходит мониторинг производительности и включена поддержка технологии Intel Speed Stepping, поскольку значения, считываемые из инструкции RDTSC, изменяются по отношению к частоте, и, следовательно, считываемые временные значения обычно искажены.
Посредством изменения энергопотребления в системе, использующей технологию Intel Speed Stepping таким образом, что технология Speed Stepping применяется в случае постоянства частоты, инструкция RDTSC демонстрирует надежность в интервале между считываниями и, следовательно, сможет использоваться для целей профилирования кода. Это можно очень просто сделать путем непрямых вызовов в прикладной программный интерфейс схемы энергопотребления процессора (вызовы ReadProcessorPwrScheme и WriteProcessorPwrScheme). Однако ОС Windows XP не позволяет выполнить метод прерывания при изменении схемы энергопотребления. Цель этого механизма заключается в предоставлении уведомления в том случае, если в результате выполнения какого-либо процесса или несанкционированного доступа пользователя схема энергопотребления изменяется на другую, в которой не установлено состояние изменения частоты во время проведения сеанса профилирования.
Описанный здесь метод использует драйвер питания для выявления тонкостей и уведомления обработчика событий в пространстве пользовательских процессов. Следовательно, пользовательский процесс может отложить любые изменения схем энергопотребления после завершения сессии профилирования, сохранив, таким образом, последовательность метрик профилирования.
Перехват событий производительности
Данный метод подразумевает первоначальную регистрацию обратного вызова события управления питанием (DeviceDispatchPower) в драйвере, запущенном на уровне ядра. Источник драйвера для этой операции указан в разделе 2.2.2.3. Данная операция реагирует на все события управления питанием в операционной системе. В стандартных сценариях управления питанием данная операция становится доступна для драйвера, благодаря чему драйвер может изменять состояние системы питания на том оборудовании, которым он управляет. Поскольку драйвер фильтрует события управления питанием для всей системы, он предоставляет простую схему фильтрации событий и вредоносных изменений событий путем их переноса к моменту завершения сеанса профилирования.
На рисунке 1 данная архитектура показана с высшего уровня

Рисунок 1. Архитектура уведомления о событиях управления питанием
Для получения уведомлений о событиях управления питанием в ОС Microsoft Windows XP было предложено простое решение. При помощи несложного драйвера и нескольких вызовов пользовательские приложения могут принимать уведомления о наступлении событий управления питанием без необходимости опроса операционной системы для получения состояния системы питания. Данный метод особенно полезен в тех случаях, когда необходимо особенно тщательно следить за производительностью системы питания в системах на основе Intel® x86 с технологией Intel SpeedStep. Метод применяется также тогда, когда использование инструкции счетчика временной метки чтения варьируется в зависимости от изменения частот, вызванного технологией Intel Speedstep.
Mr. Desylva is a Software Applications Engineering Manager in the Intel Software and Solutions Group. He and his team are responsible for the performance optimization of cutting edge consumer software titles running on Intel Desktop systems. Prior to working on application software optimization, Chuck worked as a driver developer for Intel Corporation. He was involved in developing/deploying the first device drivers for USB, AGP (GART) and Intel’s first graphics devices (i740/810(e)).
Загрузите пример исходного кода
Статьи
- Пример исходного кода для включения оповещений о перехвате информации о питании
- Руководство Intel® по архитектуре мобильных приложений
- Сообщения WM_POWERBROADCAST в системе Windows в мобильной среде
- Глубокий сон: как правильно ввести приложение в спящий режим
- Управление питанием: проектирование приложений с целью увеличения времени работы батарей
- Демонстрация силы процессора в ходе расчетов, часть 1 (необходим хороший пакет тестирования с надлежащими характеристиками)
Центры разработчика Intel
- Мобилизованное программное обеспечение
- технологии Intel® Centrino® Mobile
- процессоры Intel® PCA
- Windows*
Сообщества
Другие ресурсы
- CMP Mobilized Software Site
Архитектура данного проекта была разделена на три части. Первая часть – это тестовый исполняемый файл, вторая часть – библиотека DLL (динамическая подключаемая библиотека), отделяющая вызовы драйвера устройства, и, наконец, третья часть – сам драйвер устройства, отвечающий на события управления питанием. Данный исходный код был скомпилирован при помощи Microsoft Visual Studio* 6.0 и Microsoft Visual Studio .Net* 2003. Обратите, что стандартные заголовочные файлы Microsoft (stdafx.cpp, stdafx.h) в тексте программы опущены.
PWRCONTROLTEST.CPP:
//...
#include <windows.h>
HINSTANCE hPWRDLL;
typedef bool (CALLBACK* t_fnPWRDLL_CB)(void);
t_fnPWRDLL_CB fnPWRDLL_ESS;
t_fnPWRDLL_CB fnPWRDLL_DSS;
void main( void ) {
bool blReVal = false;
hPWRDLL = LoadLibrary("PWRDLL");
if (hPWRDLL != NULL) {
fnPWRDLL_ESS = (t_fnPWRDLL_CB)GetProcAddress( hPWRDLL,
"ReturnOriginalPowerScheme" );
if (!fnPWRDLL_ESS)
{
printf("Could not get EnableSpeedStepping Proc Handle\n");
return;
}
fnPWRDLL_DSS = (t_fnPWRDLL_CB)GetProcAddress( hPWRDLL,
"SetAlwaysOnPowerScheme" );
if (!fnPWRDLL_DSS)
{
printf("Could not get SetAlwaysOnPowerScheme Proc Handle\n");
return;
}
}
if ( !fnPWRDLL_DSS() ) {
MessageBox(NULL, "Unable to set Always on Power Scheme",
"PwrControlTest.EXE", MB_OK );
return;
}
// Sleep for 20 seconds, time enough to play with power scheme and
// test the power event interception handling.
Sleep( 20000 );
if ( !fnPWRDLL_ESS() ) {
MessageBox(NULL, "Unable to return original Power Scheme",
"PwrControlTest.EXE", MB_OK );
return;
}
FreeLibrary( hPWRDLL );
return;
}Динамическая подключаемая библиотека
PWRDLL.CPP: extern "C" {
#include <windows.h&rt;
#include <winnt.h&rt;
#include <powrprof.h&rt;
};
//...
#define ALWAYS_ON 0x0
MACHINE_PROCESSOR_POWER_POLICY mppp;
UINT uiDynThrottleAC;
UINT uiDynThrottleDC;
bool bHasBeenDisabledFirst = FALSE;
bool bStopEvent = false;
bool bDriverEnabled = false;
static HANDLE h_DeviceDriver;
HANDLE SharedEvent;
DWORD WINAPI SetupDriverEvent(LPVOID sParms)
{
DWORD bytesReturned;
DWORD WaitStatus;
UINT psIdx = 0;
bool bRet = FALSE;
// Create named event shared between the driver & calling process.
SharedEvent = CreateEvent(NULL, false, false, "SharedEvent");
if ( SharedEvent == NULL ) {
return 0;
}
// The named event has been created and can now open a handle to it
if (!DeviceIoControl( h_DeviceDriver, IOCTL_PWRNTFY_EVENT, NULL, 0,
NULL, 0, &bytesReturned, NULL )) {
return 0;
}
if (!DeviceIoControl( h_DeviceDriver, IOCTL_PWRNTFY_EVENT_START, NULL, 0,
NULL, 0, &bytesReturned, NULL )) {
return 0;
}
while ( !bStopEvent )
{
WaitStatus = WaitForSingleObject( SharedEvent, INFINITE );
if ( WaitStatus == WAIT_OBJECT_0 )
{
GetActivePwrScheme(&psIdx);
if ( ReadProcessorPwrScheme(psIdx, &mppp) )
{
uiDynThrottleAC = mppp.ProcessorPolicyAc.DynamicThrottle;
uiDynThrottleDC = mppp.ProcessorPolicyDc.DynamicThrottle;
}
mppp.ProcessorPolicyAc.DynamicThrottle = ALWAYS_ON;
mppp.ProcessorPolicyDc.DynamicThrottle = ALWAYS_ON;
WriteProcessorPwrScheme(psIdx, &mppp);
SetActivePwrScheme(psIdx, NULL, NULL);
}
}
return 1;
}
SPEEDSTEPDLL_API bool ReturnOriginalPowerScheme( void ) {
UINT psIdx = 0;
bool bRet = FALSE;
bStopEvent = TRUE;
if ( bHasBeenDisabledFirst == true ) {
if ( GetActivePwrScheme(&psIdx) ) {
if ( ReadProcessorPwrScheme(psIdx, &mppp) ) {
mppp.ProcessorPolicyAc.DynamicThrottle = uiDynThrottleAC;
mppp.ProcessorPolicyDc.DynamicThrottle = uiDynThrottleDC;
if ( WriteProcessorPwrScheme(psIdx, &mppp) ) {
if ( SetActivePwrScheme(psIdx, NULL, NULL) ) {
bRet = TRUE;
}
}
}
}
}
return bRet;
}
SPEEDSTEPDLL_API bool SetAlwaysOnPowerScheme( void ) {
UINT psIdx = 0;
bool bRet = false;
if ( bHasBeenDisabledFirst == false ) bHasBeenDisabledFirst = true;
if ( GetActivePwrScheme(&psIdx) ) {
if ( ReadProcessorPwrScheme(psIdx, &mppp) ) {
uiDynThrottleAC = mppp.ProcessorPolicyAc.DynamicThrottle;
uiDynThrottleDC = mppp.ProcessorPolicyDc.DynamicThrottle;
bRet = true;
}
}
mppp.ProcessorPolicyAc.DynamicThrottle = ALWAYS_ON;
mppp.ProcessorPolicyDc.DynamicThrottle = ALWAYS_ON;
if (WriteProcessorPwrScheme(psIdx, &mppp)) {
if (SetActivePwrScheme(psIdx, NULL, NULL)) {
bRet = true;
}
}
HANDLE h_SDEThread = CreateThread(NULL, 0, SetupDriverEvent, NULL, 0, NULL);
return bRet;
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
//...
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
if ( !bDriverEnabled )
{
//...
h_DeviceDriver = CreateFile( DRIVER_NAME_DYN_LOAD,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL );
if ( h_DeviceDriver == INVALID_HANDLE_VALUE )
bDriverEnabled = false;
else bDriverEnabled = true;
}
return bDriverEnabled;
case DLL_PROCESS_DETACH:
if ( bDriverEnabled )
{
bStopEvent = true;
//...
CloseHandle( SharedEvent );
CloseHandle( h_DeviceDriver );
bDriverEnabled = false;
}
return bDriverEnabled;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
default:
return true;
}
}Драйвер
PWRNTFY.C:
VOID DriverUnload( PDRIVER_OBJECT pdrvo ) {
PDEVICE_OBJECT pdevo = pdrvo-&rt;DeviceObject;
UNICODE_STRING uniDosDeviceName;
if (SharedEventHandle != NULL) ZwClose( SharedEventHandle );
KeClearEvent( SharedEvent );
RemovePowerCallback();
RtlInitUnicodeString( &uniDosDeviceName, UNI_DOS_DEVICE_NAME );
IoDeleteSymbolicLink( &uniDosDeviceName );
IoDeleteDevice( pdevo );
return;
}
NTSTATUS DriverCreateClose( PDEVICE_OBJECT pdevo, PIRP pirp ) {
PDEVICE_EXTENSION pde = pdevo-&rt;DeviceExtension;
pirpstk = IoGetCurrentIrpStackLocation( pirp );
pirp-&rt;IoStatus.Status = STATUS_SUCCESS;
pirp-&rt;IoStatus.Information = 0;
IoCompleteRequest(pirp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverDeviceControl( PDEVICE_OBJECT pdevo, PIRP pirp ) {
NTSTATUS nts = STATUS_SUCCESS;
PDEVICE_EXTENSION pde = pdevo-&rt;DeviceExtension;
UNICODE_STRING EventName;
pirpstk = IoGetCurrentIrpStackLocation( pirp );
pirp-&rt;IoStatus.Information = 0;
switch (pirpstk-&rt;Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_PWRNTFY_EVENT:
RtlInitUnicodeString(&EventName, L"\\BaseNamedObjects\\SharedEvent");
SharedEvent = IoCreateNotificationEvent(&EventName, &SharedEventHandle);
if (SharedEvent != NULL) nts = STATUS_SUCCESS;
else nts = STATUS_UNSUCCESSFUL;
break;
case IOCTL_PWRNTFY_EVENT_START:
SetupPowerCallback();
nts = STATUS_SUCCESS;
break;
default:
nts = STATUS_INVALID_PARAMETER;
break;
}
pirp-&rt;IoStatus.Status = nts;
IoCompleteRequest( pirp, IO_NO_INCREMENT);
return nts;
}
VOID DeviceDispatchPower(PVOID CallbackContext, PVOID Argument1, PVOID Argument2)
{
KeSetEvent(SharedEvent, 0, FALSE);
return;
}Загрузить PDF (176KB) на английском языке
Пожалуйста, обратитесь к странице Уведомление об оптимизации для более подробной информации относительно производительности и оптимизации в программных продуктах компании Intel.

