МИР ПК #07/97

OS/2 изнутри: работа с памятью

В OS/2 программисту доступно 32-разрядное линейное адресное пространство. Как же организована работа с ним?

Николай Смирнов, Евгений Лызенко

Общие принципы распределения памяти
Выделение и фиксация памяти
Страницы памяти
Способы работы с памятью
Пример использования кучи

Общие принципы распределения памяти

Как известно, при работе в IBM OS/2 программисту доступно 32-разрядное линейное (плоское) адресное пространство со страничной организацией, т. е. все операции с памятью производятся над страницами заданного размера. Каждому запущенному процессу OS/2 выделяет виртуальное адресное пространство размером 512 Мбайт, т. е. максимальный адрес, доступный программе, равен 0x1FFFFFFF (три старших разряда 32-разрядного указателя не используются).

Распределение виртуальной памяти отделено от ее отображения на физическую память компьютера. Благодаря этому разные процессы выполняются "бок о бок", но их виртуальные адресные пространства полностью изолированы друг от друга. Конечно, память, которую процесс фактически может занять при выполнении, ограничена размером ОЗУ компьютера и объемом свободного пространства в том разделе жесткого диска, где расположен файл страничной подкачки SWAPPER.DAT.

Младшие 64 Кбайт адресного пространства зарезервированы для нужд операционной системы, так что минимальный адрес равен 0x10000; он соответствует точке начала EXE-модуля. Вся остальная память делится на совместно используемую (ее должно быть не менее 64 Мбайт) и частную память процесса. Частная память, доступ к которой может получить только процесс-владелец, начинается с младших адресов и растет вверх. Совместно используемая, наоборот, выделяется начиная со старших адресов и растет вниз. Она доступна процессу всегда, даже если реально им не используется.

Выделение и фиксация памяти

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

Операция, обратная фиксации, - расфиксирование (открепление), обратная выделению - освобождение. Закончив работу с виртуальной памятью, ее обязательно следует освободить, чтобы вернуть соответствующие ресурсы операционной системе. Если программа завершится, не освободив память, возникнет так называемая "утечка памяти": операционная система будет считать фактически свободную память используемой. Характерным признаком утечки памяти является увеличение объема файла SWAPPER.DAT.

Страницы памяти

Память выделяется и фиксируется порциями по 4 Кбайт, которые, как уже говорилось, называются страницами. Страница памяти может разрешать доступ для чтения, записи, выполнения или быть защищенной (guard page) - в последнем случае обращение к ней приводит к нарушению защиты.

Защищенные страницы, очевидно, нельзя использовать как ограничители, контролирующие границы массивов: если, скажем, запросить память для массива из 512 однобайтовых элементов, система выделит целую страницу, т. е. 4096 байт, так что обращение к элементу номер 513 не вызовет нарушения защиты. Основная функция защищенных страниц - обеспечивать автоматическое возрастание стека. Для массива, размер которого должен увеличиваться в процессе работы программы, можно выделить и зафиксировать две страницы, первая из которых будет доступной для чтения и записи, а вторая - защищенной. При попытке выйти за пределы отведенной страницы произойдет нарушение защиты, и в ответ операционная система (если только программа не обрабатывает эту ошибку самостоятельно) попытается зафиксировать защищенную страницу и отметить как защищенную следующую за ней. В случае неудачи возникает ошибка "Невозможно нарастить стек", после чего программа сможет продолжить работу, но для размещения массива в ее распоряжении будет всего 4096 байт.

Способы работы с памятью

В OS/2 существует несколько способов работы с памятью, или, иначе, типов памяти. Это объекты в памяти, куча, локальная память нити и совместно используемая память. Рассмотрим их особенности.

Объекты в памяти

Основным способом работы с памятью в OS/2 является использование объектов в памяти (memory objects). Объект в памяти всегда состоит из целого числа страниц: при его создании запрошенное число байтов округляется в большую сторону до ближайшего значения, кратного 4 Кбайт. Внутри объекта память резервируется блоками от 1 байта до размера всего объекта.

Для выделения памяти объектам служит функция DosAllocMem, для ее освобождения - функция DosFreeMem, для фиксации и открепления - функция DosSetMem.

При работе с OS/2 рекомендуется не менять размер объектов в памяти, а сразу выделять достаточно большой "разреженный" объект, в котором затем по мере необходимости фиксировать и откреплять порции нужного размера. Например, если программе в большинстве случаев должно хватить около 512 Кбайт памяти, но при определенных условиях потребуется 5 Мбайт, нужно поступить следующим образом:

- при инициализации программы выделить для нее 5 Мбайт (функция DosAllocMem);
- зафиксировать первые 512 Кбайт или какую-то их часть (DosSetMem);
- продолжить работу программы, по мере необходимости фиксируя и открепляя память; когда потребуется 5 Мбайт, зафиксировать их, а после использования открепить (DosSetMem);
- по окончании работы программы освободить память (DosFreeMem).

Куча

Куча (heap) - это область внутри объекта в памяти, из которой программа может выделять небольшие блоки; размер блока округляется в большую сторону до ближайшего значения, кратного 8 байтам.

Программисты, пишущие на Си, могут воспользоваться библиотечными функциями управления кучей - new, delete, malloc, strdup, free. Собственные функции менеджера кучи OS/2 называются DosSubSetMem, DosSubUnsetMem, DosSubAllocMem и DosSubFreeMem. Программа инициализирует объект в памяти, вызывая функцию DosSubSetMem, а затем выделяет и освобождает память внутри него с помощью соответственно DosSubAllocMem и DosSubFreeMem. В листинге содержится фрагмент программы, в котором создается объект для подвыделения памяти (размер объекта 8192 байт) и затем выделяются два небольших блока памяти.

По окончании работы с кучей, которая была инициализирована функцией DosSubSetMem, следует вызвать функцию DosSubUnsetMem: она освобождает ресурсы, использовавшиеся для управления кучей. Завершив работу с объектом в памяти, частью которого была куча, память освобождают обычным образом, с помощью функции DosFreeMem. Необходимо следить, чтобы на каждый вызов DosSubSetMem приходился вызов DosSubUnsetMem и чтобы для объектов, содержавших кучу, перед вызовом DosFreeMem вызывалась DosSubUnsetMem.

Следует помнить, что при работе с кучей память фиксируется постранично, т. е. при запросе на подвыделение для массива 512 байт будет зафиксировано 4096 байт. В результате обращение к 513-му байту массива не приведет к нарушению защиты, но при этом может оказаться, что программа обратилась к области памяти, подвыделенной другой нитью процесса.

Локальная память нити

Локальная память нити управления представляет собой небольшую (не более 32 двойных слов, т. е. 128 байт) область специфической памяти, которая отводится нити при инициализации и обычно используется для хранения небольшого числа указателей. Она выделяется функцией DosAllocThreadLocalMemory и освобождается функцией DosFreeThreadLocalMemory.

Функция DosAllocThreadLocalMemory выделяет за один раз восемь двойных слов; если требуется выделить больше, ее необходимо вызвать повторно. Локальная память нити должна использоваться бережно; не следует занимать весь 128-байтовый диапазон.

Совместно используемая память

Совместно используемая память представляет собой объект в памяти, диапазон линейных адресов которого зарезервирован, как уже говорилось, в адресных пространствах всех процессов. Различается именованная и безымянная совместно используемая память. К именованной памяти процесс может обратиться непосредственно по имени, для обращения к безымянной ему необходим указатель, который он должен получить от процесса, создавшего данную безымянную память (не обязательно своего родительского).

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


Николай Смирнов - руководитель направления OS/2 в российском представительстве IBM EEA, тел.: (095) 940-20-00
Евгений Лызенко - ведущий инженер компании "ТУКА", e-mail: madm@tuka.msk.ru

Пример использования кучи

#define INCL_DOSMEMMGR /* Типы и константы системы управления памятью */
#include <os2.h>

APIRET  rc;
PBYTE   pbBase, pb1, pb2;

rc = DosAllocMem((PVOID *) &pbBase, 8192, fALLOC); /* Разместим объект размером 8 Кбайт */
rc = DosSubSetMem(pbBase, DOSSUB_INIT, 8192); /* Создадим объект для подвыделения */
rc = DosSubAllocMem(pbBase, (PVOID *) &pb1, 100); /* Выделим в нем 100 байтов */
rc = DosSubAllocMem(pbBase, (PVOID *) &pb2, 500); /* Выделим в нем еще 500 байтов */
rc = DosSubFreeMem(pbBase, pb1, 100); /* Освободим первое подвыделение */
rc = DosSubAllocMem(pbBase, (PVOID *) &pb1, 50); /* Выделим еще 50 байтов */