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

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

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

Глава 15. Использование виртуальной памяти в приложениях.docx 497

При выполнении программы вы будете периодически вызывать функцию сбора мусора, которая должна просматривать все структуры. Для каждой структуры (и существующей, и той, которая может быть создана) функция сначала определяет, передана ли под нее память; если да, то проверяет значение fInUse. Если он равен 0, структура не используется; TRUE — структура занята. Проверив все структуры, расположенные в пределах заданной страницы, функция сбора мусора вызывает VirtualFree, чтобы освободить память, — если, конечно, на этой странице нет используемых структур.

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

Лично я предпочитаю первые два способа. Однако, если ваши структуры ком-

пактны (меньше одной страницы памяти), советую применять последний метод.

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

Эта программа (см. листинг VMAlloc.cpp), демонстрирует применение механизма виртуальной памяти для управления массивом структур. Файлы исходного кода и ресурсов этой программы находятся в каталоге 15-VMAlloc внутри архива, доступного на сайте поддержки этой книги. После запуска VMAlloc на экране появится диалоговое окно, показанное ниже.

Изначально для массива не резервируется никакого региона, и все адресное пространство, предназначенное для него, свободно, что и отражено на карте памяти. Если щелкнуть кнопку Reserve Region (50,2KB Structures), программа VMAlloc вызовет VirtualAUoc для резервирования региона, что сразу отразится на карте памяти. После этого станут активными и остальные кнопки в диалоговом окне.

Теперь в поле можно ввести индекс и щелкнуть кнопку Use. При этом по адресу, где должен располагаться указанный элемент массива, передается физическая память. Далее карта памяти вновь перерисовывается и уже отражает состояние региона, зарезервированного под весь массив. Когда вы,

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

зарезервировав регион, вновь щелкнете кнопку Use, чтобы пометить элементы 7 и 46 как занятые, окно (при выполнении программы на процессоре с размером страниц по 4 Кб) будет выглядеть так:

Любой элемент массива, помеченный как занятый, можно освободить щелчком кнопки Clear. Но это не приведет к возврату физической памяти, переданной под элемент массива. Дело в том, что каждая страница содержит несколько структур и освобождение одной структуры не влечет за собой освобождения других. Если бы память была возвращена, то пропали бы и данные, содержащиеся в остальных структурах. И поскольку выбор кнопки Clear никак не сказывается на физической памяти региона, карта памяти после освобождения элемента не меняется.

Однако освобождение структуры приводит к тому, что ее элемент fInUse устанавливается в FALSE. Это нужно для того, чтобы функция сбора мусора могла вернуть не используемую больше физическую память. Кнопка Garbage Collect, если вы еще не догадались, заставляет программу VMAlloc выполнить функцию сбора мусора. Для упрощения программы я не стал выделять эту функцию в отдельный поток.

Чтобы посмотреть, как работает функция сбора мусора, очистите элемент массива с индексом 46. Заметьте, что карта памяти пока не изменилась. Теперь щелкните кнопку Garbage Collect. Программа освободит страницу, содержащую 46-й элемент, и карта памяти сразу же обновится, как показано ниже. Заметьте, что функцию GarbageCollect можно легко использовать в любых других приложениях. Я реализовал ее так, чтобы она работала с массивами структур данных любого размера; при этом структура не обязательно должна полностью умещаться на странице памяти. Единственное требование заключается в том, что первый элемент структуры должен быть значением типа BOOL, которое указывает, задействована ли данная структура.

Глава 15. Использование виртуальной памяти в приложениях.docx 499

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

Но есть в этой программе еще одна особенность, о которой я пока не упоминал. Программе приходится определять состояние памяти в адресном пространстве региона в трех случаях.

После изменения индекса. Программе нужно включить кнопку Use и отключить кнопку Clear (или наоборот).

В функции сбора мусора. Программа, прежде чем проверять значение флага fInUse, должна определить, была ли передана память.

При обновлении карты памяти. Программа должна выяснить, какие страницы свободны, какие — зарезервированы, а какие — переданы.

Все эти проверки VMAlloc осуществляет через функцию VirtualQuery, рас-

смотренную в предыдущей главе.

VMAlloc.срр

/****************************************************************************** Module: VMAlloc.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <WindowsX.h>

#include <tchar.h> #include "Resource.h" #include <StrSafe.h>

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

//The number of bytes in a page on this host machine. UINT g_uPageSize = 0;

//A dummy data structure used for the array.

typedef struct { BOOL bInUse;

BYTE bOtherData[2048 - sizeof(BOOL)]; } SOMEDATA, *PSOMEDATA;

// The number of structures in the array

#define MAX_SOMEDATA

(50)

//Pointer to an array of data structures PSOMEDATA g_pSomeData = NULL;

//The rectangular area in the window occupied by the memory map RECT g_rcMemMap;

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

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

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

chSETDLGICONS(hWnd, IDI_VMALLOC);

// Initialize the dialog box by disabling all the nonsetup controls.

EnableWindow(GetDlgItem(hWnd, IDC_INDEXTEXT),

FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_INDEX),

FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_USE),

FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_CLEAR),

FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_GARBAGECOLLECT), FALSE);

//Get the coordinates of the memory map display. GetWindowRect(GetDlgItem(hWnd, IDC_MEMMAP), &g_rcMemMap); MapWindowPoints(NULL, hWnd, (LPPOINT) &g_rcMemMap, 2);

//Destroy the window that identifies the location of the memory map DestroyWindow(GetDlgItem(hWnd, IDC_MEMMAP));

//Put the page size in the dialog box just for the user's information. TCHAR szBuf[10];

StringCchPrintf(szBuf, _countof(szBuf), TEXT("%d KB"), g_uPageSize / 1024); SetDlgItemText(hWnd, IDC_PAGESIZE, szBuf);

//Initialize the edit control.

SetDlgItemInt(hWnd, IDC_INDEX, 0, FALSE);

return(TRUE);

}

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

void Dlg_OnDestroy(HWND hWnd) {

if (g_pSomeData != NULL) VirtualFree(g_pSomeData, 0, MEM_RELEASE);

}

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

VOID GarbageCollect(PVOID pvBase, DWORD dwNum, DWORD dwStructSize) {

Глава 15. Использование виртуальной памяти в приложениях.docx 501

UINT uMaxPages = dwNum * dwStructSize / g_uPageSize; for (UINT uPage = 0; uPage < uMaxPages; uPage++) {

BOOL bAnyAllocsInThisPage = FALSE;

UINT uIndex = uPage * g_uPageSize / dwStructSize; UINT uIndexLast = uIndex + g_uPageSize / dwStructSize;

for (; uIndex < uIndexLast; uIndex++) { MEMORY_BASIC_INFORMATION mbi; VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi)); bAnyAllocsInThisPage = ((mbi.State == MEM_COMMIT) &&

*(PBOOL) ((PBYTE) pvBase + dwStructSize * uIndex));

//Stop checking this page, we know we can't decommit it. if (bAnyAllocsInThisPage) break;

}

if (!bAnyAllocsInThisPage) {

// No allocated structures in this page; decommit it. VirtualFree(&g_pSomeData[uIndexLast - 1], dwStructSize, MEM_DECOMMIT);

}

}

}

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

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

UINT uIndex = 0;

switch (id) { case IDCANCEL:

EndDialog(hWnd, id); break;

case IDC_RESERVE:

//Reserve enough address space to hold the array of structures. g_pSomeData = (PSOMEDATA) VirtualAlloc(NULL,

MAX_SOMEDATA * sizeof(SOMEDATA), MEM_RESERVE, PAGE_READWRITE);

//Disable the Reserve button and enable all the other controls.

EnableWindow(GetDlgItem(hWnd, IDC_RESERVE),

FALSE);

 

 

EnableWindow(GetDlgItem(hWnd, IDC_INDEXTEXT),

TRUE);

EnableWindow(GetDlgItem(hWnd, IDC_INDEX),

TRUE);

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

EnableWindow(GetDlgItem(hWnd,

IDC_USE),

TRUE);

EnableWindow(GetDlgItem(hWnd,

IDC_GARBAGECOLLECT), TRUE);

//Force the index edit control to have the focus. SetFocus(GetDlgItem(hWnd, IDC_INDEX));

//Force the memory map to update InvalidateRect(hWnd, &g_rcMemMap, FALSE); break;

case IDC_INDEX:

if (codeNotify != EN_CHANGE) break;

uIndex = GetDlgItemInt(hWnd, id, NULL, FALSE);

if ((g_pSomeData != NULL) && chINRANGE(0, uIndex, MAX_SOMEDATA - 1)) { MEMORY_BASIC_INFORMATION mbi;

VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi)); BOOL bOk = (mbi.State == MEM_COMMIT);

if (bOk)

bOk = g_pSomeData[uIndex].bInUse;

EnableWindow(GetDlgItem(hWnd, IDC_USE), !bOk);

EnableWindow(GetDlgItem(hWnd, IDC_CLEAR), bOk);

} else {

EnableWindow(GetDlgItem(hWnd, IDC_USE), FALSE); EnableWindow(GetDlgItem(hWnd, IDC_CLEAR), FALSE);

}

break;

case IDC_USE:

uIndex = GetDlgItemInt(hWnd, IDC_INDEX, NULL, FALSE); // NOTE: New pages are always zeroed by the system VirtualAlloc(&g_pSomeData[uIndex], sizeof(SOMEDATA),

MEM_COMMIT, PAGE_READWRITE);

g_pSomeData[uIndex].bInUse = TRUE;

EnableWindow(GetDlgItem(hWnd, IDC_USE), FALSE);

EnableWindow(GetDlgItem(hWnd, IDC_CLEAR), TRUE);

// Force the Clear button control to have the focus. SetFocus(GetDlgItem(hWnd, IDC_CLEAR));

Глава 15. Использование виртуальной памяти в приложениях.docx 503

// Force the memory map to update

InvalidateRect(hWnd, &g_rcMemMap, FALSE); break;

case IDC_CLEAR:

uIndex = GetDlgItemInt(hWnd, IDC_INDEX, NULL, FALSE); g_pSomeData[uIndex].bInUse = FALSE; EnableWindow(GetDlgItem(hWnd, IDC_USE), TRUE); EnableWindow(GetDlgItem(hWnd, IDC_CLEAR), FALSE);

// Force the Use button control to have the focus. SetFocus(GetDlgItem(hWnd, IDC_USE));

break;

case IDC_GARBAGECOLLECT:

GarbageCollect(g_pSomeData, MAX_SOMEDATA, sizeof(SOMEDATA));

// Force the memory map to update InvalidateRect(hWnd, &g_rcMemMap, FALSE); break;

}

}

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

void Dlg_OnPaint(HWND hWnd) {

// Update the memory map

PAINTSTRUCT ps;

BeginPaint(hWnd, &ps);

UINT uMaxPages = MAX_SOMEDATA * sizeof(SOMEDATA) / g_uPageSize; UINT uMemMapWidth = g_rcMemMap.right - g_rcMemMap.left;

if (g_pSomeData == NULL) {

//The memory has yet to be reserved. Rectangle(ps.hdc, g_rcMemMap.left, g_rcMemMap.top,

g_rcMemMap.right - uMemMapWidth % uMaxPages, g_rcMemMap.bottom);

}else {

//Walk the virtual address space, painting the memory map

for (UINT uPage = 0; uPage < uMaxPages; uPage++) {

UINT uIndex = uPage * g_uPageSize / sizeof(SOMEDATA); UINT uIndexLast = uIndex + g_uPageSize / sizeof(SOMEDATA); for (; uIndex < uIndexLast; uIndex++) {

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

MEMORY_BASIC_INFORMATION mbi;

VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi));

int nBrush = 0; switch (mbi.State) {

case MEM_FREE: nBrush = WHITE_BRUSH; break; case MEM_RESERVE: nBrush = GRAY_BRUSH; break; case MEM_COMMIT: nBrush = BLACK_BRUSH; break;

}

SelectObject(ps.hdc, GetStockObject(nBrush)); Rectangle(ps.hdc,

g_rcMemMap.left + uMemMapWidth / uMaxPages * uPage, g_rcMemMap.top,

g_rcMemMap.left + uMemMapWidth / uMaxPages * (uPage + 1), g_rcMemMap.bottom);

}

}

}

EndPaint(hWnd, &ps);

}

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

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

switch (uMsg) {

chHANDLE_DLGMSG(hWnd, WM_INITDIALOG,

Dlg_OnInitDialog);

chHANDLE_DLGMSG(hWnd, WM_COMMAND,

Dlg_OnCommand);

chHANDLE_DLGMSG(hWnd, WM_PAINT,

Dlg_OnPaint);

chHANDLE_DLGMSG(hWnd, WM_DESTROY,

Dlg_OnDestroy);

}

 

return(FALSE);

 

}

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

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

// Get the page size used on this CPU. SYSTEM_INFO si;

GetSystemInfo(&si); g_uPageSize = si.dwPageSize;

Глава 15. Использование виртуальной памяти в приложениях.docx 505

DialogBox(hInstExe, MAKEINTRESOURCE(IDD_VMALLOC), NULL, Dlg_Proc); return(0);

}

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

Изменение атрибутов защиты

Хоть это и не принято, но атрибуты защиты, присвоенные странице или страницам переданной физической памяти, можно изменять. Допустим, вы разработали код для управления связанным списком, узлы (вершины) которого хранятся в зарезервированном регионе. При желании можно написать функции, которые обрабатывали бы связанные списки и изменяли бы атрибуты защиты переданной памяти при старте на PAGE_READWRITE, а при завершении — обратно на

PAGE_NOACCESS.

Сделав так, вы защитите данные в связанном списке от возможных «жучков», скрытых в программе. Например, если какой-то блок кода в вашей программе изза наличия «блуждающего» указателя обратится к данным в связанном списке, возникнет нарушение доступа. Поэтому такой подход иногда очень полезен — особенно когда пытаешься найти трудноуловимую ошибку в своей программе.

Атрибуты защиты страницы памяти можно изменить вызовом Virtual-Protect

BOOL VirtualProtect(

PVOID pvAddress,

SIZE_T dwSlze,

DWORD flNewProtect,

PDWORD pflOldProtect);

Здесь pvAddress указывает на базовый адрес памяти (который должен находиться в пользовательском разделе вашего процесса), dwSize определяет число байтов, для которых вы изменяете атрибут защиты, a flNewProtect содержит один из идентификаторов PAGE_*, кроме PAGE_WRITECOPY и PAGE_EXECUTE_WRITECOPY.

Последний параметр, pflOldProtect, содержит адрес переменной типа DWORD, в которую VirtualProtect заносит старое значение атрибута защиты для данной области памяти. В этом параметре (даже если вас не интересует такая информация) нужно передать корректный адрес, иначе функция приведет к нарушению доступа.

Естественно, атрибуты защиты связаны с целыми страницами памяти и не могут присваиваться отдельным байтам. Поэтому, если на процессоре с четырехкилобайтовыми страницами вызвать VirtualProtect, например, так:

VirtualProtect(pvRgnBase + (3 * 1024), 2 * 1024,

PAGE_NOACCESS, &fl0ldProtect);

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

то атрибут защиты PAGE_NOACCESS будет присвоен двум страницам памяти. Функцию VirtualProtect нельзя использовать для изменения атрибутов защиты

страниц, диапазон которых охватывает разные зарезервированные регионы. В таких случаях VirtualProtect надо вызывать для каждого региона отдельно.

Сброс содержимого физической памяти

Когда вы модифицируете содержимое страниц физической памяти, система пытается как можно дольше хранить эти изменения в оперативной памяти. Однако, выполняя приложения, система постоянно получает запросы на загрузку в оперативную память страниц из ЕХЕ-файлов, DLL и/или страничного файла. Любой такой запрос заставляет систему просматривать оперативную память и выгружать модифицированные страницы в страничный файл.

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

Однако некоторые программы занимают блоки памяти на очень малое время, а потом им уже не требуется их содержимое. Для большего быстродействия программа может попросить систему не записывать определенные страницы в страничный файл. И тогда, если одна из этих страниц понадобится для других целей, системе не придется сохранять ее в страничном файле, что, естественно, повысит скорость работы программы. Такой отказ от страницы (или страниц) памяти на-

зывается сбросом физической памяти (resetting of physical storage) и инициирует-

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

MEM_RESET.

Если страницы, на которые вы ссылаетесь при вызове VirtualAlloc, находятся в страничном файле, система их удалит. Когда в следующий раз программа обратится к памяти, она получит новые страницы, инициализированные нулями. Если же вы сбрасываете страницу, находящуюся в оперативной памяти, система помечает ее как неизменявшуюся, и она не записывается в страничный файл. Но, хотя ее содержимое не обнуляется, читать такую страницу памяти уже нельзя. Если системе не понадобится эта страница оперативной памяти, ее содержимое останется прежним. В ином случае система может забрать ее в свое распоряжение, и тогда обращение к этой странице приведет к тому, что система предоставит программе новую страницу, заполненную нулями. А поскольку этот процесс нам не подвластен, лучше считать, что после сброса страница содержит только мусор.

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