Продолжение, начало см. в МК №46, 51-52, 4, 6-7, 10, 12-13, 16-18, 22, 24, 29, 34, 41, 46, 4, 6, 17, 21, 23, 28, 30, 32, 39, 42, 45 (165, 170-171, 175, 177-178, 181, 183-184, 187-189, 193, 195, 200, 205, 212, 217, 227, 229, 240, 244, 246, 251, 253, 255, 262, 265, 268).
Спрашивали? Отвечаю…
640 Кб для Паскаля не предел
В предыдущих статьях я упоминал о том, что Паскаль-программе может быть доступна вся свободная оперативная память, доступная операционной системе. Как известно, 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 был четным, да к тому же кратен двум.
Далее следует объявление экспортируемых процедур и функций модуля.
{$G+} unit XMS; Interface type TLinePtr = record case integer of 0: (AsPtr : pointer); 1: (AsInt : longint); 2: (LPart, HPart : word); end; TXMSPtr = record Allocated, {0 — свободен (NIL), 1 — при завершении освободить} Handle : word; LPtr : TLinePtr; end; function TestX86 : byte; function PMode : boolean; function MemXMSAvail : word; function MaxXMSAvail : word; function InitXMS : boolean; function GetXMS( var XPtr : TXMSPtr; Size : word ) : word; procedure FreeXMS( var XPtr : TXMSPtr ); procedure WriteXMSWord( Dst : TXMSPtr; Offs : longint; W : word ); function ReadXMSWord( Src : TXMSPtr; Offs : longint ) : word; procedure WriteXMSByte( Dst : TXMSPtr; Offs : longint; B : byte ); function ReadXMSByte( Src : TXMSPtr; Offs : longint ) : byte; procedure WriteXMSLong( Dst : TXMSPtr; Offs, L : longint ); function ReadXMSLong( Src : TXMSPtr; Offs : longint ) : longint; procedure MoveMemToXMS( Dst : TXMSPtr; var Buf; DstOffs, Count : longint ); procedure MoveXMSToMem( Src : TXMSPtr; var Buf; SrcOffs, Count : longint ); procedure MoveXMS( Src, Dst : TXMSPtr; SrcOffs, DstOffs, Count : longint );
В блоке реализации объявим структуру для внутреннего использования TEMBCopyRec, которую необходимо заполнять для копирования данных из одной памяти в другую. Например, для копирования данных из DOS-памяти в XMS-память, следует занести нуль в поле SrcHandle, а в поле SrcPtr занести указатель на буфер DOS-памяти, в поле DstHandle поместить идентификатор EMB-блока XMS-памяти, а в поле DstPtr указать смещение в байтах относительно начала EMB-блока. Для реверсной, то есть обратной пересылки данных из XMS-памяти в DOS-память следует поместить в поле SrcHandle идентификатор EMB и в поле SrcPtr указать смещение в байтах относительно начала EMB, а в поле DstHandle поместить нуль и в поле DstPtr дать указатель на буфер DOS-памяти. Размер пересылки данных заносится в поле Counter, причем это значение должно быть четно, иначе пересылка нечетного количества байт — например, 201 байта — не состоится.
Implementation type {структура для пересылки данных из нижней (DOS) памяти в верхнюю (XMS) и обратно} TEMBCopyRec = record Counter : longint; {четное кол-во пересылаемых байтов, т.е. пересылка 201-го байта не состоится} SrcHandle : word; {если 0 — копировать из обычной памяти, иначе из XMS-памяти} SrcPtr : TLinePtr; {смещение источника в байтах при источнике XMS, иначе сегмент:смещение для источника DOS} DstHandle : word; {если 0 — копировать в обычную память, иначе в XMS-память} DstPtr : TLinePtr; {смещение получателя в байтах при получателе XMS, иначе сегмент:смещение для получателя DOS} end;
Теперь объявим переменную XMM для хранения адреса драйвера и переменную EMBCopy для осуществления всех операций по копированию данных из одной памяти в другую.
var XMM : longint; EMBCopy : TEMBCopyRec;
Теперь рассмотрим две функции, которые имеют лишь косвенное отношение к работе с XMS, но при этом будут полезны.
Код функции TestX86 реализует «официальный» метод фирмы Intel по распознаванию типа процессора, и в качестве результата возвращает 0, если в машине установлен процессор i8086, либо 1, если в компьютере i80286, либо 2 для i80386 соответственно. Не буду вдаваться в подробности данного метода — ассемблерные магические пассы вроде нижеследующего кода следует принять как должное.
{Return: 0 – 8086, 1 – 80286, 2 – 80386} function TestX86 : byte; assembler; asm xor ax,ax; push ax; popf; pushf ; pop ax; and ax,0F000h cmp ax,0F000h; je @CPU_86; mov ax,0F000h; push ax popf; pushf; pop ax; and ax,0F000h; jz @CPU_286 mov ax,2; jmp @end @CPU_286: mov ax,1; jmp @end @CPU_86: mov ax,0 @end: end;
Вторая функция позволяет определить, в каком режиме находится центральный процессор. Результат False будет свидетельствовать о работе процессора в режиме реальных адресов (реальный режим), а True — о том, что процессор находится в виртуальном режиме процессора 8086 (V86, то есть подвиде защищенного режима).
{Return: false — Real Mode, true — Protected Mode(V86)} function PMode : boolean; assembler; asm smsw ax; test al,1; jnz @Pmode; mov al,0; jmp @end @PMode: mov al,1 @end: end;
Вот теперь мы подобрались к функциям, которые касаются непосредственно работы с XMS.
Процедура GetXMMAddr для внутреннего использования позволяет получить адрес диспетчера функций драйвера XMS (HIMEM.SYS). Для этого в регистр AX заносится код функции $43 и код подфункции $10 и вызывается программное прерывание $2F, после чего в регистровой паре ES:BX (сегмент в ES, смещение в BX) будет получен адрес драйвера для дальнего вызова.
procedure GetXMMAddr( var APtr : longint ); assembler; asm mov ax,4310h; int 2fh; mov ax,es; les di,APtr mov es:[di],bx; add di,2; mov es:[di],ax end;
Работу с драйвером следует начинать с вызова функции InitXMS, которая вовсе не инициализирует драйвер, а просто проверяет наличие драйвера XMS в памяти и пытается получить его адрес в переменную XMM. Если драйвер загружен, то функция возвращает True, иначе False. Для обнаружения драйвера в регистр AX заносится код функции $43 и код подфункции $00 и вызывается прерывание $2F. Если в регистре AL возвращено значение $80, значит, драйвер присутствует, и наоборот.
{Return: true — драйвер в памяти, false — XMS драйвер не найден} function InitXMS : boolean; var err : boolean; begin asm mov ax,4300h; int 2fh; cmp al,80h; je @yesXMS xor ax,ax; jmp @end @yesXMS: mov ax,1 @end: mov err,al end; GetXMMaddr( XMM ); InitXMS := err; end;
Вот теперь можно непосредственно заняться работой с XMS.
Для начала проведем ревизию свободной памяти. Для этого поместим номер функции 8 драйвера в AH, и вызовем диспетчер функций драйвера. В регистре AX будет возвращен размер максимального свободного EMB в килобайтах, а в регистре DX суммарный объем свободной XMS в килобайтах. Размер максимального свободного EMB можно получить функцией MaxXMSAvail, так как для удобства в ней игнорируется значение регистра DX.
function MaxXMSAvail : word; assembler; {в Кбайтах} asm mov ah,8; call [XMM] end;
Для получения суммарного объема свободной XMS при помощи функции MemXMSAvail вызываем ту же функцию драйвера, но в качестве результата возвращаем значение регистра DX.
function MemXMSAvail : word; assembler; {в Кбайтах} asm mov ah,8; call [XMM]; mov ax,dx end;
Следующие функции тоже для внутреннего использования. Первая из них, GetEMB, позволяет выделить EMB размером Size килобайт. Для этого в регистр DX заносим размер, в AH номер функции 9 драйвера. При возникновении ошибки в регистре AX будет возвращен нуль, а в регистре BL код ошибки. При успешном выделении EMB в регистре AX будет ненулевое значение, а в DX — идентификатор выделенного EMB. В итоге функция GetEMB при удачном выделении EMB возвращает нуль, иначе код ошибки — например, ошибка с кодом $0A0 означает, что не хватает XMS для выделения EMB затребованного размера.
{Return: 0 — Ok, $0A0 — не хватает XMS} function GetEMB( Size : word; var Handle : word ) : word; assembler; asm mov dx,Size; mov ah,9; call [XMM]; cmp ax,0; je @err xor ax,ax; jmp @end @err: mov al,bl {error code to AL} @end: les di,Handle; mov es:[di],dx end;
Процедура FreeEMB освобождает EMB, для чего в AH помещаем номер функции $0A, а в DX — идентификатор освобождаемого EMB.
Возможная ошибка игнорируется.
procedure FreeEMB( Handle : word ); assembler; asm mov ah,0ah; mov dx,Handle; call [XMM] end;
Ну и конечно, зачем нам нужен блок памяти, если в него нельзя пересылать и читать из него данные? Ответом на этот вопрос послужит функция CopyEMB. В регистре AH номер функции —$0B; регистровая же пара DS:SI должна содержать указатель на структуру типа TEMBCopyRec. Поэтому для удобства я объявил переменную EMBCopy в сегменте данных, чтобы не пришлось изменять содержимое регистра DS в блоке реализации данной функции. Если в регистре AX возвращено нулевое значение, значит, произошла ошибка, и ее код находится в регистре BL. Иначе в AX найдем ненулевое значение.
В итоге функция CopyEMB при удачном копировании данных возвращает нуль, иначе код ошибки.
{Return : 0 — ok, <>0-error} function CopyEMB( OffsEMBCopy : word ) : word; assembler; asm mov si,OffsEMBCopy; mov ah,0bh; call [XMM]; cmp ax,0 je @err; xor ax,ax; jmp @end @err: mov al,bl {error code to AL} @end: end;
Иногда возникает необходимость работать напрямую с линейным адресом EMB, например, в режиме реальных адресов. Для получения линейного 32-битного адреса выделенного EMB служит функция GetLinePointer. В AH загружаем номер $0C функции блокирования EMB, а в DX ее идентификатор. В регистровой паре DX:BX будет возвращен линейный адрес. Если в регистре AX возвращено нулевое значение, значит, произошла ошибка, ее код находится в регистре BL. Иначе в AX — ненулевое значение. При удаче функция GetLinePointer возвращает нуль, иначе код ошибки.
{Return: 0-ok,<>0-error} function GetLinePointer( Handle : word; var LPtr : TLinePtr ) : word; assembler; asm mov dx,Handle; mov ah,0ch; call [XMM]; cmp ax,0 je @err; xor ax,ax; jmp @end @err: mov al,bl {error code to AL} @end: les di,LPtr; mov es:[di],bx add di,2; mov es:[di],dx end;
Ну и последнее — внутренняя процедура по разблокированию EMB.
В регистр AH загружается номер функции $0D, а в DX — идентификатор разблокируемого EMB.
procedure UnlockEMB( Handle : word ); assembler; asm mov ah,0dh; mov dx,Handle; call [XMM] end;
Литература:
Диалоговая справочная система Norton Guide.
Д-р Джон М. Гудмэн. Управление памятью для всех — К.: Диалектика, 1996. — 520 с.
(Продолжение следует)
