В предыдущих статьях я упоминал о том, что Паскаль-программе может быть доступна вся свободная оперативная память, доступная операционной системе. Как известно, Turbo Pascal предназначен для проектирования программ, работающих под MS-DOS, которой может быть доступно не более 640 Кб оперативной памяти. Но порой такого объема памяти может не хватить прожорливой программе.
Сегодня я расскажу об extended-памяти, которая впервые появилась в компьютерах на базе процессора Intel 80286. В компьютерах на базе процессоров Intel 80386 и выше всегда есть extended-память (eXtended Memory Specification спецификация дополнительной памяти XMS, это вся оперативная память свыше границы первого мегабайта) и обычно нет аппаратной expanded-памяти (EMS), хотя ее можно эмулировать с помощью драйверов EMM386, QEMM и т.п.
Существует всем известный XMS-драйвер himem.sys, обеспечивающий работу программ, использующих extended-память. Настроить работу этого драйвера можно через config.sys.
В данной статье я расскажу, как составить модуль назовем его xms.pas, который бы содержал все необходимые функции для выделения больших непрерывных блоков памяти, размер которых ограничен размерами extended-памяти 64 Мб или менее. Предположим, что у нас в машине 128 Мб ОЗУ, тогда при работе под MS-DOS можно будет рассчитывать не более чем на 64 Мб XMS. При работе в сеансе MS-DOS под Windows можно будет использовать преимущество виртуальной памяти и запрашивать блоки XMS суммарным объемом намного больше 128 Мб.
В моем модуле XMS описано много полезных функций для работы с большими блоками extended-памяти (EMB Extended Memory Block), но в данной статье я постараюсь лаконично осветить лишь самые необходимые.
Для начала следует установить директиву компиляции {$G+}, которая включает генерацию машинных инструкций для процессора INTEL80286. Затем опишем структуру TLinePtr для хранения 32-битного адреса (во всех возможных вариациях) и комбинированный тип TXMSPtr для хранения информации о выделенном блоке. При этом нулевое значение поля Allocated будет означать пустой и не инициализированный указатель на EMB, а единичное значение будет указывать на то, что указатель инициализирован для EMB с идентификатором в поле Handle и линейным адресом в поле LPtr, и что по завершении программы этот блок следует освободить. В данной структуре для поля Allocated выбран тип Word, хотя можно было применить и Boolean. Это сделано для того, чтобы размер структуры TXMSPtr был четным, да к тому же кратен двум.
Далее следует объявление экспортируемых процедур и функций модуля.
В блоке реализации объявим структуру для внутреннего использования TEMBCopyRec, которую необходимо заполнять для копирования данных из одной памяти в другую. Например, для копирования данных из DOS-памяти в XMS-память, следует занести нуль в поле SrcHandle, а в поле SrcPtr занести указатель на буфер DOS-памяти, в поле DstHandle поместить идентификатор EMB-блока XMS-памяти, а в поле DstPtr указать смещение в байтах относительно начала EMB-блока. Для реверсной, то есть обратной пересылки данных из XMS-памяти в DOS-память следует поместить в поле SrcHandle идентификатор EMB и в поле SrcPtr указать смещение в байтах относительно начала EMB, а в поле DstHandle поместить нуль и в поле DstPtr дать указатель на буфер DOS-памяти. Размер пересылки данных заносится в поле Counter, причем это значение должно быть четно, иначе пересылка нечетного количества байт например, 201 байта не состоится.
Теперь объявим переменную XMM для хранения адреса драйвера и переменную EMBCopy для осуществления всех операций по копированию данных из одной памяти в другую.
Теперь рассмотрим две функции, которые имеют лишь косвенное отношение к работе с XMS, но при этом будут полезны.
Код функции TestX86 реализует «официальный» метод фирмы Intel по распознаванию типа процессора, и в качестве результата возвращает 0, если в машине установлен процессор i8086, либо 1, если в компьютере i80286, либо 2 для i80386 соответственно. Не буду вдаваться в подробности данного метода ассемблерные магические пассы вроде нижеследующего кода следует принять как должное.
Вторая функция позволяет определить, в каком режиме находится центральный процессор. Результат False будет свидетельствовать о работе процессора в режиме реальных адресов (реальный режим), а True о том, что процессор находится в виртуальном режиме процессора 8086 (V86, то есть подвиде защищенного режима).
Вот теперь мы подобрались к функциям, которые касаются непосредственно работы с XMS.
Процедура GetXMMAddr для внутреннего использования позволяет получить адрес диспетчера функций драйвера XMS (HIMEM.SYS). Для этого в регистр AX заносится код функции $43 и код подфункции $10 и вызывается программное прерывание $2F, после чего в регистровой паре ES:BX (сегмент в ES, смещение в BX) будет получен адрес драйвера для дальнего вызова.
Работу с драйвером следует начинать с вызова функции InitXMS, которая вовсе не инициализирует драйвер, а просто проверяет наличие драйвера XMS в памяти и пытается получить его адрес в переменную XMM. Если драйвер загружен, то функция возвращает True, иначе False. Для обнаружения драйвера в регистр AX заносится код функции $43 и код подфункции $00 и вызывается прерывание $2F. Если в регистре AL возвращено значение $80, значит, драйвер присутствует, и наоборот.
Вот теперь можно непосредственно заняться работой с XMS.
Для начала проведем ревизию свободной памяти. Для этого поместим номер функции 8 драйвера в AH, и вызовем диспетчер функций драйвера. В регистре AX будет возвращен размер максимального свободного EMB в килобайтах, а в регистре DX суммарный объем свободной XMS в килобайтах. Размер максимального свободного EMB можно получить функцией MaxXMSAvail, так как для удобства в ней игнорируется значение регистра DX.
Для получения суммарного объема свободной XMS при помощи функции MemXMSAvail вызываем ту же функцию драйвера, но в качестве результата возвращаем значение регистра DX.
Следующие функции тоже для внутреннего использования. Первая из них, GetEMB, позволяет выделить EMB размером Size килобайт. Для этого в регистр DX заносим размер, в AH номер функции 9 драйвера. При возникновении ошибки в регистре AX будет возвращен нуль, а в регистре BL код ошибки. При успешном выделении EMB в регистре AX будет ненулевое значение, а в DX идентификатор выделенного EMB. В итоге функция GetEMB при удачном выделении EMB возвращает нуль, иначе код ошибки например, ошибка с кодом $0A0 означает, что не хватает XMS для выделения EMB затребованного размера.
Процедура FreeEMB освобождает EMB, для чего в AH помещаем номер функции $0A, а в DX идентификатор освобождаемого EMB.
Возможная ошибка игнорируется.
Ну и конечно, зачем нам нужен блок памяти, если в него нельзя пересылать и читать из него данные? Ответом на этот вопрос послужит функция CopyEMB. В регистре AH номер функции $0B; регистровая же пара DS:SI должна содержать указатель на структуру типа TEMBCopyRec. Поэтому для удобства я объявил переменную EMBCopy в сегменте данных, чтобы не пришлось изменять содержимое регистра DS в блоке реализации данной функции. Если в регистре AX возвращено нулевое значение, значит, произошла ошибка, и ее код находится в регистре BL. Иначе в AX найдем ненулевое значение.
В итоге функция CopyEMB при удачном копировании данных возвращает нуль, иначе код ошибки.
Иногда возникает необходимость работать напрямую с линейным адресом EMB, например, в режиме реальных адресов. Для получения линейного 32-битного адреса выделенного EMB служит функция GetLinePointer. В AH загружаем номер $0C функции блокирования EMB, а в DX ее идентификатор. В регистровой паре DX:BX будет возвращен линейный адрес. Если в регистре AX возвращено нулевое значение, значит, произошла ошибка, ее код находится в регистре BL. Иначе в AX ненулевое значение. При удаче функция GetLinePointer возвращает нуль, иначе код ошибки.
Ну и последнее внутренняя процедура по разблокированию EMB.
В регистр AH загружается номер функции $0D, а в DX идентификатор разблокируемого EMB.
Литература:
Диалоговая справочная система Norton Guide.
Д-р Джон М. Гудмэн. Управление памятью для всех К.: Диалектика, 1996. 520 с.