Оперативная память

         

Адрес свободного пространства



Адрес свободного пространства

Для определения адреса свободного пространства надо знать, где заканчивается последний сегмент задачи. Для этого, в свою очередь, необходимо выяснить, какой из сегментов, описанных в исходном тексте программы, окажется последним в теле задачи.

Иногда рекомендуют включать в исходный текст задачи пустой сегмент, имя которого начинается на букву 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 нужно потому, что свободный сегмент должен начинаться после стекового, не перекрывая его.

Замечание
Замечание
Какой бы сегмент вы не выбрали в качестве точки отсчета, советуем при построении задачи обязательно задать файл листинга (карту памяти) и убедиться в том, что выбранный сегмент расположен в теле задачи последним.



Блок задачи



Блок задачи

Пространство, выделенное DOS для выполнения задачи, в технической документации принято называть блоком задачи. Он состоит из трех основных частей. Первые юоь (256) байтов блока занимает специальная структура данных — префикс программного сегмента (PSP). В нем хранятся величины, которые могут быть нужны при выполнении задачи. Сразу после PSP в памяти расположены сегменты, описанные в исходном тексте программы.

Как уже говорилось, порядок расположения сегментов в блоке задачи зависит от их имен и от наличия директив .Alpha, .seq или Dosseg, которые могут указываться в начале исходного текста программы. После последнего сегмента в блоке задачи находится свободное пространство, которым задача может распоряжаться по своему усмотрению (по усмотрению программиста). Но для доступа к этому пространству надо знать его размер и адрес начала, точнее, сегмент, с которого оно начинается. При составлении программы эти величины неизвестны, поскольку их формирует DOS, исходя из реально имеющихся ресурсов на момент загрузки задачи для выполнения.





Динамическое управление памятью



Динамическое управление памятью

Перед началом выполнения задачи DOS выделяет для нее всю свободную часть пространства обычной памяти. Задача может произвольно распоряжаться выделенным пространством ОЗУ, но она не должна выходить за его пределы, т. к. это приведет к непредсказуемым результатам. В данном разделе будет описан один из способов работы задачи с обычной памятью.

Функции DOS



Функции DOS

По запросам прикладных программ DOS выполняет несколько функций, связанных с распределением оперативной памяти. Нас будут интересовать только три из них.

Все обращения к DOS происходят через прерывание int 2ih, при этом код запрашиваемой функции указывается в регистре ah, а входные и выходные параметры располагаются в регистрах общего назначения и иногда в сегментных регистрах ds или es.



Функция 4Ah shrink or Expand a Memory Block



Функция 4Ah shrink or Expand a Memory Block

урезает или расширяет суще-ствующий блок памяти. Код сегмента, начиная с которого расположен изменяемый блок, указывается в регистре es, а новый размер блока — в регистре bx. Если функция выполнена успешно, т. е. размер указанного блока изменен, то при возврате из DOS признак переполнения отсутствует. Если он установлен, то блок не может быть расширен или сокращен. В этом случае в регистре bx возвращается наибольший (последний) доступный блок памяти.

Функция 40h Get EMM status



Функция 40h Get EMM status

предназначена для получения информации о состоянии драйвера. Входные параметры отсутствуют. Результат проверки возвращается в регистре ah. Если он очищен, то все в порядке. Код 8lh означает неисправность расширенной памяти, других кодов ошибки в данном случае не должно быть. В случае ошибки надо прекратить выполнение задачи и проверить состояние системного программного обеспечения.

Функция 41h Get Physical Segment Address of EMS Frame



Функция 41h Get Physical Segment Address of EMS Frame

возвращает зна-
чение сегмента, используемого для доступа к расширенной памяти. Входные параметры отсутствуют, код сегмента указан в регистре bx. Его надо сохранить и при чтении или записи данных помещать в один из сегментных регистров. Код сегмента зависит от конфигурации оборудования, установленного на компьютере, в процессе выполнения задачи он не может и не должен изменяться. Размер сегмента 64 Кбайт, он делится на 4 страницы по 16 Кбайт каждая. Адреса (смещения) страниц равны 0, 4000h, s000h и осоооь.

Функция 42h Get EMS Memory size



Функция 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



ФуНКЦИЯ 43h Allocate Memory and Open EMM handle

запрашивается для выделения требуемого пространства расширенной памяти. Входным параметром является необходимое количество страниц, указываемое в регистре bx. При успешном выполнении функции в регистре dx возвращается EMM Handle — идентификатор выделенного блока, который используется при запросах других функций драйвера. Его обязательно надо сохранить.

Два специальных кода ошибок означают, что запрошено слишком много страниц. Код 87h выдается, если общее пространство expanded memory меньше запрошенного размера, а код 88h означает, что недостаточно свободного пространства памяти. Если вы предварительно проверяли свободную память с помощью функции 42h, то эти две ошибки не должны возникать, в противном случае надо прекратить выполнение задачи.
Выделенное пространство закреплено за задачей, но перед записью или чтением данных надо отображать его конкретную часть на рабочий сегмент, значение которого возвращает функция 4ih. Как уже говорилось ранее, в этом сегменте размещается 4 страницы. Для отображения 16 Кбайт ОЗУ на одну из этих страниц предназначена функция 44h.



Функция 44h Map Memory



Функция 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



Функция 45h Release Memory

освобождает пространство памяти, выделенное для задачи функцией 43п. Входным параметром является идентификатор блока, указываемый в регистре dx. Отказ от выполнения функции может быть связан только с его неправильным заданием.

Важно
Важно
Данная функция обязательно должна вызываться перед завершением выполнения задачи, в противном случае выделенная для завершенной задачи память будет недоступна для других задач вплоть до перезагрузки системы.



Функция 46h Get EMM version number



Функция 46h Get EMM version number

позволяет определить версию менеджера EMM. Входные параметры отсутствуют, номер версии в двоично-десятичном коде возвращается в регистре ai. Например, версии 6.2 соответствует КОД 62п.
Таким образом, при использовании стандартного набора функций дра EMM задача получает возможность резервирования нужного простран в дополнительной памяти ПК и размещения в нем данных.

Функция 48h Allocate Memory



Функция 48h Allocate Memory

выделяет запрашиваемое пространство памяти. Требуемый размер памяти, выраженный в параграфах, указывается в регистре bx. Если такое пространство существует, то при возврате из DOS признак переполнения отсутствует, содержимое bx не изменяется, а в ах находится код сегмента, с которого начинается выделенное пространство памяти. Если свободное пространство нужного размера отсутствует, то при возврате из DOS вырабатывается признак переполнения (устанавливается в 1 С-разряд регистра флагов).

Функция 49h Free Allocated Memory Block



Функция 49h Free Allocated Memory Block

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

Функция 87h Move Extended Memory Block



Функция 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.



ФУНКЦИЯ 88h Get Extended Memory Size



ФУНКЦИЯ 88h Get Extended Memory Size

возвращает В регистре ах размер
доступного пространства расширенной памяти, выраженный в килобайтах. Это то значение, которое указано при установке драйвера himem.sys, адрес его первого байта юооооь (1 Мбайт).

При работе с Extended memory задача, прежде всего, должна издать эту функцию для проверки наличия требуемого пространства ОЗУ. Если его недостаточно, то выполнение задачи надо прервать, поскольку она не может затребовать дополнительный объем расширенной памяти. Доступным пространством памяти задача распоряжается самостоятельно. Ни DOS, ни драйвер himem.sys не выполняют никаких функций контроля и распределения пространства Extended memory.



ФУНКЦИЯ 89h Enter Protected Moded



ФУНКЦИЯ 89h Enter Protected Moded

выполняет действия, необходимые ДЛЯ
перехода в защищенный режим и переводит микропроцессор в этот режим, т. е. после возврата из BIOS задача уже будет выполняться в защищенном режиме. Напомним, что временный переход в защищенный режим производится при выполнении функции 87h перед пересылкой блока, но он не заметен для задачи. В данном случае речь идет о полном переходе на выполнение задачи в защищенном режиме.

Перед обращением к BIOS надо сформировать специальную структуру данных (Global Descriptor Table И Interrupt Descriptor Table). Для заполнения этой структуры вы должны иметь представление о том, что такое дескриптор сегментного регистра, зачем и как надо изменять содержимое векторов прерываний и другие особенности перехода из реального режима в защищенный и обратно. Поэтому советуем отложить эксперименты с данной функцией до тех пор, пока вы не начнете изучать программирование для защищенного режима.



Использование функций DOS



Использование функций DOS

Для того чтобы DOS могла распределять оперативную память, ей надо вернуть все свободное пространство, расположенное за пределами сегментов, образующих тело задачи. Иначе говоря, надо сократить размер блока выделенного для выполнения задачи до ее реальных размеров.

Использование функций драйвера



Использование функций драйвера

Для корректной работы с расширенной памятью задачи должны выполнять определенную последовательность действий, а именно: 1. Проверить наличие менеджера, поддерживающего функции EMS. 2. Получить код сегмента, на который отображается расширенная память. 3. Определить наличие требуемого пространства расширенной памяти. 4. Отобразить часть пространства ОЗУ на физические страницы. 5. В пределах сегмента работать с расширенной памятью как с обычной. 6. При достижении границ сегмента повторять пункты 4 и 5. 7. Перед завершением задачи вернуть память менеджеру.

Выполнение первого пункта списка является скорее данью традиции, чем необходимостью, поскольку ПК на базе Intel 386 и всех последующих моделей обязательно имеют оборудование для доступа к расширенной памяти. Для проверки можно, например, с помощью функции 40h определить статус, а с помощью функции 4бп — номер версии драйвера и убедиться, что он не меньше чем 4.0. Остальные пункты списка обязательно должны выполняться, причем в той последовательности, в которой они перечислены.



Контроль выполнения запросов



Контроль выполнения запросов

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

Результат исполнения функции 43h надо обязательно проверять. Признак переполнения (c=i) при возврате из DOS означает, что в памяти нет места для размещения блока нужного размера (если исключены ошибки в тексте программы). Что делать в таких случаях?

Выдача аварийного сообщения и прекращение выполнения задачи является самым плохим способом реагирования на недостаток памяти. Всегда можно найти вариант продолжения нормального выполнения задачи. Если освободить место для блока в основной памяти невозможно, то его надо разместить в расширенной памяти, тем более что это никак не скажется на дальнейшей работе с блоком.



Менеджер Extended memory



Менеджер 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 используется второй способ.



Менеджер расширенной памяти



Менеджер расширенной памяти

(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.



Несколько блоков в расширенной памяти



Несколько блоков в расширенной памяти

При работе с обычной памятью каждому блоку соответствует свой уникальный код сегмента. В отличие от обычной, при работе с расширенной памятью доступ ко всем зарезервированным задачей блокам осуществляется через один и тот же сегмент 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, нам важно было напомнить о ее существовании.



Обычная память (Conventional Memory)



Обычная память (Conventional Memory)

Conventional — общепринятый, обычный. Так называют младшую часть ОЗУ, занимающую первые 640 Кбайт пространства адресов и имеющую большое значение в обеспечении работоспособности ПК. В ней хранятся векторы прерываний, данные, используемые BIOS и DOS, рабочая область и резидентная часть DOS, драйверы внешних устройств, а также другие программы и данные, необходимые для поддержки нормальной работы ПК. Наконец, DOS загружает прикладные задачи только в обычную память. Если при отсутствии или неисправности дополнительной памяти ПК может выполнять свои функции, то при неисправности младшей части памяти его работа просто невозможна.
 


Оперативная память



Оперативная память

Оперативная память (ОЗУ, RAM) является одним из важнейших ресурсов персонального компьютера. В англоязычной технической литературе вы можете встретить три термина, характеризующие тип памяти, а именно: conventional memory, extended memory И expanded memory. У современных ПК они относятся к разным частям одного физического устройства и являются характеристиками способа доступа к этим частям. Различие способов доступа к отдельным частям памяти является специфической особенностью (родимым пятном) и одним из существенных недостатков семейства IBM PC. В чем именно оно заключается, описано в данном приложении.

Предельно допустимый объем памяти зависит от системной (материнской) платы ПК, точнее от набора микросхем (chip set), на базе которого она собрана. Реально существующий объем выводится на экран монитора в процессе загрузки ПК, когда BIOS проверяет (тестирует) память. В процессе работы ПК можно с помощью специальных задач узнать объем и текущее распределение пространства ОЗУ. Например, в состав DOS входит задача mem.exe, а в состав Norton Commander— sysinfo.exe. Прикладные задачи, нуждающиеся в больших объемах памяти, должны самостоятельно определять размер ее доступного пространства.

 


Определение размера задачи



Определение размера задачи

Будем предполагать, что стековый сегмент расположен в теле задачи последним. В таком случае нас интересует расстояние от начала PSP до конца стекового сегмента, выраженное в параграфах. Напомним, что код сегмента, содержащего PSP, находится в регистре es, код стекового сегмента — в ss, а если стек еще не использовался, то его размер в байтах содержится в регистре sp.

Размер тела задачи, выраженный в параграфах, вычисляется так:
tasksize = [ss] - [es] + [sp] /16

В этой формуле квадратные скобки указывают на то, что при вычислении используется содержимое регистров ss, es и sp. При. программировании деление [sp] на 16 заменяется сдвигом на 4 разряда вправо. Для того чтобы не потерять один параграф, размер стека должен быть кратен 16-ти, в противном случае его надо округлить в сторону увеличения.

Зная реальный размер задачи, можно запросить у DOS сокращение ее блока. В результате появится свободное пространство памяти, которое можно использовать по запросам задачи.



Освобождение блоков



Освобождение блоков

Освободившийся блок возвращается DOS по запросу 49h (Free Allocated Memory Block), но с этим действием можно не спешить. В среде DOS задача, как правило, выполняется в монопольном режиме, и кроме нее претендентов на свободную память нет. Перед завершением выполнения задачи освобождать использованные блоки не обязательно. После завершения задачи DOS обязательно освобождает все пространство памяти, которое было за ней закреплено.



Освобождение памяти



Освобождение памяти

Для освобождения выделенного задаче блока выполняются следующие действия:

mov dx, Ehndlr ; dx = идентификатор блока
mov ax, 4500h ; ах = код функции освобождения блока
int 67h ; обращение к драйверу

При исполнении запроса драйвер открепляет пространство блока от задачи, и оно становится общедоступным.

Как уже говорилось, перед завершением задачи освобождение выделенной для нее расширенной памяти обязательно. В противном случае это пространство окажется недоступным для других претендентов и будет освобождено только при выключении или перезагрузке компьютера.

Освобождаются все блоки, затребованные задачей. Для этого описанные команды повторяются, с указанием в регистре dx идентификаторов разных блоков, закрепленных за задачей.



Отображение страниц



Отображение страниц

Для отображения логической страницы блока на одну из физических страниц сегмента EMS запрашивается функция 44h. Мы рассмотрим универсальный вариант подпрограммы отображения страниц.

В зависимости от логики выполняемых в задаче действий может потребоваться отображение от одной до четырех страниц. Поэтому подпрограмма, текст которой приведен в примере Б.6, имеет две точки входа.

При вызове call mapseg отображаются четыре подряд расположенные страницы. Предварительно в регистре bx указывается номер первой логической страницы, а в регистре dx — идентификатор блока, которому принадлежат отображаемые страницы. Вариант вызова подпрограммы mapseg показан в примере Б.7 (см. раздел).

При вызове call mapip дополнительно (кроме заполнения регистров bx и dx) в регистре ах указывается номер первой физической страницы, а в регистре сх — количество страниц.

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



Поддержка BIOS



Поддержка BIOS

После выпуска микропроцессора Intel 286 в состав BIOS была включена группа функций с названием AT services, доступных через прерывание int ish. Две из них имеют отношение к работе с Extended memory, а еще одна используется для перехода в защищенный режим. Следует отметить, что изначально они создавались для специальных целей и не рассчитаны на использование в прикладных задачах. Полное описание всех функций группы ish можно найти в Tech Help или в одном из руководств по BIOS.



Подпрограмма пересыки блока



Подпрограмма пересыки блока

Программная реализация пересылки показана в примере Б.7. Перед вызовом подпрограммы в регистрах es и fs указываются коды видеобуфера и сегмента EMS, в примере Б.8 показано, как это делается.

Предполагается, что в разделе данных задачи описана переменная bisize, а в расширенной памяти зарезервирован блок, размер которого не меньше размера отображаемой части видеопамяти.



Б 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 Специальные директивы описания основных сегментов |



Пример Б.2. Специальные директивы описания основных сегментов |

Dosseg ; задает расположение сегментов
.Model small ; описание модели памяти
.Stack 100h ; описание стекового сегмента
.Data ; начало сегмента данных
; В этом сегменте располагается описание
; используемых в программе данных
^ , . Code ; начало сегмента кодов
; В этом сегменте располагается текст основной
; программы и входящих в нее подпрограмм
END ; конец текста программы

В примере Б.2 директивы .stack, .Data, .code указывают начало соответствующего сегмента, директива Ends при этом не нужна. Признаком конца текущего сегмента является начало следующего или директива END, завершающая текст программы, поэтому вложение сегментов исключено. В тексте программы, кроме указанных сегментов, могут использоваться другие, описанные обычным способом.

Перед специальными директивами имена сегментов не указываются, но они могут понадобиться для использования в программе. Макроассемблер присваивает стековому сегменту имя stack, сегменту данных — имя _data (нижняя черта обязательна). Имя сегмента кодов зависит от модели памяти, например, при моделях small и compact оно будет _text. Присвоенные сегментам имена можно посмотреть в карте памяти, которую формирует компоновщик при указании файла с типом тар (см. выше).

Для управления последовательностью расположения сегментов в примере Б.2 использована директива Dosseg, которая размещает сегменты в соответствии с соглашениями DOS. Это значит, что первым в теле задачи будет расположен кодовый, а последним — стековый сегмент. Если сегментов всего три, то данные будут размещены после кодов, перед областью стека. Дополнительные сегменты располагаются между кодами и данными.



Б 4 Получение от DOS значений переменных SwpSeg и GenSeg



Пример Б.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, поэтому указанная проверка выполняется иначе.



Б 5 Создание в расширенной памяти блока размером 1 Мбайт



Пример Б. 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. В этом заключается одно из существенных отличий доступа к блокам расширенной памяти от доступа к блокам обычной памяти.



Б 6 Подпрограмма отображения 4х страниц сегмента EMS



Пример Б.6. Подпрограмма отображения 4-х страниц сегмента EMS

mapseg: mov ex, 04 ; количество повторов цикла
xor al, al нулевая физическая страница
maplp : reov ah, 44h код функции отображения памяти
int 67h обращение к драйверу
or ah, ah отображение выполнено?
jne @F -> нет, переход на локальную метку
inc ax следующая физическая страница
inc bx следующая логическая страница
loop maplp управление повторами цикла
clc очистка С-разряда
ret возврат на основную программу
@@: stc установка С-разряда
ret возврат на основную программу

При входе в точку mapseg задается 4 повтора цикла отображения начиная с нулевой физической страницы. После этого выполняется цикл отображения, имеющий метку mapip (вторая точка входа).

Если отображение происходит без ошибок, то номера физической и логической страницы увеличиваются на 1 и цикл повторяется до тех пор, пока не будет отображено заданное количество страниц.

При возникновении ошибки ее код возвращается в регистре ah. В этом случае выполнение цикла прекращается и происходит переход на локальную метку, что приведет к возврату из подпрограммы с установленным признаком переполнения.



Б 7 Пересылка содержимого рабочей области экрана



Пример Б.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 и сохраненных регистров, восстанавливается исходное окно видеопамяти и происходит возврат на вызывающий модуль.



Б 9 Структура таблицы GDI



Пример Б.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.



Для выполнения задачи не хватает



Пример Б.З. Вычисление значений переменных 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 для выполнения действий, связанных с распределением простраж тва оперативной памяти.

 


Работа с именами сегментов



Работа с именами сегментов

Макроассемблер рассматривает имя сегмента как константу (а не как переменную), ее прямая пересылка в сегментный регистр невозможна, поэтому приходится использовать регистр-посредник. Например, в большинстве случаев в начале выполнения задачи надо определить содержимое сегментного регистра 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 ; конец сегмента

В приведенном примере сегмент становится частью тела задачи и увеличивает его размер на соответствующее количество байтов. Поэтому такой способ распределения пространства ОЗУ является вспомогательным и применять его можно только в порядке исключения.

Более гибкий и универсальный вариант заключается в том, что для размещения буферов различного назначения пространство оперативной памяти не резервируется в исходном тексте программы, а распределяется в процессе выполнения задачи.

 


Работа с расширенной памятью



Работа с расширенной памятью

После резервирования блока и отображения части или всех его логических страниц с расширенной памятью могут работать все без исключения команды микропроцессора. В данном разделе описан пример пересылки большого массива данных и обсуждается возможность одновременного использования двух блоков, расположенных в расширенной памяти.

Расположение адресов в регистрах



Расположение адресов в регистрах

Микропроцессор выбирает части адреса из двух разных регистров. Коды сегментов хранятся в специальных сегментных регистрах, которые предназначены только для этих целей. Начиная с модели 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 используют строковые операции, а в остальных случаях они не имеют фиксированного назначения.

 


Расширенная память (Expanded Memory)



Расширенная память (Expanded Memory)

Очень скоро после начала массового производства IBM PC XT стало ясно, что заложенная в нем возможность работы с адресным пространством в 1 Мбайт является серьезным препятствием для создания все более усложняющегося программного обеспечения. К этому времени технические средства уже позволяли преодолеть барьер 1 Мбайт, но для их использования надо было радикально изменить некоторые фундаментальные концепции, положенные в основу DOS, чего явно не хотели делать ни IBM, ни Microsoft. В то время многие фирмы предлагали различные компромиссные решения в виде аппаратных и программных средств, позволяющих работать с дополнительной памятью без изменения структуры DOS и BIOS. В конечном итоге только одно из них стало стандартом при работе с дополнительной памятью, не потому, что оно было самым лучшим, а потому что его разработку, массовый выпуск и дальнейшую поддержку взяли на себя такие крупные корпорации, как Lotus, Intel и Microsoft.

Расширенная память (Extended Memory)



Расширенная память (Extended Memory)

Термин Extended memory относится к тому же пространству памяти, которое описано в предыдущем разделе, но обозначает другой способ доступа, а именно, непосредственную работу с его адресами. Такой способ доступа возможен при работе микропроцессора в защищенном режиме (protected-address mode). Напомним, что свое название режим получил потому, что микропроцессор контролирует адреса при любых обращениях к внешним устройствам, в том числе и к оперативной памяти.

Начиная с модели 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 выполняет простейшую форму поддержки работы в защищенном режиме. В данном разделе приведена ее краткая характеристика.



Размер свободного пространства



Размер свободного пространства

В PSP слово со смещением 2 содержит последний доступный для задачи адрес оперативной памяти, выраженный в параграфах, т. е. это код последнего доступного сегмента. После загрузки задачи DOS помещает код сегмента, содержащего PSP в регистры es и ds.

Содержимое регистра ds изменяется первыми командами задачи, а ев можно использовать для чтения указанной величины. Если ее уменьшить на freeseg, то получится размер свободного пространства, выраженный в параграфах. Теперь надо проверить, достаточно ли выделенное пространство для нужд задачи, и если да, то его можно использовать.



Резервирование блока



Резервирование блока

Предположим, что для выполнения задачи требуется непрерывное пространство расширенной памяти (блок) размером в 1 Мбайт. Для резервирования такого блока задача должна запросить у драйвера исполнение функции 43h, указав в регистре bx 64 страницы (размер страницы составляет 16 Кбайт). Если это первый запрос, обращенный к драйверу, то предварительно надо выполнить функцию 4 in для определения состояния драйвера и получения кода сегмента для доступа к памяти.

Фрагмент программы, выполняющий резервирование блока размером в 1 Мбайт, приведен в примере Б.5. Его надо включить в ту часть задачи, где выполняются подготовительные действия. Например, сразу после команд, приведенных в примерах Б.З или Б.4.



Сегменты оперативной памяти



Сегменты оперативной памяти

Первые компьютеры IBM PC имели 16-разрядную шину данных, что соответствовало пространству адресов 64 Кбайт. У IBM PC/XT, собранного на базе процессора Intel 8086, адресная шина увеличилась на 4 разряда и появилась возможность работы с пространством адресов размером в 1 Мбайт. Однако основные регистры процессора остались 16-разрядными, поэтому 20-разрядные адреса в них просто не помещались. Для работы с пространством адресов в 1 Мбайт потребовалось создание специального механизма.

Разработчики выбрали не самое лучшее из возможных решений, ограничив предельный размер пространства адресов значением 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, ее дополнений, расположенных на платах контроллеров внешних устройств, адресов, через которые происходит доступ к этим устройствам, и т. п.

Сохранение и восстановление рабочей области экрана



Сохранение и восстановление рабочей области экрана

В примере Б. 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.



Специальные переменные



Специальные переменные

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

Если задача запрашивает у драйвера только один блок большого размера, то для работы с ним нужны следующие переменные:

EBuff dw 0 ; код сегмента для доступа к расширенной памяти
Ehndlr dw 0 ; идентификатор блока выделенного для задачи
Curpg db 0 ; номер текущей логической страницы блока
Lastpg db 0 ; номер последней логической страницы блока

Номера текущей и последней страниц нужны при работе с большими блоками, размер которых превышает 65 536 байтов (4 страницы). При работе с ними приходится многократно отображать его логические страницы на физические. Если же размер блока не превышает стандартного сегмента ОЗУ, то он отображается на физические страницы только один раз и при дальнейшей работе номера страниц не нужны.
При работе с несколькими блоками в разделе данных задачи можно организовать простую таблицу, состоящую из строк фиксированного размера, содержащих характеристики каждого блока. В таком случае характеристики выбираются из таблицы по порядковому номеру блока, и сокращается количество имен переменных.



Спецификация расширенной памяти



Спецификация расширенной памяти

Спецификация расширенной памяти (Expanded Memory Specification ИЛИ
EMS) содержит перечень требований к оборудованию, предназначенному для работы с дополнительным пространством адресов ОЗУ, и совокупность правил, которых надо придерживаться при работе с этим оборудованием. Если в ПК используется микропроцессор Intel 386 и выше, то необходимое оборудование заведомо существует и нет необходимости в приобретении и установке каких-либо дополнительных устройств. В данном разделе нас будет интересовать возможность его использования.
Основная концепция EMS. Дополнительная память занимает физические адреса от дооооь и далее вплоть до верхнего предела. Если на ПК установлено 16 Мбайт ОЗУ, то последнему байту соответствует физический адрес OFFFFFFh. Напомним, что для DOS и BIOS последний физический адрес ОЗУ равен 9FFFFh, а адреса от одооооь до OFFFFFh к оперативной памяти не относятся. Таким образом, при работе в DOS прямой доступ к расширенной памяти невозможен и применяется косвенный доступ.

В доступном для DOS пространстве адресов выделяется окно (сегмент), на которое отображается фрагмент расширенной памяти. Отображение окна на пространство физических адресов и его перемещение выполняет специальная программная компонента (менеджер). Ее присутствие является необходимым условием для работы с расширенной памятью.

Системные и прикладные задачи получают доступ к расширенной памяти, издавая специальные команды, которые исполняет менеджер. В соответствии с идеологией DOS и BIOS они оформляются в виде программных запросов.

Впервые EMS была опубликована сразу в виде версии 3.0 в 1985 году, в ее подготовке приняли участие только Lotus и Intel. Примерно в это же время Microsoft в связи с разработкой Windows заинтересовалась EMS и принимала активное участие в разработке ее последующих версий. В 1987 году была опубликована версия 4.0, которая предусматривала возможность размещения и выполнения нескольких задач в расширенной памяти. Автору неизвестно существование более поздних версий EMS, но для изложения материала это не существенно, т. к. соблюдается строгая преемственность версий и то, что описано в данном разделе может использоваться в ваших программах.



Способ пересылки большого блока



Способ пересылки большого блока

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

Размер рабочего пространства видеопамяти зависит от установленного видеорежима, он вычисляется умножением размера строки в байтах на количество строк на экране (bperiine versize). Для временного хранения произведения нужна переменная, состоящая из двух слов (но не одно двойное слово), назовем ее bisize. В первом слове будет храниться старшая часть произведения, а во втором слове — младшая. Старшая часть указывает количество полных окон видеопамяти, а младшая часть — количество байтов в последнем окне, оно может быть равно нулю.

Если в результате умножения в первом слове bisize окажется число N, а во втором bisize+2 число м, то размер отображаемой части видеопамяти составляет N* 6553б + м байтов, т. е. надо переслать N полных сегментов и еше м байтов. Поэтому перед пересылкой очередного фрагмента данных необходимо уточнять его размер.

Кроме того, перед пересылкой каждого фрагмента производится отображение очередной группы логических страниц блока, выделенного в расширенной памяти на физические с помощью подпрограммы mapseg, приведенной в примере Б.6 Для исключения лишних проверок можно каждый раз отображать по 4 страницы. В процессе отображения mapseg изменяет номера логических страниц (содержимое регистра bx), поэтому при работе с ней достаточно задать номер исходной страницы и в дальнейшем просто не изменять текущее содержимое регистра bx.

Для упрощения и ускорения пересылки нужен микропрограммный цикл на основе строковой операции movs, работающей с двойными словами.



Виртуальный диск



Виртуальный диск

Если на компьютере установлен достаточно большой объем оперативной памяти, то часть его можно использовать для размещения виртуального (или электронного) диска. Большой объем понятие условное, но при наличии 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.

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



Вычисление SwpSeg и GenSeg



Вычисление SwpSeg и GenSeg

В приведенных в основной части книги примерах использовались буфер обмена и буфер общего назначения. Мы предполагали, что код сегмента, содержащего буфер обмена, хранится в переменной SwpSeg, а буфер общего назначения — в переменной GenSeg. Покажем, как можно сформировать значения этих переменных после вычисления размера и адреса начала свободного пространства описанным выше способом.

В примере Б.З приведен фрагмент начала программы, в котором выполняются все необходимые вычисления. Для описания сегментов в нем использованы обычные директивы (см. пример Б.1).




Вычисление SwpSeg и GenSeg

В примере Б.4 показан фрагмент начала текста программы, в котором производится сокращение размера блока задачи и выделение двух блоков для размещения буферов обмена и общего назначения. Для описания основных сегментов в примере использованы специальные директивы (см. пример Б.2).



Мы закончили описание основных видов



Заключение.
Мы закончили описание основных видов оперативной памяти, поэтому можно подвести общий итог. При работе в среде DOS для программ доступна как основная, так и дополнительная память. Реальные размеры последней существенно больше размеров первой, поэтому при разработке задач следует отдавать предпочтение расположению больших блоков в расширенной памяти, а блоки небольшого размера размещать в обычной памяти. Кроме того, следует избегать одновременного использования нескольких блоков, расположенных в расширенной памяти, т. к. это связано с ограничением доступного пространства адресов.


Заключение.
При создании задач, предназначенных для выполнения в реальном режиме работы микропроцессора, дополнительную память ПК имеет смысл использовать как Expanded memory. Функции EMS позволяют прикладным задачам распоряжаться пространством расширенной памяти без существенных ограничений. Работа с Extended memory применяется, если задача выполняется в защищенном режиме. В таком случае использовать функции EMS не целесообразно, поскольку задаче доступно все свободное пространство оперативной памяти без каких-либо ограничений.



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