Лабораторная работа № 5. Использование механизма виртуальной памяти в ОС Windows

 

Цель: Изучение виртуальной памяти в операционной системе Windows.

Задачи:

1. Изучение теоретического материала по виртуальной памяти.

2. Составление алгоритма программы.

3. Программная реализация.

 

Ход работы:

1. Ознакомиться со спецификациями функций WinAPI по работе с разделами виртуального адресного пространства процессов и «кучами».

2. Получить индивидуальный вариант задания у преподавателя.

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

4. Написать отчет и представить его для защиты вместе с исполняемым модулем программы и ее исходными текстами.

 

Ход защиты:

1. Продемонстрировать преподавателю программу, использующую механизмы виртуальной памяти.

2. Пояснить программный код разработанного приложения.

 

Виртуальное адресное пространство каждого процесса в ОС Windows NT /2000/ XP организовано следующим образом. В момент своего создания, оно почти полностью пусто. Для использования какой-то его части, необходимо выделить в нем определенные регионы с помощью функции
VirtualAlloc, эта операция называется резервированием. При этом ОС должна выравнивать начало региона в соответствии с так называемой гранулярностью выделения памяти, которая составляет на текущий момент времени
64 Кб. Также система должна учитывать, что размер региона должен быть кратен размеру страницы. Для процессоров Pentuim размер страницы составляет 4 Кб. Иными словами, если процесс попытается зарезервировать 10 Кб, то будет выделен регион размером 12 Кб. Когда регион становится не нужен, его необходимо освободить вызовом функции VirtualFree.

Чтобы использовать выделенный регион виртуального адресного пространства (ВАП), для него необходимо также выделить физическую память, спроецировав ее на регион. Эта операция называется передачей физической памяти и осуществляется с помощью функции VirtualAlloc. Для физической памяти определяют ее возврат, что выполняется с помощью функции
VitualFree. Обе упомянутые функции будут описаны ниже.

Отдельным страницам физической памяти можно устанавливать атрибуты защиты:

 

PAGE_NOACCESS Любая операция вызовет нарушение доступа
PAGE_READONLY Попытки записи или исполнения могут вызвать нарушение доступа
PAGE_READWRITE Попытки исполнить содержимое страницы вызывают нарушение доступа
PAGE_EXECUTE Чтение и запись могут вызвать нарушение доступа
PAGE_EXECUTE_READ Нарушение доступа при попытке записи
PAGE_EXECUTE_READWRITE Возможны любые операции
PAGE_WRITECOPY При исполнении этой страницы нарушение доступа, при записи процессу дается личная копия страницы
PAGE_EXECUTE_WRITECOPY Любые операции, при записи процессу дается личная копия страницы
PAGE_NOCACHE Отключает кэширование страницы (флаг)
PAGE_WRITECOMBINE Объединение нескольких операций записи (флаг)
PAGE_GUARD Используется для получения информации о записи на какую-либо страницу (флаг)

Узнать размеры страницы, гранулярность выделения памяти и другие параметры ОС можно с помощью функции Win 32 API

VOID GetSystemInfo (LPSYSTEM_INFO SysInfo);

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

typedef struct {

union {

DWORD Oem; // не используется

struct {

    WORD ProcArchitecture; // типа архитектуры процессора

    WORD Reserved; // не используется

};

};

DWORD PageSize; // размер страницы в байтах

LPVOID MinApplnAddress; // минимальный адрес доступного ВАП

LPVOID MaxApplnAddress; // максимальный адрес доступного ВАП

DWORD_PTR ActiveProcessors; // процессоры, выполняющие потоки

DWORD NumberOfProc; // количество установленных процессоров

DWORD ProcType; // тип процессора для Windows 98/Me

DWORD Granularity; // гранулярность

WORD ProcLevel, ProcRevizion;

// дополнительные параметры для ОС Windows 2000

} SYSTEM_INFO, *LPSYSTEM_INFO;

 

Если есть необходимость узнать параметры, которые имеют отношение к памяти (а их всего четыре), достаточно выполнить всего два оператора

SYSTEM_INFO SysInfo;

GetSystemInfo (&SysInfo);

 

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

Следующая функция позволяет отслеживать состояние памяти на
текущий момент времени, однако она имеет довольно странное название

VOID GlobalMemoryStatus (LPMEMORY_STATUS MemStat);

 

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

typedef struct {

DWORD Length; // размер структуры в байтах

DWORD MemLoad;

// Занятость подсистемы управления памятью (0 - 100)

SIZE_T TotalPhysMem; // объем физической памяти

SIZE_T AvailablePhysMem; // объем свободной физической памяти

SIZE_T TotalPageFile; // максимальный размер файла подкачки

SIZE_T AvailablePageFile;

// размер свободного места в файле подкачки

SIZE_T TotalVirtual; // максимальный размер ВАП процесса

SIZE_T AvailableVirtual; // размер доступного ВАП процесса

} MEMORY_STATUS, *LPMEMORY_STATUS;

 

Если нужно получить какие-то параметры памяти, потребуется еще несколько операторов, но предварительно потребуется установить длину всей структуры.

MEMORY_STATUS MemStat;

MemStat.Length = {sizeof (MemStat)};

GlobalMemoryStatus (&SysInfo);

 

В дальнейшем можно смело пользоваться значениями остальных полей структуры MemStat.

 

В ОС Windows NT /2000/ XP есть функция, которая позволяет получать информацию о конкретном участке памяти в пределах ВАП процесса.

DWORD VirtualQuery (

LPCVOID Address, // адрес участка памяти

PMEMORY_BASIC_INFORMATION MemBase,

// адрес структуры с информацией о памяти

DWORD Length); // размер структуры с информацией о памяти

 

Этой функции требуется адрес структуры типа MEMORY_BASIC_INFORMATION, описанной как

typedef struct {

PVOID Base;

// Address, округленный до адреса, кратного размеру страницы

PVOID AllocBase;

// Базовый адрес региона, в который входит адрес Address

DWORD AllocProtection; // атрибут защиты для региона

SIZE_T RegionSize;

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

DWORD State; // состояние всех смежных страниц

DWORD Protection; // атрибуты защиты всех смежных страниц

DWORD Type; // тип физической памяти всех смежных страниц

} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

 

В том случае, если понадобится вывести информацию о регионах ВАП текущего процесса, можно это сделать следующим образом, однако весьма «грубо»:

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

SYSTEM_INFO SysInfo;

GetSystemInfo (&SysInfo);

 

PVOID BaseAddr = NULL;

MEMORY_BASIC_INFORMATION MemBase;

for (;;) {

if(VirtalQuery(BaseAddr,&MemBase,sizeof(MemBase))!=

sizeof(MemBase)) break;

printf (“%Lp\t\”, MemBase.AllocBase); // Базовый адрес

printf (“%d\t”, MemBase.RegionSize / SysInfo.PageSize);

// Страниц на регион

switch (MemBase.State) { // Состояние региона

case MEM_FREE: printf (“Свободный\t”); break;

case MEM_RESERVE: printf (“Зарезервирован,

        память не передана\t”); break;

case MEM_COMMIT: printf (“Зарезервирован,

        память передана\t”); break;

}

switch (MemBase.Protection) { // Атрибуты защиты региона

case PAGE_NOACCESS: printf (“----\t”); break;

case PAGE_READONLY: printf (“R---\t”); break;

case PAGE_READWRITE: printf (“RW--\t”); break;

case PAGE_EXECUTE: printf (“--E-\t”); break;

case PAGE_EXECUTE_READ: printf (“R-E-\t”); break;

case PAGE_EXECUTE_ READWRITE: printf (“RWE-\t”); break;

case PAGE_EXECUTE_ WRITECOPY: printf (“RWEC\t”); break;

}

switch (MemBase.Type) { // Тип региона

case MEM_FREE: printf (“Свободный”); break;

case MEM_PRIVATE: printf (“Закрытый”); break;

case MEM_IMAGE: printf (“Образ файла”); break;

case MEM_MAPPED: printf (“Отображаемый файл”); break;

default: printf (“Неизвестно”);

}

printf (“\n”);

BaseAddr = MemBase.BaseAddress+ MemBase.RegionSize;

}

 

 

Виртуальная память очень удобна для работы с большими массивами данных. Для малых по размеру объектов больше подходят так называемые «кучи». Если есть надобность в обмене данными между процессами, то ОС Windows предлагает еще один механизм – отображаемые на память файлы.
О них более подробно будет рассказано там, где речь пойдет о подсистеме управления файлами. Ниже будут описаны функции для управления кучами. Функции, имеющие дело с виртуальной памятью, позволяют резервировать регион, отдавать ему физическую память и устанавливать параметры защиты.

 

Для резервирования предназначена функция

PVOID VirtualAlloc (

PVOID Address,

// адрес, где система должна резервировать память

SIZE_T Size, // размер региона, который надо зарезервировать

DWORD AllocType,

//тип резервирования(резервировать или передать физ.память)

DWORD Protect); // атрибут защиты (PAGE_*)

 

Функция возвращает NULL, если не удалось выделить память для
региона. Для резервирования достаточно при вызове указать тип MEM_RESERVE. Если теперь надо передать ему физическую память, нужно еще раз вызвать VirtualAlloc с уже знакомым флагом доступа MEM_COMMIT. Можно выполнить обе операции одновременно

PVOID Region = VirtualAlloc (NULL, 25 * 1024,

MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

Здесь содержится запрос на выделение региона размером 25 Кб и передачи ему физической памяти. Поскольку первый параметр функции равен NULL, ОС попытается найти подходящее место, просматривая все ВАП.
Регион и переданная ему память получат одинаковый атрибут защиты PAGE_EXECUTE_READWRITE.

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

BOOL VirtualFree (

PVOID Address,  // адрес, где система зарезервировала память

SIZE_T Size,  // размер региона, который был зарезервирован

DWORD FreeType); //тип освобождения

 

Для освобождения региона нужно вызвать VirtualFree с его адресом, в Size указать 0, поскольку система знает размер региона, а в FreeTypeMEM_RELEASE. Если же возникла необходимость просто вернуть часть физической памяти, то Address должен адресовать первую возвращаемую страницу, Size – количество освобождаемых байтов, а FreeType – идентификатор MEM_COMMIT.

PVOID Region;

VirtualFree (Region, 0, MEM_RELEASE);

 

Атрибуты защиты страницы памяти можно вызовом функции

PVOID VirtualProtect (

PVOID Address, // адрес, где система зарезервировала память

SIZE_T Size, // число байтов, для которых меняется защита

DWORD NewProtection, // новые атрибуты защиты

PDWORD OldProtecttion); //старые атрибуты защиты

 

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

PVOID Region = VirtualAlloc (NULL, 25 * 1024,

MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

DWORD OldProt;

VirtualProtect (Region, 3 * 1024, PAGE_READONLY, &OldProt);

 

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

В ОС Windows NT /2000/ XP при инициализации процесса в его ВАП создается стандартная куча, размер которой 1 Мб. Описатель этой кучи можно при помощи вызова функции

HANDLE GetProcessHeap ();

 

Можно создать и дополнительные кучи, для этого нужна функция

HANDLE HeapCreate (

DWORD Options, // способ выполнения операций над кучей

SIZE_T StartSize, // начальное количество байтов в куче

SIZE_T MaxSize); // максимальное количество байтов в куче

 

Если в Options указан 0, то к куче могут одновременно обращаться несколько потоков. Атрибут HEAP_NO_SERIALIZE позволяет потоку осуществлять доступ к куче монопольно, однако пользоваться таким способом не рекомендуется. Другой флаг HEAP_GENERATE_EXCEPTIONS при ошибке обращения к куче дает системе возможность уведомлять программы об этом. Если в третьем параметре MaxSize указать значение больше 0, то будет создана нерасширяемая куча именно такого размера, в противном случае система резервирует регион и может расширять его до максимального размера. Данная функция в случае успеха возвращает описатель вновь созданной кучи.

 

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

PVOID HeapAlloc (

HANDLE Heap, // описатель кучи

DWORD Flags, // флаги выделения памяти

SIZE_T Bytes); // количество выделяемых байтов

 

Если в качестве флага указан HEAP_ZERO_MEMORY, функция возвратит блок памяти, заполненный нулями. Назначение HEAP_GENERATE_EXCEPTIONS и HEAP_NO_SERIALIZE очевидно.

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

PVOID HeapReAlloc (

HANDLE Heap, // описатель кучи

DWORD Flags, // флаги изменения памяти

PVOID Memory, // текущий адрес блока

SIZE_T Bytes); // новый размер в байтах

 

Возможны четыре значения флага: HEAP_NO_SERIALIZE, HEAP_GENERATE_EXCEPTIONS, HEAP_ZERO_MEMORY и HEAP_REALLOC_IN_PLACE_ONLY. Два первых из них знакомы по
HeapAlloc. При использовании HEAP_ZERO_MEMORY нулями заполняются только дополнительный байты. Флаг HEAP_REALLOC_IN_PLACE_ONLY говорит о том, что блок перемещать внутри кучи нельзя. Возвращает эта функция адрес нового блока либо NULL, если не удалось изменить размер.

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

SIZE_T HeapSize (

HANDLE Heap, // описатель кучи

DWORD Flags,    

// флаги изменения памяти (0 или HEAP_NO_SERIALIZE)

PVOID Memory); // текущий адрес блока

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

BOOL HeapFree (

HANDLE Heap, // описатель кучи

DWORD Flags,     

// флаги изменения памяти (0 или HEAP_NO_SERIALIZE)

PVOID Memory); // текущий адрес блока

 

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

BOOL HeapDestroy (HANDLE Heap); // описатель кучи

 

Небольшой фрагмент кода демонстрирует использование некоторых из этих функций.

// получение описателя стандартной кучи активного процесса

HANDLE SysHeap = GetProcessHeap ();

 

UINT MAX_ALLOCATIONS = 15;

// максимальное количество выделений памяти

UINT NumOfAllocations = 0;

// текущее количество выделений памяти

 

for (;;) {

// выделение из кучи 2 Кб памяти

if (HeapAlloc (SysHeap, 0, 2 * 1024) == NULL) break;

else ++ NumOfAllocations;

// условие прерывания цикла

if (NumOfAllocation == MAX_ALLOCATIONS) break;

}

 

 

// вывод соответствующих сообщений в зависимости от ситуации

if (NumOfAllocations == 0)

printf (“Память из кучи не выделялась.”);

else printf (“Память из кучи выделялась %d раз.”,

        NumOfAllocations);

 

В ОС Windows NT /2000/ XP есть пара функций Win 32 API, которые позволяют блокировать (или зафиксировать) и разблокировать страницу в оперативной памяти. Функция VirtualLock позволяет предотвратить запись памяти на диск.

BOOL VirtualLock (

LPVOID Address, // адрес начала памяти

SIZE_T Size); // количество байтов

 

Если фиксация больше не нужна, то ее можно убрать функцией.

BOOL VirtualUnlock (LPVOID Address, // адрес начала памяти. SIZE_T Size); // количество байтов

 

Обе функции возвращают ненулевое значение в случае успеха.
 Следующий фрагмент демонстрирует их использование.

int MEMSIZE = 4096;PVOID Mem = NULL; int num; Mem = VirtualAlloc(NULL,4 * 1024, MEM_RESERVE,              PAGE_EXECUTE_READWRITE);if (Mem!= NULL) { if (VirtualLock (Mem, MEMSIZE)) printf ("Привязка\n"); else printf (“Ошибка привязки”); scanf (“%d”, &num); if (VirtualUnlock (Mem, MEMSIZE))         printf ("Привязка снята\n"); else printf ("Ошибка снятия привязки\n"); if (VirtualFree (Mem, 0, MEM_RELEASE))         printf ("Память освобождена\n"); else printf ("Память не освобождена\n"); }else printf ("Память не выделена\n");}









Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: