Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6265
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

Глава 14. Исследование виртуальной памяти.docx 469

///////////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hWnd, HWHD hWndFocus, LPARAM lParam) {

chSETDLGICONS(hWnd, IDI_SYSINFO);

SYSTEM_INFO sinf;

GetSystemInfo(&sinf);

ShowCPUInfo(hWnd, sinf.wProcessorArchitecture, sinf.wProcessorLevel, sinf.wProcessorRevision);

TCHAR szBuf[50];

SetDlgItemText(hWnd, IDC_PAGESIZE,

BigNumToString(sinf.dwPageSize, szBuf, _countof(szBuf)));

StringCchPrintf(szBuf, _countof(szBuf), TEXT("%p"), sinf.lpMinimumApplicationAddress);

SetDlgItemText(hWnd, IDC_MINAPPADDR, szBuf);

StringCchPrintf(szBuf, _countof(szBuf), TEXT("%p"), sinf.lpMaximumApplicationAddress);

SetDlgItemText(hWnd, IDC_MAXAPPADDR, szBuf);

StringCchPrintf(szBuf, _countof(szBuf), TEXT("0x%016I64X"), (__int64) sinf.dwActiveProcessorMask);

SetDlgItemText(hWnd, IDC_ACTIVEPROCMASK, szBuf);

SetDlgItemText(hWnd, IDC_NUHOFPROCS,

BigNumToString(sinf.dwNumberOfProcessors, szBuf, _countof(szBuf)));

SetDlgItemText(hWnd, IDC_ALLOCGRAN,

BigNumToString(sinf.dwAllocationGranularity, szBuf, _countof(szBuf)));

ShowBitness(hWnd);

return(TRUE);

}

/////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify)

{

switch (id) { case IDCANCEL:

EndDialog(hWnd, id);

470 Часть III. Управление памятью

break;

}

}

/////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

chHANDLE_DLGMSG(hDlg, WM_INITDIALOG,

Dlg_OnInitDialog);

chHANDLE_DLGMSG(hDlg, WM_COMMAND,

Dlg_OnCommand);

}

 

return(FALSE);

 

}

/////////////////////////////////////////////////////////////////////

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int) {

DialogBox(hInstExe, MAKEINTRES0URCE(IDD_SYSINFO), NULL, Dlg_Proc); return(0);

}

//////////////////////////// End of File ////////////////////////////

Статус виртуальной памяти

Windows-функция GlobalMemoryStatus позволяет отслеживать текущее состояние памяти:

VOID GlobalMemoryStatus(LPMEMORYSTATUS prost);

На мой взгляд, она названа крайне неудачно; имя GlobalMemoryStatus подразумевает, что функция каким-то образом связана с глобальными кучами в 16разрядной Windows. Мне кажется, что лучше было бы назвать функцию GlobalMemoryStatus по-другому — скажем, VirtualMemoryStatus.

При вызове функции GlobalMemoryStatus вы должны передать адрес структуры MEMORYSTATUS. Вот эта структура:

typedef struct _MEMORYSTATUS { DWORD dwLength;

DWORD dwMenoryLoad; SIZE_T dwTotalPhys; SIZE_T dwAvailPhys; SIZE_T dwTotalPageFile; SIZE_T dwAvailPageFile;

Глава 14. Исследование виртуальной памяти.docx 471

SIZE_T dwTotalVirtual; SIZE_T dwAvailVirtual;

} MEMORYSTATUS, *LPMEMORYSTATUS;

Перед вызовом GlobalMemoryStatus надо записать в элемент dwLength размер структуры в байтах. Такой принцип вызова функции дает возможность Майкрософт расширять эту структуру в будущих версиях Windows, не нарушая работу существующих приложений. После вызова GlobalMemoryStatus инициализирует остальные элементы структуры и возвращает управление. Назначение элементов этой структуры вы узнаете из следующего раздела, в котором рассматривается программа-пример VMStat.

Если вы полагаете, что ваше приложение будет работать на машинах с объемом оперативной памяти более 4 Гб или файлом подкачки более 4 Гб, используй-

те новую функцию GlobalMemoryStatusEx.

BOOL GlobalMemoryStatusEx(LPHEHORYSTATUSEX pmst);

Вы должны передать ей адрес новой структуры MEMORYSTATUSEX:

typedef struct _MEMORYSTATUSEX { DWORD dwLength;

0W0RD dwMemoryLoad; DW0RDL0NG ullTotalPhys; DW0RDL0N6 ullAvailPhys; DW0RDL0NG ullTotalPageFile; DW0RDL0N6 ullAvailPageFile; DW0RDL0NG ullTotalVirtual; DW0RDL0NG ullAvailVirtual;

DW0RDL0NG ullAvallExtendedVirtual; } MEMORYSTATUSEX, *LPMEMORYSTATUSEX;

Эта структура идентична первоначальной структуре MEMORYSTATUS с одним исключением: все ее элементы имеют размер по 64 бита, что позволяет оперировать со значениями, превышающими 4 Гб. Последний элемент, ullAvailExtendedVirtual, указывает размер незарезервированной памяти в самой большой области памяти виртуального адресного пространства вызывающего процесса. Этот элемент имеет смысл только для процессоров определенных архитектур при определенных конфигурациях.

Управление памятью на компьютерах с архитектурой NUMA

Как сказано в главе 7, процессоры компьютера с архитектурой NUMA используют память, установленною как на своей, так и на других платах. Однако обращение к «своей» памяти происходит намного быстрее, чем к «чужой». По умолчанию при выделении физической памяти потоком операционная система в целях повышения быстродействия пытается сопоставить

472 Часть III. Управление памятью

ей оперативную память, смонтированную на той же плате, что и процессор. И только при нехватке оперативной памяти для сопоставления может быть использована память с «чужой» платы.

Вызов функции GlobalMemoryStatusEx возвращает в параметре ullAvailPhus суммарное количество доступной памяти на всех платах. Узнать размер памяти, установленной на заданной плате, позволят следующая функция:

BOOL GetNumaAvailableMerooryNode(

UCHAR uNode,

PUL0NGL0NG pulAvailableBytes);

В переменную типа LONGLONG, на которую указывает параметр pulAvailableBytes, записывается объѐм памяти на плате, заданной параметром uNode. Чтобы узнать, на какой плате смонтирован процессор, достаточно вызвать функцию

GetNumaProcessorNode:

BOOL WINAPI GetNumaProcessorNode(

UCHAR Processor,

PUCHAR NodeNumber);

Общее число установленных в NUMA-системе плат возвращает следующая функция:

BOOL GetNumaHighestNodeNumber(PULONG pulHighestNodeNumber);

Указав номер платы (от 0 до значения параметра pulHighestNodeNumber), можно получить список смонтированных на ней процессоров, вызвав следующую функцию:

BOOL GetNumaNodeProcessorNask(

UCHAR uNode,

PULONGLONG pulProcessorMask);

Параметр uNode — это числовой идентификатор платы; в переменную типа LONGLONG, на которую ссылается параметр pulProcessorMask, записывается битовая маска. Процессоры, смонтированные на заданной плате, соответствуют установленным битам в этой маске.

Как сказано выше, Windows старается, чтобы потоки использовали оперативную память «своей» платы, чтобы повысить производительность. Однако Windows также поддерживает функции для ручного управления распределением памяти между потоками (подробнее об этом — в главе 15).

Дополнительные сведения о NUMA в Windows см. в статьях MSDN «Application Software Considerations for NUMA-Based Systems» (http://www.microsoft.com/whdc/system/platform/server/datacenter//numa_isv.aspx) и «NUMA Support» (http://msdn2.microsoft.com/en-us/library/aa363804.aspx).

Программа-пример VMStat

Эта программа, «14 VMStat.exe» (14-VMStat.exe), выводит на экран окно с результатами вызова GlobalMemoryStatus. Информация в окне обновляется

Глава 14. Исследование виртуальной памяти.docx 473

каждую секунду, так что VMStat вполне пригодна дли мониторинга памяти в системе. Файлы исходного кода и ресурсов этой программы находятся в каталоге 14VMStat внутри архива, доступного на веб-сайте поддержки этой книги. Окно этой программы после запуска в Windows Vista на машине с 1 Гб оперативной памяти показано ниже.

Элемент dwMemoryLoad (показываемый как Memory Load) позволяет оценить, насколько занята подсистема управления памятью. Это число может быть любым в диапазоне от 0 до 100. Кроме того, в будущих версиях операционных систем этот алгоритм почти наверняка придется модифицировать. Но, честно говоря, на практике от значения этого элемента толку немного.

Элемент dwTotalPhys (показываемый как TotalPhys) отражает общий объем физической (оперативной) памяти в байтах. На данной машине с 1 Гб оперативной памяти его значение составляет 1 072 627 712, что на 1 114 112 байта меньше 1 Гб. Причина, по которой GlobalMemoryStatus не сообщает о полном 1 Гб, кроется в том, что система при загрузке резервирует небольшой участок оперативной памяти, недоступный даже ядру. Этот участок никогда не сбрасывается на диск. А элемент dwAvailPhys (показываемый как AvailPhys) дает число байтов свободной физической памяти.

Элемент dwTotalPageFile (показываемый как TotalPageFile) сообщает максимальное количество байтов, которое может содержаться в страничном файле (файлах) на жестком диске (дисках). Хотя VMStat показывает, что текущий размер страничного файла составляет 2 414 112 768 байтов, система может варьировать его по своему усмотрению. Элемент dwAvailPageFile (показываемый как AvailPageFile) подсказывает, что в данный момент 1 741 586 432 байта в страничном файле свободно и может быть передано любому процессу.

Элемент dwTotalVirtual (показываемый как TotalVirtual) отражает общее количество байтов, отведенных под закрытое адресное пространство процесса. Значение 2 147 352 576 ровно на 128 Кб меньше 2 Гб. Два раздела недоступного адресного пространства — от 0x00000000 до 0x0000FFFF и от 0x7FFF0000 до 0x7FFFFFFF — как раз и составляют эту разницу в 128 Кб.

И, наконец, dwAvailVirtual (показываемый как AvailVirtual) — единственный элемент структуры, специфичный для конкретного процесса, вызывающего GlobalMemoryStatus (остальные элементы относятся исключительно к самой системе и не зависят от того, какой именно процесс вызывает эту функцию). При подсчете значения dwAvailVirtual функция суммирует размеры

474 Часть III. Управление памятью

всех свободных регионов в адресном пространстве вызывающего процесса. В данном случае его значение говорит о том, что в распоряжении программы VMStat имеется 2 106 437 632 байтов свободного адресного пространства. Вычтя из значения dwTotalVirtual величину dwAvailVirtual, вы получите 40 914 944 байтов — такой объем памяти VMStat зарезервировала в своем виртуальном адресном пространстве. Отдельного элемента, который сообщал бы количество физической памяти, используемой процессом в данный момент, не предусмотрено. Страницы адресного пространства процесса, загруженные в оперативную память, называются рабочим набором (working set) процесса. Следующая функция, объявленная в файле psapi.h, позволяет определить текущий и максимальный размер рабочего набора для процесса:

B00L GetProcessMemoryInfo(

HANDLE hProcess,

PPR0CESS_MEM0RY_C0UNTERS ppmc,

DWORD cbSize);

Параметр hProcess — это описатель нужного вам процесса, у этого описателя должны быть права доступа PROCESS_QUERY_INFORMATION и PROCESS_VM_READ. Функция GetCurrentProcess возвращает для текущего процесса псевдоописатель, соответствующий этим требованиям. Параметр ppmc указывает на структуру PROCESS_MEMORY_COUNTERS_EX, размер которой задается параметром cbSize. Если GetProcessMemoryInfo возвращает TRUE, в элементы следующей структуры будут записаны сведения о заданном процессе:

typedef struct _PR0CESS_MEM0RY_C0UNTERS_EX { DWORD cb;

DWORD PageFaultCount; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize;

SIZE_T QuotaPeakPagedPoolUsage; SIZE_T QuotaPagedPoolUsage; SIZE_T QuotaPeakNonPagedPoolUsage; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PageflleUsage;

SIZE_T PeakPagefileUsage; SIZE_T PrlvateUsage;

} PROCESS_MEMORY_COUNTERS_EX,

*PPR0CESS_MEM0RY_C0UNTERS_EX;

Поле WorkingSetSize содержит число байтов RAM, используемых процессом,

заданным параметром hProcess при вызове GetProcessMemoryInfo. Поле PeakWorkingSetSize содержит пиковое число байтов, занятых процессом в RAM, за весь период его работы.

Знать размер рабочего набора процесса чрезвычайно полезно, поскольку эта цифра отражает среднее количество оперативной памяти, необходимое

Глава 14. Исследование виртуальной памяти.docx 475

вашей программе. Чем меньше рабочий набор приложения, тем выше его производительность. Вероятно, вы знаете, что лучшая мера для повышения быстродействия Windows-приложений — наращивания размера оперативной памяти. Хотя Windows может хранить содержимое оперативной памяти в страничном файле, такая «оперативная» намять работает намного медленнее настоящей RAM. Чем больше размер физической RAM, тем меньше Windows использует страничный файл, а значит, производительность будет выше. Разработчики также могут управлять размером той части данных приложения, которая в каждый конкретный момент времени должна находиться в оперативной памяти. Чем меньше эта часть, тем выше производительность.

Помимо уменьшения рабочего набора, при «настройке» приложения желательно знать, сколько памяти оно явно выделяет для себя с помощью функций new, malloc и VirtualAlloc. Эту информацию предоставляет поле PrivateUsage (PrivateBytes). В остальных разделах этой главы я расскажу о функциях, позволяющих получить дополнительную информацию об адресном пространстве процесса.

Определение состояния адресного пространства

В Windows имеется функция, позволяющая запрашивать определенную информацию об участке памяти по заданному адресу (в пределах адресного пространства вызывающего процесса): размер, тип памяти и атрибуты защиты. В частности, с ее помощью программа VMMap выводит карты виртуальной памяти, с которыми мы познакомились в главе 13. Вот эта функция:

DW0RD VirtualQuery( LPCVOID pvAddress,

PHEMORY_BASIC_INFORMATION pmbi,

DWORD dwLength);

Парная ей функция, VirtualQueryEx, сообщает ту же информацию о памяти, но в другом процессе:

DW0R0 VirtualQueryEx(

HANDLE hProcess,

LPCVOID pvAddress,

PMEMORY_BASIC_INFORMATION pmbi,

DWORD dwLength);

Эти функции идентичны с тем исключением, что VirtualQueryEx принимает описатель процесса, об адресном пространстве которого вы хотите получить информацию. Чаще всего функцией VirtualQueryEx пользуются отладчики и системные утилиты — остальные приложения обращаются к

VirtualQuery. При вызове VirtualQuery(Ex) параметр pvAddress должен содер-

жать адрес виртуальной памяти, о которой вы хотите получить информацию. Параметр pmbi — это адрес структуры MEMORY_BASIC_INFORMATION,

476 Часть III. Управление памятью

которую надо создать перед вызовом функции. Данная структура определена в файле WinNT.h так:

typedef struct _MEMORY_BASIC.INFORMATION { PVOID BaseAddress;

PVOID AllocationBase; DWORD AllocationProtect; SIZE_T RegionSize; DWORD State;

DWORD Protect;

DW0R0 Туре;

} MEMORY_BASIC_INFORMATION, *PHEMORY_BASIC_INFORMATION;

Параметр dwLength задает размер структуры MEMORY_BASIC_INFORMATION. Функция VirtualQuery(Ex) возвращает число байтов, скопированных в буфер.

Используя адрес, указанный вами в параметре pvAddress, функция VirtualQuery(Ex) заполняет структуру информацией о диапазоне смежных страниц, имеющих одинаковые состояние, атрибуты защиты и тип. Описание элементов структуры приведено в таблице ниже.

Табл. 144. Элементы структуры MEMORY_BASIC_INFORMATION

Элемент

Описание

BaseAddress

Сообщает то же значение, что и параметр pvAddress, но округленное до

 

ближайшего меньшего адреса, кратного размеру страницы.

AllocationBase

Идентифицирует базовый адрес региона, включающего в себя адрес, ука-

 

занный в параметре pvAddress.

AllocationProtect

Идентифицирует атрибут защиты, присвоенный региону при его резервиро-

 

вании.

RegionSize

Сообщает суммарный размер (в байтах) группы страниц, которые начина-

 

ются с базового адреса BaseAddress и имеют те же атрибуты защиты, со-

 

стояние и тип, что и страница, расположенная по адресу, указанному в па-

 

раметре pvAddress.

State

Сообщает состояние (MEM_FREE, MEM_RESERVE или MEM_ СОММГТ)

 

всех смежных страниц, которые имеют те же атрибуты защиты, состояние и

 

тип, что и страница, расположенная по адресу, указанному в параметре

 

pvAddress.

 

При MEM_FREE элементы AllocationBase, AllocationProtect, Protect и Type

 

содержат неопределенные значения, а при MEM_RESERVE неопределенное

 

значение содержит элемент Protect.

Protect

Идентифицирует атрибут защиты (PAGE_*) всех смежных страниц, кото-

 

рые имеют те же атрибуты защиты, состояние и тип, что и страница, распо-

 

ложенная по адресу, указанному в параметре pvAddress.

 

Глава 14. Исследование виртуальной памяти.docx 477

Табл. 14-3. (окончание)

Элемент

Описание

Туре

Идентифицирует тип физической памяти (MEM_IMAGE, MEM_MAPPED

 

или MEM_PRIVATE), связанной с группой смежных страниц, которые

 

имеют те же атрибуты защиты, состояние и тип, что и страница, располо-

 

женная по адресу, указанному в параметре pvAddress. В Windows 98 этот

 

элемент всегда даст MEM_PRIVATE.

Функция VMQuery

Начиная изучать архитектуру памяти в Windows, я пользовался функцией VirtualQuery как «поводырем». Если вы читали первое издание моей книги, то заметите, что программа VMMap была гораздо проще ее нынешней версии, представленной в следующем разделе. Прежняя была построена на очень простом цикле, из которого периодически вызывалась функция VirtualQuery, и для каждого вызова я формировал одну строку, содержавшую элементы структуры MEMORYJBASIC_№FORMATION. Изучая полученные дампы и сверяясь с документацией из SDK (в то время весьма неудачной), я пытался разобраться в архитектуре подсистемы управления памятью. Что ж, с тех пор я многому научился и теперь знаю, что функция VirtualQuery и структура MEMORY_BASIC_INFORMATION не дают полной картины.

Проблема в том, что в MEMORY_BASIC_INFORMATION возвращается отнюдь не вся информация, имеющаяся в распоряжении системы. Если вам нужны простейшие данные о состоянии памяти по конкретному адресу, VirtualQuery действительно незаменима. Она отлично работает, если вас интересует, передана ли по этому адресу физическая память и доступен ли он для операций чтения или записи. Но попробуйте с ее помощью узнать общий размер зарезервированного региона и количество блоков в нем или выяснить, не содержит ли этот регион стек потока, — ничего не выйдет.

Чтобы получать более полную информацию о памяти, я создал собственную функцию и назвал ее VMQuery:

BOOL VMQuery(

HANDLE hProcess,

LPCVOID pvAddress,

PVMQUERY pVMQ);

По аналогии с VirtualQueryEx она принимает в hProcess описатель процесса, в pvAddress — адрес памяти, а в pVMQ — указатель на структуру, заполняемую самой функцией. Структура VMQUERY (тоже определенная мной) представляет собой вот что:

typedef struct {

 

// Region information

 

PVOID pvRgnBaseAddress;

 

DWORD dwRgnProtection;

// PAGE_*

478 Часть III. Управление памятью

SIZE_T RgnSize;

 

DWORD dwRgnStorage;

// MEM_*: Free, Image, Napped, Private

DWORD dwRgnBlocks;

 

DWORD dwRgnGuardBlks;

// если > 0, регион содержит стек потока

BOOL bRgnIsAStack;

// TRUE, если регион содержит стек потока

// Block information

 

PVOID pvBlkBaseAddress;

 

DWORD dwBlkProtection;

// PAGE_*

SIZE_T BlkSize;

 

DWORD dwBlkStorage;

// NEN_*: Free, Reserve, Image, Napped, Private

}VMQUERY, *PVMQUERY;

Спервого взгляда заметно, что моя структура VMQUERY содержит куда больше информации, чем MEMORY_BASIC_INFORMATION. Она разбита (условно, конечно) на две части: в одной — информация о регионе, в другой — информация о блоке (адрес которого указан в параметре pvAddress). Элементы этой структуры описываются в следующей таблице.

Табл. 14-4. Элементы структуры VMQUERY

Элемент

Описание

pvRgnBaseAddress

Идентифицирует базовый адрес региона виртуального адресного про-

 

странства, включающего адрес, указанный в параметре pvAddress

dwRgnProtection

Сообщает атрибут защиты, присвоенный региону при его резервирова-

 

нии

RgnSize

Указывает размер (в байтах) зарезервированного региона

dwRgnStorage

Идентифицирует тип физической памяти, используемой группой блоков

 

данного региона: MEM_FREE, MEM_IMAGE, MEM_MAPPED или

 

MEM_PRIVATE

dwRgnBlocks

Содержит значение — число блоков в указанном регионе

dwRgnGuardBlks

Указывает число блоков с установленным флагом атрибутов защиты

 

PAGE_GUARD. Обычно это значение либо 0, либо 1. Если оно равно 1,

 

то регион, скорее всего, зарезервирован под стек потока

fRgnIsAStack

Сообщает, есть ли в данном регионе стек потока. Результат определяет-

 

ся на основе взвешенной оценки, так как невозможно дать стопроцент-

 

ной гарантии тому, что в регионе содержится стек

pvBlkBaseAddress

Идентифицирует базовый адрес блока, включающего адрес, указанный

 

в параметре pvAddress

dwBlkProtection

Идентифицирует атрибут защиты блока, включающего адрес, указан-

 

ный в параметре pvAddress

BlkSize

Содержит значение — размер блока (в байтах), включающего адрес,

 

указанный в параметре pvAddress

Соседние файлы в предмете Программирование на C++