Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр. Работа с пулами памятиФункции, предназначенные для работы с виртуальной памятью, которые мы рассмотрели выше, обычно используют для получения в пользование блоков памяти относительно большого размера (больше одной страницы). Однако наиболее часто приложению требуется всего несколько десятков байт, например, для создания динамических структур или для загрузки ресурсов приложения в оперативную память. Очевидно, оперируя с отдельными страницами, вы едва ли сможете легко и эффективно работать с блоками памяти небольшого объема. Поэтому в программном интерфейсе Microsoft Windows NT были предусмотрены другие функции, к изучению которых мы и переходим. Все эти функции вызывают только что рассмотренные нами функции, работающие с виртуальной памятью. Пулы памяти в Microsoft Windows NTКак мы уже говорили, приложения Microsoft Windows версии 3.1 могли заказывать память из двух областей или двух пулов - из глобального пула, доступного всем приложениям, и локального, создаваемого для каждого приложения. Адресные пространства приложений Microsoft Windows NT разделены, поэтому в этой операционной системе нет глобальных пулов памяти. Вместо этого каждому приложению по умолчанию выделяется один стандартный пул памяти в его адресном пространстве. При необходимости приложение может создавать (опять же в своем адресном пространстве) произвольное количество так называемых динамических пулов памяти. По умолчанию для стандартного пула резервируется 1 Мбайт сплошного адресного пространства, причем 4 Кбайта памяти выделяются приложению для непосредственного использования. Если приложению требуется больше памяти, в адресном пространстве резервируется еще один или несколько Мбайт памяти. Если ваше приложение работает с большими объемами данных, для загрузки этих данных в непрерывное адресное пространство можно увеличить размер стандартного пула двумя способами. Во-первых, параметры стандартного пула можно задать в параметре /HEAP редактора связи: /HEAP: 0x2000000, 0x10000 В данном случае для стандартного пула будет зарезервировано 2 Мбайта памяти, причем сразу после загрузки приложения 10 Кбайт памяти будет получено в пользование. Во-вторых, параметры стандартного пула можно указать в файле определения модуля (который является необязательным). Например, так: HEAPSIZE 0x2000000 0x10000 Однако для резервирования очень больших адресных пространств памяти лучше создавать динамические пулы. Создание динамического пула не ведет к излишней загрузке физической оперативной памяти, так как пока вы не получаете из этого пула память, соответствующее адресное пространство является зарезервированным. Как мы уже говорили, резервирование адресного пространства не вызывает выделения памяти и изменения файлов страниц. Так что резервируйте сколько угодно, но в только в пределах 2 Гбайт. Функции для работы с пулами памятиИтак, в распоряжении приложения Microsoft Windows NT имеется один стандартный пул и произвольное количество динамических пулов памяти. В качестве первого парметра всем функциям, предназначенным для получения памяти из стандартного или динамического пула, необходимо передать идентификатор пула. Получение идентификатора стандартного пулаИдентификатор стандартного пула получить очень просто. Этот идентификатор возвращает функция GetProcessHeap XE "GetProcessHeap" , не имеющая параметров: HANDLE GetProcessHeap XE GetProcessHeap (VOID); Создание динамического пулаЕсли вам нужен динамический пул, вы можете его создать при помощи функции HeapCreate XE "HeapCreate" : HANDLE HeapCreate( DWORD flOptions, // флаг создания пула DWORD dwInitialSize, // первоначальный размер пула в байтах DWORD dwMaximumSize);// максимальный размер пула в байтах Параметры dwMaximumSize и dwInitialSize определяют, соответственно, размер зарезервированной для пула памяти и размер памяти, полученной для использования. Через параметр flOptions вы можете передать нулевое значение, а также значения HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE" и HEAP_GENERATE_EXCEPTIONS XE "HEAP_GENERATE_EXCEPTIONS" . Параметр HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE" имеет отношение к мультизадачности, которая будет рассмотрена в отдельной главе нашей книги. Если этот параметр не указан, работающие параллельно задачи одного процесса не могут одновременно получать доступ к такому пулу. Вы можете использовать флаг HEAP_NO_SERIALIZE для повышения производительности, если создаваемым вами пулом будет пользоваться только одна задача процесса. При выделении памяти из пула могут возникать ошибочные ситуации. Если не указан флаг HEAP_GENERATE_EXCEPTIONS XE "HEAP_GENERATE_EXCEPTIONS" , при ошибках соотвтетвующий функции будут возвращать значение NULL. В противном случае в приложении будут генерироваться исключения. Флаг HEAP_GENERATE_EXCEPTIONS удобен в тех случаях, когда в вашем приложении предусмотрена обработка исключений, позволяющая исправлять возникающие ошибки. В случае удачи функция HeapCreate XE "HeapCreate" возвращает идентификатор созданного динамического пула памяти. При ошибке возвращается значение NULL (либо возникает исключение, если указан флаг HEAP_GENERATE_EXCEPTIONS). Удаление динамического пулаДля удаления динамического пула памяти, созданного функцией HeapCreate XE "HeapCreate" , вы должны использовать функцию HeapDestroy XE "HeapDestroy" : BOOL HeapDestroy XE HeapDestroy (HANDLE hHeap); Через единственный параметр этой функции передается идентификатор удаляемого динамического пула. Заметим, что вам не следует удалять стандартный пул, передавая этой функции значение, полученное от функции GetProcessHeap XE "GetProcessHeap" . Функция HeapDestroy XE "HeapDestroy" выполняет безусловное удаление пула памяти, даже если из него были получены блоки памяти и на момент удаления пула они не были возвращены системе. Получение блока памяти из пулаДля получения памяти из стандартного или динамического пула приложение должно воспользоваться функцией HeapAlloc XE "HeapAlloc" , прототип которой мы привели ниже: LPVOID HeapAlloc( HANDLE hHeap, // идентификатор пула DWORD dwFlags, // управляющие флаги DWORD dwBytes); // объем получаемой памяти в байтах Что касается параметра hHeap, то для него вы можете использовать либо идентификатор страндартного пула памяти, полученного от функции GetProcessHeap XE "GetProcessHeap" , либо идентификатор динамического пула, созданного приложением при помощи функции HeapCreate XE "HeapCreate" . Параметр dwBytes определяет нужный приложению объем памяти в байтах. Параметр dwFlags может быть комбинацией следующих значений:
Изменение размера блока памятиС помощью функции HeapReAlloc XE "HeapReAlloc" приложение может изменить размер блока памяти, выделенного ранее функцией HeapAlloc XE "HeapAlloc" , уменьшив или увеличив его. Прототип функции HeapReAlloc приведен ниже: LPVOID HeapReAlloc( HANDLE hHeap, // идентификатор пула DWORD dwFlags, // флаг изменения размера блока памяти LPVOID lpMem, // адрес блока памяти DWORD dwBytes); // новый размер блока памяти в байтах Для пула hHeap эта функция изменяет размер блока памяти, расположенного по адресу lpMem. Новый размер составит dwBytes байт. В случае удачи функция HeapReAlloc XE "HeapReAlloc" возвратит адрес нового блока памяти, который не обязательно будет совпадать с адресом, полученным этой функцией через параметр lpMem. Через параметр dwFlags вы можете передавать те же параметры, что и через аналогичный параметр для функции HeapAlloc XE "HeapAlloc" . Дополнительно можно указать параметр HEAP_REALLOC_IN_PLACE_ONLY XE "HEAP_REALLOC_IN_PLACE_ONLY" , определяющий, что при изменении размера блока памяти его нужно оставить на прежнем месте адресного пространства. Очевидно, что если указан этот параметр, в случае успешного завершения функция HeapReAlloc XE "HeapReAlloc" вернет то же значение, что было передано ей через параметр lpMem. Определение размера блока памятиЗная адрес блока памяти, полученного из пула, вы можете определить его размер при помощи функции HeapSize XE "HeapSize" : DWORD HeapSize( HANDLE hHeap, // идентификатор пула DWORD dwFlags, // управляющие флаги LPCVOID lpMem); // адрес проверяемого блока памяти В случае ошибки эта функция возвращает значение 0xFFFFFFFF. Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE" . Освобождение памятиПамять, выделенную с помощью функции HeapAlloc XE "HeapAlloc" , следует освободить, как только в ней отпадет надобность. Это нужно сделать при помощи функции HeapFree XE "HeapFree" : BOOL HeapFree( HANDLE hHeap, // идентификатор пула DWORD dwFlags, // флаги освобождения памяти LPVOID lpMem); // адрес освобождаемого блока памяти Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE XE "HEAP_NO_SERIALIZE" . Если размер блока памяти, выделенного функцией HeapAlloc XE "HeapAlloc" , был изменен функцией HeapReAlloc XE "HeapReAlloc" , для освобождения такого блока памяти вы все равно должны использовать функцию HeapFree XE "HeapFree" . Использование функций malloc XE "malloc" и free XE "free"В библиотеке Microsoft Visual C++ имеются стандартные функции, предназначенные для динамического получения и освобождения памяти, такие как malloc XE "malloc" и free XE "free" . У нас есть хорошая новость для вас - в среде Microsoft Windows NT вы можете использовать эти функции с той же эффективностью, что и функции, предназначенные для работы с пулами - HeapAlloc XE "HeapAlloc" , HeapFree XE "HeapFree" и так далее. Правда, эти функции получают память только из стандартного пула. Одно из преимуществ функций malloc XE "malloc" и free XE "free" заключается в возможности их использования на других платформах, отличных от Microsoft Windows NT. Старые функции управления памятьюВ 32-разрядных приложениях Microsoft Windows NT вы можете пользоваться многими функциями управления памятью операционной системы Microsoft Windows версии 3.1, которые оставлены в новой операционной системе для совместимости. Мы подробно рассмотрели эти функции в главе “Управление памятью” 13 тома “Библиотеки системного программиста”. Напомним, что в 16-разрядном программном интерфейсе Microsoft Windows версии 3.1 существует два набора функций (глобальные и локальные), предназначенных для работы с глобальным и локальным пулом памяти. Это такие функции, как GlobalAlloc XE "GlobalAlloc" , LocalAlloc XE "LocalAlloc" , GlobalFree XE "GlobalFree" , LocalFree XE "LocalFree" и так далее. В 32-разрядных приложениях Microsoft Windows NT вы можете пользоваться как глобальными, так и локальными функциями, причем результат будет совершенно одинаковый. Причина этого заключается в том, что все эти функции пользуются функциями программного интерфейса Microsoft Windows NT, предназначенными для работы со стандартным пулом памяти: HeapAlloc XE "HeapAlloc" , HeapReAlloc XE "HeapReAlloc" , HeapFree XE "HeapFree" и так далее. Вот список функций старого программного интерфейса, доступных приложениям Microsoft Windows NT:
Заметим, что хотя при получении памяти с помощью функции GlobalAlloc XE "GlobalAlloc" вы по-прежнему можете указывать флаг GMEM_DDESHARE XE "GMEM_DDESHARE" , другие приложения, запущенные в среде Microsoft Windows NT, не будут иметь к этой памяти доступ. Причина очевидна - адресные пространства приложений изолированы. Однако в документации SDK сказано, что этот флаг можно использовать для увеличения производительности приложений, использующих механизм динамической передачи сообщений DDE XE "механизм динамической передачи сообщений DDE" . Этот механизм мы подробно описали в главе “Обмен данными через DDE XE "DDE" ” в 17 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1. Дополнительные главы”. Обратим ваше внимание также на то, что в среде Microsoft Windows версии 3.1 вы могли получать фиксированную (fixed), перемещаемую (moveable) и удаляемую (discardable) память. В среде Microsoft Windows NT вы по-прежнему можете пользоваться различными типами памяти, если для получения блоков памяти используете функции GlobalAlloc XE "GlobalAlloc" или LocalAlloc XE "LocalAlloc" . Однако теперь вам едва ли потребуется перемещаемая память, так как новая система управления памятью выполняет операцию перемещения с помощью механизма страничной адресации, не изменяя значение логического адреса. В том случае, если вы все же решили получить блок перемещаемой памяти, перед использованием его необходимо зафиксировать функцией GlobalLock XE "GlobalLock" или LocalLock XE "LocalLock" (соответственно, для блоков памяти, полученных функциями GlobalAlloc XE "GlobalAlloc" и LocalAlloc XE "LocalAlloc" ). Это нужно сделать потому что если вы заказываете перемещаемый блок памяти, функции GlobalAlloc и LocalAlloc возвращают не адрес блока памяти, а его идентификатор. Если же вы получаете фиксированный блок памяти, то функции GlobalAlloc XE "GlobalAlloc" и LocalAlloc XE "LocalAlloc" вернут вам его адрес, который можно немедленно использовать. При этом надо иметь в виду, что операционная система сможет перемещать этот блок памяти без изменения его логического адреса. Что же касается удаляемой памяти, то ее можно использовать для хранения таких данных, которые можно легко восстановить, например, прочитав их из ресурсов приложений. |