На главную Назад
Добро пожаловать, уважаемый посетитель!

Ранее уже рассматривался оператор SEGMENT. Теперь есть возможность

рассмотреть его более подробно и исследовать дополнительные

возможности, которые он предоставляет.

 

До сих пор в большинстве примеров программ присутствовал только

один оператор SEGMENT. Так как программный код должен находиться в

некотором сегменте, то нужно присвоить ему имя. Учитывая, что

ассемблер должен суметь определить адрес сегмента, единственный

оператор ASSUME в прграмме идентифицирует только один сегмент

программы. В подобных случаях возможности сегментации программ

микропроцессора 8088 используются не полностью, но часто это и не

нужно. Если программа и ее данные помещаются в пределах одной и той

же адресуемой области памяти объемом 64 кбайт, то нет необходимости

использовать возможности процессора в сегментации памяти.

 

Существуют ситуации, когда в программе нужно использовать более

одного оператора SEGMENT. Одно из таких применений рассматривалоясв

гл.5 в нескольких примерах, использующих DOS. В этих примерах в

программе определялся сегмент STACK. Имя, выбранное для сегмента,

несущественно, но его тип, указанный в операторе SEGMENT, должен

быть STACK, так как файлу типа .EXE для выполнения программы

необходимо отвести стековую область. Если в программе не задать

сегмент STACK, то загрузчик DOS сохранит организацию стека в

некотором месте памяти, которое может оказаться неприемлемым. В

этом случае программа может работать недостаточно хорошо.

 

Другое назначение оператора SEGMENT - расположением данных в

определенном месте памяти. Как известно, при использовании DOS

лучше всего, если программа имеет перемещаемый программный сегмент.

В этом случае нас не заботит, куда DOS загружает программу. Но в

некоторых случаях фактическое расположение команд или данных

оказывается существенным. В этих случаях для задания местоположения

данных можно воспользоваться директивой AT оператора SEGMENT.

 

Чтобы понять значение указателя AT, рассмотрим пример. В этом

примере программа использует как Отправную точку систему BIOS, хра-

нящаяся в ПЗУ персональной ЭВМ. Хотя язык ассемблера является очень

эффективным средством программирования, с другой стороны это

довольно трудный инструмент, особенно для больших программ. Поэтому

выбор языка ассемблера обусловливается свойствами, которые делают

его выгодным для решения определенной задачи. В случае IBM PC язык

ассемблера - лучший язык для программирования функций, выполняемых

ROM BIOS. Эти функции можно охарактеризовать как управление устрой-

ствами ввода-вывода, где обычно требуется оперировать с отдельными

битами. Программирование подобных задач сводится к возможности ма-

нипулировать содержимым точно заданных ячеек памяти и портов ввода-

вывода. Язык ассемблера также используется в тех случаях, когда

необходима минимизация размера программы или максимальное быстро-

действие программы. Всем эти требования предъявляет и система ROM

BIOS.

В рассматриваемом примере используется часть BIOS. В одной из

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

BIOS. Однако в данном случае нас интересует доступ к наборам

данных, которые использует ROM BIOS. Если вы посмотрите

ассемблерный листинг для ROM BIOS (он приводится в приложении A

технического руководства по IBM PC), то увидите, что сегмент DATA

располагается в сегменте 40H или по абсолютному адресу 400H.

Приведенная на Фиг. 6.12 программа обращается в область данных ПЗУ

системы BIOS c определенной целью. В сегменте DATA имеется

переменная KB_FLAG, которая указывает текущее состояние

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

поводу клавиатуры IBM, состоит в том, что неизвестно, работаете ли

вы в верхнем регистре (CAPS LOCK) или в нижнем. Программа на Фиг.

6.12 считывает значение бита, соответствующего CAPS LOCK, и

изображает его в верхнем правом углу цветного графического дисплея.

Хотя в данной программе это не реализовано, мы будем предполагать,

что при реальном использовании этого фрагмента программы, верхний

правый угол экрана зарезервируется для описанного индикатора.

Сегмент DATA на Фиг. 6.12 показывает, как программист может

передать в программу информацию, расположенную по абсолютным адре-

сам. Оператор DATA SEGMENT использует директиву AT для того, чтобы

обеспечить безусловную привязку данного сегмента к параграфу 40H.

Microsoft (R) Macro Assembler Version 5.001/1/80 04:03:25

Фиг. 6.12 Использование сегментов Page1-1

 

PAGE,132

TITLEФиг. 6.12 Использование сегментов

 

0000 DATASEGMENT AT 40H

0017ORG17H

0017?? KB_FLAGDB?

= 0040CAPS_STATEEQU40H

0018DATAENDS

 

0000VIDEOSEGMENT AT 0B800H

009EORG158

009E?? INDICATORDB?

009FVIDEOENDS

 

0000CODESEGMENT

ASSUMECS:CODE

 

0000CAPSPROCFAR

00001E START:PUSHDS; Адрес возврата

0001B8 0000MOVAX,0

000450 PUSHAX

0005B8 ---- RMOVAX,DATA; Адрес сегмента DATA

00088E D8MOVDS,AX

ASSUMEDS:DATA

000AB8 ---- RMOVAX,VIDEO; Адрес сегмента VIDEO

000D8E C0MOVES,AX

 

Фиг. 6.12 Расположение сегмента (начало)

ASSUMEES:VIDEO

000FDISPLAY_CAPS:

000FB0 18MOVAL,18H; Символ "стрелка вверх" имеет код 18H

0011F6 06 0017 R 40TESTKB_FLAG,CAPS_STATE; Определение состояния клавиши CAPS

001675 02JNZCAPS_LOCK

0018B0 19MOVAL,19H; Символ "стрелка вниз" имеет код 19H

001ACAPS_LOCK:

001A26: A2 009E RMOVINDICATOR,AL; Вывод в верхний левый угол экрана

001EB4 06MOVAH,6; Функция ДОС ввода с клавиатуры

;и вывода на дисплей

0020B2 FFMOVDL,0FFH; Направление - ввод с клавиатуры

0022CD 21INT21H

00243C 00CMPAL,0; Проверка на наличие символа

002674 E7JZDISPLAY_CAPS; Нет символа

00283C 25CMPAL,'%'; Проверка на символ конца

002A74 08JERETURN

002CB4 02MOVAH,2; Функция вывода на дисплей

002E8A D0MOVDL,AL; Выводимый символ

0030CD 21INT21H

0032EB DBJMPDISPLAY_CAPS; Повторение

0034RETURN:

0034CB RET; Возврат в ДОС

0035CAPSENDP

0035CODEENDS

ENDSTART

 

Фиг. 6.12 Расположение сегмента (продолжение)

 

Просматривая листинг ROM BIOS, мы находим переменную KB_FLAG со

смещением 17H в сегменте DATA. Оператор ORG 17H данной программы

задает смещение этой переменной в оттранслированной программе.

Наконец, смысл оператора EQU, определяющего константу CAPS_STATE

следует непосредственно из листинга BIOS ПЗУ. Заданный этой

константой бит указывает текущее состояние переключателя CAPS LOCK.

 

В приведенной на Фиг. 6.12 программе имеется еще один оператор

SEGMENT. Он определяет сегмент VIDEO с адресом 0B800H. Это

сегментный адрес буфера для адаптера цветного- графического

дисплея. Этот адрес нужен для вывода состояния индикатора на экран

дисплея. Если мы хотим поместить символ в правый верхний угол

экрана, при условии, что строка на экране содержит 80 символов, то

смещение соответствующей ячейки должно быть равно 158 в десятичном

представлении. Программируемые характеристики оборудвания ПК

описываются в гл.8, а пока вы можете принять сказанное на веру.

 

Первая часть программы устанавливает необходимую адресацию

сегментов. Регистр DS указывает на сегмент DATA, а регистр ES - на

сегмент VIDEO. Хотя в программе эти сегменты объявлены директивой

AT абсолютными, ассемблер все же обозначает их значком "R", как

перемещаемые. Программа LINK, тем не менее, подставляет в

соответствующие поля данных правильные значения.

 

Программа тестирует переменную KB_FLAG, а ассемблер в

результате генерирует правильное смещение, равное 17H. В данном

примере символ стрелка вниз используется для обозначения обычного

режима, а стрелка вверх обозначает режим CAPS LOCK. Введенные с

клавиатуры символы считываются программой с помощью функции DOS,

выводящей эим символя на дисплей. В данном примере для выхода из

программы был произвольно выбран символ %. Если пользователь вводит

любой другой символ, то программа выводит его на дисплей и

возвращается к ожиданию ввода следующих.

 

Если ввести и запустить данную программу, то вы увидите в

верхнем правом углу цветного графического дисплея направленную вниз

или вверх стрелку. Если для цветного дисплея установлен режим 40

символов в строке, при выполнении данной программы

стрелка-индикатор будет выводиться во второй сверху строке. Если

нужно использовать эту программу с адаптером монохромного дисплея,

то измените адрес сегмента VIDEO на адрес 0B000H, соответственно

местоположению буфера монохромного дисплея.

 

При выполнении данной программы с адаптером цветного

графического дисплея в режиме 80 символов в строке вы увидите на

экране сильную помеху, "снег". Эта интерференция на экране

происходит из-за прямой передачи данных из программы в буфер

дисплея. В случае монохромного адаптера или цветного-графического

дисплея в режиме 40 символов в строке этой помехи не будет. О

причинах этого эффекта и о том, как его избежать, мы узнаем при

рассмотрении аппаратного обеспечения IBM PC.

 

Существуют и другие применения нескольких операторов SEGMENT в

одной программе. Если программе требуется область данных объмом

более 64 кбайт, то она должна организовать доступ к этим данным.

Как правило, вы воспользуетесь для обращения к этой области данных

некоторой схемой управления памятью. В такой ситуации вам будет

доступна вся эта область данных (за исключением некоторых

фиксированных участков) косвенную адресацию.

 

В качестве примера рассмотрим, как интерпретатор команд DOS

загружает программы. DOS загружает транзитную программу на границу

параграфа сразу за резидентной частью DOS. Размер этой резидентной

части может варьироваться в зависимости от числа дисководов в

системе. Кроме того, этот размер может существенно возрастать при

использовании в DOS прерывания INT 27H, которое заканчивает

выполнение программы, но оставляет ее резидентной в памяти. При

этом программный загрузчик DOS должен адресоваться к сегментному

префиксу PSP той программы, которую он загружает. Проще всего

задать эту структуру данных с помощью отдельного оператора SEGMENT.

 

На Фиг. 6.13 показано объявление сегмента, которое можно

использовать в двух различных местах. Если бы можно было посмотреть

текст исходной программы для загрузчика DOS, то мы бы обнаружили

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

.EXE, такая сегментация могла бы обеспечить доступ к переменным в

сегментном префиксе PSP. В приведенном на Фиг. 5.6 примере

программы с применением функций DOS, использовалась структура файла

типа .COM. Это позволяло нам обращаться к различным ячейкам

сегмента PSP через смещение относительно блока PSP. Задача весьма

облегчалась тем, что DOS загружала программу в тот сегмент, который

содержал PSP.

В случае .EXE-файла блок PSP находится не в том же сегменте,

что и команды программы. Так как при передаче управления программе

типа .EXE DOS устанавливает регистры DS и ES на сегмент PSP, то

имеет смысл обращаться с PSP как с отдельным сегментом. Приведенный

на Фиг. 6.13 фрагмент программы из сегмента CODE, показывает, как

можно обращаться к данным в блоке PSP.

Microsoft (R) Macro Assembler Version 5.001/1/80 04:03:31

Фиг. 6.13 Структура Программного ПрефиксаPage1-1

 

 

PAGE,132

TITLEФиг. 6.13 Структура Программного Префикса

0000PROGRAM_SEGMENT_PREFIXSEGMENT

 

00000002[INT_20DB2 DUP (?)

??

]

 

0002????MEMORY_SIZEDW?

00040005[LONG_CALLDB5 DUP (?)

??

]

 

0009???????? TERMINATE_ADDRDD?

000D???????? CTRL_BREAKDD?

005CORG05CH

005C0010[FCB1DB16 DUP (?)

??

]

 

006CORG06CH

006C0010[FCB2DB16 DUP (?)

??

]

 

0080ORG080H

00800080[DTADB128 DUP (?)

??

]

 

 

0100PROGRAM_SEGMENT_PREFIXENDS

 

0000CODESEGMENT

ASSUMECS:CODE,DS:PROGRAM_SEGMENT_PREFIX

 

0000A1 0002 RMOVAX,MEMORY_SIZE

 

0003CODEENDS

END

 

Фиг. 6.13 Префикс программного сегмента

Обратите внимание, что сегмент PSP на Фиг. 6.13 на самом деле

не содержит никаких значений для переменных. Например, мы знаем,

что в первых двух байтах PSP содержится код прерывания INT 20H.

Однако мы решили показать, что в этом месте находится поле длиной 2

байта без каких-либо указаний о содержащихся там значениях. Мы

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

сегмента ни редактор связей, ни загрузчик не попытались записать в

память каких-либо данных. Фактически, мы используем этот сегмент

как средство для объявления данных. Оператор SEGMENT объявляет

структуру данных, которую мы называем префиксом программного

сегмента. Ее местоположение в памяти не фиксировано, а определяется

одним из сегментных регистров. В нашем примере на Фиг. 6.13 это

местоположение определяется регистром DS.

 

Точно такой же способ можно использовать для обозначения любой

структуры данных, которая может быть расположена в произвольном

месте памяти микропроцессора 8088. Эта структура данных может быть,

например, болком управления для операционной системы, либо строкой

текста для текстового редактора, или даже блоком параметров

конкретной подпрограммы. Каждый объект структуры данных

располагается в своем отдельном сегменте. Таким образом, при

обращении программы к каждому элементу структуры данных сегментный

регистр указывает на начало (или на близкую к началу точку) этого

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

одним и тем же значением сегментного регистра. Для каждого элемента

всегда устанавливается свой адрес сегмента.

 

Здесь следует немного остановиться на том, какие вообще есть

методы распределения памяти микропроцессора 8088. IBM PC с

микропроцессором 8088 может адресовать до 1Мбайт оперативной

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

четырьмя сегментными регистрами программа не имеет возможности

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

 

Если все данные помещаются в 64К, то нет нужды волноваться:

просто поместите все данные в один сегмент. Если же мы полагаем,

что программе требуется область данных, превышающая 64К, то нам

придется решать задачу распределения памяти. При этом возможны две

стратегии. В обоих случаях мы будем предполагать, что вся

совокупность данных может быть разбита на меньшие блоки (такие как

отдельные переменные, строки текста, управляющие блоки или

массивы), объемом не более 64К каждый.

 

Первый метод распределения памяти применяется в ситуации, когда

вашей главной заботой является экономия памяти. При этом методе вы

располагаете объекты данных в первых же свободных участках памяти.

Программа, управляющая доступом к областям данных, должна при этом

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

них два байта используется для смещения и еще два байта для значе-

ния сегмента. Когда программе нужно полусить доступ к данным, она

извлекает адрес из области хранения адресов с помощью команд LDS

или LES. Если вам требуется еще большая экономия памяти, то вы

фактически можете хранить указатель в трехбайтовом поле. Два байта

содержат адрес сегмента данных, а оставшийся байт содержит смещение

данного объекта внутри сегмента. Начальное смещение всегда будет

иметь значение от 0 до 15, так как значение сегмента всегда кратно

16.

 

Хотя описанный метод наиболее эффективен в отношении объема

памяти, занимаемой данными, у него имеются пара недостатков.

Максимальная длина объекта данных немного меньше, чем 64 кбайт. В

рамках данной стратегии наихудшим окажется случай, когда абсолютный

адрес объекта данных кончается на 0FH. Так как максимальное

значение смещения в любом сегменте равно 0FFFFH, то максимальная

длина переменной будет 64К - 15, или 65521 байт. Второй недостаток

этого метода связан с затратами памяти для хранения указателей к

объектам данных. При большом числе объектов для хранения наряду с

ними всех четырехбайтовых (или трехбайтовых) указателей потребуется

много памяти.

 

Примером использования описанного метода распределения памяти

может служить блок управления файлом FCB. В последнем примере

работающей с DOS программы мы располагали блок FCB в произвольном

месте программы. Какого-либо выравнивания местоположения этой

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

выполнения файловой операции программе понадобился четырехбайтовый

указатель. Идентификация блока FCB для DOS осуществлялось парой

регистров DS:DX.

 

При втором методе распределиня памяти все объекты данных

располагаются на границах параграфов. Это сразу же упрощает

указатель, определяющий объект данных. Этот указатель состоит

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

с этими данными. Так как распределение памяти всегда начинается с

границы параграфа, то начальное смещение данных будет всегда равно

нулю. Однако при таком методе, расходуется дополнительная память.

Каждый раз, когда вы располагаете в памяти новый объект, возможна

потеря до 15 байт памяти. Это происходит, если последний байт

предыдущего объекта попадает точно на границу параграфа. Так как

граница следующего параграфа будет через 15 байт, то эти 15 байт в

промежутке теряются. Кроме того, при такой стратегии минимальная

длина объекта равна 16 байт. Даже если данные будут занимать меньше

места, оставшиеся байты все равно не могут быть использованы.

 

Как было отмечено, второй метод распределения памяти

используется загрузчиком DOS при запуске программ. DOS загружает

программу на ближайшую границу параграфа. Так как DOS исходит из

того, что в памяти располагается мало больших по размерам объектов,

то при данном методе издержки памяти будут невелики. Однако, если

ваша прикладная программа использует много небольших объектов, то

выравнивание по параграфам может оказаться слишком дорогим.

 

Второй метод распределения памяти, использующий выравнивание по

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

SEGMENT. Если же хотите использовать первый метод распределения

памяти, то вам потребуется другой способ определения структур

данных. Такой способ объявления данных как раз рассматривается в

следующем разделе.


 

Mail.ru