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

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

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

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

При сбросе физической памяти надо учитывать и несколько других моментов. Во-первых, когда вы вызываете VirtualAlloc, базовый адрес обычно округляется до ближайшего меньшего значения, кратного размеру страниц, а количество байтов — до ближайшего большего значения, кратного той же величине. Такой механизм округления базового адреса и количества байтов был бы очень опасен при сбросе физической памяти; поэтому VirtualAlloc при передаче ей флага MEM_RESET округляет эти значения прямо наоборот. Допустим, в вашей программе есть следующий исходный код:

PINT pnData = (PINT) VirtualAlloc(NULL, 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

pnData[0] = 100; pnData[1] = 200;

VirtualAlloc((PV0ID) pnData, sizeof(int), MEM.RESET, PAGE_READWRITE);

Этот код передает одну страницу памяти, а затем сообщает, что первые четыре байта (sizeof (int)) больше не нужны и их можно сбросить. Однако, как и при любых других действиях с памятью, эта операция выполняется только над блоками памяти, размер которых кратен размеру страниц. В данном случае вызов завершится неудачно (VirtualAlloc вернет NULL, а GetLastError — ошибку ERROR_INVALID_ADDRESS, определенную в WinError.h как ошибка 487). Поче-

му? Дело в том, что при вызове VirtualAlloc вы указали флаг MEM_RESET и базовый адрес, переданный функции, теперь округляется до ближайшего большего значения, кратного размеру страниц, а количество байтов — до ближайшего меньшего значения, кратного той же величине. Так делается, чтобы исключить случайную потерю важных данных. В предыдущем примере округление количества байтов до ближайшего меньшего.

Второе, о чем следует помнить при сбросе памяти, — флаг MEM_RESET нельзя комбинировать (логической операцией OR) ни с какими другими флагами. Следующий вызов всегда будет заканчиваться неудачно:

PV0ID pvMem = VirtualAlloc(NULL, 1024,

MEM_RESERVE | MEM_COMMIT | MEM_RESET, PAGE_READWRITE);

Впрочем, комбинировать флаг MEM_RESET с другими флагами все равно бессмысленно.

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

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

Эта программа (см. листинг MemReset.cpp), демонстрирует, как работает флаг MEM RESET. Файлы исходного кода и ресурсов этой программы находятся в каталоге 15-MemReset внутри архива, доступного на веб-сайте поддержи этой книги.

Первое, что делает код этой программы» — резервирует регион и передает ему физическую память. Поскольку размер региона, переданный

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

вVirtualAlloc, равен 1024 байтам, система автоматически округляет это значение до размера страницы. Затем функция _tcscpy_s копирует в этот буфер строку, и содержимое страницы оказывается измененным. Если система впоследствии сочтет, что ей нужна страница, содержащая наши данные, она запишет эту страницу

встраничный файл. Когда наша программа попытается считать эти данные, система автоматически загрузит страницу из страничного файла в оперативную память.

После записи строки в страницу памяти наша программа спрашивает у пользователя, понадобятся ли еще эти данные. Если пользователь выбирает отрицательный ответ (щелчком кнопки No), программа сообщает системе, что страница не изменялась, для чего вызывает VirtualAlloc с флагом MEM_RESET.

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

1. Получим общий размер оперативной памяти на компьютере вызовом GlobalMemoryStatus.

2. Передадим эту память вызовом VirtualAlloc. Данная операция выполняется очень быстро, поскольку система не выделяет оперативную память до тех пор, пока процесс не изменит какие-нибудь страницы.

3. Изменим содержимое только что переданных страниц через функцию ZeroMemory. Это создает высокую нагрузку на оперативную память, и отдельные страницы выгружаются в страничный файл.

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

После вызова ZeroMemory я сравниваю содержимое страницы данных со строкой, которая была туда записана. Если данные не сбрасывались, содержимое идентично, а если сбрасывались — то ли идентично, то ли нет. В моей программе содержимое никогда не останется прежним, поскольку я заставляю систему выгрузить все страницы оперативной памяти в страничный файл. Но если бы размер выгружаемой области был меньше общего объема оперативной памяти, то не исключено, что исходное содержимое все равно осталось бы в памяти. Так что будьте осторожны!

MemReset.cpp

/****************************************************************************** Module: MemReset.cpp

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

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

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

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

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

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

TCHAR szAppName[] = TEXT("MEM_RESET tester");

TCHAR szTestData[] = TEXT("Some text data");

// Commit a page of storage and modify its contents. PTSTR pszData = (PTSTR) VirtualAlloc(NULL, 1024,

MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); _tcscpy_s(pszData, 1024, szTestData);

if (MessageBox(NULL, TEXT("Do you want to access this data later?"), szAppName, MB_YESNO) == IDNO) {

//We want this page of storage to remain in our process but the

//contents aren't important to us anymore.

//Tell the system that the data is not modified.

//Note: Because MEM_RESET destroys data, VirtualAlloc rounds

//the base address and size parameters to their safest range.

//Here is an example:

//VirtualAlloc(pvData, 5000, MEM_RESET, PAGE_READWRITE)

//resets 0 pages on CPUs where the page size is greater than 4 KB

//and resets 1 page on CPUs with a 4 KB page. So that our call to

//VirtualAlloc to reset memory below always succeeds, VirtualQuery

//is called first to get the exact region size.

MEMORY_BASIC_INFORMATION mbi; VirtualQuery(pszData, &mbi, sizeof(mbi));

VirtualAlloc(pszData, mbi.RegionSize, MEM_RESET, PAGE_READWRITE);

}

//Commit as much storage as there is physical RAM. MEMORYSTATUS mst;

GlobalMemoryStatus(&mst);

PVOID pvDummy = VirtualAlloc(NULL, mst.dwTotalPhys, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

//Touch all the pages in the dummy region so that any

//modified pages in RAM are written to the paging file. if (pvDummy != NULL)

ZeroMemory(pvDummy, mst.dwTotalPhys);

//Compare our data page with what we originally wrote there. if (_tcscmp(pszData, szTestData) == 0) {

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

//The data in the page matches what we originally put there.

//ZeroMemory forced our page to be written to the paging file. MessageBox(NULL, TEXT("Modified data page was saved."),

szAppName, MB_OK);

}else {

//The data in the page does NOT match what we originally put there

//ZeroMemory didn't cause our page to be written to the paging file

MessageBox(NULL, TEXT("Modified data page was NOT saved."), szAppName, MB_OK);

}

//Don't forget to release part of the address space.

//Note that it is not mandatory here since the application is exiting. if (pvDummy != NULL)

VirtualFree(pvDummy, 0, MEM_RELEASE); VirtualFree(pszData, 0, MEM_RELEASE);

return(0);

}

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

Механизм Address Windowing Extensions

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

Для таких приложений Windows предлагает новый механизм — Address Windowing Extensions (AWE). Создавая AWE, Майкрософт стремилась к тому, чтобы приложения могли:

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

обращаться к таким объемам оперативной памяти, которые превышают размеры соответствующих разделов в адресных пространствах их процессов.

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

AWE дает возможность приложению выделять себе один и более блоков оперативной памяти, невидимых в адресном пространстве процесса. Сделав это, приложение резервирует регион адресного пространства (с помощью VirtualAlloc), и он становится адресным окном (address window). Далее программа вызывает функцию, которая связывает адресное окно с одним из выделенных блоков оперативной памяти. Эта операция выполняется чрезвычайно быстро (обычно за пару миллисекунд).

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

Вот пример, демонстрирующий использование AWE:

//сначала резервируем для адресного окна регион размером 1 Мб

ULONG_PTR ulRAMBytes = 1024 * 1024;

PVOID pvWindow = VirtualAlloc(NULL, ulRAMBytes, MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE);

//получаем размер страниц на данной процессорной платформе

SYSTEM_INFO sinf; GetSystemlnfo(&sinf);

//вычисляем, сколько страниц памяти нужно для нашего количества байтов

UL0NG_PTR ulRAMPages = (ulRAMBytes + sinf.dwPageSize - 1) / sinf. dwPageSize;

//создаем соответствующий массив для номеров фреймов страниц

ULONG_PTR* aRAMPages = (UL0NG_PTR*) new UL0NG_PTR[ulRAMPages];

//выделяем страницы оперативной памяти (в полномочиях пользователя

//должна быть разрешена блокировка страниц в памяти)

AllocateUserPhysicalPages(

GetCurrentProcessO,

// выделяем память для нашего процесса

&ulRAMPages,

// на входе: количество запрошенных страниц

RAM,

 

aRAMPages);

// на выходе: количество выделенных страниц RAM

// назначаем страницы оперативной памяти нашему окну

MapUserPhysicalPages(pvWindow,

// адрес адресного окна

ulRAMPages,

// число элементов в массиве

aRAMPages);

// массив страниц RAM

//обращаемся к этим страницам через виртуальный адрес pvWindow

//освобождаем блок страниц оперативной памяти

FreeUserPhysicalPages(

GetCurrentProcessO, // освобождаем RAM, выделенную нашему процессу

&ulRAMPages,

// на входе: количество страниц RAM,

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

 

// на выходе: количество освобожденных страниц

RAN aRAMPages);

// на входе: массив, идентифицирующий освобождаемые

 

// страницы RAM

 

// уничтожаем адресное окно

VirtualFree(pvWindow, 0, MEM_RELEASE); delete[] aRAMPages;

Как видите, пользоваться AWE несложно. А теперь хочу обратить ваше внимание на несколько интересных моментов, связанных с этим фрагментом кода.

Вызов VirtualAlloc резервирует адресное окно размером 1 Мб. Обычно адресное окно гораздо больше. Вы должны выбрать его размер в соответствии с объемом блоков оперативной памяти, необходимых вашему приложению. Но, конечно, размер такого окна ограничен размером самого крупного свободного (и непрерывного!) блока в адресном пространстве процесса. Флаг MEM_RESERVE указывает, что я просто резервирую диапазон адресов, а флаг MEMPHYSICAL — что в конечном счете этот диапазон адресов будет связан с физической (оперативной) памятью. Механизм AWE требует, чтобы вся память, связываемая с адресным окном, была доступна для чтения и записи; поэтому в данном случае функции VirtualAlloc можно передать только один атрибут защиты — PAGE_RE ADWRITE. Кроме того, нельзя пользоваться функцией VirtualProtect и пытаться изменять тип защиты этого блока памяти.

Для выделения блока в физической памяти надо вызвать функцию AllocateUserPhysicalPages:

BOOL AllocateUserPhysicalPages(

HANDLE hProcess,

PULONG_PTR pulRAMPages,

PULONG_PTR aRAMPages);

Она выделяет количество страниц оперативной памяти, заданное в значении, на которое указывает параметр pulRAMPages, и закрепляет эти страницы за процессом, определяемым параметром hProcess.

Операционная система назначает каждой странице оперативной памяти номер фрейма страницы (page frame number). По мере того как система отбирает страницы памяти, выделяемые приложению, она вносит соответствующие данные (номер фрейма страницы для каждой страницы оперативной памяти) в массив, на который указывает параметр aRAMPages. Сами по себе эти номера для приложения совершенно бесполезны; вам не следует просматривать содержимое этого массива и тем более что-либо менять в нем. Вы не узнаете, какие страницы оперативной памяти будут выделены под запрошенный блок, да это и не нужно. Когда эти страницы связываются с адресным окном, они появляются в виде непрерывного блока памяти. А что там система делает для этого, вас не должно интересо-

вать.

 

Когда функция

AllocateUserPhysicalPages возвращает управление, значе-

ние в pulRAMPages

сообщает количество фактически выделенных страниц.

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

Обычно оно совпадает с тем, что вы передаете функции, но может оказаться и поменьше.

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

Примечание. Конечно, оперативная память — ресурс драгоценный, и приложение может выделить лишь ее незадействованную часть. Не злоупотребляйте механизмом AWE: если ваш процесс захватит слишком много оперативной памяти, это может привести к интенсивной перекачке страниц на диск и резкому падению производительности всей системы. Кроме того, это ограничит возможности системы в создании новых процессов, потоков и других ресурсов. (Мониторинг степени использования физической памяти можно реализовать через функцию GlobalMemoryStatusEx.)

AllocateUserPhysicalPages требует также, чтобы приложению была разрешена блокировка страниц в памяти (т. е. у пользователя должно быть право «Lock Pages in Memory»), а иначе функция потерпит неудачу. По умолчанию таким правом пользователи или их группы не наделяются. Оно назначается учетной записи Local System, которая обычно используется различными службами. Если вы хотите запускать интерактивное приложение, вызывающее AllocateUserPhysicalPages, администратор должен предоставить вам соответствующее право еще до того, как вы зарегистрируетесь в системе.

Для вызова функции AllocateUserPhysicalPages необходимо право Lock Pages In Memory. О том, как предоставить и активировать его, рассказывается выше, в разделе «Резервирование региона с одновременной передачей физической памяти».

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

BOOL MapUserPhysicalPages(

PVOID pvAddressWindow,

ULONG_PTR ulRAMPages,

PULONG_PTR aRAMPages);

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

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

Примечание. Функция MapUserPhysicalPages отключает текущий блок оперативной памяти от адресного окна, если вместо параметра aRAMPages передается NULL. Вот пример:

// Отключает текущий блок оперативной памяти от адресного окна

MapUserPhysicalPages(pvWindow, ulRAMPages, NULL);

Связав блок оперативной памяти с адресным окном, вы можете легко обращаться к этой памяти, просто ссылаясь на виртуальные адреса относительно базового адреса адресного окна (в моем примере это pvWindow).

Когда необходимость в блоке памяти отпадет, освободите его вызовом функ-

ции FreeUserPkysicalPagesr.

BOOL FreeUserPhysicalPages(

HANDLE hProcess,

PULONG_PTR pulRAMPages,

PULONG_PTR aRAMPages);

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

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

В моем простом примере создается одно адресное окно и единственный блок памяти. Это позволяет моей программе обращаться к оперативной памяти, которая никогда не будет сбрасываться на диск. Однако приложение может создать несколько адресных окон и выделить несколько блоков памяти. Эти блоки разрешается связывать с любым адресным окном, но операционная система не позволит связать один блок сразу с двумя окнами.

64-разрядная Windows полностью поддерживает AWE, так что перенос 32разрядных приложений, использующих этот механизм, не вызывает никаких проблем. Однако AWE не столь полезен для 64-разрядных приложений, поскольку размеры их адресных пространств намного больше. Но все равно он дает возможность приложению выделять физическую память, которая никогда не сбрасывается на диск.

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

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

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

Изначально первый блок занимает строка «Text in Storage 0», второй — строка «Text in Storage 1». Далее первый блок связывается с первым адресным окном, а второй — со вторым окном. При этом окно программы выглядит так, как показано ниже.

Оно позволяет немного поэкспериментировать. Во-первых, эти блоки можно назначить разным адресным окнам, используя соответствующие поля со списками. В них, кстати, предлагается и вариант No Storage, при выборе которого память отключается от адресного окна. Во-вторых, любое изменение текста немедленно отражается на блоке памяти, связанном с текущим адресным окном.

Если вы попытаетесь связать один и тот же блок памяти с двумя адресными окнами одновременно, то, поскольку механизм AWE это не разрешает, на экране появится следующее сообщение.

Исходный код этой программы-примера предельно ясен. Чтобы облегчить работу с механизмом AWE, я создал три С++-класса, которые содержатся в файле AddrWindows.h. Первый класс, CSystemInfo, — очень простая оболочка функции GetSystemInfo. По одному его экземпляру создают остальные два класса.

Второй С++-класс, CAddrWindow, инкапсулирует адресное окно. Его метод Create резервирует адресное окно, метод Destroy уничтожает это окно, метод UnmapStorage отключает от окна связанный с ним блок памяти, а метод оператора приведения PVOID просто возвращает виртуальный адрес адресного окна.

Третий С++-класс, CAddrWmdowStorage, инкапсулирует блок памяти, который можно назначить объекту класса CAddrWindow. Метод Allocate разрешает блокировать страницы в памяти, выделяет блок памяти, а затем отменяет право на блокировку. Метод Free освобождает блок памяти. Метод HowManyPagesAllocated возвращает количество фактически выделенных страниц. Наконец, метод MapStorage связывает блок памяти с объектом класса CAddrWindow, а UnmapStorage отключает блок от этого объекта.

Применение С++-классов существенно упростило реализацию программы AWE. Она создает по два объекта классов CAddrWindow и CAddr-

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

WindowStorage. Остальной код просто вызывает нужные методы в нужное время. Обратите также внимание на добавленный к программе манифест, заставляющий систему выводить окно с запросом повышения привилегий.

AWE.cpp

/****************************************************************************** Module: AWE.cpp

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

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

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

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

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

CAddrWindow g_aw[2];

// 2

memory address windows

CAddrWindowStorage g_aws[2];

//

2

storage blocks

const ULONG_PTR g_nChars = 1024;

//

1024 character buffers

const DWORD g_cbBufferSize = g_nChars * sizeof(TCHAR);

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

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

chSETDLGICONS(hWnd, IDI_AWE);

//Create the 2 memory address windows chVERIFY(g_aw[0].Create(g_cbBufferSize)); chVERIFY(g_aw[1].Create(g_cbBufferSize));

//Create the 2 storage blocks

if (!g_aws[0].Allocate(g_cbBufferSize)) {

chFAIL("Failed to allocate RAM.\nMost likely reason: "

"you are not granted the Lock Pages in Memory user right.");

}

chVERIFY(g_aws[1].Allocate(g_nChars * sizeof(TCHAR)));

// Put some default text in the 1st storage block g_aws[0].MapStorage(g_aw[0]);

_tcscpy_s((PTSTR) (PVOID) g_aw[0], g_cbBufferSize, TEXT("Text in Storage 0"));

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