Введение: от MS DOS к MS Windows.
при переходе от MS DOS к MS Windows 3.x и далее к MS Windows 9x/Me и NT/2000/XP система управления памятью (СУП) претерпела существенные изменения. Чем проще операционная система (ОС) тем больше ее СУП отражает особенности аппаратной платформы, на которой выполняется ОС. MS DOS, ориентированная на работу с младшими моделями процессоров семейства Intel 80x86, поддерживала сегментированную модель памяти. В этой модели полный адрес ячейки ОЗУ формируется из двух частей: базового адреса (сегмент) и смещения от этой базы (собственно смещение). Такая модель соответствует стандартному (реальному) режиму работы процессоров Intel 80x86, в котором для хранения сегментной части адреса используются специальные 16-битные сегментные регистры. Смещение также хранится в 16-битных регистрах процессора. В такой архитектуре для доступа к ячейкам памяти, отстоящим от базы не более чем на 216 байт, достаточно менять только смещение, оставляя значение сегмента неизменным. Отсюда возникает "магическое число" - максимальный размер сегмента MS DOS - 64 Кб (216 байт).
Языки программирования, поддерживающие создание системных приложений, для хранения адресов используют специальные переменные - указатели. В MS DOS возможно использование ближних (только смещение) и дальних (сегмент и смещение) указателей. Когда программа загружается в ОЗУ, часть адресов уже занята ядром ОС, драйверами устройств, другими программами (резидентными). Поэтому реальные адреса объектов программы формируются совместными действиями компилятора языка программирования и загрузчика ОС. В простейшем случае, компилятор формирует два сегмента: кода (содержит коды всех функций программы) и данных (глобальные/статические переменные и программный стек). Все идентификаторы программы после компиляции представлены в программе их адресами. При этом компилятор отсчитывает адреса от некоторого условного стартового или базового адреса. Загрузчик ОС определяет свободный адрес в ОЗУ и, используя его в качестве базового, настраивает все адреса программы. Глобальные и статические переменные существуют все время, пока выполняется программа (занимают фиксированный диапазон адресов в сегменте данных). Локальные переменные размещаются в программном стеке и, хотя стек - это динамически изменяющаяся структура данных, но все изменения стека определяются компилятором. Между адресом последней глобальной или статической переменной и верхушкой стека образуется область незанятых адресов. Эта область называется локальной "кучей" (local heap). Все свободные адреса за программой образуют глобальную кучу (global heap). Когда говорят о динамическом распределении памяти, то подразумевают возможность захвата, использования и освобождения дополнительных блоков памяти из глобальной или локальной кучи во время выполнения программы. В Си для динамического распределения памяти используют функции стандартной библиотеки: malloc(), calloc(), realloc(), free(). В C++ - операции new и delete.
MS Windows 3.x переводит процессор в защищенный режим работы, с иной, более сложной схемой обращения к памяти. В этом режиме память распределяется страницами по 4 Кб каждая. На аппаратном уровне для адресации используются регистры дескрипторов страниц. Программа же работает не с физическими, а с виртуальными (логическими) адресами. ОС использует файл подкачки, позволяющий поддерживать диапазон виртуальных адресов больший, чем диапазон физически адресуемой памяти. При нехватке физической памяти часть страниц может быть выгружена в файл подкачки, а затем, при необходимости, вновь загружена в ОЗУ. При повторной загрузке, в общем случае, меняются как физический, так и виртуальный адреса страницы, поэтому программа не может полагаться на то, что указатель на динамически выделенный блок памяти содержит действительный адрес ячейки ОЗУ. ОС учитывает занятые блоки памяти, присваивая им уникальные целые положительные значения - дескрипторы. В связи с этим техника работы с динамически распределяемой памятью в MS Windows такова:
· с помощью специальных функций MS Windows API (Application Programming Interface - набор функций, вызываемых приложением для запроса определенных сервисов у ОС), но не функций malloc(), calloc() или операции new, запрашивается блок памяти нужного размера из локальной (LockalAlloc()) или глобальной (GlobalAlloc()) кучи. При этом функция возвращает дескриптор блока;
· всякий раз, когда необходим доступ к захваченному блоку, необходимо зафиксировать его в памяти (т.е. зафиксировать виртуальные адреса ячеек блока) с помощью другой функции (LocalLock() или GlobalLock()), которой передается полученный прежде дескриптор (дескрипторы не меняются при перемещении блоков памяти). Функция возвратит указатель, с которым теперь можно работать так же, как и в MS DOS;
· завершив операции по доступу к блоку, необходимо отменить его фиксацию (функция LockalUnlock() или GlobalUnlock());
· если блок больше не нужен - освободить память (LockalFree() или GlobalFree()).
Несмотря на столь существенные изменения, MS Windows 3.x все еще тесно связаны с понятием сегмента. Само разделение на локальную и глобальную кучи связано со все тем же 16-битным смещением или полным 32-битным адресом (сегмент:смещение). Более того, многие функции переключают процессор в реальный режим и являются по сути MS DOS функциями. Поэтому API MS Windows 3.x называют Win16 API.
Семейство ОС MS Windows 9x/Me и, тем более, MS Windows NT/2000/XP поддерживают не сегментированную, "плоскую" модель памяти. Весь (виртуальный) адрес вырождается в смещение, но это смещение 4-х байтовое (32 двоичных разряда). На текущий момент существует 64-разрядная версия MS Windows XP, в которой адрес задается 8-ю байтами. На аппаратном уровне, безусловно, используются регистры дескрипторов страниц, но приложение оперирует только с 4-х (или 8-и) байтовыми виртуальными адресами. В такой модели уже не может быть локальной кучи. Функции LocalAlloc(), GlobalAlloc() и т.д. поддерживаются, но, во-первых, работают они одинаково, а, во-вторых, лишь производят обращение к другой, базовой для Win32 API функции - VirtualAlloc(). теперь не нужно заботиться о фиксации блока в ОЗУ, т.к. виртуальные (но не физические) адреса ячеек блока не изменяются при любых его перемещениях, проводимых ОС. Учет блоков по-прежнему ведется с помощью дескрипторов, но, единожды получив указатель на блок, можно пользоваться им вплоть до удаления (освобождения) блока. Более того, VirtualAlloc() позволяет размещать блок по указанным (свободным) адресам (с учетом выравнивания на границу страницы) и задавать для него уровень доступа, т.е. обеспечивает уровень управления недоступный для функций динамического распределения памяти в MS DOS и MS Windows 3.x. С другой стороны это усложняет работу по динамическому распределению памяти. Иногда старая, простая дисциплина работы с некоторой "кучей" более удобна. И программист может использовать ее в программе. Для этого существует набор функций работы с "кучами" (HeapCreate(), HeapAlloc() и т.д.). Эти функции позволяют создать в программе несколько куч. Но, в любом случае, процесс окажется владельцем хотя бы одной кучи. Эту кучу не нужно создавать и нельзя уничтожить, но стандартные функции malloc(), calloc() и т.д. и операции new, delete распределяют память из этой кучи.
I. Имена, используемые в Windows для указателей и дескрипторов.
Application Programming Interface (API) включает в себя, помимо функций, также большой набор имен для различных типов данных - синонимов для стандартных типов и новых имен для новых составных типов. Каждая Windows-программа содержит файл windows.h "... поскольку он определяет очень широкий набор констант, структур, макросов и функций, которые составляют скелет всех Windows-программ". "Внутри windows.h вы найдете объявления ... большинства функций и типов среды Windows". "windows.h - это место, где объясняются многие таинства" [2]. Структура файла windows.h для новых (после 3.x) версий Windows изменилась радикально. Теперь это небольшой файл, в который помещаются директивы #include. Сами же определения разбросаны по многим .h-файлам. Поэтому для поиска нужной информации удобно использовать утилиты типа grep.exe из пакета Borland C++.
ЗАДАНИЕ 1. найдите в файлах windows.h, windef.h и т.п. объявления имен UINT, LPSTR, HANDLE, HINSTANCE, DWORD, HGLOBAL, LPVOID, BOOL, GLOBALHANDLE и запишите как, в конечном счете, соответствующие описания выражаются через стандартные типы языка Си. Попробуйте определить назначение именованной константы STRICT.
II. Динамическое распределение памяти.
При запросе на динамическое выделение блока памяти указывается тип блока: фиксированный (fixed), перемещаемый (movable) или удаляемый (снимаемый, отбрасываемый) (discardable). Фиксированный блок памяти Windows может перемещать в физической памяти, но его виртуальный адрес остается неизменным. Приложение имеет дело только с виртуальными адресами, поэтому для работы с таким блоком можно использовать указатель, не интересуясь его дескриптором. Movable-блок может быть перемещен Windows как в физическом, так и в виртуальном адресном пространстве (чтобы избежать фрагментации виртуальной памяти). Неизменным останется лишь дескриптор блока. Поэтому при работе с перемещаемым блоком памяти нужно сначала его заблокировать (вызвав функцию GlobalLock см. ниже). Это приведет, во-первых, к фиксации виртуальных адресов ячеек блока, во-вторых, функция вернет указатель на начало блока и, в-третьих, к увеличению на единицу значения счетчика блокирований данного блока. Дальнейшая работа с блоком проводится обычным образом через полученный указатель. Когда текущая работа с блоком заканчивается его нужно снова разблокировать (вызвав функцию GlobalUnlock). Отбрасываемые блоки памяти (только Win16) отдают Windows полный контроль над блоком. При необходимости эти блоки могут уничтожаться самой ОС. Если затем они понадобятся в программе их нужно создавать вновь. В этом случае необходимо перед использованием блока проверять "жив" он или нет (если нет, то GlobalLock вернет NULL). Отбрасываемые блоки обязательно должны объявляться также и перемещаемыми.
В общем случае для динамического распределения памяти можно использовать функции стандартной библиотеки Си, операции C++ или специальные функции из MS Windows API.
1). Использование функций стандартной библиотеки Си.
Фиксированные блоки памяти можно динамически захватывать с помощью стандартных функций Си (прототипы в файле stdlib.h):
void *malloc(UINT size);
void *calloc(UINT elem_count, UINT elem_size);
void *realloc(void *old_ptr, UINT new_size);
void free(void *ptr);
Техника работы с динамической памятью в этом случае та же, что и при программировании в MS DOS. Отличия связаны лишь с вопросом о поддерживаемой модели памяти: сегментированная (ближние, дальние указатели) или с прямой 32-разрядной адресацией. Старые модели Windows (до Windows 95) поддерживали сегментированную модель памяти. В этом случае, как и для MS DOS, размер сегмента не может превышать 64K. При использовании прямой 32-разрядной адресации, программа может непосредственно обращаться к любой ячейке виртуальной памяти в пределах доступных 4 гигабайт (232). На самом деле программа для собственных нужд может использовать лишь половину этого адресного пространства. Остальную часть Windows "... использует в системных целях".
2). Использование операций С++.
Операции new и delete можно использовать вместо стандартных функций Си для динамического захвата фиксированных блоков памяти.
3). Функции из WinAPI.
Windows API содержит набор функций для работы как с фиксированными, так и перемещаемыми и удаляемыми (отбрасываемыми) блоками памяти. Некоторые функции приведены в следующей таблице, взятой из [2].
Имя функции |
Описание |
CopyMemory |
Копирование блока памяти |
FillMemory |
Заполняет блок памяти заданными значениями |
GlobalAlloc, LocalAlloc |
Выделяет блок памяти заданного размера |
GlobalDiscard, LocalDiscard |
Отбрасывает выделенный блок памяти |
GlobalFlags, LocalFlags |
Возвращает флаги, соотв-е блоку памяти |
GlobalFree, LocalFree |
Освобождает выделенный блок памяти |
GlobalHandle, LocalHandle |
Получает дескриптор выделенного блока памяти |
GlobalLock, LocalLock |
Блокирует выделенный блок памяти |
GlobalMemoryStatus |
Возвращает размеры доступной физ. и вирт. памяти |
GlobalRealloc, LocalRealloc |
Изменяет размер выделенного блока памяти |
GlobalSize, LocalSize |
Возвращает размер выделенного блока памяти |
GlobalUnlock, LocalUnlock |
Отменяет блокировку блока памяти |
MoveMemory |
Перемещает блок памяти |
VirtualAlloc |
Выделяет виртуальную память |
VirtualFree |
Освобождает виртуальную память |
ZeroMemory |
Заполняет блок памяти нулями |
В Win16 наиболее часто используются функции:
HGLOBAL GlobalAlloc(UINT uFlags, DWORD dwBytes);
HGLOBAL GlobalRealloc(HGLOBAL hMem, DWORD dwBytes, UINT uFlags);
HGLOBAL GlobalFree(HGLOBAL hMem);
GlobalAlloc позволяет захватить блок памяти любого типа. Этот тип задается параметром uFlags. Возможные значения объявлены в виде символических констант:
GMEM_FIXED - фиксированный блок
GMEM_MOVEABLE - перемещаемый
GMEM_DISCARDABLE - отбрасываемый (только Win16).
Вместе с этими константами может задаваться флаг GMEM_ZEROINIT, тогда выделенный блок памяти заполняется нулями. Для комбинаций констант определены свои имена:
GPTR как GMEM_FIXED | GMEM_ZEROINIT
GHND как GMEM_MOVEABLE | GMEM_ZEROINIT. Остальные возможные значения см. в файле GlobalAlloc.rtf.
Параметр dwBytes содержит размер запрашиваемого блока в байтах.
Функция возвращает дескриптор для перемещаемого и указатель для фиксированного блока памяти.
Пример 1.
int *ptr = (int *)GlobalAlloc(GPTR,4096); // Фиксированный, заполненный нулями
// блок, размером 4K
ptr[100] += 10;
GlobalFree(ptr);
int *Ptr = (int *)malloc(4096); // Фиксированный, не инициализированный блок,
// размером 4K
Ptr[100] = -1;
free(Ptr);
Для работы с перемещаемыми (и отбрасываемыми) блоками нужно использовать функции GlobalLock и GlobalUnlock.
LPVOID GlobalLock(GLOBALHANDLE hMem);
BOOL GlobalUnlock(GLOBALHANDLE hMem);
Пример 2.
GLOBALHANDLE h = GlobalAlloc( GHND, 4096 ); // Перемещаемый, заполненный
// нулями блок
int *ptr = (int *)GlobalLock( h );
ptr[100] += 10;
GlobalUnlock( h );
GlobalFree( h );
См. Help по этим функциям в MSDN и в приложении.
Для определения параметров виртуальной и физической памяти можно использовать функцию GlobalMemoryStatus:
VOID GlobalMemoryStatus( LPMEMORYSTATUS lpBuffer );
См. Help по структуре LPMEMORYSTATUS, выдаваемый MSDN, в приложении.
Блоки памяти перемещаются из памяти в файл подкачки и наоборот фиксированными порциями - страницами. Размер страницы зависит от аппаратной платформы, на которой работает Windows. Для архитектуры Intel страницы имеют размер 4Kb. Узнать (в частности) размер страниц можно с помощью функции GetSystemInfo см. приложение.
В Win32 базовыми функциями являются VirtualAlloc и VirtualFree. Они обеспечивают возможности захвата или резервирования блоков памяти в любом месте адресного пространства процесса. Но, часто в программе удобнее использовать простую традиционную схему динамического распределения памяти.
В Win32 каждый процесс может использовать не одну, но произвольное количество отдельных куч. Windows обязательно создает для процесса "стандартную" кучу размером 1Mb, который, при необходимости может увеличиваться. Память, захватываемая с помощью malloc, calloc, realloc или new принадлежит этой куче. Но любой поток процесса может создавать (HeapCreate) и освобождать (HeapDestroy) дополнительные кучи. Каждая куча (в том числе стандартная) управляется через собственный дескриптор. Для вновь создаваемой кучи дескриптор возвращается функцией HeapCreate, дескриптор стандартной кучи возвращает функция GetProcessHeap, кроме того (только в Windows NT 3.5 и старше), используя функцию GetProcessHeaps, можно получить массив дескрипторов для всех куч процесса.
Зная дескриптор кучи, можно захватывать в ней блок памяти (HeapAlloc), изменять размер захваченного блока (HeapReAlloc) и освобождать блок (HeapFree), узнать размер блока, захваченного в куче и адресуемое заданным указателем (HeapSize). В Windows NT 3.5 и старше, кроме того, можно блокировать (HeapLock), разблокировать (HeapUnlock), проверять состояние (HeapValidate), дефрагментировать (HeapCompact) и просматривать информацию об отдельных блоках кучи (HeapWalk).
Описание функций см. в приложении.
4). Утилита heapwalk (только Windows 9x).
Эта утилита позволяет проследить за тем, как приложение использует глобальную память. В главном окне этой программы отображается список строк, каждая из которых содержит информацию об одном блоке памяти: адрес (ADDRESS), дескриптор (HANDLE), размер в байтах (SIZE), счетчик фиксирования блока памяти (плюс к тому, если P - для блока запрещен страничный обмен, если L - блок зафиксирован) (LOCK), флаг (FLG) содержащий F - если блок фиксированный и D - если discardable, наличие локальной кучи (HEAP) - если есть, то Y, имя приложения, владеющего блоком (OWNER), тип объекта (сегмент кода, данных, ресурс и т.п.) (TYPE). В меню Walk пункт Walk Heap позволяет увидеть список блоков. Пункт Walk LRU List - список всех удаляемых объектов, GC(0) - дефрагментировать глобальную кучу и выделить блок памяти размером 0 байт, GC(-1) and Walk - удалить все удаляемые сегменты и посмотреть список объектов.
ЗАДАНИЕ 2. 1) Используйте функцию GlobalMemoryStatus для определения параметров конкретной системы, на которой Вы работаете. Запишите и объясните полученные результаты. 2) Определите, сколько реально памяти можно динамически захватить в Вашей вычислительной системе. Объясните результат. 3) Используйте heapwalk (только Windows 9x) для нахождения заблокированных участков памяти. Можно ли использовать heapwalk для освобождения заблокированных участков памяти?
ЗАДАНИЕ 3. (только для MS Windows NT). Используйте функцию HeapWalk для исследования всех блоков всех куч процесса. Создайте консольное приложение для отображения информации, выдаваемой HeapWalk. HepWalk возвращает нулевое значение, если возникла ошибка или просмотрены все блоки кучи. Чтобы получить информацию о первом блоке в куче необходимо перед вызовом HeapWalk присвоить полю lpData структуры PROCESS_HEAP_ENTRY значение NULL. Вызывая HepWalk снова, не меняя значений полей структуры PROCESS_HEAP_ENTRY, получаем информацию о следующем блоке и т.д. Чтобы не возникало коллизий между разными потоками, которые могут менять состояние кучи, прежде чем вызывать HeapWalk нужно заблокировать (зафиксировать) кучу, после возврата из HeapWalk - разблокировать.
Приложение.
1. GlobalAlloc
Функция GlobalAlloc захватывает указанное число байт в куче. Поддерживается только для совместимости с Win16.
HGLOBAL GlobalAlloc(
UINT uFlags, // аттрибуты захватываемого блока
DWORD dwBytes // число байт в захватываемом блоке
);
Параметры:
uFlags - определяет, каким будет захваченный блок. Возможны следующие значения и их комбинации:
GMEM_FIXED - фиксированный блок, возвращаемое функцией GlobalAlloc значение - указатель на блок;
GMEM_MOVEABLE - перемещаемый блок, возвращаемое функцией значение - дескриптор блока, чтобы получить указатель, нужно передать дескриптор функции GlobalLock; не может использоваться в комбинации с GMEM_FIXED;
GPTR - комбинация GMEM_FIXED | GMEM_ZEROINIT;
GHND - комбинация GMEM_MOVEABLE | GMEM_ZEROINIT;
GMEM_ZEROINIT - все байты блока проинициализированы нулевыми значениями.
Если задать для uFlags нулевое значение, то это будет эквивалентно флагу GMEM_FIXED.
dwBytes - число байт в блоке.
Возвращаемые значения:
В случае успешного завершения возвращает дескриптор блока, в случае ошибки возвращает NULL. Чтобы получить более подробную информацию об ошибке, можно вызвать функцию GetLastError.
2. GlobalFree
Функция GlobalFree освобождает захваченный прежде блок памяти. Поддерживается только для совместимости с Win16.
HGLOBAL GlobalFree(
HGLOBAL hMem // дескриптор блока
);
Параметры:
hMem - дескриптор, возвращенный прежде функцией GlobalAlloc или GlobalReAlloc.
Возвращаемые значения:
В случае успешного завершения возвращает NULL, в случае ошибки возвращает переданный ей дескриптор блока. Чтобы получить более подробную информацию об ошибке, можно вызвать функцию GetLastError.
3. GlobalReAlloc
Функция GlobalReAlloc изменяет размер или атрибуты блока памяти. Размер можно уменьшать или увеличивать.
Поддерживается только для совместимости с Win16.
HGLOBAL GlobalReAlloc(
HGLOBAL hMem, // дескриптор изменяемого блока
DWORD dwBytes, // новый размер
UINT uFlags // новые аттрибуты
);
Параметры:
hMem - дескриптор, возвращенный прежде функцией GlobalAlloc или GlobalReAlloc.
dwBytes - новый размер блока в байтах. Если uFlags равен GMEM_MODIFY, этот параметр игнорируется.
uFlags - определяет, как перераспределять память. Если задан флаг GMEM_MODIFY, этот параметр определяет аттрибуты блока, и dwBytes игнорируется. В противном случае, этот параметр управляет переспределением памяти.
Флаг GMEM_MODIFY указывается в комбинации с флагом GMEM_MOVEABLE. Тогда блок объявленый прежде фиксированным становится перемещаемым.
Вместо GMEM_MODIFY можно указать GMEM_ZEROINIT. Тогда, если размер блока увеличивается, дополнительные байты будут обнулены.
Возвращаемые значения:
В случае успешного завершения возвращает дескриптор нового блока, в случае ошибки возвращает NULL. Чтобы получить более подробную информацию об ошибке, можно вызвать функцию GetLastError.
4. GlobalLock
Функция GlobalLock фиксирует блок в глобальной куче и возвращает указатель на его начало.
С каждым блоком памяти связан счетчик его блокировок (фиксаций). Функция при каждом обращении увеличивает значение счетчика на единицу.
Поодерживается только для совместимости с Win16.
LPVOID GlobalLock(
HGLOBAL hMem // дескриптор захваченного прежде блока
);
Параметры:
hMem - дескриптор, возвращенный прежде функцией GlobalAlloc() или GlobalReAlloc().
Возвращаемые значения:
В случае успеха, указатель на первый байт блока, иначе NULL.
5. GlobalUnlock
Функция GlobalUnlock уменьшает значение счетчика фиксаций блока памяти на единицу, если блок был захвачен с атрибутом GMEM_MOVEABLE, блок фиксированный (флаг GMEM_FIXED), функция не делает ничего.
Поодерживается только для совместимости с Win16.
BOOL GlobalUnlock(
HGLOBAL hMem // дескриптор захваченного прежде блока
);
Параметры:
hMem - дескриптор, возвращенный прежде функцией GlobalAlloc() или GlobalReAlloc().
Возвращаемые значения:
Если счетчик фиксаций блока не равен нулю - возвращает ненулевое значение. Если неуспех - возвращает ноль. Для получения более подробной информации об ошибке нужно вызвать функцию GetLastError(). Если GetLastError() возвращает NO_ERROR, блок уже не зафиксирован в памяти.
6. MEMORYSTATUS
Структура MEMORYSTATUS предназначена для хранения информации о физической и о виртуальной памяти чичтемы. Функция GlobalMemoryStatus() сохраняет информацию в структуре MEMORYSTATUS.
typedef struct _MEMORYSTATUS {
DWORD dwLength; // sizeof(MEMORYSTATUS)
DWORD dwMemoryLoad; // процент используемой памяти
DWORD dwTotalPhys; // физической памяти (в байтах)
DWORD dwAvailPhys; // свободно физической памяти (в байтах)
DWORD dwTotalPageFile; // байт в файле подкачки
DWORD dwAvailPageFile; // свободно в файле подкачки (байт)
DWORD dwTotalVirtual; // адресуемое пользователькое пространство адресов (в байтах)
DWORD dwAvailVirtual; // свободно байт в пользовательском адресном пространстве
} MEMORYSTATUS, *LPMEMORYSTATUS;
7. GlobalMemoryStatus
Функция GlobalMemoryStatus позволяет получить информацию об использовании физической и виртуальной памяти.
VOID GlobalMemoryStatus(
LPMEMORYSTATUS lpBuffer // указатель на структуру memorystatus
);
Параметр:
lpBuffer - указатель на структуру MEMORYSTATUS, в которой функция GlobalMemoryStatus() сохраняет информацию о текущем состоянии памяти системы.
8. SYSTEM_INFO
Структура SYSTEM_INFO содержит информацию о вычислительной системе: архитектуре и типе процессора, количестве процессоров в системе, размере страницы памяти и т.п.
typedef struct _SYSTEM_INFO {
union {
DWORD dwOemId; // для совместимости со старыми версиями Windows NT,
// теперь не используется
struct {
WORD wProcessorArchitecture;
// для Win9x - всегда PROCESSOR_ARCHITECTURE_INTEL
// для Windows NT возможны значения: PROCESSOR_ARCHITECTURE_INTEL,
// PROCESSOR_ARCHITECTURE_MIPS, PROCESSOR_ARCHITECTURE_ALPHA,
// PROCESSOR_ARCHITECTURE_PPC, PROCESSOR_ARCHITECTURE_UNKNOWN
WORD wReserved;
};
};
DWORD dwPageSize; // размер страницы памяти
LPVOID lpMinimumApplicationAddress; // указатель на наименьший доступный адрес
LPVOID lpMaximumApplicationAddress; // указатель на наибольший доступный адрес
DWORD dwActiveProcessorMask; // маска, представляющая множество доступных в
// системе процессоров: бит ноль - нулевой процессор: 0 - нет, 1 - есть; …; 31 бит - 31-й
// процессор
DWORD dwNumberOfProcessors; // число процессоров в системе
DWORD dwProcessorType; // тип процессора:
// PROCESSOR_INTEL_386, PROCESSOR_INTEL_486,
// PROCESSOR_INTEL_PENTIUM. Только для NT еще два значения:
// PROCESSOR_MIPS_R4000, // PROCESSOR_ALPHA_21064
DWORD dwAllocationGranularity; // минимальный размер реально захватываемого
// блока, обычно 64 Кб.
WORD wProcessorLevel; // В Win9x не используется, в NT: если
// wProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL, то возможны
// значения 3 (Intel 80386), 4 (Intel 80486), 5 (Pentium)
WORD wProcessorRevision; // В Win9x не используется, в NT номер модели и
// stepping
} SYSTEM_INFO;
Более подробно см. MSDN.
9. GetSystemInfo
Функция GetSystemInfo возвращает информацию о вычислительной системе.
VOID GetSystemInfo(
LPSYSTEM_INFO lpSystemInfo // указатель на структуру SYSTEM_INFO
);
10. VirtualAlloc
Функция VirtualAlloc резервирует или распределяет диапазон страниц в виртуальном адресном пространстве вызывающего ее процесса. Захваченная с помощью этой функции память автоматически инициализируется нулями, если не установлен флаг MEM_RESET.
LPVOID VirtualAlloc(
LPVOID lpAddress, // стартовый адрес резервируемого или распределяемого диапазона
DWORD dwSize, // размер блока
DWORD flAllocationType, // тип распределения
DWORD flProtect // способ доступа к блоку
);
Параметры:
lpAddress - стартовый адрес захватываемого или резервируемого блока. При резервировании блока, указанный адрес выравнивается на 64-килобайтную границу. Если распределяется память из прежде уже зарезервированного диапазона адресов, проводится выравнивание на границу страницы памяти. Если значение параметра NULL, адрес начала блока определяется ОС.
dwSize - размер блока в байтах. Реально выделяется память под целое число страниц. Если значение параметра lpAddress равно NULL, то dwSize плюс все байты до следующей страницы. Иначе, все страницы, содержащие адреса от lpAddress до (lpAddress+dwSize). Т.е. если, например, захватываем блок размером в страницу, но сос стартовым адресом смещенным от границы страницы на два байта, то реально будут захвачены две страницы.
flAllocationType - тип распределения. Могут указываться следующие флаги (или их комбинации):
Флаг |
Значение |
MEM_COMMIT |
Захватывает нужное число страниц в физической памяти или в файле подкачки. Попытка захватить уже захваченную прежде страницу не приводит к ошибке. |
MEM_RESERVE |
Резервирует указанный диапазон в адресном пространстве процесса не проводя реального захвата (распределения) физической памяти. Этот диапазон не может использоваться другими операциями распределения (функции LocalAlloc, GlobalAlloc и т.п.) до тех пор, пока не будет захвачен последующими вызовами VirtualAlloc. |
MEM_RESET |
только Windows NT: Указывает, что страницы памяти из диапазона от lpAddress до lpAddress +dwSize не будут читаться или записываться в файл подкачки. Фдаг MEM_RESET декларирует, что соделжимое страниц более не существенно и ОС может перезаписывать их содержимое. |
MEM_TOP_DOWN |
Распределяет память по наибольшим доступным адресам. |
flProtect - указывает тип защиты доступа к блоку. Если память захватывается, могут быть указаны следующие флаги (если нужно, то совместно с флагами PAGE_GUARD и PAGE_NOCACHE):
Флаг |
Значение |
PAGE_READONLY |
Доступ к распределенным страницам только для чтения. Генерируется исключение "запрет доступа" при попытке записи или выполнения страниц блока. |
PAGE_READWRITE |
Доступ на чтение и запись. |
PAGE_EXECUTE |
Разрешает доступ на запуск из адресов, принадлежащих страницам блока. Попытка чтения или записи в страницы блока приводит к исключению "запрет доступа". |
PAGE_EXECUTE_READ |
Позволяет использовать страницы только для запуска и чтения. |
PAGE_EXECUTE_READWRITE |
Позволяет запускать, читать и записывать в страницы блока. |
PAGE_GUARD |
Попытка записи или чтения для таких страниц вызывает исключение STATUS_GUARD_PAGE а затем сбрасывает GUARD флаг. Не может использоваться вместе с флагом PAGE_NOACCESS. |
PAGE_NOACCESS |
Запрещает доступ к распределенным страницам памяти. Любое обращение вызывает генерацию исключения "запрет доступа". |
PAGE_NOCACHE |
Позволяет не использовать кэширование распределенных страниц. Обычно не используется. Может быть полезен для драйверов устройств. Используется только вместе с другими флагами ограничения доступа (кроме PAGE_NOACCESS). |
Возвращаемые значения:
В случае успеха - стартовый адрес блока, в противном случае NULL.
11. VirtualFree
Функция VirtualFree освобождает зарезервированный или распределенный блок памяти в адресном пространстве текущего процесса.
BOOL VirtualFree(
LPVOID lpAddress, // адрес блока
DWORD dwSize, // размер блока
DWORD dwFreeType // тип операции
);
Параметры:
lpAddress - указатель на блок. Если параметр dwFreeType содержит флаг MEM_RELEASE, это должен быть стартовый адрес блока, возвращенный VirtualAlloc() при его резервировании.
dwSize - размер освобождаемого блока в байтах. Если параметр dwFreeType содержит флаг MEM_RELEASE, dwSize должен быть равен нулю. Иначе, освобождаются все страницы, содержащие адреса от lpAddress до (lpAddress+dwSize).
dwFreeType - тип операции. Может быть одно из следующих значений (но не оба сразу):
Флаг |
Значение |
MEM_DECOMMIT |
Освободить захваченный прежде блок. Попытка освободить не захваченную страницу не приводит к ошибке. |
MEM_RELEASE |
Освобождает зарезервированный прежде блок. Параметр dwSize должен быть равен нулю, иначе функция возвращает признак ошибки. |
Возвращаемые значения:
В случае успеха возвращает не нулевое значение, иначе ноль.
12. GetProcessHeap
Функция GetProcessHeap возвращает дескриптор стандартной кучи процесса. Этот дескриптор может использоваться, затем, при вызовах функций HeapAlloc, HeapReAlloc, HeapFree, и HeapSize.
HANDLE GetProcessHeap(VOID)
Возвращаемые значения:
В случае успеха возвращает дескриптор стандартной кучи процесса, иначе NULL. Замечание:
Полученный дескриптор нельзя использовать при вызове HeapDestroy.
GetProcessHeaps
Функция GetProcessHeaps возвращает дескрипторы всех доступных процессу куч.
DWORD GetProcessHeaps(
DWORD NumberOfHeaps, // максимальное число дескрипторов в буфере
PHANDLE ProcessHeaps // указатель на буфер с полученными дескрипторами
);
Параметры:
NumberOfHeaps - максимальное количество дескрипторов куч, которое может быть сохранено в буфере, адресуемом параметром ProcessHeaps.
ProcessHeaps - указатель на буфер для дескрипторов куч.
Возвращаемые значения:
Возвращаемое значение - число доступных процессу куч. Если это значение меньше или равно значению параметра NumberOfHeaps, оно совпадает с числом дескрипторов, помещенных в буфер, адресуемый ProcessHeaps. Если возвращаемое значение больше NumberOfHeaps, т.е. размер буфера недостаточен для хранения всех дескрипторов, то ни один дескриптор не сохраняется в буфере. В случае ошибки возвращается 0, т.к. любой процесс имеет как минимум одну, стандартную кучу.
Замечание:
Не поддерживается в Win9x.
13. HeapCreate
Функция HeapCreate резервирут диапазон адресов в виртуальном адресном пространстве процесса и распределяет (захватывает) блок указанного начального размера.
HANDLE HeapCreate(
DWORD flOptions, // флаги распределения
DWORD dwInitialSize, // начальный размер блока
DWORD dwMaximumSize // максимальный размер кучи
);
Параметры:
flOptions - возможные атрибуты новой кучи:
Флаг |
Значение |
HEAP_GENERATE_EXCEPTIONS |
При возникновении ошибки, например, нехватке памяти, будет генерироваться исключение. Без этого флага функция, в случае ошибки, возвращает NULL. |
HEAP_NO_SERIALIZE |
Используется, чтобы отменить сериализацию, при одновременном доступе нескольких потоков к куче. |
dwInitialSize - начальный размер кучи в байтах, значение выравнивается по границе следующей страницы.
dwMaximumSize - если значение этого параметра больше нуля, то он задает максимальный размер кучи (с учетом выравнивания на границу страницы). Кроме того, максимальный размер захватываемого в этой куче блока должен быть немного меньше чем 0x7FFF8 байт, даже если максимальный размер кучи больше этой величины. Если dwMaximumSize равен нулю, куча может менять свой максимальный размер. Ограничение - только общий размер доступной памяти. Запрос блока, размер которого больше чем 0x7FFF8 байт приводит к вызову VirtualAlloc для захвата такого блока памяти. Возвращаемые значения:
В случае успеха - дескриптор новой кучи. В случае неуспеха - NULL.
14. HeapDestroy
Функция HeapDestroy освобождает память занятую "кучей" и ее дескриптор становится недействительным.
BOOL HeapDestroy(
HANDLE hHeap // дескриптор кучи
);
Возвращаемые значения:
В случае успеха возвращает не нулевое значение, в противном случае - ноль.
15. HeapAlloc
Функция HeapAlloc распределяет фиксированный (не перемещаемый) блок памяти из кучи.
LPVOID HeapAlloc(
HANDLE hHeap, // дескриптор кучи
DWORD dwFlags, // флаги распределения
DWORD dwBytes // размер лока в байтах
);
Параметры:
hHeap - дескриптор кучи, возвращенный прежде функцией HeapCreate или функцией GetProcessHeap.
dwFlags - флаги распределения, которые могут перекрывать флаги заданные при создании кучи:
Флаг |
Значение |
HEAP_GENERATE_EXCEPTIONS |
При возникновении ошибки функция не будет возвращать NULL, но выбрасывается исключение, подобное "out-of-memory". |
HEAP_NO_SERIALIZE |
Не использовать "mutual exclusion" при доступе к куче нескольких потоков одновременно. Нельзя использовать для стандартной кучи. |
HEAP_ZERO_MEMORY |
Инициализировать выделенный блок нулями. |
dwBytes - число байт в блоке.
Возвращаемые значения:
В случае успеха возвращает указатель на распределенный блок, иначе, если не задан флаг HEAP_GENERATE_EXCEPTIONS, возвращает NULL. Если возникла ошибка и флаг HEAP_GENERATE_EXCEPTIONS задан, могут генерироваться следующие исключения:
Код |
Значение |
STATUS_NO_MEMORY |
Недостаточно памяти или куча повреждена. |
STATUS_ACCESS_VIOLATION |
Повреждена куча или заданы неправильные параметры. |
16. HeapReAlloc
Функция HeapReAlloc перераспределяет блок в куче, позволяя менять его размер или свойства. Новый блок будет фиксированным (not movable).
LPVOID HeapReAlloc(
HANDLE hHeap, // дескриптор кучи
DWORD dwFlags, // флаги нового блока
LPVOID lpMem, // указатель на старый (подлежащий изменению) блок
DWORD dwBytes // новый размер блока
);
Возможно задание следующих флагов:
HEAP_GENERATE_EXCEPTIONS, HEAP_NO_SERIALIZE, HEAP_ZERO_MEMORY и HEAP_REALLOC_IN_PLACE_ONLY. Последний указывает, что если новый блок больше старого, то его нельзя перемещать в памяти. Если это невозможно работа функции завершается ошибкой, а старый блок не меняется.
Возвращаемые значения:
В случае успеха возвращает указатель на новый блок, иначе, если не задан флаг HEAP_GENERATE_EXCEPTIONS, то возвращает NULL, а, если задан, генерируется исключение STATUS_NO_MEMORY или STATUS_ACCESS_VIOLATION.
17. HeapFree
Функция HeapFree освобождает блок, распределенный в куче с помощью HeapAlloc или HeapRealloc.
BOOL HeapFree(
HANDLE hHeap, // дескриптор кучи
DWORD dwFlags, // флаги
LPVOID lpMem // указатель на освобождаемый блок
);
Может быть указан только один флаг: HEAP_NO_SERIALIZE.
18. HeapSize
Функция HeapSize возвращает размер в байтах блока, распределенного прежде с помощью HeapAlloc или HeapRealloc.
DWORD HeapSize(
HANDLE hHeap, // дескриптор кучи
DWORD dwFlags, // флаги
LPCVOID lpMem // указатель на блок
);
Может быть указан только один флаг: HEAP_NO_SERIALIZE.
19. HeapWalk
Функция HeapWalk позволяет получать информацию о блоках памяти в указанной куче.
BOOL HeapWalk(
HANDLE hHeap, // дескриптор кучи
LPPROCESS_HEAP_ENTRY lpEntry // указатель на структуру, в которой будет
// сохранена информация о блоке
);
Параметры:
hHeap - дескриптор кучи.
lpEntry - указатель на структуру PROCESS_HEAP_ENTRY, в которой будет сохранена информация о конкретном блоке.
Возвращаемые значения:
В случае успеха, функция возвращает значение TRUE, иначе - ноль. Для получения более подробной информации нужно вызвать GetLastError. Если вызов завершается успешно, но достигнут конец кучи, функция возвращает FALSE, а GetLastError - код ошибки ERROR_NO_MORE_ITEMS.
Замечания:
Чтобы начать просмотр блоков кучи, нужно вызвать HeapWalk задав для поля lpData структуры PROCESS_HEAP_ENTRY (см. ниже описание PROCESS_HEAP_ENTRY) значение NULL. Получим информацию о первом блоке кучи. Чтобы продолжить, нужно вызвать HeapWalk с тем же самым значением hHeap и lpEntry, не изменяя значения полей структуры PROCESS_HEAP_ENTRY. Этот процесс можно продолжать, пока функция не вернет FALSE, а GetLastError - ERROR_NO_MORE_ITEMS, что говорит о том, что все блоки в куче просмотрены.
В многопоточных приложениях HeapWalk может вернуть ошибку, если куча не заблокирована. Используйте функции HeapLock и HeapUnlock для блокирования кучи перед обращениями к HeapWalk и разблокирования после этих обращений соответственно.
Эта функция не поддерживается в Win9x.
20. PROCESS_HEAP_ENTRY
Структура PROCESS_HEAP_ENTRY содержит информацию об элементе (блоке) кучи. Используется функцией HeapWalk.
typedef struct _PROCESS_HEAP_ENTRY {
PVOID lpData;
DWORD cbData;
BYTE cbOverhead;
BYTE iRegionIndex;
WORD wFlags;
union {
struct {
HANDLE hMem;
DWORD dwReserved[ 3 ];
} Block;
struct {
DWORD dwCommittedSize;
DWORD dwUnCommittedSize;
LPVOID lpFirstBlock;
LPVOID lpLastBlock;
} Region;
};
} PROCESS_HEAP_ENTRY;
Поля:
lpData - указатель на блок в куче.
cbData - размер блока в байтах.
cbOverhead - размер данных, в байтах, используемых системой для управления информацией об элементе кучи. Эти байты добавляются к cbData байтам данных блока.
iRegionIndex - дескриптор секции кучи, содержащей блок. Куча состоит из одной или большего числа секций виртуальной памяти, каждая из которых имеет свой уникальный индекс. Функция HeapAlloc время от времени использует функцию VirtualAlloc для распределения дополнительной памяти при возрастании размера кучи. Менеджер куч воспринимает эти дополнительные области как отдельные секции.
wFlags - множество битовых флагов, специфицирующих свойства блока кучи:
PROCESS_HEAP_REGION, PROCESS_HEAP_UNCOMMITTED_RANGE,
PROCESS_HEAP_ENTRY_BUSY, PROCESS_HEAP_ENTRY_MOVEABLE,
PROCESS_HEAP_ENTRY_DDESHARE. См. MSDN.
Block - эта структура имеет смысл, только если установлены оба флага PROCESS_HEAP_ENTRY_BUSY и PROCESS_HEAP_ENTRY_MOVEABLE в поле wFlags.
Поля структуры Block:
Поле |
Описание |
hMem |
Дескриптор распределенного, перемещаемого блока. |
dwReserved |
Зарезервировано; не используется. |
Region - эта структура имеет смысл, только если установлен флаг PROCESS_HEAP_REGION в поле wFlags.
Поля структуры Region:
Поле |
Описание |
dwCommittedSize |
задает число байт в свободных блоках, занятых блоки или управляющих структурах кучи. |
dwUnCommittedSize |
задает число байт, освобожденных на данный момент в куче. |
lpFirstBlock |
Указатель на первый доступный блок в этом диапазоне адресов кучи. |
lpLastBlock |
Указатель на первый не доступный блок в этом диапазоне адресов кучи. |
Эта функция не поддерживается в Win9x.