http://sulfurzona.com/
News
Service
Magazine
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
 
 

Паскаль для новичков (часть 26)

 

Спрашивали? Отвечаю…

 

640К для Паскаля не предел

 
Паскаль для новичков
В предыдущих статьях я упоминал о том, что Паскаль-программе может быть доступна вся свободная оперативная память, доступная операционной системе. Как известно, Turbo Pascal предназначен для проектирования программ, работающих под MS-DOS, которой может быть доступно не более 640К оперативной памяти. Но порой, такого объёма памяти может не хватить прожорливой программе.
 
Сегодня я расскажу об extended-памяти, которая впервые появилась в компьютерах на базе процессора INTEL80286. В компьютерах на базе процессоров INTEL80386 и выше всегда есть extended-память (eXtended Memory Specification – спецификация дополнительной памяти XMS, это вся оперативная память свыше границы первого мегабайта) и обычно нет аппаратной expanded-памяти (EMS), хотя её можно эмулировать с помощью драйвера EMM386, QEMM и т.п.
 
Существует всем известный XMS-драйвер HIMEM.SYS, обеспечивающий работу программ, использующих extended-память. Как настраивать работу этого драйвера через файл CONFIG.SYS, думаю, объяснять не нужно.
  
В данной статье я расскажу как составить модуль, назовём его, XMS.PAS, который бы содержал все необходимые функции для выделения больших непрерывных блоков памяти, размер которых ограничен лишь 64Мб или всей 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;
 
 

Литература

1. Диалоговая справочная система Norton Guide.
2. Д-р Джон М. Гудмэн. Управление памятью для всех – К.:Диалектика, 1996. – 520 с.
 
Продолжение следует…
 
© Владислав Демьянишин
 
 
Вы находитесь на официальном сайте Владислава Демьянишина - разработчика игры Dune IV (Dune 4). На нашем сайте Вы можете бесплатно скачать игры Dune IV (Dune 4), Battle City (Танчики с Dendy/Nintendo), читы к играм и многое другое. Также Вы можете скачать бесплатно программы и полезные утилиты. Все программы чистые, т.е. не содержат вирусов и иного вредоносного ПО. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 
 
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 26): 640К для Паскаля не предел
 
 
 
535
 
ВКонтакте
Facebook
 
 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
Рейтинг@Mail.ru Рейтинг Сайтов YandeG