Предельно допустимый объем памяти зависит от системной (материнской) платы ПК, точнее от набора микросхем (chip set), на базе которого она собрана. Реально существующий объем выводится на экран монитора в процессе загрузки ПК, когда BIOS проверяет (тестирует) память. В процессе работы ПК можно с помощью специальных задач узнать объем и текущее распределение пространства ОЗУ. Например, в состав DOS входит задача mem.exe, а в состав Norton Commander— sysinfo.exe. Прикладные задачи, нуждающиеся в больших объемах памяти, должны самостоятельно определять размер ее доступного пространства.
Обычная память (Conventional Memory) Conventional — общепринятый, обычный. Так называют младшую часть ОЗУ, занимающую первые 640 Кбайт пространства адресов и имеющую большое значение в обеспечении работоспособности ПК. В ней хранятся векторы прерываний, данные, используемые BIOS и DOS, рабочая область и резидентная часть DOS, драйверы внешних устройств, а также другие программы и данные, необходимые для поддержки нормальной работы ПК. Наконец, DOS загружает прикладные задачи только в обычную память. Если при отсутствии или неисправности дополнительной памяти ПК может выполнять свои функции, то при неисправности младшей части памяти его работа просто невозможна.Разработчики выбрали не самое лучшее из возможных решений, ограничив предельный размер пространства адресов значением 1 Мбайт. Возможно, им казалось, что такой объем памяти удовлетворит нужды пользователей на многие годы. Так или иначе, но заведомо ограниченное решение стало стандартом и используется во всех без исключения компьютерах семейства IBM PC.
Режимы работы микропроцессора. Компьютеры IBM .PC/AT, собранные на базе процессора Intel 80286, уже обеспечивали доступ к пространству адресов размером в 16 Мбайт. Однако для реального использования такого пространства требовалось новое программное обеспечение. К этому времени было создано много системных и прикладных программ, ориентированных на возможности процессора Intel 8086, и изменять способ работы с адресами было слишком поздно. Разработчики пошли по другому пути. Начиная с модели 80286, все микропроцессоры Intel поддерживают два режима работы. Один из них называется реальным режимом (real-address mode), а другой защищенным режимом (protected-address mode).
Реальным режим назван потому, что используются физические адреса оперативной памяти и внешних устройств, значения которых не превышают 1 Мбайт. При включении ПК любой современный микропроцессор переключается в реальный режим. В нем он работает в точности как Intel 8086, только значительно быстрее.
Работа с дополнительным пространством адресов возможна только в защищенном режиме. При этом микропроцессор контролирует допустимость значений адресов, откуда и произошло название режима. Для распределения и контроля расширенного пространства адресов в состав микропроцессоров включены специальные регистры и команды для их обслуживания. Те и другие доступны только при работе микропроцессора в защищенном режиме.
У микропроцессора Intel 386 шины адреса и данных стали
32-разрядными, а размер пространства адресов увеличился до 4 Гбайт. Кроме
того, был введен
еще один виртуальный режим (virtual-address mode), иногда его обозначают
V86. Он позволяет при работе в защищенном режиме эмулировать процессор
8086. Отличие от реального режима в том, что адреса не являются физическими
(реальными), а отображаются на конкретную часть 32-разрядного пространства.
Это позволяет современным операционным системам "параллельно"
выполнять несколько задач, рассчитанных на работу в реальном режиме, загружая
их в свободные адреса ОЗУ.
Таким образом, работа любого современного IBM PC начинается в реальном
режиме, затем его можно перевести в защищенный режим, а из последнего
— в виртуальный. Изменение текущих режимов работы микропроцессора обычно
выполняет операционная система. Прикладные задачи могут это делать только
при работе в монопольном режиме, на практике такие случаи встречаются
редко.
В реальном режиме работы процессора пространство оперативной памяти делится на сегменты, размер которых не превышает 64 Кбайт, а адрес начала обязательно кратен 16.
При выборке команд и операндов микропроцессор вычисляет абсолютный адрес, исходя из значения (кода) сегмента и смещения (относительного адреса) в нем. Для этого он сдвигает значение сегмента на 4 разряда вправо и прибавляет смещение к результату сдвига. Табл. Б.1 иллюстрирует схему формирования 20-разрядного адреса.
Таблица Б. 1. Схема формирования 20-разрядного адреса
15 | 14 | 13 | ... | c | м | е | щ | е | н | и | е | ... | 2 | 1 | 0 | ||||
15 | 14 | 13 | 12 | ... | с | е | г | м | е | н | т | ... | 2 | 1 | 0 | ||||
19 | 18 | 17 | ... | п | о | л | н | ы | й | а | д | р | е | с | ... | 2 | 1 | 0 |
При записи на бумаге или на экране монитора сегмент и
смещение разделяет символ "двоеточие". Например, записи 1111:2222
соответствует адрес 11110 + 2222 = 13332. Такая форма записи адресов используется
при работе с большинством отладчиков.
Один и тот же полный адрес может быть задан различными способами. Например,
область данных BIOS расположена в оперативной памяти, начиная с абсолютного
адреса 400h. Его можно записать в виде 0000:0400, 0040:0000, 0020:0200
И Т. П.
Оперативная память занимает не все пространство адресов, а только младшие 640 Кбайт. Ее можно разделить на 10 сегментов максимального размера (64 Кбайт), коды которых будут изменяться от оооо до эоооь. Обычно далеко не все сегменты имеют максимальный размер, поэтому их количество бывает больше 10, но в любом случае сегмент с кодом AOOOh уже не относится к оперативной памяти, обычно это код видеосегмента при работе в графических режимах. И вообще, пространство от 640 Кбайт до 1 Мбайт выделено для размещения BIOS, ее дополнений, расположенных на платах контроллеров внешних устройств, адресов, через которые происходит доступ к этим устройствам, и т. п.
Расположение адресов в регистрах
Микропроцессор выбирает части адреса из двух разных регистров. Коды сегментов хранятся в специальных сегментных регистрах, которые предназначены только для этих целей. Начиная с модели 80386, у микропроцессоров Intel таких регистров 6. На языке ассемблера они имеют имена cs, DS, ES, FS, GS, ss. По умолчанию, т. е., если явно не указано другое, регистр cs используете т при выборке команд, DS — при выборке данных, a ss — при работе со стеком. Умолчаний для ES, FS и GS не существует, они всегда указываются явно.
Смещения в сегментах (относительные адреса) процессор может выбирать из шести регистров, которые на языке ассемблера имеют имена вх, ВР, IP, SP, DI, si. Первый (вх) называется регистром базы, относится к регистрам общего назначения и делится на два байта. Остальные пять на байты не делятся, три из них имеют фиксированное назначение. В SP хранится указатель стека, с ним работают команды push, pop и др. Регистр ВР используется для доступа обычных команд (mov, add и пр.) к области стека (см. приложение В). Из IP процессор выбирает относительный адрес очередной выполняемой команды. Индексные регистры DI и si используют строковые операции, а в остальных случаях они не имеют фиксированного назначения.
Составленные на языке Макроассемблера программы обязательно сегментируются. В простейшем случае вся программа может состоять только из одного сегмента. Подобная программа была приведена в примере 4.1.
Описание и расположение сегментов. В большинстве случаев текст программы состоит, по крайней мере, из трех сегментов, содержащих область стека, раздел данных и коды. В примере Б.1 показана общая структура программы, содержащей три основных сегмента.
Пример Б.1. Структура программы, состоящей из трех сегментов
stack Segment word stack "stack"; начало стекового
сегмента
db 10Oh dup (?) ; размер области стека 10Oh байтов stack Ends ; конец
стекового сегмента .
data Segment ; начало сегмента данных
; в этом сегменте располагается описание
используемых в программе данных data Ends : конец сегмента данных
code Segment ; начало кодового сегмента ; в этом сегменте располагается
текст основной ; программы и входящих в нее подпрограмм code Ends ; конец
кодового сегмента
END ; конец текста программы
Из текста примера Б.1 видно, что описание сегмента открывает директива Segment, а закрывает Ends. Перед обеими директивами указывается одинаковая метка, символ "двоеточие" в данном случае отсутствует. Метка указывается дважды для того, чтобы при работе с вложенными сегментами Макроассемблер мог определить, какой из них закрывает данная директива. Формально вложенные сегменты допустимы, но особых преимуществ их использование не дает.
После директивы segment могут располагаться параметры, которые обычно не нужны. Исключением является стековый сегмент, если не указать параметр stack, то компоновщик (link.exe) выдаст сообщение об его отсутствии в теле задачи. Это не аварийное, а предупреждающее сообщение, т. к. стековый сегмент действительно может отсутствовать. Однако если он явно описан, а компоновщик его не обнаружил, значит, в тексте программы допущена ошибка, и ее надо исправить.
Последовательность расположения сегментов в тексте программы не имеет значения ни для Макроассемблера, ни для компоновщика, ни для DOS. Поэтому разработчик располагает их в нужном ему порядке, а основным критерием является наглядность текста программы и удобство его анализа. Правда в некоторых случаях бывает важно знать, какой сегмент расположен последним в тексте построенной задачи. Такой случай будет рассмотрен в следующем разделе.
Расположение сегментов в задаче. Макроассемблер может
располагать сегменты в тексте будущей задачи либо в порядке их описания
в исходной программе, либо по именам в алфавитном порядке. Вариант расположения
можно выбрать с помощью директив .SEQ и .ALPHA (в обоих случаях указание
точки обязательно), которые располагаются перед описанием первого сегмента.
Директива .SEQ устанавливает расположение сегментов в порядке их описания,
а директива .ALPHA— в алфавитном порядке. Обычно по умолчанию сегменты
располагаются в порядке их описания.
Реальное расположение сегментов в построенной задаче компоновщик выводит
в файл, имя которого надо указать в ответ на подсказку "List file
>", по умолчанию такой файл имеет тип тар. Если в ответ на подсказку
вы введете конкретное имя, то получите файл, содержащий имена, адреса
и размеры описанных в программе сегментов.
Специальные директивы описания сегментов. В документации на Макроассемблер
для описания сегментов, содержащих константы, данные, коды и стек, рекомендуется
использовать специальные директивы: .CONST, .DATA, .CODE, .STACK (указание
точки обязательно), которые автоматически формируют все параметры сегмента.
Им обязательно должна предшествовать директива .MODEL с указанием названия
используемой модели памяти (пример Б.2). Допустимо использование следующих
имен: small, compact, medium, large и huge.
Перечисленные типы моделей памяти поддерживают компиляторы таких языков программирования, как Паскаль, Си, Фортран и некоторые другие, что и объясняет введение директивы .MODEL. В MASM 6.0 специально для защищенного режима введена еще одна модель — flat.
Пример Б.2. Специальные директивы описания основных сегментов |
Dosseg ; задает расположение сегментов
.Model small ; описание модели памяти
.Stack 100h ; описание стекового сегмента
.Data ; начало сегмента данных
; В этом сегменте располагается описание
; используемых в программе данных
^ , . Code ; начало сегмента кодов
; В этом сегменте располагается текст основной
; программы и входящих в нее подпрограмм
END ; конец текста программы
В примере Б.2 директивы .stack, .Data, .code указывают начало соответствующего сегмента, директива Ends при этом не нужна. Признаком конца текущего сегмента является начало следующего или директива END, завершающая текст программы, поэтому вложение сегментов исключено. В тексте программы, кроме указанных сегментов, могут использоваться другие, описанные обычным способом.
Перед специальными директивами имена сегментов не указываются, но они могут понадобиться для использования в программе. Макроассемблер присваивает стековому сегменту имя stack, сегменту данных — имя _data (нижняя черта обязательна). Имя сегмента кодов зависит от модели памяти, например, при моделях small и compact оно будет _text. Присвоенные сегментам имена можно посмотреть в карте памяти, которую формирует компоновщик при указании файла с типом тар (см. выше).
Для управления последовательностью расположения сегментов в примере Б.2 использована директива Dosseg, которая размещает сегменты в соответствии с соглашениями DOS. Это значит, что первым в теле задачи будет расположен кодовый, а последним — стековый сегмент. Если сегментов всего три, то данные будут размещены после кодов, перед областью стека. Дополнительные сегменты располагаются между кодами и данными.
Работа с именами сегментовМакроассемблер рассматривает имя сегмента как константу (а не как переменную), ее прямая пересылка в сегментный регистр невозможна, поэтому приходится использовать регистр-посредник. Например, в большинстве случаев в начале выполнения задачи надо определить содержимое сегментного регистра DS, поместив в него код сегмента данных. Это можно сделать с помощью двух следующих команд:
start: mov ax, data ; ax = код сегмента данных mov ds, ax ; ds = ax
Этот пример составлен исходя из предположения, что сегмент данных описан так, как приведено в примере Б.1. Если же он описан, как в примере Б.2, то в первой команде надо изменить имя data на _data или @data.
Макроассемблер позволяет определить код сегмента тремя разными способами: указать его явно в виде числа, указать имя сегмента или указать имя метки, расположенной в нужном сегменте. В последнем случае перед именем метки помещается оператор seg. Следующие три команды иллюстрируют сказанное:
mov ax, 0400h ; указание конкретного числового значения
mov ax, job ; явное указание имени сегмента
mov ax, seg buf ; косвенная ссылка на сегмент по имени метки
Команды первого типа нужны при обращении к сегментам, расположенным вне тела программы, например к области данных BIOS.
Команды второго типа применяются при работе с сегментами, описанными в тексте программы. Точное значение таких сегментов заранее неизвестно, его вычисляет DOS при загрузке программы в память для выполнения.
Команды третьего типа полезны в тех случаях, когда метка
является внешним именем, не описанным в программе, и нужно узнать, какому
сегменту она принадлежит.
После того, как с помощью одной из этих команд значение сегмента окажется
в регистре ах, оно копируется в нужный сегментный регистр командой пересылки,
как это было показано выше на примере определения содержимого регистра
ds.
Резервирование пространства памяти. Большинство графических задач нуждается в определенном пространстве ОЗУ для размещения буферов различного назначения. В простейшем случае для резервирования требуемого пространства памяти в текст программы включаются дополнительные сегменты.
Это можно сделать, например, так:
Job Segment ; начало сегмента
buffer db 65536 dup (?) ; директива резервирует 65536 байтов
Job Ends ; конец сегмента
В приведенном примере сегмент становится частью тела задачи и увеличивает его размер на соответствующее количество байтов. Поэтому такой способ распределения пространства ОЗУ является вспомогательным и применять его можно только в порядке исключения.
Более гибкий и универсальный вариант заключается в том, что для размещения буферов различного назначения пространство оперативной памяти не резервируется в исходном тексте программы, а распределяется в процессе выполнения задачи.
Пространство, выделенное DOS для выполнения задачи, в технической документации принято называть блоком задачи. Он состоит из трех основных частей. Первые юоь (256) байтов блока занимает специальная структура данных — префикс программного сегмента (PSP). В нем хранятся величины, которые могут быть нужны при выполнении задачи. Сразу после PSP в памяти расположены сегменты, описанные в исходном тексте программы.
Как уже говорилось, порядок расположения сегментов в блоке задачи зависит от их имен и от наличия директив .Alpha, .seq или Dosseg, которые могут указываться в начале исходного текста программы. После последнего сегмента в блоке задачи находится свободное пространство, которым задача может распоряжаться по своему усмотрению (по усмотрению программиста). Но для доступа к этому пространству надо знать его размер и адрес начала, точнее, сегмент, с которого оно начинается. При составлении программы эти величины неизвестны, поскольку их формирует DOS, исходя из реально имеющихся ресурсов на момент загрузки задачи для выполнения.
Адрес свободного пространства
Для определения адреса свободного пространства надо знать, где заканчивается последний сегмент задачи. Для этого, в свою очередь, необходимо выяснить, какой из сегментов, описанных в исходном тексте программы, окажется последним в теле задачи.
Иногда рекомендуют включать в исходный текст задачи пустой сегмент, имя которого начинается на букву z, например:
Zero Segment ; начало сегмента Zero Zero Ends ; конец сегмента Zero
Если при этом задана директива .Alpha, т. е. надежда, что сегмент zero будет расположен в теле задачи последним. В таком случае он и является началом свободного пространства в блоке задачи.
Несмотря на очевидную простоту, этот способ не универсален, поскольку сегмент zero не всегда оказывается последним. Напомним, что имена могут состоять не только из букв. Например, если кодовый сегмент имеет имя _text, то сегмент Zero будет расположен перед ним. Поэтому нужен более надежный способ определения последнего сегмента.
Если указана директива Dosseg, то последним в теле задачи будет расположен стековый сегмент. Тот же результат получается при использовании специальных директив, показанных в примере Б.2. Учитывая, что они рекомендованы разработчиками в качестве основных, имеет смысл исходить из допущения, что последним в теле задачи расположен стековый сегмент.
Стековый сегмент имеет определенный размер, который надо учесть при вычислении первого свободного сегмента в блоке задачи. При входе в задачу регистр SP содержит адрес верхушки, который равен размеру стека, выраженному в байтах. Его надо преобразовать в параграфы (разделить на 16) и сложить с кодом стекового сегмента, хранящимся в регистре ss. Таким образом, адрес начала свободного пространства (код свободного сегмента) вычисляется по формуле:
freeseg = [ss] + ([sp] /16) + 1
В этой формуле квадратные скобки указывают на то, что используется содержимое регистров ss и sp. При программировании деление [sp] на 16 заменяется сдвигом на 4 разряда вправо. Для того чтобы не потерять один параграф, размер стека должен быть кратен 16-ти, в противном случае его надо округлить в сторону увеличения. Прибавление 1 нужно потому, что свободный сегмент должен начинаться после стекового, не перекрывая его.
Замечание
Какой бы сегмент вы не выбрали в качестве точки отсчета, советуем при
построении задачи обязательно задать файл листинга (карту памяти) и убедиться
в том, что выбранный сегмент расположен в теле задачи последним.
Размер свободного пространства
В PSP слово со смещением 2 содержит последний доступный для задачи адрес оперативной памяти, выраженный в параграфах, т. е. это код последнего доступного сегмента. После загрузки задачи DOS помещает код сегмента, содержащего PSP в регистры es и ds.
Содержимое регистра ds изменяется первыми командами задачи, а ев можно использовать для чтения указанной величины. Если ее уменьшить на freeseg, то получится размер свободного пространства, выраженный в параграфах. Теперь надо проверить, достаточно ли выделенное пространство для нужд задачи, и если да, то его можно использовать.
Вычисление SwpSeg и GenSeg
В приведенных в основной части книги примерах использовались буфер обмена и буфер общего назначения. Мы предполагали, что код сегмента, содержащего буфер обмена, хранится в переменной SwpSeg, а буфер общего назначения — в переменной GenSeg. Покажем, как можно сформировать значения этих переменных после вычисления размера и адреса начала свободного пространства описанным выше способом.
В примере Б.З приведен фрагмент начала программы, в котором выполняются все необходимые вычисления. Для описания сегментов в нем использованы обычные директивы (см. пример Б.1).
Пример Б.З. Вычисление значений переменных SwpSeg и GenSeg 1
.Alpha порядок расположения сегментов
Dosseg порядок расположения сегментов
stack Segment word stac : "stack" ; начало стекового сегмента
db 200h dup (?) размер области стека 200h байтов
stack Ends конец стекового сегмента
data Segment начало сегмента данных
prmpt db ODh,OAh, ' ! Для выполнения задачи не хватает памяти ! $ '
freeseg dw 0 первый свободный сегмент
msize dw 0 размер памяти в параграфах
needm dw 2000h требуемый размер памяти
SwpOffs dw 0 смещение в буфере обмена
SwpSeg dw 0 сегмент буфера обмена
GenOffs dw 0 смещение в буфере
GenSeg dw 0 сегмент буфера общего назначения
; Далее описываются другие используемые данные
data Ends конец сегмента данных
code Segment начало кодового сегмента
.386 набор команд процессора
start : mov ax, data ах = код сегмента данных
mov ds, ax ds = код сегмента данных
mov bx , sp bx = размер стека в байтах
shr bx, 04 превращаем его в параграфы
mov ax, ss ах = код стекового сегмента
add bx, ax bx = последний параграф стека
inc bx bx = первый свободный сегмент
mov freeseg, bx ireeseg = DX
mov ax, es: [02] ax = последний доступный сегмент
sub ax, bx ax = ax — bx
mov msize, ax размер памяти в параграфах
cmp ax, needm памяти достаточно ?
jae @F -> да
lea dx, prmpt dx = адрес аварийного сообщения
mov ah, 09 ah = код функции DOS
int 21h вывод текста сообщения
mov ax, 4COOh ah = 4С, код функции DOS
int 21h завершение выполнения задачи
@@: mov SwpSeg, bx SwpSeg = bx
add bx, lOOOh bx = bx + 65536/16
mov GenSeg, bx GenSeg = bx
; Далее расположен текст основной программы и подпрограмм
code Ends конец кодового сегмента
END start конец текста программы
В начале примера Б.З подряд расположены директивы .Alpha и Dosseg. При таком их сочетании стековый сегмент будет расположен в теле задачи последним, независимо от имен других сегментов (по крайней мере, так его располагает MASM 5.1).
Директива .386 определяет набор команд, которые можно использовать в программе. Если ее не указать, то по умолчанию будет выбран набор команд микропроцессора Intel 8086. В таком случае Макроассемблер обнаружит ошибку в записи команды shr bx, 04. Подробнее о назначении и месте расположения этой директивы сказано в приложении В.
После загрузки задачи DOS передает управление на метку start, для этого ее имя указано не только перед первой командой, но и после директивы END. Первые две команды записывают в регистр ds код сегмента данных, этот вопрос мы уже обсуждали. Шесть следующих команд вычисляют в регистре bx код первого свободного сегмента и сохраняют его в переменной freeseg. Затем в регистр ах считывается из 2-го слова PSP код последнего доступного для задачи сегмента, вычисляется размер свободной памяти в параграфах и сохраняется в переменной msize.
Реальный размер памяти сравнивается с необходимым (needm), и если он достаточен для выполнения задачи, то произойдет переход на локачьную метку @@. В противном случае на экран будет выведено аварийное сообщение и прекратится выполнение задачи. Способы вывода текстовых сообщений описаны в разделе основной части книги, а завершение выполнения задачи описано в разделе .
Если памяти достаточно, то в переменную SwpSeg копируется код первого доступного сегмента. Для буфера обмена отведено 65 536 байтов, что в параграфах составляет 1000h. Эта величина прибавляется к содержимому регистра bx, и результат записывается в GenSeg. Значения переменных swpoffs и GenOffs определяются в процессе выполнения задачи.
После выполнения команд примера Б.З размер буфера обмена ограничен, поскольку после него расположен буфер общего назначения, поэтому при работе с swpSeg нельзя выходить за пределы 65 536 байтов. Во всех ранее приведенных примерах такое ограничение нас вполне устраивало. Размер буфера общего назначения пока ограничен величиной (msize - ioooh)*16 байтов. В зависимости от конкретных особенностей задачи это пространство может быть разделено на блоки меньшего размера или использовано как большой буфер общего назначения.
При работе с блоками большого размера задача должна контролировать текущий адрес ОЗУ и при достижении границы 65 536 байтов изменять код в сегментном регистре, который используется для доступа к блоку (увеличивать его содержимое на 1000h). Необходимость работы с блоками ОЗУ большого размера возникает, например, при сохранении и восстановлении содержимого всей рабочей области экрана.
Мы описали простой пример размещения блоков в ОЗУ. Для выполнения более сложных функций, связанных с распределением памяти в текст задачи, придется включать специальные подпрограммы и поддерживать структуру данных, описывающих свободное и использованное пространство ОЗУ. Альтернативой является обращение к DOS для выполнения действий, связанных с распределением простраж тва оперативной памяти.
Будем предполагать, что стековый сегмент расположен в теле задачи последним. В таком случае нас интересует расстояние от начала PSP до конца стекового сегмента, выраженное в параграфах. Напомним, что код сегмента, содержащего PSP, находится в регистре es, код стекового сегмента — в ss, а если стек еще не использовался, то его размер в байтах содержится в регистре sp.
Размер тела задачи, выраженный в параграфах, вычисляется
так:
tasksize = [ss] - [es] + [sp] /16
В этой формуле квадратные скобки указывают на то, что при вычислении используется содержимое регистров ss, es и sp. При. программировании деление [sp] на 16 заменяется сдвигом на 4 разряда вправо. Для того чтобы не потерять один параграф, размер стека должен быть кратен 16-ти, в противном случае его надо округлить в сторону увеличения.
Зная реальный размер задачи, можно запросить у DOS сокращение ее блока. В результате появится свободное пространство памяти, которое можно использовать по запросам задачи.
Функции DOS
По запросам прикладных программ DOS выполняет несколько функций, связанных с распределением оперативной памяти. Нас будут интересовать только три из них.
Все обращения к DOS происходят через прерывание int 2ih, при этом код запрашиваемой функции указывается в регистре ah, а входные и выходные параметры располагаются в регистрах общего назначения и иногда в сегментных регистрах ds или es.
Функция 48h Allocate Memory выделяет запрашиваемое пространство памяти. Требуемый размер памяти, выраженный в параграфах, указывается в регистре bx. Если такое пространство существует, то при возврате из DOS признак переполнения отсутствует, содержимое bx не изменяется, а в ах находится код сегмента, с которого начинается выделенное пространство памяти. Если свободное пространство нужного размера отсутствует, то при возврате из DOS вырабатывается признак переполнения (устанавливается в 1 С-разряд регистра флагов).
Функция 49h Free Allocated Memory Block открепляет выделенный ранее для задачи блок памяти, после этого задача не может с ним работать. Код сегмента, начиная с которого расположен освобождаемый блок, помещается в регистр es, размер блока указывать не требуется, т. к. DOS хранит его в своей области данных. Если функция выполнена успешно, т. е. блок освобожден, то при возврате из DOS признак переполнения отсутствует. Если он установлен, то наиболее вероятно, что в регистре es был неверно указан сегмент блока.
Функция 4Ah shrink or Expand a Memory Block урезает или расширяет суще-ствующий блок памяти. Код сегмента, начиная с которого расположен изменяемый блок, указывается в регистре es, а новый размер блока — в регистре bx. Если функция выполнена успешно, т. е. размер указанного блока изменен, то при возврате из DOS признак переполнения отсутствует. Если он установлен, то блок не может быть расширен или сокращен. В этом случае в регистре bx возвращается наибольший (последний) доступный блок памяти.
Вычисление SwpSeg и GenSeg
В примере Б.4 показан фрагмент начала текста программы, в котором производится сокращение размера блока задачи и выделение двух блоков для размещения буферов обмена и общего назначения. Для описания основных сегментов в примере использованы специальные директивы (см. пример Б.2).
Пример Б.4. Получение от DOS значений переменных SwpSeg и GenSeg
dosseg задаем расположение сегментов
.model small выбор модели памяти
.stack 200h задаем стековый сегмент
.data начало сегмента данных
wpOffs dw 0 смещение в буфере обмена
wpSeg dw 0 сегмент буфера обмена
enOffs dw 0 смещение в буфере
enSeg dw 0 сегмент буфера общего назначения
Далее располагаются другие данные, используемые в задаче
.code начало кодового сегмента
.386 набор команд процессора
tart: mov ax, @data ax = код сегмента данных
mov ds, ax ds = ax
mov ax, ss ax = код стекового сегмента
mov bx, es bx = код сегмента, содержащего PSP
sub ах, bx ах = ах — bx
mov bx, sp bx = размер стека в байтах
shr bx, 04 превращаем его в параграфы
add bx, ax bx = bx + ах, размер задачи
mov ax, 4AOOh код запроса на сокращение бЛока
int 2 In DOS сокращает блок задачи
mov bx, lOOOh bx = размер блока в параграфах
mov ax, 480Ch код запроса на выделение блока
int 21h обращение к DOS
mov SwpSeg, ax SwpSeg = код сегмента блока
mov bx, lOOOh bx = размер блока в параграфах
mov ax, 4800h код запроса на выделение блока
int 21h обращение к DOS
mov GenSeg, ax GenSeg = код сегмента блока
; Далее следует продолжение текста программы
END start конец текста программы
ервые две команды примера Б.4 записывают в регистр ds код сегмента шных, который в этом случае имеет имя Sdata. Следующие 6 команд формируют в регистре bx значение tasksize, затем происходит вызов функции OS 4дь для сокращения размера блока задачи. При выполнении этой ункции неиспользуемая память возвращается DOS, и теперь ее можно запрашивать для размещения дополнительных блоков.
В примере Б.4 сначала запрашивается место для размещения буфера обмена, затем буфера общего назначения. В обоих случаях в регистре bx указывает на размер блока в параграфах (юооь), а в регистре ah — код функции 48h iiocate memory) и происходит обращение к DOS. После возврата из DOS код выделенного сегмента сохраняется в переменных SwpSeg или GenSeg. В отличие от примера Б.З размер буфера общего назначения в данном случае ограничен величиной 65 536 байтов.
В примере Б.З перед вычислением значений переменных SwpSeg и GenSeg производилась проверка достаточности объема памяти для выполнения задачи. В данном случае распределением памяти занимается DOS, поэтому указанная проверка выполняется иначе.
Контроль выполнения запросов
При описании функций говорилось, что при возврате из DOS состояние С-разряда указывает, успешно или неудачно завершилось выполнение запроса. Контролировать результат выполнения функции 4Ah не имеет смысла, лучше внимательно проверить запись команд, предшествующих обращению к DOS.
Результат исполнения функции 43h надо обязательно проверять. Признак переполнения (c=i) при возврате из DOS означает, что в памяти нет места для размещения блока нужного размера (если исключены ошибки в тексте программы). Что делать в таких случаях?
Выдача аварийного сообщения и прекращение выполнения задачи является самым плохим способом реагирования на недостаток памяти. Всегда можно найти вариант продолжения нормального выполнения задачи. Если освободить место для блока в основной памяти невозможно, то его надо разместить в расширенной памяти, тем более что это никак не скажется на дальнейшей работе с блоком.
Освобождение блоков
Освободившийся блок возвращается DOS по запросу 49h (Free Allocated Memory Block), но с этим действием можно не спешить. В среде DOS задача, как правило, выполняется в монопольном режиме, и кроме нее претендентов на свободную память нет. Перед завершением выполнения задачи освобождать использованные блоки не обязательно. После завершения задачи DOS обязательно освобождает все пространство памяти, которое было за ней закреплено.
Заключение.
Мы описали три варианта выделения нужного пространства в основной памяти
ПК: явное описание в исходном тексте задачи, размещение в свободной части
блока задачи и его запросы у DOS. Первый способ предельно упрощает программирование,
но значительно увеличивает размер файла, содержащего задачу. Два других
варианта примерно равноценны, они не существенно увеличивают количество
вспомогательных действий и почти не увеличивают размер задачи.Размер основной
памяти ПК невелик, поэтому при выборе моста для размещения дополнительного
пространства предпочтение следует отдавать расширенной памяти, которая
описана в следующем разделе.
В доступном для DOS пространстве адресов выделяется окно (сегмент), на которое отображается фрагмент расширенной памяти. Отображение окна на пространство физических адресов и его перемещение выполняет специальная программная компонента (менеджер). Ее присутствие является необходимым условием для работы с расширенной памятью.
Системные и прикладные задачи получают доступ к расширенной памяти, издавая специальные команды, которые исполняет менеджер. В соответствии с идеологией DOS и BIOS они оформляются в виде программных запросов.
Впервые EMS была опубликована сразу в виде версии 3.0 в 1985 году, в ее подготовке приняли участие только Lotus и Intel. Примерно в это же время Microsoft в связи с разработкой Windows заинтересовалась EMS и принимала активное участие в разработке ее последующих версий. В 1987 году была опубликована версия 4.0, которая предусматривала возможность размещения и выполнения нескольких задач в расширенной памяти. Автору неизвестно существование более поздних версий EMS, но для изложения материала это не существенно, т. к. соблюдается строгая преемственность версий и то, что описано в данном разделе может использоваться в ваших программах.
Менеджер расширенной памяти (Expanded Memory Manager или EMM) оформлен в виде драйвера, который располагается в оперативной памяти при загрузке DOS и остается резидентным до выключения ПК. Обычно его имя EMM386.EXE, оно обязательно указывается в файле config.sys, например, так:
DEVICE = C:\DOS\EMM386.EXE
В данном случае предполагается, что файл Еммзвб.ЕХЕ расположен на диске С, в каталоге DOS. Драйвер многофункциональный, поэтому после его имени в командной строке могут указываться параметры, которые используются при загрузке. Описание всех параметров вы найдете в файле HELP, входящем в комплект DOS
Важно
Один из параметров, а именно NOEMS запрещает поддержку описываемых ниже
функций драйвера, поэтому его указание в командной строке недопустимо.
EMS 4.0 исполняет 30 различных функций, разделенных на три группы: стандартные (standard), расширенные (advanced) и для многозадачных режимов (os). Нас будут интересовать только стандартные функции, необходимые для работы с данными, расположенными в расширенной памяти, их всего 7. Описание остальных функций вы найдете в TECH HELP или в специальной литературе по работе с DOS.
Доступ к драйверу осуществляется через прерывание int 67h. Перед выдачей запроса код функции помещается в регистр ah (старший байт регистра ах). Регистр ai используется либо для уточнения запрашиваемой функции (для advanced и os), либо для указания параметров функции. В случае успешного исполнения запроса EMM возвращает в регистре ah 0, а случае неудачного — 1.
EMS 4.0 фиксирует 36 различных ошибок, им присваиваются коды от 80п до ОА4п включительно. Полный список ошибок содержится в TECH HELP, некоторые из них будут названы при описании функций. Ошибки возникают по разным причинам. На стадии отладки они, чаще всего, вызваны некорректностью программы. Если корректность программы не вызывает сомнений и задача неоднократно выполнялась успешно, то имеет смысл посмотреть, какие параметры EMM386 заданы в файле config.sys. Например, если указан параметр NOEMS, то при запросах памяти обязательно будет возникать "внутренняя ошибка EMM драйвера", имеющая код еоь.
Описание стандартных функций. К категории стандартных относятся следующие функции драйвера EMM.
Функция 40h Get EMM status предназначена для получения информации о состоянии драйвера. Входные параметры отсутствуют. Результат проверки возвращается в регистре ah. Если он очищен, то все в порядке. Код 8lh означает неисправность расширенной памяти, других кодов ошибки в данном случае не должно быть. В случае ошибки надо прекратить выполнение задачи и проверить состояние системного программного обеспечения.
Функция 41h Get Physical Segment
Address of EMS Frame возвращает зна-
чение сегмента, используемого для доступа к расширенной памяти. Входные
параметры отсутствуют, код сегмента указан в регистре bx. Его надо сохранить
и при чтении или записи данных помещать в один из сегментных регистров.
Код сегмента зависит от конфигурации оборудования, установленного на компьютере,
в процессе выполнения задачи он не может и не должен изменяться. Размер
сегмента 64 Кбайт, он делится на 4 страницы по 16 Кбайт каждая. Адреса
(смещения) страниц равны 0, 4000h, s000h и осоооь.
Функция 42h Get EMS Memory size позволяет определить общее и свободное пространство expanded memory. Входные параметры у нее отсутствуют. Общий размер памяти возвращается в регистре dx, а размер свободного пространства — в bx. Обе величины указаны в виде количества страниц, размер страницы равен 16 Кбайт.
Обычно не вся внешняя память ПК используется как expanded memory. Часть ее занимает DOS и другие резидентные программы. Кроме того, при установке EMM (в файле config.sys) может быть указан размер доступного для него пространства ОЗУ. Поэтому не следует ожидать, что находящееся в регистре dx количество страниц всегда соответствует размеру старшей памяти ПК.
Для выполнения задачи важно знать размер свободной части памяти, т. е. количество страниц, указанное в регистре bx. При программировании задачи необходимо предусмотреть проверку доступного размера памяти и выбор дальнейших действий в зависимости от его значения.
ФуНКЦИЯ 43h Allocate Memory and Open EMM handle запрашивается для выделения требуемого пространства расширенной памяти. Входным параметром является необходимое количество страниц, указываемое в регистре bx. При успешном выполнении функции в регистре dx возвращается EMM Handle — идентификатор выделенного блока, который используется при запросах других функций драйвера. Его обязательно надо сохранить.
Два специальных кода ошибок означают, что запрошено
слишком много страниц. Код 87h выдается, если общее пространство expanded
memory меньше запрошенного размера, а код 88h означает, что недостаточно
свободного пространства памяти. Если вы предварительно проверяли свободную
память с помощью функции 42h, то эти две ошибки не должны возникать, в
противном случае надо прекратить выполнение задачи.
Выделенное пространство закреплено за задачей, но перед записью или чтением
данных надо отображать его конкретную часть на рабочий сегмент, значение
которого возвращает функция 4ih. Как уже говорилось ранее, в этом сегменте
размещается 4 страницы. Для отображения 16 Кбайт ОЗУ на одну из этих страниц
предназначена функция 44h.
Функция 44h Map Memory связывает
физическую страницу с логической. Входными параметрами являются номера
физической (ai) и логической (bx) страниц и EMM handle (dx). Номер физической
страницы может изменяться от 0 до 3, а номер логической страницы — от
0 до n-i, где n — количество страниц, выделенное функцией 43h.
Возможные ошибки имеют следующие коды: 83h — неверно задан идентификатор
блока (EMM handle), 8Ah — логическая страница вне диапазона значений,
выделенных функцией 43h, 8Bh — недопустимая физическая страница.
После отображения логическая страница доступна для использования. Для работы с полным сегментом (65 536 байт) функция 44h выполняется 4 раза. При этом не обязательно указывать физические и логические страницы в порядке увеличения их номеров.
Функция 45h Release Memory освобождает пространство памяти, выделенное для задачи функцией 43п. Входным параметром является идентификатор блока, указываемый в регистре dx. Отказ от выполнения функции может быть связан только с его неправильным заданием.
Важно
Данная функция обязательно должна вызываться перед завершением выполнения
задачи, в противном случае выделенная для завершенной задачи память будет
недоступна для других задач вплоть до перезагрузки системы.
Функция 46h Get EMM version number
позволяет определить версию менеджера EMM. Входные параметры отсутствуют,
номер версии в двоично-десятичном коде возвращается в регистре ai. Например,
версии 6.2 соответствует КОД 62п.
Таким образом, при использовании стандартного набора функций дра EMM задача
получает возможность резервирования нужного простран в дополнительной
памяти ПК и размещения в нем данных.
Выполнение первого пункта списка является скорее данью традиции, чем необходимостью, поскольку ПК на базе Intel 386 и всех последующих моделей обязательно имеют оборудование для доступа к расширенной памяти. Для проверки можно, например, с помощью функции 40h определить статус, а с помощью функции 4бп — номер версии драйвера и убедиться, что он не меньше чем 4.0. Остальные пункты списка обязательно должны выполняться, причем в той последовательности, в которой они перечислены.
Специальные переменныеПри работе с Expanded memory обязательно используются код сегмента расширенной памяти и идентификаторы выделенных блоков. В некоторых случаях могут быть нужны номера последних логических страниц в выделенных блоках и другие величины. Для их хранения в разделе данных задачи надо выделить специальные переменные, количество которых зависит от количества открытых блоков.
Если задача запрашивает у драйвера только один блок большого размера, то для работы с ним нужны следующие переменные:
EBuff dw 0 ; код сегмента для доступа к расширенной памяти
Ehndlr dw 0 ; идентификатор блока выделенного для задачи
Curpg db 0 ; номер текущей логической страницы блока
Lastpg db 0 ; номер последней логической страницы блока
Номера текущей и последней страниц нужны при работе
с большими блоками, размер которых превышает 65 536 байтов (4 страницы).
При работе с ними приходится многократно отображать его логические страницы
на физические. Если же размер блока не превышает стандартного сегмента
ОЗУ, то он отображается на физические страницы только один раз и при дальнейшей
работе номера страниц не нужны.
При работе с несколькими блоками в разделе данных задачи можно организовать
простую таблицу, состоящую из строк фиксированного размера, содержащих
характеристики каждого блока. В таком случае характеристики выбираются
из таблицы по порядковому номеру блока, и сокращается количество имен
переменных.
Резервирование блока
Предположим, что для выполнения задачи требуется непрерывное пространство расширенной памяти (блок) размером в 1 Мбайт. Для резервирования такого блока задача должна запросить у драйвера исполнение функции 43h, указав в регистре bx 64 страницы (размер страницы составляет 16 Кбайт). Если это первый запрос, обращенный к драйверу, то предварительно надо выполнить функцию 4 in для определения состояния драйвера и получения кода сегмента для доступа к памяти.
Фрагмент программы, выполняющий резервирование блока размером в 1 Мбайт, приведен в примере Б.5. Его надо включить в ту часть задачи, где выполняются подготовительные действия. Например, сразу после команд, приведенных в примерах Б.З или Б.4.
Пример Б. 5. Создание в расширенной памяти блока размером 1 Мбайт
mov ax, 4100h код функции запроса сегмента
int 67h обращение к драйверу
or ah, ah функция выполнена ?
je @F -> да
jmp emmerr -> ошибка при исполнении функции
@@: mov EBuff, bx сохраняем код сегмента
mov bx, 64 размер запрашиваемого блока
mov ax, 4300h код функции выделения памяти
int 67h обращение к драйверу
or ah, ah блок выделен ?
je @F -> да
jmp emmerr -> ошибка при выделении блока
@@: mov Ehndlr, dx сохраняем идентификатор блока
mov Curpg, 0 номер текущей страницы блока
mov Lastpg, 63 номер последней страницы блока
; Продолжение текст а программы
Выполнение примера Б.5 начинается с запроса кода сегмента для доступа к расширенной памяти. Если при возврате из драйвера регистр ah очищен, то запрос исполнен успешно, в противном случае произойдет переход на метку emmerr. для вывода аварийного сообщения.
На следующем шаге выдается запрос на выделение блока размером в 64 страницы. Если после возврата из драйвера регистр ah очищен, то блок выделен, в противном случае происходит переход на метку emmerr.
В случае успешного выделения блока формируются Curpg и Lastpg и на этом выполнение фрагмента завершено.
В примере Б.5 отсутствуют команды, обработки аварийных ситуаций. Предполагается, что первая из них имеет метку emmerr. Что делать в случае ошибки, решать вам, например, можно вывести на экран текст аварийного сообщения и завершить выполнение задачи. На стадии отладки полезно предусмотреть вывод кода ошибки, который находится в регистре ah.
Напоминаем, что после выполнения команд примера Б.5 блок только закреплен за задачей, но не доступен для записи или чтения. Для работы с его конкретными страницами их надо отобразить на физические страницы сегмента EMS. В этом заключается одно из существенных отличий доступа к блокам расширенной памяти от доступа к блокам обычной памяти.
Отображение страниц
Для отображения логической страницы блока на одну из физических страниц сегмента EMS запрашивается функция 44h. Мы рассмотрим универсальный вариант подпрограммы отображения страниц.
В зависимости от логики выполняемых в задаче действий может потребоваться отображение от одной до четырех страниц. Поэтому подпрограмма, текст которой приведен в примере Б.6, имеет две точки входа.
При вызове call mapseg отображаются четыре подряд расположенные страницы. Предварительно в регистре bx указывается номер первой логической страницы, а в регистре dx — идентификатор блока, которому принадлежат отображаемые страницы. Вариант вызова подпрограммы mapseg показан в примере Б.7 (см. Работа с расширенной памятью После резервирования блока и отображения части или всех его логических страниц с расширенной памятью могут работать все без исключения команды микропроцессора. В данном разделе описан пример пересылки большого массива данных и обсуждается возможность одновременного использования двух блоков, расположенных в расширенной памяти. Способ пересылки большого блока
При выполнении графических задач может потребоваться сохранение содержимого всего рабочего пространства видеопамяти. Учитывая его большие размеры, сохранение в обычной памяти либо не целесообразно, либо просто невозможно, для этого лучше подходит расширенная память.
Размер рабочего пространства видеопамяти зависит от установленного видеорежима, он вычисляется умножением размера строки в байтах на количество строк на экране (bperiine versize). Для временного хранения произведения нужна переменная, состоящая из двух слов (но не одно двойное слово), назовем ее bisize. В первом слове будет храниться старшая часть произведения, а во втором слове — младшая. Старшая часть указывает количество полных окон видеопамяти, а младшая часть — количество байтов в последнем окне, оно может быть равно нулю.
Если в результате умножения в первом слове bisize окажется число N, а во втором bisize+2 число м, то размер отображаемой части видеопамяти составляет N* 6553б + м байтов, т. е. надо переслать N полных сегментов и еше м байтов. Поэтому перед пересылкой очередного фрагмента данных необходимо уточнять его размер.
Кроме того, перед пересылкой каждого фрагмента производится отображение очередной группы логических страниц блока, выделенного в расширенной памяти на физические с помощью подпрограммы mapseg, приведенной в примере Б.6 Для исключения лишних проверок можно каждый раз отображать по 4 страницы. В процессе отображения mapseg изменяет номера логических страниц (содержимое регистра bx), поэтому при работе с ней достаточно задать номер исходной страницы и в дальнейшем просто не изменять текущее содержимое регистра bx.
Для упрощения и ускорения пересылки нужен микропрограммный цикл на основе строковой операции movs, работающей с двойными словами.
Подпрограмма пересыки блока
Программная реализация пересылки показана в примере Б.7. Перед вызовом подпрограммы в регистрах es и fs указываются коды видеобуфера и сегмента EMS, в примере Б.8 показано, как это делается.
Предполагается, что в разделе данных задачи описана переменная bisize, а в расширенной памяти зарезервирован блок, размер которого не меньше размера отображаемой части видеопамяти.
Пример Б.7. Пересылка содержимого рабочей области экрана
movebl: push Cur_win pusha
mov ax, BaseWin
mov Cur_win, ax
call Setwin
mov ax, bperline
mul versize
mov bisize, dx
mov blsize+2, ax
xor bx, bx
mov dx, Ehndlr
xor di, di
xor si, si
сохраняем текущее окно видеопамяти
сохраняем "все" регистры
ах = BaseWin (или ах = 0)
Cur_win = BaseWin
установка нулевого окна видеопамяти
ах = размер строки в байтах
dx:ax = bperline * versize
bisize = число полных окон
bisize +2 = размер последнего окна
bx = исходная логическая страница
dx = идентификатор файла
di = 0 исходный адрес
si = 0 исходный адрес
mloop: mov ex, 4000h 0,25 размера стандартного сегмента
dec blsize уменьшаем количество сегментов
jns sc I -> пересылка полного окна
mov ex, blsize+2 ex = размер последнего окна
shr ex, 02 уменьшаем его в 4 раза
je sc__2 -> окно пустое, пересылка окончена
sc I: call mapseg отображаем очередные 4 страницы
rep movs dword ptr [di], fs:[si]; цикл пересылки
call Nxtwin следующее окно видеопамяти
cmp blsize, -1 пересылка завершена ?
jne mloop -> нет, продолжаем пересылку
sc_2: pop Cur_win исходное значение видеоокна
рора восстановление "всех" регистров
call Setwin восстановление исходного окна
ret возврат из подпрограммы
Выполнение примера Б. 7 начинается с сохранения исходного окна видеопамяти, содержимого всех регистров и установки базового окна. Если переменная Basewin в задаче не используется, то надо просто установить нулевое окно. Затем вычисляется размер отображаемой области видеопамяти, и результат сохраняется в словах bisize и bisize+2. В регистр bx помещается номер нулевой логической странице, а в dx — идентификатор блока. Содержимое этих двух регистров использует только подпрограмма mapseg. Подготовка оканчивается очисткой содержимого индексных регистров si и di.
Основной цикл имеет метку mloop. Его первые шесть команд определяют размер фрагмента пересылаемых данных. Он составляет 16 384 двойных слова, если окно заполнено полностью, или равен значению слова bisize+2, уменьшенному в 4 раза, если окно заполнено частично.
Команда, имеющая метку sc_i, отображает очередные четыре страницы блока на сегмент EMS. Затем микропрограммный цикл пересылает очередной фрагмент данных. Он выполняет основную работу, все остальные команды примера Б. 7 являются вспомогательными.
После пересылки очередного фрагмента проверяется содержимое bisize, и работа подпрограммы продолжается до тех пор, пока его значение не окажется равным "-1". В этом случае из стека выталкиваются значения переменной Cur_win и сохраненных регистров, восстанавливается исходное окно видеопамяти и происходит возврат на вызывающий модуль.
Сохранение и восстановление рабочей области экрана
В примере Б. 7 основные действия выполняет строковая
операция movs, у которой расположение источника задает регистр fs, а приемника
— es. Следовательно, для сохранения содержимого видеопамяти в расширенной
памяти в регистр fs надо записать код видеосегмента, а в es — код сегмента
EMS. Для восстановления содержимого видеопамяти, сохраненного в расширенной
памяти в регистре fs, указывается код сегмента EMS, а в es — код видеобуфера.
Формирование нужных значений в регистрах es и fs выполняют подпрограммы,
приведенные в примере Б.8. Для сохранения содержимого видеопамяти используется
обращение к подпрограмме scrsave, а для восстановления — к scrrest. Входные
параметры отсутствуют.
Пример Б.8. Сохранение или восстановление рабочей
области экрана
scrsave: PushReg <fs,es,vbuff,ebuff>; размещение
в стеке
jmp short @F обход макровызова
scrrest: PushReg <fs,es,ebuff vbuff>; размещение в стеке
@@: call Hidepnt удаление изображения курсора
PopReg <es,fs> формируем содержимое es и fs
call Movebl перемещение блока
PopReg <es,fs> восстановление содержимого es и fs
call Showpnt вывод курсора на экран
ret возврат из подпрограммы
При вызове scrsave в стеке сохраняется исходное содержимое регистров fs, es и переменных vbuff, ebuff. При обращении к scrrest порядок записи в стек переменных ebuff, vbuff противоположный. После размещения в стеке нужных величин выполняется общая часть обеих подпрограмм.
Прежде всего надо удалить изображение курсора с экрана, иначе при сохранении оно станет частью общей картины, а при восстановлении на экране могут появиться два курсора, или при перемещении на месте курсора окажется прямоугольник другого цвета.
После этого в регистры fs и es выталкиваются из стека нужные величины, происходит обращение к подпрограмме movebi и восстанавливается исходное содержимое регистров fs и es.
Выполнение подпрограмм заканчивается восстановлением изображения курсора на экране. Напомним, что варианты подпрофамм Hidepnt и Showpnt описаны в главе 6.
Несколько блоков в расширенной памяти
При работе с обычной памятью каждому блоку соответствует свой уникальный код сегмента. В отличие от обычной, при работе с расширенной памятью доступ ко всем зарезервированным задачей блокам осуществляется через один и тот же сегмент EMS. В таком случае по коду сегмента невозможно определить блок, к которому происходит обращение. Для этой цели можно использовать только смещение (адрес), по которому выбираются или записываются данные. Для того чтобы понять, к чему это приводит, рассмотрим простой вариант работы с двумя блоками.
Предположим, что доступны два блока, и надо переписать данные из одного в другой. Если один из блоков расположен в видео или в обычной памяти, а другой в расширенной, то размер пересылаемой порции данных ограничен адресным пространством сегмента, т. е. величиной 65 536 байтов. В этом вы могли убедиться на примере Б.7.
Если же оба блока расположены в расширенной памяти, то пространство сегмента EMS придется разделить пополам и использовать младшие адреса для работы с одним из блоков, а старшие — с другим. В результате этого в каждом блоке будет доступно пространство размером 32 768 байтов. Никаких других ограничений нет.
В таком случае перед копированием порции данных придется дважды обратиться к подпрограмме отображения страниц, описанной в примере Б.6, через точку входа mapip. Сначала отображаются две очередные логические страницы блока 1 на физические страницы 0 и 1, затем две очередные логические страницы блока 2 на физические страницы 2 и 3. Блок 1 начинается с нулевого адреса сегмента EMS, а блок 2 с адреса soooh того же сегмента. Предельный размер доступного пространства в обоих блоках составляет soooh или 32 768 байтов. После этого можно использовать любые команды для работы с отображенным пространством, например, строковую операцию movs, выполняющую перемещение данных из блока в блок.
Если особенности алгоритма требуют одновременной работы с тремя или четырьмя блоками, то доступное пространство будет ограничено размером одной страницы, т. е. величиной 16 384 байта.
Здесь уместно отметить, что в набор Advanced Functions драйвера EMM включена специальная функция, предназначенная для перемещения или обмена содержимого (перестановки) двух блоков данных размером до 1 Мбайт, ее код 57h. Блоки могут располагаться в обычной или расширенной памяти. Перед вызовом функции 57h в регистре ai указывается 0 для пересылки или 1 для перестановки блоков. Кроме того, в регистрах ds:si указывается адрес начала специальной структуры данных, содержащей размер блока и данные об источнике и приемнике. Описание этой функции вы можете найти, например, в Tech Help, нам важно было напомнить о ее существовании.
Заключение.
Мы закончили описание основных видов оперативной памяти, поэтому можно
подвести общий итог. При работе в среде DOS для программ доступна как
основная, так и дополнительная память. Реальные размеры последней существенно
больше размеров первой, поэтому при разработке задач следует отдавать
предпочтение расположению больших блоков в расширенной памяти, а блоки
небольшого размера размещать в обычной памяти. Кроме того, следует избегать
одновременного использования нескольких блоков, расположенных в расширенной
памяти, т. к. это связано с ограничением доступного пространства адресов.
Начиная с модели Intel 386, в защищенном режиме микропроцессоры оперируют 32-разрядными адресами, что соответствует пространству в 4 Гбайт или 4096 Мбайт. Это очень большое пространство, для рационального использования и контроля допустимости адресов оно делится на страницы размером по 4 Кбайт. Учитывая, что реальный объем оперативной памяти намного меньше 4 Гбайт, предусмотрен механизм подкачки страниц.
Как и при работе в реальном режиме, адрес ОЗУ состоит из двух частей, одна из которых находится в сегментном регистре, а другая — в индексных регистрах, регистрах указателях или в регистрах общего назначения. Отличие в том, что доступное пространство может быть больше чем 65 536 байтов, но оно всегда ограничено конкретной величиной, иначе будет невозможен контроль адресов. Также изменяется содержимое сегментных регистров (cs, ds, es, fs, gs, ss), в них кроме кода сегмента хранятся его характеристики, необходимые микропроцессору для контроля адресов.
DOS сама не использует и не поддерживает выполнение прикладных задач в защищенном режиме. Тем не менее задача может самостоятельно перевести микропроцессор в защищенный режим, а после выполнения, восстановить реальный режим перед возвратом в DOS. Однако в таком случае в ней придется выполнять много специфических действий, которые обычно возлагаются на операционные системы. Для выполнения таких действий предназначены расширители (DOS extenders), которые подключаются к прикладной задаче и создают на время ее выполнения вычислительную среду, необходимую для работы в защищенном режиме. Наиболее известными из них являются DOS4GW, DOS32A, PMODE/W.
В некоторых случаях DOS и служебные программы все же переключаются в защищенный режим для использования Extended memory. Поэтому BIOS выполняет простейшую форму поддержки работы в защищенном режиме. В данном разделе приведена ее краткая характеристика.
Менеджер Extended memoryВ состав DOS входит драйвер, хранящийся в файле himem.sys, его спецификация обязательно указывается в первой строке файла config.sys. Этот драйвер выполняет несколько функций, связанных с доступом к дополнительной памяти режиме Extended memory.
Одна из них заключается в тестировании и определении объема дополнительной памяти, сообщение о том, что himem тестирует память, можно увидеть в процессе загрузки DOS. Тестирование можно запретить, указав в config.sys ключ /testmem:off. Основное назначение himem.sys заключается в загрузке в дополнительную память резидентной части DOS.
В дополнительную память могут загружаться и драйверы различного назначения. В autoexec.bat признаком этого является команда LH, которая предшествует спецификации файла драйвера. В config.sys в таком случае вместо команды DEVICE используется DEVICEHIGH. При первоначальной установке DOS на компьютере все драйверы загружаются в обычную память. После того как файлы autoexec и config окончательно сформированы (завершено конфигурирование системы), выполняется специальная задача memmaker.exe, которая перемещает драйверы в старшую память, для увеличения свободного пространства в обычной памяти.
Драйвер himem. sys только загружает резидентные задачи в старшую память. Для выполнения таких задач надо либо переводить микропроцессор в защищенный режим, либо вызывать их так, как будто они находятся в Expanded memory. При работе в среде DOS используется второй способ.
Поддержка BIOS
После выпуска микропроцессора Intel 286 в состав BIOS была включена группа функций с названием AT services, доступных через прерывание int ish. Две из них имеют отношение к работе с Extended memory, а еще одна используется для перехода в защищенный режим. Следует отметить, что изначально они создавались для специальных целей и не рассчитаны на использование в прикладных задачах. Полное описание всех функций группы ish можно найти в Tech Help или в одном из руководств по BIOS.
Функция 87h Move Extended Memory Block перемещает блок данных из расширенной памяти в обычную, или в обратном направлении. Размер блока, выраженный в словах, указывается в регистре сх, он не может превышать воооь, т. е. 32К слов, или 64 Кбайт. В регистры es:si помещается адрес Global Descriptor Table (GOT), содержащий описание источника и приемника.
Признаком успешного выполнения пересылки является очищенный С-разряд при возврате из BIOS. При возникновении аварийной ситуации BIOS прекращает пересылку, устанавливает С-разряд при возврате в задачу, а в регистре ah указывает код ошибки (1,2, 3).
Для GDT надо зарезервировать 48 байтов памяти, 38 из которых имеют постоянное значение, а 10 заполняются задачей перед обращением к BIOS, они содержат адреса и размеры источника и приемника. Напомним, что данные читаются из источника и записываются в приемник. Таблицу можно зарезервировать, например, с помощью директив, приведенных в примере Б.9.
Пример Б.9. Структура таблицы GDI
GDtab: db 16 dup (0) 16 пустых байтов
dw ? размер источника в байтах (2*[сх]+1)
dw ? младшая часть адреса источника
db ? старшая часть адреса источника
db 93h разрешены чтение и запись
dw 0 пустое (резервное) слово
dw ? размер приемника в байтах (2*[сх]+1)
dw ? младшая часть адреса приемника
db ? старшая часть адреса приемника
db 93h разрешены чтение и запись
dw 0 пустое (резервное) слово
db 16 dup (0) 16 пустых байтов
Формат GDT должен строго соблюдаться, поэтому обратите ьнимание на то, в каких случаях в примере Б.9 употребляются директивы db, а в каких dw. Важно также правильно указывать коды доступа к источнику и приемнику, в частности, эзь разрешает чтение и запись.
Коды адресов источника и приемника 24-разрядные. Адрес
обычной памяти вычисляется по схеме, показанной в табл. Б.1. Адрес'Extended
memory может
ИЗМеНЯТЬСЯ В Пределах ОТ 10:ООООпДО OFF:OFFFFh.
Замечание
Именно эту функцию используют: DOS для загрузки своей резидентной части
в старшую память, задача memmaker.exe для перемещения драйверов в старшую
память, а также драйверы vdisk и ramdrive. Для ее использования прикладными
задачами в Extended memory надо выделить блок нужного размера. Для этого
в файле config.sys после имени драйвера himem.sys укажите ключ /intlS
= xxxx, где хххх соответствует размеру (в килобайтах) пространства ОЗУ,
которое будет доступно при работе с функцией 87п прерывания int 15h.
ФУНКЦИЯ 88h Get Extended Memory
Size возвращает В регистре ах размер
доступного пространства расширенной памяти, выраженный в килобайтах. Это
то значение, которое указано при установке драйвера himem.sys, адрес его
первого байта юооооь (1 Мбайт).
При работе с Extended memory задача, прежде всего, должна издать эту функцию для проверки наличия требуемого пространства ОЗУ. Если его недостаточно, то выполнение задачи надо прервать, поскольку она не может затребовать дополнительный объем расширенной памяти. Доступным пространством памяти задача распоряжается самостоятельно. Ни DOS, ни драйвер himem.sys не выполняют никаких функций контроля и распределения пространства Extended memory.
ФУНКЦИЯ 89h Enter Protected Moded
выполняет действия, необходимые ДЛЯ
перехода в защищенный режим и переводит микропроцессор в этот режим, т.
е. после возврата из BIOS задача уже будет выполняться в защищенном режиме.
Напомним, что временный переход в защищенный режим производится при выполнении
функции 87h перед пересылкой блока, но он не заметен для задачи. В данном
случае речь идет о полном переходе на выполнение задачи в защищенном режиме.
Перед обращением к BIOS надо сформировать специальную структуру данных (Global Descriptor Table И Interrupt Descriptor Table). Для заполнения этой структуры вы должны иметь представление о том, что такое дескриптор сегментного регистра, зачем и как надо изменять содержимое векторов прерываний и другие особенности перехода из реального режима в защищенный и обратно. Поэтому советуем отложить эксперименты с данной функцией до тех пор, пока вы не начнете изучать программирование для защищенного режима.
Виртуальный диск
Если на компьютере установлен достаточно большой объем оперативной памяти, то часть его можно использовать для размещения виртуального (или электронного) диска. Большой объем понятие условное, но при наличии 64 Мбайт памяти 32 Мбайт можно отдать под виртуальный диск, причем с явной пользой для дела. Если, например, вы измените настройки Windows так, чтобы область свопинга находилась на виртуальном диске, то система будет работать гораздо быстрее.
В состав устаревших версий DOS входил специальный драйвер vdisk.sys, предназначенный для создания и поддержки работы диска в дополнительной памяти ПК. Для чтения с диска и записи на него драйвер использовал функцию 87ь прерывания int I5h, т. е. при каждом обращении к виртуальному диску происходил временный переход в защищенный режим.
В состав современных версий DOS входит улучшенная версия драйвера ramdrive.sys. При его установке можно выбирать способ доступа к дополнительной памяти. При указании ключа /Е диск будет расположен в области Extended memory, 3 При указании КЛЮЧЗ /А — В Expanded memory. При каждом обращении к диску в первом случае будет происходить временный переход в защищенный режим, а во втором случае для пересылки данных будут использоваться функции EMS, описанные в предыдущем разделе.
Для установки виртуального диска в конец файла config.sys надо записать следующую команду:
device = c:\dos\ramdrive.sys 8192 /а.
В данном случае предполагается, что файл ramdrive.sys хранится на диске С: в каталоге DOS, а для размещения виртуального диска выделяется 8192 байта В Expanded memory.
Если ваша графическая задача будет использовать виртуальный диск для выборки и временного хранения данных, то это ускорит ее работу. Только не забывайте, что содержимое виртуального диска теряется при выключении или перезагрузке ПК.
Заключение.
При создании задач, предназначенных для выполнения в реальном режиме работы
микропроцессора, дополнительную память ПК имеет смысл использовать как
Expanded memory. Функции EMS позволяют прикладным задачам распоряжаться
пространством расширенной памяти без существенных ограничений. Работа
с Extended memory применяется, если задача выполняется в защищенном режиме.
В таком случае использовать функции EMS не целесообразно, поскольку задаче
доступно все свободное пространство оперативной памяти без каких-либо
ограничений.