Assembler для начинающих
Биты байты и слова
Биты, байты и слова
Мы назвали "битом" двоичную цифру, еденичное значение 0 или 1. Для
удобства введем специальные названия для некоторых последо-
вательностей битов. Группу из 8 бит принято называть байтом. Во
всей документации IBM и в этой книге о любых 8 битах информации
говорится как о байте. Байт заслужил свое собственное имя по
нескольким причинам. Элементарная ячейка памяти имеет длину 8 бит.
При каждом обращении к паамяти IBM PC для процессора запрашивыается
ровно 8 бит информации. Как мы увидим позднее, отдельные команды
8088 могут производить арифметические и логические опреации над
группами в 8 бит. Байт - наименьшая еденица информации, с которой
8088 может манипулировать непосредственно. 8088 может одной
операцией сложить два 8-битовых числа, но не может этого проделать
с 4-битовыми. Кроме того IBM PC использует байт для представления
одного символа. Используя один байт можно представить 256 (2**8)
отдельных элементов, таких, например, как графические символы. В
следующем пункте мы рассмотрим набор символов IBM PC.
Поскольку байт является элементом памяти, мы должны иметь сред-
ство определения в ней отдельных байтов. Задача ассемблера
фактически и будет состоять в определении содержимого памяти для
выполнения программы. В основном исходный текст ассемблера состоит
из выполняемых инструкций. Но для помещения определенного значения
в байт памяти ассемблер располагает специальным механизмом -
определением байта (dtfine byte) или псевдокомандой DB. DB не
является командой 8088. Это команда ассемблеру поместить в память
определенные значения. Псевдокоманда
DB 23
дает ассемблеру задание сохранить десятичное значение 23 в текущий
байт памяти. А оператор
DB 1,2,3,4
сохраняет значения от 1 до 4 по четырем последовательным адресам в
памяти.
В программах на языке ассемблера оператор DB применяют для
определения областей памяти. В предыдущих примерах мы размещали в
памяти определенные значения. Это может быть поисковая таблица или
информация для перекодировки чисел. Мы составим несколько
примеров, в которых используется определенная подобным образом
информация. Кроме того встречаются ситуации, когда программе
требуется место в памяти для сохранения данных в процессе
исполнения. Во время ассемблирования программы содержимое этого
участка памяти неизвестно: собственно, это содержимое будет
переменным во время исполнения программы. Инструкция
DB ?
сообщает ассемблеру о необходимости выделить один байт памяти, не
изменяя его содержимое. В Этом байте может оказаться любое
случайное число, которое будет там оставаться пока какая-либо
команда не поместит в него определенное значение.
Нам может потребоваться выделить и большое количество байтов,
например, чтобы оставить область памяти для массива. Мы можем это
сделать так:
DB 25 DUP(?)
Этой инструкцией выделяется 25 байт памяти. Ключевое слово DUP в
этой псевдокоманде означает повторить (duplicate). Число 25
указывает, сколько раз ассемблер повторит определение байта в
памяти. Значение или значения в скобках ассемблер использует для
инициализации этой области памяти. В данном случае это значение
неизвестно. Для инициализации области с одним и тем же значением
выражение, например,
DB 17 DUP(31)
создает 17 байт со значением 31 каждый. Наконец,
DB 30 DUP(1,2,3,4,5)
выделяет 30 байт со значениями от 1 до 5 в первых пяти байтах. Сле-
дующие пять байт тоже имеют значения от 1 до 5 и т.д. Ассемблер
повторяет значения в скобках пока не будут заполнены все 30 байт.
Иногда нам хочется обратиться к набору бит меньшему чем байт.
Принят размер 4 бит. В 4 битах мы можем представить все 10
десятичных цифр. Для значений такого размера мы будем
пользоваться термином "полубайт". Этот термин (в оригинале
"nybble" - прим. перев.), который достиг широкого применения,
позволяет нам говорить о данных, меньших, чем "байт".
Термин "Слово" имеет для программиста значение отличное от
принятого в языке. В применении к ЭВМ слово - это наибольшее
количество бит, с которым машина может обращаться как с единым
элементом. Для системы IBM/370 слово составляет 32 бит, а для
семейства Intel 8088 - 16. Поэтому термин "слово" имеет
неопределенный смысл, пока не известна конкретная машина.
Размер слова в 8088 составляет 16 бит. Этот размер
определяется каналами передачи данных в процессоре. Над числами до
16 бит 8088 может призводить операции одной командой. Любое более
крупное число потребует более одной команды. Существуют команды,
которые манипулируют и с меньшими объемами памяти, как, например,
команда сложения двух 8-битовых чисел. Несколько инструкций
позволяют манипулировать и с отдельными битами. Но для сложения
двух 32-битовых чисел потребуется уже две команды, складывающие по
16 бит каждая. Наибольшее число над которым мы можем производить
элементарные операции типа сложения имеет размер машинного слова.
Аналогично команде определения байта памяти, существует и
инструкция для определения слова памяти. Оператор ассемблера DW
означает определение слова (defie word). Первое утверждение DW на
Фиг. 2.10 определяет 16 бит памяти со значением 1234H. Как и в
случае с байтами, мы можем использовать оператор DUP для
определения больших областей памяти, разбитых на слова. И точно
также, для обозначения неинициируемых областей можно использовать
операнд "?".
Microsoft (R) Macro Assembler Version 5.00 10/29/88
16:10:44
Фиг. 2.10 Примеры определения слов Page 1-1
1 PAGE ,132
2 TITLE Фиг. 2.10 Примеры определения слов
3
4 0000 1234 DW 1234H
5 0002 0003[ DW 3 DUP(5678H)
6 5678
7 ]
8
9 0008 ???? DW ?
10
11 END
Фиг. 2.10 Примеры определения слов
Одна из обескураживающих черт 8088 - это его манера хранения
слов в памяти. На Фиг. 2.10, хоть мы и определяли значение слова
как 1234Н, ассемблер сохранит в памяти значение 3412Н, по крайней
мере так это выглядит. Посмотрим, как это получается.
Допустим, слово 1234Н сохранено в ячейках 100 и 101. 8088 тре-
бует, чтобы ассемблер поместил значение 34Н в ячейку 100, а 12Н - в
101. Легче всего запомнить это так, что ассемблер сохраняет
младший байт слова в ячейку памяти с меньшим адресом, а старший
байт - с большим. На Фиг. 2.11 показано, содержимое памяти после
того, как ассемблер поместит в нее данные. Пока вы не привыкнете к
такому методу, вам
Адрес Значение
-------------------------
. .
. .
100 34Н
101 12Н
. .
. . Фиг. 2.11 Представление
------------------------- в памяти DW 1234H
будет казаться, что все в памяти наоборот. К счастью, если вы не
будете смешивать операции над байтами и над словами одной и той же
области в памяти, вам незачем беспокоиться об этом неожиданном
"переключении" байтов. Программа может спокойно работать со
словами, а 8088 всегда разберется что к чему. Только в том случае,
если вы захотите обратиться к конкретному байту какого-либо слова,
вам придется иметь дело с фактическим способом хранения слов в
памяти семейства 8088. Ассемблер обращает внимание на структуру
слов в распечатке программы, то есть изображает слова в объектном
коде как слова, а не как байты, которые выглядели бы перевернутыми
наоборот. Вы сможете различать слова благодаря тому, что ассемблер
записывает их шестнадцатеричными цифрами без пробелов.
Однако остался еще один тип данных, который постоянно использу-
ется программах на языке ассемблера для микропроцессора 8088. Это
- двойное слово, значение в 32 бита длиной. Программы пользуются
двойными словами для хранения адресов и очень больших чисел. Чтобы
определить область, содержащую значение двойного слова, оператор
ассемблера
DD значение
генерирует поле размером в 4 байта. DD означает операцию выделения
двойнго слова (define doubleword). Так же как в случае с DW -
опратором, ассемблер размещает в памяти младший байт ниже, а
старший - выше. В таком же порядке сохраняются средние два байта.
Аналогично операторам DB и DW вы можете пользоваться функцией DUP и
применять операнд "?" для того чтобы оставить область
неопределенной.
Ассемблер может генерировать и другие структуры данных. Их об-
суждение мы отложим, пока не дойдем до некоторых свойств макроас-
семблера и сопроцессора 8087. Остальные структуры данных
используются в программах прежде всего для очень больших чисел в
Числовом сопроцессоре или для определения собственных структур
данных.
Двоичная арифметика
Двоичная арифметика
Все компьютеры используют для хранения информации двоичную систему.
Это значит, что каждый элемент хранимой информации может иметь
только два состояния. Эти состояния обозначаются как "включен" и
"выключен", "истина" и "ложь", или "1" и "0". Компьютер хранит эти
значения в виде уровней напряжения. К счастью у нас нет нужды свя-
зываться с напряжением. При написании программ мы имеем дело
только с числами. Используя простейшие числа 0 и 1, можно
выполнять очень сложные вычисления. Из-за двоичного представления
данных компьютеры используют в своих вычислениях арифметику с
двоичным основанием. Арифметика с основанием 2 пользуется только
двумя цифрами: 0 и 1. Мы обычно применяем систему исчисления по
основанию 10. В десятичной арифметике употребляется десять
различных цифр - от 0 до 9. Двоичную арифметику можно представить
себе как систему для людей, имеющих только два пальца.
Ограничение лишь десятью цифрами в десятичной арифметике не ме-
шает нам представлять более крупные числа. Мы пользуемся
многозначными числами, в каждой позиции которых стоят разные
степени 10. Самая правая цифра любого числа обозначает число
едениц, соседняя слева - количество десятков, следующая - число
сотен и т.д. Прогрессия справа налево выстраивается такая:
10**0, 10**1, 10**2 и т.д. Число 2368 в дейстительности
представляет 2 тысячи, 3 сотни, 6 десятков и 8 едениц. Фиг. 2.1
показывает ,говоря матическим языком, разложение числа 2368.
_____________________________________________________
2368 = 2 * 10**3 + 3 * 10**2 + 6 * 10**1 + 8 * 10**0
= 2000 + 300 + 60 + 8
_____________________________________________________
Фиг. 2.1 Десятичное представление
Арифметика с основанием 2 или двоичная система аналогична деся-
тичной, за исключением того, что разряды числа здесь соответствуют
степеням 2 а не 10. Числа больше 1 представляются многозначными
числами, так же как в десятичной арифметике многозначное представ-
ление получают числа больше 9. Каждая цифра в двоичной системе
называется бит от Binary digIT (двоичная цифра). Позиция каждого
бита в числе соответствует некоторой степени 2. Фиг. 2.2
показывает значение двоичного числа 101101B.
101101B = 1*2**5 + 0*2**4 + 1*2**3 + 1*2**2 + 0*2**1 + 1*2**0
= 32 + 0 + 8 + 4 + 0 + 1
= 45
____________________________________________________________
Фиг. 2.2 Двоичное представление
Мы будем пользоваться суффиксом "B" для обозначения чисел в
двоичном представлении. Этим они будут отличаться от десятичных,
не имеющих суффикса. Например, 2368 - это десятичное число, а
101101B - двоичное. В математической литературе для обозначения
системы исчисления обычно используется индекс. Мы будем
пользоваться символом "B", поскольку ассемблер IBM для обозначения
двоичных чисел применяет именно его.
В таблице Фиг. 2.3 для представления максимального десятичного
числа требуется 4 бита. Для более крупных чисел потребуется еще
больше бит. Двоичным числом состоящим из n бит можно изобразить
число величиной 2**n-1. То есть двоичное число в n бит длиной
может
Десятичное Двоичное
-----------------------
1 1
2 10
3 11
4 100
5 101
6 110
7 111
8 1000
9 1001
10 1010
-----------------------
Фиг. 2.3 Первые 10 целых
единственным образом представить любое целое от 0 до 2**n-1. Для
4-х битового примера на Фиг 2.3 самое большое такое число равно 15
(2**4-1).
Для каждого конкретного микропроцессора сществует максмальный
размер двоичных чисел, которые могут быть в нем представлены. Для
микропроцессора 8088, используемого в IBM PC, внутренние операции
производятся над числами длиной 16 бит. Максимальное целое, кото-
рое можно представить в 16 битах, равно 2**16-1 или 65 535. Однако
такая беззнаковая арифметика допускает числа только от 0 до 65 535.
Для обозначения отрицательных чисел нам потребуются изменения в
этой схеме.
Двоичное дополнение
Двоичное дополнение
Для изображения как положительных, так и отрицательных чисел 8088
применяет арифметику двоичного дополнения. В такой знакопеременной
арифметике самый левый бит целого числа указывает на его знак. По-
ложительные числа имеют 0 в старшем бите, а отрицательные - 1. По-
ложительные числа имеют одинаковое значение в знаковой и без-
знаковой арифметике. У отрицательных же значение иное. Для того
чтобы сделать число отрицательным, изменить его знак на минус, оно
дополняется и результат увеличиваетя на единицу. В 4-х битовом
примере 5 имеет значение 0101B, в то время как -5 равно 1011B.
Пример на Фиг. 2.4 показывает этот метод.
Пример на Фиг. 2.5 показывает единственность нуля в арифметике
двоичного дополнения. То есть -0 равен 0. Для любого n-битового
числа в системе с двоичным дополнением наибольшее значение сос-
тавляет 2**(n-1)-1, а наименьшее -2**(n-1). Ноль - единственен. В
4-х битовой системе наибольшее число, как показано на Фиг. 2.6,
равно 7, а наименьшее -8.
_________________________
5 = 0101B
Для изменения знака:
Дополняем 1010B
Добавляем 1 0001B
----- __________________________________
-5 = 1011B 0 = 0000B
Для изменеия знака: Для изменения знака:
Дополняем 0100B Дополняем 1111B
добавляем 1 0001B Добавляем 1 0000B
----- -----
5 = 0101B -0 = 0000B = 0
_________________________ __________________________________
Фиг. 2.4 Двоичное допол- Фиг. 2.5 То же самое с нулем
нение 5
Как мы увидим позднее, процессор 8088 может работать с одним и
тем же целым как со значением имеющим или не имеющим знак. Выбор
оставлен программисту языка ассемблера. Во всяком случае, когда
программа работает в арифметике со знаком, процессор 8088 применяет
при проведении операций двоичное дополнение.
Десятичное Двоичное Десятичное Двоичное
-------------------------------------------------
7 0111 -1 1111
6 0110 -2 1110
5 0101 -3 1101
4 0100 -4 1100
3 0011 -5 1011
2 0010 -6 1010
1 0001 -7 1001 Фиг. 2.6 Числа
0 0000 -8 1000 с двоичным
------------------------------------------------- дополнением
Машинный язык и язык Ассемблера
Машинный язык и язык Ассемблера
Мы уже видели, как из нулей и единиц, хранимых в ЭВМ, формируются
числа.Теперь мы посмотрим как комбинации тех же значений 0 и 1
могут быть использованы для программирования компьютера.
Машинная программа представляет собой последовательность ко-
манд (инструкций). Эти команды "объясняют" компьютеру, что он
должен делать. Это похоже на рецепты в кулинарной книге. В
рецепте имеется описание действий, которые необходимы для
приготовления определенного блюда. Подобным образом, компьютер
имеет последовательность команд, которые точно описывают ему
последовательность действий. Этот набор команд называется
программой. Процесс построения корректного набора команд
называют программированием компьютера. В нашей аналогии с рецептом
рецепт является программой, а тот кто его написал - программистом.
Роль компьютера здесь играет повар, готовящий еду.
Реальная программа, которую выполняет компьютер, это последова-
тельность едениц и нулей, связанных с памятью компьютера. Эту стро-
ку бит принято называть машинным языком. Машинный язык - это тот
язык который машина понимает. Компьютер извлекает команды машинного
языка из памяти точно определенным способом. Затем компьютер выпол-
няет команду, обозначенную данной конфигурацией бит. Этот цикл изв-
лечения и исполнения будет разобран в одном из последующих разделов
данной главы.
Однако машинный язык мало о чем говорит людям. Если вы хотите
сложить два числа в 8088 (например, содержимое регистров AX BX -
краткое описание регистров сейчас последует), команда будет
выглядеть таким образом:
0000001111000011B (или 03C3H)
Эти два байта точно указывают компьютеру какую опреацию произвести.
Аналогично, для вычитания двух чисел (вычитание регистра BX из ре-
гистра AX) мы будем иметь в машинном языке
0010101111000011B (или 2BC3H)
Здесь необходимо коротко пояснить, что такое регистры, поскольку в
обсуждении основ работы 8088 с ними приходится чато сталкиваться.
Регистр - это часть процессора, предназанченная для сохранения дан-
ных. К данным, сохраненным в регистре, процессор получает доступ
очень быстро - намного быстрее, чем к данным, хранимым в памяти.
Возможно еще специальное использование регистров в некоторых коман-
дах. В третьей главе будет дано полное описание регистров 8088.
Хотя машинный язык - это действительно прекрасно, если вы явля-
етесь компьютером, он труден для программистов - людей. К счастью,
существует более простой способ программирования. Этим методом,
более близким людям, чем машинам, является программирование на
языке ассемблера.
Язык ассемблера, как язык программирования, т.е. более понятный
программисту, чем машинный, язык, все еще сохраняет все значения
машинного языка. Компьютер читает программы на языке ассемблера и
переводит их в машинный язык, в ту форму, которая понятна ЭВМ. Этот
процесс, называемый "ассемблированием" программы, фактически
является переводом с одного языка на другой. Операцию перевода с
языка ассемблера на машинный язык выполняет программа, называемая
ассемблером.
Чтобы лучше понять разницу, давайте взглянем на примеры,
которыми мы уже пользовались выше. Ассемблерная команда для
сложения содержимого регистров AX и BX проста:
ADD AX,BX
Аналогично, для вычитания регистра BX из регистра AX мы напишем:
SUB AX,BX
Ассемблер превращает эти предложения в форму, которую мы видели вы-
ше. Компьютер сам управляется с проблемой превращения файла с по-
нятным человеку текстом в программу на машинном языке, которую мог
бы выполнить процессор.
Язык ассемблера не похож на языки типа Фортран, Кобол или
Паскаль. Эти языки, как и многие подобные им, являются языками
высокого уровня. Языки высокого уровня разработаны для того, чтобы
можно было иметь дело непосредственно с задачей, решаемой
программой. В этом качестве они иногда называются процедурными
языками, поскольку описывают процедуру, используемую для решения
задачи. Языки высокого уровня машинно-независимы. Программа,
написанная на Фортране для IBM PC будет правильно работать и давать
те же самые результаты при выполнении на IBM/370. язык
программирования не зависит от машины.
Програмы же на языке ассемблера непосредственно относятся к той
машине, на которой они должны выполняться. Язык ассемблера машин-
нозависимый. Язык ассемблера для IBM PC принципиально отличен от
языка ассемблера для IBM/370. Это связано с тем, что команды языка
ассемблера почти один к одному переводятся в команды машинного
языка т.е. каждая команда языка ассемблера обычно преобразуется
точно в одну команду машинного языка. Поскольку машинные языки
разных компьютеров различны, то различаются и языки ассемблера.
Обычно каждое утверждение языка ассемблера генерирует одну команду
машинного языка. В некоторых случаях это не так, потому что
существуют команды, которые не являются частью выполняемой програм-
мы, а предназачены для ассемблера. Они описывают действия ассембле-
ра, который должен выполнять их во время ассемблирования. Пример
директивы ассемблеру (такой предназначенной только для него
команды) -
TITLE Пример Программы
Эта инструкция сообщает ассемблеру заголовок программы. После
трансляции ассемблером прграммы этот заголовок - "Пример
программы"- появляется в верхней части каждой страницы сообщений
ассемблера. Эта инструкция имеет смысл только для ассемблера. В
8088 нет команды, которая могла бы выполнить эту опреацию.
Набор символов
Набор символов
Как мы заметили выше, мы можем рассматривать каждый байт информации
не как двоичное число, а как символьное значение. Каждое из
двоичных чисел от 0 до 255 может представлять определенный символ.
Фиг. 2.13 показывает множество символов IBM PC. Колонки здесь
соответствуют старшим 4 битам символьного кода, а ряды - младшим 4
битам этого кода. Так, позиция таблицы 41Н соответствует символу
"A", а код 5ЕН представляет символ "^".
ЪДДДВДДДТДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї ЪДДДВДДДТДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї
ідесі= >є 0 і16 і32 і48 і64 і80 і96 і112і ідесі= >є128і144і160і176і192і208і224і240і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і ішстє 0 і 1 і 2 і 3 і 4 і 5 і 6 і 7 і і ішстє 8 і 9 і A і B і C і D і E і F і
ЖНННШНННОНННШНННШНННШНННШНННШНННШНННШНННµ ЖНННШНННОНННШНННШНННШНННШНННШНННШНННШНННµ
і 0 і 0 єпусі > іпрбі 0 і @ і P і ` і p і і 0 і 0 є А і Р і а і ° і А і Р і р і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 1 і 1 є і < і ! і 1 і A і Q і a і q і і 1 і 1 є Б і С і б і ± і Б і С і с і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 2 і 2 є і і " і 2 і B і R і b і r і і 2 і 2 є В і Т і в і І і В і Т і т і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 3 і 3 є і ! і # і 3 і C і S і c і s і і 3 і 3 є Г і У і г і і і Г і У і у і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 4 і 4 є і і $ і 4 і D і T і d і t і і 4 і 4 є Д і Ф і д і ґ і Д і Ф і ф і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 5 і 5 є і і % і 5 і E і U і e і u і і 5 і 5 є Е і Х і е і µ і Е і Х і х і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 6 і 6 є і і & і 6 і F і V і f і v і і 6 і 6 є Ж і Ц і ж і ¶ і Ж і Ц і ц і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 7 і 7 є і і ' і 7 і G і W і g і w і і 7 і 7 є З і Ч і з і · і З і Ч і ч і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 8 і 8 є і і ( і 8 і H і X і h і x і і 8 і 8 є И і Ш і и і ё і И і Ш і ш і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і 9 і 9 є і і ) і 9 і I і Y і i і y і і 9 і 9 є Й і Щ і й і № і Й і Щ і щ і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і10 і A є і і * і : і J і Z і j і z і і10 і A є К і Ъ і к і є і К і Ъ і ъ і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і11 і B є і і + і ; і K і [ і k і { і і11 і B є Л і Ы і л і » і Л і Ы і ы і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і12 і C є і і , і < і L і \ і l і | і і12 і C є М і Ь і м і ј і М і Ь і ь і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і13 і D є і і - і = і M і ] і m і } і і13 і D є Н і Э і н і Ѕ і Н і Э і э і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і14 і E є і і . і > і N і ^ і n і ~ і і14 і E є О і Ю і о і ѕ і О і Ю і ю і і
ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
і15 і F є і і / і ? і O і _ і o і і і15 і F є П і Я і п і ї і П і Я і я і і
АДДДБДДДРДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ АДДДБДДДРДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
Фиг. 2.13 Набор символов IBM
Набор символов IBM PC является расширением набора символов
ASCII (Американский стандартный код для обмена информацией). В
наборе ASCII значения символов от 20Н до 7ЕН представляют обычные
символы латинского алфавита, числовые символы и знаки препинания.
Коды от 0Н до 1FH обычно служат управляюшими символами. На Фиг.
2.14 показаны управляющие символы ASCII из этого диапазона. Эти
символы имеют значение при передаче на принтеры IBM или другие
ASCII-принтеры. Однако на Фиг. 2.13 видно, что эти управляющие
символы могут также появляться на экране в виде графических симво-
лов. В IBM PC управляющая часть таблицы ASCII используется для
графических изображений, с целью более полно реализовать возмож-
ности видеоадапторов. Поскольку видеоадапторы могут изобразить
любой из 256 кодов, то нет оснований строго регламентировать
применение какого-либо из кодов. Разработчики рассматривали все 32
символа из управляющей части таблицы как предназначенные главным
образом для графического изображения и обычно не печатаемые
принтером. Короче говоря, первые 32 значения являются управляющими
кодами при передаче их на принтер, но изображаются как графические
символы при выводе их на дисплей.
Символьные значения от 80Н до 0FFH являются расширением набора
символов ASCII для IBM PC. Эти символы подобраны разработчиками
IBM так, чтобы расширить изобразительные возможности компьютера.
Наборы иностранных, графических и научных символов позволяют
использовать IBM PC в самых разнообразных приложениях.
Код Символ Значение
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
0 NUL Пусто
7 BEL Сигнал
9 HT Горизонтальная табуляция
0A LF Пропуск строки
0B VT Вертикальная табуляция
0C FF Прогон страницы
0D CR Возврат каретки
0E SO Шаг назад
0F SI Шаг вперед
11 DC1 Управление 1
12 DC2 Управление 2
13 DC3 Управление 3
14 DC4 Управление 4
18 CAN Стоп
1B ESC Выход
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД Фиг. 2.14 Управляющие коды IBM
В некоторых случаях вы захотите вводить символьные коды в па-
мять для их дальнейшего использования программой. Примером может
служить сообщение, которое в определенный момент выполнения прог-
раммы должно быть выдано оператору. Вместо потска кодов символов в
таблице, мы можем сразу ввести строку символов в текст программы.
Ассемблер позволяет это сделать с помощю оператора DB. В поле
Microsoft (R) Macro Assembler Version 5.00 10/31/88 22:30:38
Фиг. 2.15 Определение байтов для текста ASCII Page 1-1
1 PAGE ,132
2 TITLE Фиг. 2.15 Определение байтов для текста ASCII
3
4 0000 9D E2 AE 20 E1 AE AE DB 'Это сообщение',10,13
5 A1 E9 A5 AD A8 A5 0A
6 0D
7
8 END
Фиг. 2.15 Определение байтов для текста ASCII
операндов мы вместо ввода чисел (кодов) помещаем заключенную в
кавычки строку символов. Ассемблер подберет соответствующие
значения кодов и поместит их в память - каждый символ в отдельный
байт. Так ассемблер может работать только с символами в диапазоне
от 20Н до 0FFH. в диапазоне от 0Н до 1FH символы должны вводиться
в программу в виде чисел, а не ограниченной кавычками строки. Это
связано с тем, что в тексте исходного файла некоторые управляющие
символы используются для обозначения начала и конца строки.
Пример на Фиг. 2.15 показывает создание 15 байт данных в
программе. Первые 13 байтов соответствуют 13-ти символам текстовой
строки заключенной в кавычки. Первый байт имеет значение 9DH,
второй 0E2H и т.д. Последние два байта в 17-ти байтном сообщении -
это коды возврата каретки и прогона строки. Если мы отправим это
17-байтное собщение на принтер, он напечатает заключенный в кавычки
текст. Управляющие символы предписывают принтеру перейти после
этого на следующую строку документа.
Нумерация бит
Нумерация бит
Иногда нам будет требоваться идентифицировать отдельные биты в бай-
те или слове. Для этого мы называем номер бита. Индекс или номер
каждого бита - это степень двойки, соответствующая позиции этого
бита. Самый младший бит - нулевой, поскольку он представляет два в
нулевой степени. Самый старший бит в байте - седьмой - 2**7.
Самый старший бит в слове - 15-й. Фиг. 2.12 показывает 16-битовое
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
ЪДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДї
АДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДЩ
Фиг. 2.12 Нумерация бит
слово с пронумерованными битами. Такой способ нумерации бит принят
во всей документации IBM PC.
Прерывания
Прерывания
Механизм прерываний - существенная часть любой вычислительной сис-
темы. Как мы увидим, он важен и для IBM PC. Структура прерываний
предоставляет эффективное средство для связи устройств вводоа-выво-
да с процессором. Нам прерывания интересны потому, что управление
прерываниями - прерогатива программирования на языке ассемблера. В
языках высокого уровня отсутствуют средства для работы с
прерываниями на машинном уровне.
Прерывания обычно вызываются внешними устройствами. Прерывание
сигнализирует процесору о необходимости прервать текущие действия и
ответить внешнему устройству. В IBM PC клавиатура посылает сигнал
прерывания всякий раз при нажатии любой клавиши. Прерывание
клавиатуры заставляет процессор прекратить текущую деятельность и
считать набранный на клавиатуре символ.
Легко понять, за что прерывания получили свое название. Преры-
вание сигнализирует о необходимости "прервать" текущее действие
процессора. Прерывания хороши тем, что избавляют процессор
постоянного контроля за внешними устройствами. Если бы, например,
клавиатура пользователя не вызывала прерываний, то процессор был бы
вынужден непрерывно проверять клавиатуру, чтобы обнаружить нажатие
клавиши. Каждая написанная для компьютера программа была бы
вынуждена делать одно и то же, и им пришлось бы очень часто
тестировать клавиатуру. Но наличие прерываний снимают это
требование, и программа может выполняться без постоянного
тестирования клавиатуры. Каждый раз, как клавиатура получает
какую-либо информацию, она сигнализирует об этом процессору. После
того, как микропроцессор удовлетворит запрос клавиатуры, он может
возобновить нормальный ход выполнения программы.
Работа 8088 с прерываниями во многом напоминает его обращение с
процедурами. Прерывание не может прекратить работу процессора во
время выполнения команды. Сначала 8088 закончит выполнение текущей
команды, но следующую уже проигнорирует. Вместо ее выполнения про-
цессор действует так, как будто следующая команда была вызовом про-
цедуры. Он сохраняет адрес очередной команды в стеке и переходит в
специальную процедуру, которую называют программой обработки
прерываний. Эта процедура содержит команды для работы с вызвавшим
прерывание устройством. В случае с клавиатурой программа обработки
прерывания считывает символ и сохраняет его для дальнейшего
использования. После того как она закончит работу с устройством,
происходит возврат в точку прерывания. Процессор извлекает из
стека адрес возврата и продолжает выполнение программы как будто
ничего не случилось.
Поскольку прерывание вызывается внешним устройством, оно может
произойти в любой момент выполнения программы. Программа не может
предпринять каких-либо действий чтобы подготовиться к прерыванию,
так как не может предвидеть, когда пользователь нажмет на клавишу
клавиатуры. Отсюда следует, что прерывание не должно изменять
данные в прерываемой программе. Если прерывание иозменит
какое-либо значение в программе, то она не сможет нормально
работать когда к ней вернется управление.
В ходе прерывания 8088 автоматически сохраняет некоторые уста-
новленные программой значения в стек. В свою очередь, программа
обработки прерываний отвечает за сохранение любых других данных,
которые она может изменить во время своего выполнения. Эти данные
обычно сохраняются в стеке. Затем, перед возвращением управления в
прерванную программу, программа обработки прерывания должна вернуть
измененным данным те значения, которые они имели в момент
прерывания. Факт возникновения прерывания должен остаться
"невидимым" для выполняемой программы.
Поскольку сигнал прерывания могут посылать процессору многие
устройстваэ, 8088 имеет механизм ориентации прерываний. Это озна-
чет, что 8088 определяет, какое устройство вызвало прерывание и пе-
редает управление программе обработки прерывания, соответствующей
этому устройству. Процессор атоматически управляет веторизацией
зап росов на прерывания. Программе обработки прерывания не
требуется перед обработкой прерывания определять, какое устройство
его вызвало. Это сокращает время реакции на прерывание и упрощает
программирование прерываний.
В программамах встречаются такие участки, выполнение которых не
может быть прервано. Например, это может быть кусок программы,
который должен выполняться очень быстро, чтобы закончить выполнение
специфической задачи, или момент работы с данными, которые могут
быть изменены программой обработки прерывания. В обоих случаях
программа должна иметь возможность задержать или предотвратить
прерывания, т.е. программа должна уметь не допускать возникновения
прерываний во время выполнения таких критических участков. После
прохождения этих участков программа должна восстановить способность
системы прерываний вызывать прерывания. Программа не может
отключать прерывания на слишком долгое время, иначе с устройством,
запросившим прерывание, может произойти какая-нибудь неприятность.
Если прерывание клавиатуры не считает символ до того как оператор
нажмет другую клавишу, второй симол может быть потерян. В 8088
имеется возможность блокировать все внешние прерывания. IBM PC
имеет более развитую возможность выбирать, каким из устройств можно
вызывать прерывание, а каким нет. Программа может использовать эту
возможность для выбора наиболее важных устройств, которым можно
разрешить прерывания, а менее критическим запретить. Способы
отключения прерываний мы обсудим в следующих главах.
Принципы работы Ассемблера
Принципы работы Ассемблера
Рассмотрим теперь работу ассемблера в целом. Детали будут
обсуждены позднее, но сейчас нам нужно ввести новые термины и
ознакомиться с реальным результатом работы ассемблера.
Ассемблер берет программу, написанную на языке ассемблера, и
превращает ее в машинный язык. Файл, который содержит программу на
языке ассемблера, называют исходным файлом. Выход и ассемблера в
действительности является не собственно машинным языком, а
некоторым промежуточным представлением программы. Этот выходной
файл называют объектным файлом. Данные в нем называются объектным
кодом. Для получения из него настоящего машинного кода объектный
код должен быть несколько изменен. Для IBM PC это делает программа
редактор связей LINK. Шаг преобразования объектных кодов в
машинные принято называть построением связей или редактированием
связей. Как пользоваться редактором связей мы увидим в одной из
следующих глав.
Помимо преобразования исходного кода в объектный ассемблер
создает несколько других выходных файлов. Один из них -
ассемблерный листинг. Он содержит сообщение о действиях
ассемблера. Зтот файл содержит исходный код вместе с
комментариями, а также объектный код, сформированный ассемблером.
Фиг. 2.9 дает пример листинга ассемблера, иногда называемого
распечаткой.
Microsoft (R) Macro Assembler Version 5.00 10/28/88
16:35:34
Фиг. 2.9 Пример ассемблирования Page 1-1
1 PAGE ,132
2 TITLE Фиг. 2.9 Пример ассемблирования
3 0000 CODE SEGMENT
4 ASSUME CS:CODE
5
6 0000 03 C3 PART1: ADD AX,BX ; Сложить с длиной буфера
7
8 0002 CODE ENDS
9 END
Фиг. 2.9 Пример ассемблирования
Взяв пример команды ассемблера, рассмотрим результаты работы
ассемблера. В правой части распечатки находятся исходные команды.
В левой части - информация, сгенерированная ассемблером. Первая
колонка содержит номер каждой строки распечатки. Ассемблер
устанавливает эти номера для исходного файла. Они строк не
обязательно соотносятся с номерами строк в исходном файле
сформированном текстовым редактором.
Во второй колонке содержатся адреса инструкций. Программа LINK
может их изменить, но они являются лучшим предположением, которое
может сделать ассемблер на шаге ассемблирования. Следующая колонка
- код команды на машинном языке. Поскольу команды 8088 имеют длину
от 8 до 56 бит, это поле будет изменяться в размере. Кроме того,
программа LINK может изменить некоторую информацию в поле объектных
кодов. Редактор связей может изменить любую группу команд,
оперирующих с адресами. Однако, за исключением адресов, листинг
ассемблера дает верные машинные коды, которые и будут в дальнейшем
исполняться.
В большинстве примеров программ мы будем использовать листинг
ассемблера. Это позволит нам сразу видеть вырабатываемый ассембле-
ром код.
Другой создаваемый ассемблером файл - файл перекрестных сыылок.
Этот файл описывает все связи между метками и командами, которые их
используют. Такая информация незаменима, когда вы пытаетесь
изменить программу. Вы можете воспользовваться перекрестными
ссылками для того, чтобы выявить все команды, которые обращаются к
определенному участку памяти. Это позволяет программисту
определить все команды, на которые может повлиять изменение в
другой чассти программы. Использование информации о перекрестных
ссылках будет обсуждаться в главе 5.
Принципы работы компьютера
Принципы работы компьютера
Ниже описаны некоторые основные принципы работы компьютера. Эти
принципы важны для понимания 8088 и его работы. Все, что говорится
в этом разделе, верно и для других компьютеов. В соответствующих
местах мы будем специально оговариваться, что речь идет об Intel
8088, хотя основная часть сведений, относящихся только к 8088,
появится в следующей главе.
Работа компьютера состоит в выборке команд из памяти и их
выполнении. Каждая команда проходит через этот двухшаговый
процесс. Выборкой очередной порции в этом цикле управляет один из
регистров процессора. Этот регистр называют счетчиком программы
или указателем команды. Он является "маркером" текущей
выполняемой команды. То место в памяти, на которое указывает этот
регистр, содержит следующую команду, которую должен будет выбрать и
выполнить процессор. Процессор читает в этом месте один или
несколько байтов, интерпретирует их как комнду и выполняет ее.
Затем процессор увеличивает указатель в соответствии с числом
байтов в команде. Теперь счетчик программы указывает на следующую
команду. Этот цикл повторяется для всех без исключения команд.
Нормальное выполнение программы является последовательным, от одной
команды к другой, расположенной следом.
Процессор может изменить последовательный цикл выборки-исполне-
ния при выполнении команды, которая помещает в указатель команд но-
вое значение. Такие команды являются командами передачи
управления, поскольку выполнение программы переходит в новую
область. Инструкция перехода или выбора варианта является самым
распространенным способом передачи управления. Команда перехода
задает адрес команды, которая должна выполняться следующей. Цикл в
программе является примером использования команды перехода. Пример
на Фиг. 2.16 на языке ассемблера 8088 показывает сохранение одного
и того же значения в последовательных байтах памяти. Команда
перехода в конце цикла приводит к повторному выполнению его команд.
Microsoft (R) Macro Assembler Version 5.00 11/2/88 21:30:42
Фиг. 2.16 Команда перехода Page 1-1
1 PAGE ,132
2 TITLE Фиг. 2.16 Команда перехода
3 0000 CODE SEGMENT
4 ASSUME CS:CODE
5
6 0000 MEM LABEL BYTE
7
8 0000 FIG2_16:
9 0000 2E: C6 87 0000 R 00 MOV MEM[BX],0
10 0006 43 INC BX
11 0007 EB F7 JMP FIG2_16
12
13 0009 CODE ENDS
14 END
Фиг. 2.16 Команда перехода
Обратите внимание, что в команде JMP для определения сле-
дующего выполняемого адреса используется метка, в данном случае
"FIG2_16". Это - еще одна из возможностей ассемблера. Хотя в
машинном языке требуется абсолютный адрес следующей команды, язык
ассемблера требует лишь программно определенную метку. Ассемблер
сам определяет абсолютный адрес и ставит правильное значение в
команду машинного языка.
Команда перехода не обязательно должна быть безусловной как в
приведенном примере. 8088 располагает множством команд перехода,
которые выполняются в соответствии с некоторым кодом условия.
Значение кода условия устанавливают другие команды при их
выполнении процессором. Условие, указанное в команде условного
перехода, сравнивается с кодом условия, сохраненного в регистре
состояний. Если условия совпадают, то процессор переходит по
указанному адресу. В противном случае процессор игнорирует
переход, и выполнение программы продолжается в обычном
последовательном порядке. На Фиг. 2.17 предыдущий пример изменен.
Цикл в этом примере прерывается, когда значение BX становится
равным 1000.
На Фиг. 2.17 появляется новая команда сравнения, которая
устанавливает коды состояния. Команда условного перехода (JNE
(Jump if Not Equal) переход, если не равны) выполняет переход на
"FIG2_17", если условие выполнено. Если условие не выполняется,
8088 выполняет команду, следующую за условным переходом, в данном
случае команду NOP. Команда условного перехода позволяет проверить
Microsoft (R) Macro Assembler Version 5.00 11/2/88 22:31:33
Фиг. 2.17 Команда условного перехода Page 1-1
1 PAGE ,132
2 TITLE Фиг. 2.17 Команда условного перехода
3 0000 CODE SEGMENT
4 ASSUME CS:CODE
5
6 0000 MEM LABEL BYTE
7
8 0000 FIG2_17:
9 0000 2E: C6 87 0000 R 00 MOV MEM[BX],0
10 0006 43 INC BX
11 0007 81 FB 03E8 CMP BX,1000
12 000B EB F3 JMP FIG2_17
13
14 000D CODE ENDS
15 END
Фиг. 2.17 Команда условного перехода
значения данных в процессе выполнения программы. Ход выполнения
программы может меняться в зависимости от результатов этой
проверки.
Процедуры
Процедуры
Другая форма команды перехода - переход к подпрограмме. Некоторая
последовательность команд образует процедуру. Эта последователь-
ность реализует функцию, которая выполняется в программе
неоднократно и в разных местах. Вместо многократного повторения
этой последовательности во всех необходимых местах, программист
помещает эти команды в одном месте. Такая часть программы
становится подпрограммой или процедурой.
Каждый раз как в программе потребуется выполняемая процедурой
функция, она передает управление в эту процедуру командой перехода
на нее. Переход на процедуру называется вызовом процедуры или
командой вызова. Вызов процедуры отличается от команды перехода.
Команда вызова сохраняет адрес следующей за ней команды. Этот
адрес, называемый адресом возврата, указывает дорогу обратно к
исходной последовательности команд.
Давайте посмотрим, как работает вызов процедуры. Пусть, напри-
мер, нам надо написать программу, которая складывает в нескольких
местах 32-битовые числа. У микропроцессора 8088 нет команд,
которые выполняли бы такое сложение. Мы можем написать короткую
последовательность команд, которая будет выполнять сложение
32-битовых чисел. Эта часть программы будет процедурой.
Программист пишет эту подпрограмму точно также как любую другую
часть программы. Она является частью программы на языке
ассемблера. При написании основной части прикладной программы
программист будет иногда сталкиваться с необходимостью сложить два
32-битовых числа. Вместо того, чтобы писать команды для выполнения
этого сложения, в программу включают вызов процедуры 32-битового
сложения. Сразу после него продолжаются команды основной части
программы. Вызов этой процедуры производит впечатление мощной
команды 8088, так как один такой вызов выполняет 32-битовое
сложение.
При выполнении программы выозов процедуры выполняет не само
сложение с двойной точностью, а передачу управления соответствующей
процедуре. Процессор выполняет команды процедуры, реализующей
сложение. Последняя команда процедуры является специальной
командой для процедур и называется возвратом. Команда возврата
берет адрес, который был сохранен командой вызова и помещает его
обратно в указатель команд. Это заставляет программу вернуться к
команде, следующей за вызовом процедуры. Вызов процедуры как бы
временно отводит течение программы в русло процедуры. После
выполнения процедуры выполнение возвращается к основной программе.
Команды, которые обеспечивают выполнение процедуры - CALL и
RETURN. CALL - это переход на процедуру. CALL сохраняет текущее
значение указателя команд в специальном месте памяти. Это
сохраненное значение указателя команд является адресом возврата.
Команда RETURN читает сохраненное значение указателя команд, поме-
щает его в указатель команд процессора и возвращает управление в
точку, следующую за командой CALL. Пример на Фиг. 2.18 показывает
процедуру, вызываемую из двух различных точек программы.
Поскольку программа начинает свое выполнение с самого начала,
она сразу же попадает на команду A1. Команда CALL передает
управление в точку SOBROUTINE. Выполняя команду CALL, процессор
в том числе сохраняет адрес точки A2. После выполнения процедуры
команда RET (от английского return - возврат) восстанавливает
сохраненное значение A2. Управление возвращается к главной
программе. Дальше в главной прграмме выполняется CALL в точке A3,
что приводит к повторному выполнению подпрограммы. На этот раз
процессор сохраняет значение A4. После выполнения процедуры во
второй раз управление возвращается в A4. Обратите внимание, что
оба раза выполнялась одна процедура. В первый раз возврат после ее
Microsoft (R) Macro Assembler Version 5.00 5/11/80 16:25:59
Фиг. 2.18 Использование процедуры Page 1-1
PAGE ,132
TITLE Фиг. 2.18 Использование процедуры
0000 CODE SEGMENT
ASSUME CS:CODE
0000 E8 0008 R A1: CALL SUBROUTINE
0003 40 A2: INC AX
0004 E8 0008 R A3: CALL SUBROUTINE
0007 43 A4: INC BX
;----- Здесь программа продолжается . . .
0008 SUBROUTINE PROC NEAR
0008 B8 0000 MOV AX,0
000B BB 0000 MOV BX,0
000E C3 RET
000F SUBROUTINE ENDP
000F CODE ENDS
END
Фиг. 2.18 Использование процедуры
выполнения осуществлялся на A2, во второй раз - на A4.
Преимущество процедуры заключается в ее способности вызываться из
множества различных мест и каждый раз правильно находоить точку
возврата.
Где же хранится адрес возврата во время выполнения процедуры?
Существует множество возможностей, но микропроцессор 8088
использует для хранения этого значения стек.
Шестнадцатиричное представление
Шестнадцатиричное представление
Двоичная арифметика хороша для компьютера, поскольку он имеет дело
только с еденицами и нулями. Но человеческое восприятие требует
более компактного представления. Мы будем пользоваться шестнадца-
теричным представлением данных для собственного удобства.
Шестнадцатеричное представление чисел - это система исчисления
по основанию 16. Каждая цифра в числе может иметь значение от 0 до
15. Каждый разряд в числе является степенью 16. Шестнадцатеричное
представляение - удобный метод записи двоичной информации. Каждая
шестнадцатеричная цифра соответствует четырем битам. Для преобра-
зования двоичного числа в шестнадцатеричное разбейте его на группы
по 4 бита и прочитайте каждую группу как шестнадцатеричную цифру.
Это дает уплотнение записи один к четырем - очень удобно для
разумного существа.
Небольшая трудность здесь связанна с тем, что у нас имеются
цифры только от 0 до 9. Числа от 10 до 15 мы будем представлять
первыми шестью буквами латинского алфавита: от A до F. Таблица
соответствия между десятичными, шестнадцатеричными и двоичными
цифрами приводится на Фиг. 2.7.
Как показано в этой таблице, каждая шестнадцатеричная цифра со-
ответствует точно 4-м битам какого-либо двоичного числа.
Шестнадцатеричное представление обычно для машин, в которых
размер слова кратен 4. Поскольку слово в 8088 составляет 16 бит,мы
будем пользо- ваться шестнадцатеричной записью. Каждое 16-битовое
значение пред- ставляется четырьмя шестнадцатеричными цифрами. В
этой книге числа в шестнадцатеричной записи будут обозначаться
суффиксом "H", а двоичные числа - суффиксом "B".
Десятичные Двоичные Шестнадцатер. Десятичные Двоичные Шестнадцатер.
-------------------------------------------------------------------
0 0000 0 8 1000 8
1 0001 1 9 1001 9
2 0010 2 10 1010 A
3 0011 3 11 1011 B
4 0100 4 12 1100 C
5 0101 5 13 1101 D
6 0110 6 14 1110 E
7 0111 7 15 1111 F
------------------------------------------------------------------
Фиг. 2.7 Шестнадцатеричная нумерация
Десятичные числа пишутся без суффикса или с суффиксом "D". Это в
точности соответствует записи чисел в языке ассемблера. Для
предсталения данных в ассемблерной программе можно пользоваться лю-
бой из трех рассмотренных систем (десятичная, двоичная и шестнадца-
теричная).
При записи шестнадцатеричных чисел важно убедиться, что ассемб-
лер воспримет их как числа. Если вы ввели "FAH", то это может быть
или шестнадцатеричное число FA, или имя переменной FAH. Ассемблер
предполагает, что число начинается с цифры и что метка начинается с
буквы. Поэтому "FAH" для ассемблера оказывается переменной. Если
мы имеем в виду не переменную а число, то его надо записать как
"0FAH": это число имеет желаемое значение и начинается заведомо с
цифры. Воизбежание путаницы каждому шестнадцатеричному числу,
которое начинается со значений от A до F должен предшествовать 0.
Синаксис языка Ассемблера
Синаксис языка Ассемблера
Прежде чем двигаться дальше, обсудим синтаксис команд языка ассем-
блера.Мы должны выделить основные компоненты языка ассемблера,
чтобы можно было затем обозначать эти компоненты с помощью
сиандартных терминов.
Команда языка ассемблера состоит из четырех частей. Фиг. 2.8
показывает типичную команду ассемблера и названия этих частей.
--------------------------------------------------------
PART1: ADD AX,BX ;Добавить к длине буфера
Метка ОпКод Операнды Комментарий
--------------------------------------------------------
Фиг. 2.8 Синтаксис языка ассемблера
Единственная обязательная часть команды языка ассемблера - ОпКод
(сокращение от ОПерационный КОД). Программисты иногда называют ма-
шинные команды кодами операций. Операционный код в утверждении язы-
ка асемблера определяет, какую опреацию должен будет выполнить про-
цессор, в нашем примере - операцию сложения (по английски - add -
прим. перев.).
Поле операндов содержит дополнительную информацию о команде,
например, какие значения участвуют в операции. Поле операндов
определяется операционным кодом. Каждому коду операции должно
соответствовать определенное число операндов. Для команды ADD
требуется два операнда; операция перемены знака (NEG) обходится
лишь одним, а для некоторых команд, например, команды десятичной
коррекции DAA, операнды не нужны. В главе 4 описаны эти команды и
их операнды.
Метка и комментарий необязательны в команде. Поле метки позво-
ляет обозначить какое-либо конкретное место в памяти компьютера.
Собственный адрес имеется у любого участка памяти, но выделить
адрес какой либо команды трудно, если вообще возможно. Метка
позволяет идентифицировать определенное место в памяти заданным
программистом именем. Говоря технически, поле метки содержит
символический указатель расположения команды. Если мы хотим обра-
титься к этой команде позднее, то мы делаем это через символьное
имя и нам не требуетсся указывать абсолютное расположение данной
инструкции. Использование меток - одна из причин предпочтительности
языка ассемблера перед машинным языком. Превращением же симво-
лических имен в реальные адреса ведает ассемблер.
Поле комментариев служит для удобства программиста.
Программист может использовать это поле для сообщения
дополнительной информации о команде. Комментарий не обязательно
жестко связан с командой. Вы можете отвести под комментарий целую
строку, поставив в ее начале символ ";". Это позвляет программисту
в ключить в листинг ассембле- ра блок собственной информации, к
примеру, описание используемого алгоритма.
У каждого есть собственное представление о том, как следует
комментировать программы, и вы наверняка тоже скоро выработаете
свое. Как правило, вы будете пытаться включать в них информацию,
которая относится непосредственно к решаемой проблеме. В при-
веденном примере было бы бессмысленно комментировать команду
чем-нибудь вроде "сложить AX и BX". Это не более, чем повторение
операционного кода и операндов (разве что в переводе с английского
- прим.перев.). Если уж вы намерены связаться с комментариями, то
делайте их достойными труда их написания и чтения.
Стек
Стек
Стек - это структура данных, которая используется для временного
хранения информации. Программа может поместить данные в стек
(PUSH) или забрать их оттуда (POP). Стековая структура данных
предполагает упорядочивание помещенных в него данных специальным
образом. Во всех случаях первым из стека извлекается то, что было
в нем сохранено последним. Такая организация хранения данных
сокращенно обозначается LIFO (last in, first out - последний
введенный первым выводится). Если мы поместили в стек сначала A,
затем B, то первое, что мы извлечем из него будет B. Следующая
команда извлечения (POP) вернет A. Информация возвращается из
стека в порядке, строго противоположном порядку ее помещения в
стек.
Стек противоположен очереди. Очередь - это обычная последова-
тельность, подобная очередям на почте или в магазине. Это
структура данных типа "первым вошел - первым вышел" (first in,
first out: FIFO). Тот, кто первым встал в очередь, первым и
покинет ее. Стек и очередь - очень разные вещи.
Компьютер снабжает стек зарезервированным участком памяти и
указателем, называемым указателем стека. Программа использует ука-
затель стека для того, чтобы фиксировать последние помещенные в
стек данные, в отличие от почты, где сами элементы очереди продви-
гаются вперед по мере движения очереди. В компьютере намного легче
использовать для слежения за данными указатель и при записи или
считывании данных из стека изменять только его. В ответ на
выполнение операций POP и PUSH указатель стека соответственно
увеличивается или уменьшается.
Фиг. 2.19 дает пример стека. В части (a) изображен стек после
того как в него последовательно помещены значения A, B, C. Ука-
затель стека указывает на текущую вершину стека, в данном случае на
C. В части (b) в стек помещается еще одно значение: D. Операция
PUSH уменьшает указатель стека SP (от Stack Pointer - указатель
стека), который теперь указывает на новую вершину D. Указатель
стека всегда фиксирует то, что было последним помещено в стек.
Фиг. 2.19(c) показывает состояние стека после операции POP.
Этой операцией значение D было извлечено из стека. Команда POP
помещает извлеченное из стека значение в указанное место. Если в
части (c) выполнялась команда POP AX, то процессор поместил
значение D в регистр AX (это уже дополнительный аспект, который мы
обсудим в следующей главе). POP увеличивает указатель стека.
Теперь он указывает на новую вершину, C. Заметим, что элементы
извлекаются из стека по описанному принципу LIFO. Последним
помещенным в стек элементом был D и он же первым извлечен из стека.
Обратите также внимание, что D так и осталось в памяти, однако
теперь уже не является частью стека. Логическая граница стека
находится по адресу, хранящемуся в его указателе. В данном случае
вершина стека оказывается ниже ячейки со значением D.
На Фиг. 2.19(d) видно, что происходит с D при помещении в сетк
нового элемента E. Элемент E записывается на место D и становится
новой вершиной стека. Мораль из этой истории такова, что хотя
извлеченные из стека значения могут оставаться в памяти, полагаться
на это не следует.
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
і і і D і<ДД SP і D і і E і<ДД SP
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
і C і<ДД SP і C і і C і<ДД SP і C і
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
і B і і B і і B і і B і
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
і A і і A і і A і і A і
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
(a) (b) (c) (d)
Фиг. 2.19 Пример работы стека
В приведенном примере подразумевался принцип построения стека
процессора 8088. Указатель стека постоянно указывает на текущую
вершину стека. Операция PUSH уменьшает указатель стека, POP
увеличивает его. Стек растет в направлении уменьшения адресов в
памяти. Основание стека располагается в памяти по большему адресу,
чем его вершина. Если вы нарисуете изображения стека с наименьшим
адресом сверху, как на Фиг. 2.19, то вершина стека окажется в
верхней части рисунка.
Мы занялись обсуждением стека потому, что стек используется для
хранения адреса возврата из процедуры. Как это делается?
Каждая команда CALL вызывает как бы выполнению команды PUSH для
стека - сохраняет в стеке адрес возврата. Команда RET извлекает из
стека, подобно команде POP, адрес возврата и помещает его в
указатель команд. 8088 использует стек для хранения адресов
возврата потому, что это позволяет вкладывать процедуры одна в
другую. Что такое вложение? На Фиг. 2.20 показан пример
вложенных процедур.
На Фиг. 2.20 показана абсурдная программа, которую мы
используем как пример вложения процедур. Часть (a) показывает
стек перед выполнением программы. Как только начинает выполняться
процедура MAIN, она вызывает процедуру SUBROUTINE_A. В это время
процессор сохраняет в стек адрес возврата. Часть (b) показывает
адрес возврата 103 помещенным в стек. SUBROUTINE_A в процессе
своего выполнения вызывает SUBROUNINE_B. Команда этого вызова
сохраняет адрес возврата 108 в SUBROUNINE_A. Когда SUBROUNINE_B
заканчивается, команда возврата извлекает из стека значение 108,
как показано в части (d). Процессор помещает это значение в
указатель команд, как требуется при команде возврата. Как видно на
листинге ассемблера, адрес 108 относится к SUBROUNINE_A и следует
сразу за вызовом SUBROUNINE_B. Затем SUBROUNINE_A заканчивается.
Команда возврата извлекает из стека значение 103 для указателя
команд. Адрес 103 относится к процедуре MAIN и следует сразу за
вызовом SUBROUNINE_A.
Наиболее важным в примере на Фиг. 2.20 является вложение
процедур. Одна процедура может вызывать другую, а команда возврата
всегда обеспечивает правильный возврат управления. Единственное,
Microsoft (R) Macro Assembler Version 5.00 11/10/88 23:18:17
Фиг. 2.20 Вызов вложенных процедур Page 1-1
PAGE ,132
TITLE Фиг. 2.20 Вызов вложенных процедур
0000 CODE SEGMENT
ASSUME CS:CODE
0100 ORG 100H
0100 E8 0104 R MAIN: CALL SUBROUTINE_A
0103 40 INC AX
;----- Здесь главная процедура продолжается . . .
0104 SUBROUTINE_A PROC NEAR
0104 43 INC BX
0105 E8 0109 R CALL SUBROUTINE_B
0108 C3 RET
0109 SUBROUTINE_A ENDP
0109 SUBROUTINE_B PROC NEAR
0109 41 INC CX
010A C3 RET
010B SUBROUTINE_B ENDP
010B CODE ENDS
END
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
і і і і і108іДД SP і108і
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
і і і103іДД SP і103і і103іДД SP
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
іxxxіДД SP іxxxі іxxxі іxxxі
ГДДДґ ГДДДґ ГДДДґ ГДДДґ
(a) (b) (c) (d)
Фиг. 2.20 Вызов вложенных процедур
что ограничивает глубину вложения процедур (сколько процедур может
вызывать другие) - это размер стека. Пока в стеке имеется место
для очередного адреса возврата, можно производить вложенный вызов
процедуры. Структура стека LIFO дает гарантию правильной
последовательности возвратов.
Пример программы на Фиг. 2.20 показывает также использование
еще одной псевдооперации ассемблера - PROC. Оператор PROC
используется ассемблером для идентификации процедур. Как мы
дальше увидим, ассемблер должен знать, как далеко располагается
процедура и как возвращаться к точке ее вызова. Операнд NEAR
определяет процедуру как расположенную в пределах легкой
досигаемости вызывающей программы. Мы еще вернемся к оператору
PROC, когда будем обсуждать реальное действие команд CALL и JMP.
Assembler для начинающих
Адресация через базу и смещение
Адресация через базу и смещение
Поскольку вычисление для операнда адреса, состоящего из базы и
индекса, встречается довольно часто, среди способов адресации 8088
есть такие, что позволяют автоматически производить идексирующее
сложение. Вместо выполнения всех вычислений, программа может
определить только величину 2*I и поместить ее в регистр BX. Команда
INC [OPND + BX]
вычисляет исполнительный адрес через сложение адреса базы OPND со
значением индекса в BX. Этой командой достигается тот же самый ре-
зультат что и в предыдущем случае, но меньшим числом команд. Обра-
тите внимание, что в этой команде ассемблеру не требуется подсказка
WORD PTR, потому что ассемблер уже знает, что OPND является пере-
менной типа WORD. Оператор PTR требуется только в тех случаях, ког-
да ассемблер не может определить тип операнда.
Любой из четырех адресных регистров может быть использован в
качестве индекса при базе. Фиг. 3.2 показывает возможные способы
адресации через базу и индекс. Вы видите, что ассемблер допускает
несколько способов записи операции адресации. В группе, состоящей
из пяти команд на Фиг. 3.2, во всех командах адрес базы OPND
складывается с указанным рядом индексным регистром.
Надо отметить, что в команде, содержащей базисный адрес,
регистр не обязательно должен содержать именно значение индекса.
Действительно, поскольку BX называется базисным регистром, кажется
разумным воспользоваться противоположной конфигурацией. В качестве
примера предположим, что программа использует множество разных
векторов с одинаковой длиной и размером элементов. Такую структуру
может иметь, например, классный журнал, в котором векторам
соответствуют наборы оценок за каждую контрольную работу.
Программа, вычисляющая оценку пятого ученика в классе по I-й работе
будет иметь уже известное значение индекса (5) а базу (вектор
данной контрольной работы) - вычисляемую в ходе выполнения
программы.
Индексный регистр может содержать как адрес базы вектора, так и
значение индекса в векторе. Поскольку константное значение в
команде может оказаться и базой и индексом (или вовсе чем-нибудь
известным только программисту), то это значение называют смещением.
Оно соответствует расстояню или смещению от адреса в регистре до
исполнительного адреса, по которому происходит обращение.
Байт MOD RM
Байт MOD R-M
Как же адресная информация передается микропроцессору в машинном
языке? 8088 использует почти для всех операций адресации байт
MOD-R/M (байт режима адресации и регистра/модификатора - прим.
перев.). Фиг.3.4 показывает формат этого байта команды. Байт
MOD-R/M следует за байтом кода операции и определяет один операнд
памяти команды 8088. Этот байт может вместо ячейки памяти указывать
и регистр. Такое единство структуры позволяет реализовать все
возможности адресации операндов.
ЪДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДДї
і і і і
АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
Режим Заданная Регистр-
команда модификатор
Режим Смещение
-------------------------------------------------
00 DISP=0, нет байтов смещения
01 -128
Фиг. 3.4 Байт режима адресации и регистра-модификатора
Первые два бита байта MOD-R/M определяют выбранный способ ад-
ресации. Эти два бита определяют число байт смещения, которые
следуют за байтом MOD-R/M - ни одного, один или два. Последние 3
бита байта MOD-R/M определяют вид адреса - одну из восьми комбина-
ций базисных и индексных регистров. Это поле называется полем R/M -
полем регистра/модификатора. Это те самые 3 бита из колноки R/M на
Фиг.3.3, где показаны возможные комбинации при адресации. Значение
оставшихся 3-х бит в середине байта MOD-R/M зависят от конкретной
команды. Для команды с двумя операндами, вроде ADD, это поле
указывает регистр, являющийся вторым операндом. Для команды с одним
операндом, как INC, эти три бита обычно составляют часть самого
кода операции. 8088 не знает, что имеет дело с командой INC, пока
не расшифрует средние три бита байта MOD-R/M.
Этот же байт используется еще в паре специальных случаев. Если
в команде определен регистр, а не адрес памяти, то в поле режима
помещается код 11B, чтобы сообщить микропроцессору, что поле R/M
содержит код регистра, а не адрес памяти. Наконец, вы возможно
заметили, что в механизме работы с байтом MOD-R/M не предусмотрена
прямая адресация. 8088 рассматривает как прямую адресацию случай
типа [BP + СМЕЩЕНИЕ] при нулевом смещении. В этом случае поле
смещения имеет длину два байта, и в вычислении адреса не участвует
ни один регистр. Из-за этого особого случая доступ к ячейке памяти,
на которую указывает регистр BP, в машинном коде требуется
однобайтовое поле смещения с нулевым значением. Использование в той
же ситуации регистров BX, SI и DI не требует байта смещения. В
следующем пункте будет показано еще одно различие между адресацией
через регистры BP и BX.
База + индекс + смещение
База + индекс + смещение
Программа может также комбинировать вычисляемый адрес базы с
вычисляемым индексом. Как показано на Фиг.3.2, программа может
использовать режим адресации с двумя разными адресными регистрами.
Для формирования исполнительного адреса в команде могут сочетаться
любой из регистров базы (BX и BP) с любым из индексных регистров
(SI и DI). В программе может быть также указано смещение,
добавляемое к сумме значений этих двух регистров. Этот способ
адресации обладает максимальной гибкостью, так как позволяет
вычислять во время выполнения программы и адрес базы, и индексное
значение. Такая возможность не всегда требуется, но доступна в
любой момент.
Пример с классным журналом демонстрирует случай, когда програм-
ма может вычислять и адрес базы, и индекс вектора. Для того, чтобы
определить отметку I-го ученика за J-ю контрольную, потребовалось
бы установить адрес базы на J-й вектор и индексировать его по I-му
элементу.
На Рисунок 3.3 приведена сводка восьми различных способов
адресации, возможных в микропроцессоре 8088. В команде могут
использоваться любые из четырех адресных регистров и смещение либо
комбинация базисного регистра и индексного вместе со смещением.
Смысл колонки, обозначенной R/M, будет объяснен позже.
R/M Адрес операнда
-----------------------------
000 [BX + SI + СМЕЩЕНИЕ]
001 [BX + DI + СМЕЩЕНИЕ]
010 [BP + SI + СМЕЩЕНИЕ]
011 [BP + DI + СМЕЩЕНИЕ]
100 [SI + СМЕЩЕНИЕ]
101 [DI + СМЕЩЕНИЕ]
110 [BP + СМЕЩЕНИЕ]
111 [BX + СМЕЩЕНИЕ]
----------------------------- Фиг. 3.3 Способы адресации 8088
В наборе команд 8088 поле смещения для адресации оптимизирова-
но таким образом, чтобы минимизировать кличество треуемых байт. Ко-
манда может не содержать поле смещения, когда смещение равно нулю.
Если смещение находится в диапазоне от -127 до 127, то для него
достаточно одного байта. Когда же для его обозначения требуется
полное 16-битовое поле адреса, то поле смещения будет занимать два
байта. Таким образом, поле смещения может по необходимости иметь
длину 0, 1 или 2 байта. Когда оно имеет длину один байт, двоичное
число перед форимроваием адереса получает распостранение знака. Это
означает, что процессор перед выполнением сложения помещает старший
бит смещения в старшие 8 бит 16-битового значения. Это позволяет
представлять отрицательные смещения в одном байте. Самое лучшее
здесь то, что ассемблер сам определяет нужную длину и выбирает
правильную и наиболее короткую команду для выполнения этой работы.
Но не смотря даже на все эти возможности адресации, набор
команд 8088 допускает только по одному операнду памяти в одной
команде. Двухоперандная команда ADD позволяет складывать либо
регистр с ячейкой памяти, либо два регистра. В одной команде нельзя
сложить две ячейки памяти. Это и означает, что команда содержит
только один адрес памяти.
Дополнительный флаг переноса
Дополнительный флаг переноса
Возможно, вам никогда не придется пользоваться флагом
дополнительного переноса (AUX), по крайней мере непосредственно.
Изучив команды условных переходов микропроцессора 8088, вы увидите,
что прямое тестирование этого флага невозможно. Микропроцессор
имеет флаг дополнительного переноса AUX для очень конкретной цели:
он позволяет микропроцессору выполнять десятичные вычисления в
двоичной кодировке (Binary-coded-decimal arithmetic - BCD - арифметика).
BCD - арифметика отличается от той, которую мы обсудили в гл.2.
Она основана на десятичной системе счисления. При осуществлении
десятичной арифметики в двоичной кодировке любая дасятичная цифра
представляется четырьмя битами (полубайтом). Каждый полубайт может
представлять значения от 0 до 9; значения от 0AH до 0FH не
используются. Это означает, что один байт может представлять
десятичные числа от 0 до 99.
На первый взгляд такой способ хранения числовой информации
кажется расточительным, двоичная кодировка десятичныч чисел не
использует 6 из 16 возможных состояний каждого полубайта. Однако во
многих случаях применения микропроцессора этот способ дает
непосредственное представление вводимых оператором чисел.
Большинство людей более подготовлено к работе с десятичными, а не с
двоичными или шестнадцатеричными числами. Как только потребности
ввода-вывода могут перевесить аспекты хранения и вычислений в
приложениях, так удобнее становится выстраивать информацию в виде,
легко преобразуемом к формату ввода-вывода. Двоичная кодировка
десятичных чисел дает такую возможность. BCD - арифметика также
решает возникающую в некоторых двоичных системах проблему
представления чисел. При использовании чисел с плавающей точкой
иногда возникает проблема округления, потому что двоичная система
счисления не полностью соответствует десятичной системе. В
некоторых случаях последняя цифра в результате простого вычисления
может оказаться неверной. Иногда это называют проблемой "потери
пенни", так как эти случаи наиболее заметны в финансовых
приложениях. BCD - представление чисел дает альтернативу для таких
вычислений.
Набор команд микропроцессора 8088 не имеет специальных команд
сложения и вычитания для BCD-арифметики. Здесь используется обычное
сложение и вычитание, применяемые как и при обычном двоичном
представлении. Результат сложения двух BCD-чисел на обычном
математическом процессоре может оказаться неправельным BCD-числом;
Для устранения этого микропроцессор 8088 имеет несколько команд,
которые преобразуют результат к правильной двоичной кодировке
десятичного числа. Для корректного проведения этого преобразования
процессор должен знать, был ли перенос из младшего полубайта в
старший во время сложения. Пример на Фиг. 3.13 показывает, почему
38
+ 29
----
61 Фиг. 3.13 Десятичное сложение
это необходимо. Десятичная сумма 38 + 29 равна 67. Но двоичная
сумма 38H + 29H равна 61H. Если числа 38 и 29 представляют
BCD-значения, то после операции сложения программа должна выполнить
коррекцию. Команда десятичной коррекции для сложения (DAA)
преобразует число в форму BCD. В нашем случае число представлено в
десятичной форме, т.е. оба полубайта находятся в диапазоне от 0 до
9, однако результат неверен. В данном случае сложение устанавливает
флаг дополнительного переноса, который показаывает, что произошел
перенос из младшей цифры (8 + 9). Этот флаг сообщает команде DAA,
чтобы она прибавила к результату 6, давая правильный результат 67.
Микропроцессор также использует флаг дополнительного переноса
при коррекции десятичной арифметики вслед за вычитанием с помощью
команды десятичной коррекции для вычитания DAS. Существуют также
две другие команды, которые используют флаг AUX для определения
правильности действий. Эти команды, символьная коррекция для
вычитания AAS и символьная коррекция для сложения AAA, выполняют ту
же BCD-коррекцию, что и команды DAA и DAS. Команды AAA и AAS
используются в программах для работы с таким представлением чисел,
при котором каждая десятичная цифра занимает один байт. Это
представление, еще более расточающее память, чем BCD, допускает
очень удобную перекодировку из кода ASCII в числовое представление
и обратно. Числа от 0 до 9 представляются в коде ASCII значениями
от 30H до 39H, и преобразование в этот код и обратно просто
означает сложение или вычитание 30H. В следующей главе
рассматривается использование команд десятичной и сомвольной
коррекции.
Физическая адресация
Физическая адресация
Все, что до сих пор говорилось об адресации, относится к генерации
так называемого смещения (offset) адреса. Смещение имеет 16-битовое
значение. 8088 сегментирует память таким образом, что можно адресо-
ваться к памяти большей чем 64K. В этом пункте бует показан способ
сегментауии 8088.
Поскольку размер слова в микропроцессоре 8088 равен 16 бит, для
него естественно генерировать адреса в 16 бит длиной. Это делает
доступными для прямой адрессации 2**16 элементов или 65 535 байт
памяти. Однако для некоторых программ 64K ячеек памяти
недостаточно. Поэтому фирма INTEL сконструировала 8088 для
адресации 2**20 байт или одного мегабайта памяти.
Для получения 20-битовой адресации требуется еще четыре бита к
имеющимся 16-ти. Добавочные 4 бита адресной информации берутся из
сегментных регистров. Сегментные регистры сами имеют размер 16 бит.
8088 комбинирует 16-битовый адрес смещения и 16-битовый регистр
сегмета как показано на Фиг.3.5. Процессор дополняет сегментный ре-
гистр 4-мя нулевыми битами, что составляет вместе полное 20-битовое
значение. К расширенному значению сегмента процессор добавляет ад-
рес сммещения, определяемый через вычисление адреса. 20-битовый ре-
зультат является указателем на исполнительный адрес.
ЪДДДДДДДДДї
і Сегмент і 0000
АДДДДДДДДДЩ
ЪДДДДДДДДДДДї
+ і Смещение і
АДДДДДДДДДДДЩ
----------------
ЪДДДДДДДДДДДДДДДДї
і20-битовый адресі
АДДДДДДДДДДДДДДДДЩ
Фиг.3.5 Вычисление адреса с сегментом и смещением
Каждая обращающаяся к памяти команда может сформировать только
16-битовый адрес операнда. В действительности процессор применяет
этот адрес внутри определенного сегмента. Фиг. 3.6 иллюстриирует
такой способ применения сегментации.
ЕДДДДДДДДДДДДДДДДДДґ ДДДДДВДДДДСегментный регистр
іі і і
іі і смещение
іі і і
Сегмент іГДДДДДДДДДДДДДДДДДДЕ ДДДДДБДДДДАдресуемая область
64К ГДДДДДДДДДДДДДДДДДДґ
іі і
іі і
іі і
ЕДДДДДДДДДДДДДДДДДДґ
і і
Фиг.3.6 Сегментация.
Начальный адрес сегмента всегда имеет нули в младших четырех
битах. Адрес с этим свойством имеет каждая шестнадцатая ячейка
памяти. Конструируя расположение данных в своей программе помните,
что сегмент всегда должен приходиться на такую 16-битовую границу.
Эти границы называются также границами параграфов.
Флаг четности
Флаг четности
Флаг четности (PF) показывает, является ли число едениц результата
последеней операции четным. Четность - это способ контроля значений
данных. Бит четности - это дополнительный бит, который проверяет
значения других бит. Программа может использовать флаг четности для
определения, в какое значение следует установить бит четности.
Четность используется в оперативной памяти IBM PC для контроля
сохраненных в ней данных. Такой контроль четности выполняется
непосредственно аппаратными средствами и не влияет на бит флага
четности.
Флаг четности устанавливается в "1", если результат операции
имеет четное число едениц и переустанавливается в "0", если число
едениц результата нечетно. В обычных арифметических и логических
операциях знак четности использкется мало.
Флаг направления
Флаг направления
Последним флагом в регистре флагов является флаг направления DF.
Набор команд микропроцессора 8088 содержит несколько команд
обработки строк, которые работают с большими блоками данных. Эти
команды обработывают блоки данных побайтно или по одному слову
памяти за раз. Индексные регистры указывают на блоки данных. После
обработки байта или слова процессор изменяет индексный регистр так,
чтобы он указывал на следующий элемент блока.
Строковые операции используют флаг направления для определения
направления продвижения по блоку данных. Если флаг направления
сброшен в 0, команды обработки строк увеличивают значение
индексного регистра, а если флаг направления установлен в 1, то они
уменьшают это значение. Флаг направления позволяет одному набору
строковых команд обслуживать оба направления в зависимости от
установки флага. В некоторых случаях желательно пересылать строку с
увеличением адресов, а в других лучше всего использовать уменьшение
адреса.
В качестве примера предположим, что в программе используется
команда пересылки строк для пересылки блока данных на новое место.
Если программа пересылает блок, с большего адреса памяти на
меньший, она сбрасывает флаг направления, чтобы увеличивать
значения индексных регистров после каждой пересылки; если же
пересылка производится на больший адрес памяти, флаг направления
устанавливается в 1, показывая уменьшение индексных регистров. В
случае большинства пересылок не имеет значения, как именно
установлен этот флаг. Но если конечное положение блока перекрывает
его начальное положение, а флаг направления уствновлен неверно, то
информация в блоке будет во время пересылки испорчена.
Рисунок 3.16 иллюстрирует пример пересылки блоков. Исходный
блок данных имеет длину 200H байт и расположен от 300H до 4FFH.
Нужно переслать его на новое место, расположив от 400H до 5FFH;
исходное и результирующее поля перекрываются.
300 ГДДДДДДДДДДДЕДДД Указатель 300 ГДДДДДДДДДДДґ
і і источника і і
і і SI і і
і і і і
Источник 400 ГДДДДДДДДДДДЕДДД Указатель 400 ГДДДДДДДДДДДґ
і і назначения і і
і і DI і і
і і і і
Назначение 500 ГДДДДДДДДДДДґ 500 ГДДДДДДДДДДДґ<-- Указатель
і і і і источника
і і і і
і і і і
600 ГДДДДДДДДДДДґ 600 ГДДДДДДДДДДДґ<-- Указатель
Фиг. 3.16 Флаг направления
В примере на Фиг.3.16(а) указатели источника и результата
установлены на начала соответствующих блоков: указатель источника
на 300H, а указатель результата на 400H. Флаг направления в примере
сброшен, так, чтобы указатели увеличивались после каждой пересылки.
Как показано на рисунке, после пересылки с помощью строковой
операции 100H байт, указатель источника переместится на блок
результата, а эта область блока уже заполнена данными после
пересылки. Пересылка последних 100H байт будет неправильной, так
как потеряны исходные данные блока.
В части (b) примера указатели и источника, и результата
установлены на концы блоков. Флаг направления установлен так, что
содержимое указателей уменьшается после пересылки. При таком
способе данные пересылаются верно.
Программы ввода-вывода для IBM PC дают характерный пример
использования флага направления для перемещения изображения на
экране дисплея. Программа ввода-вывода использует команды пересылки
строк микропроцессора 8088 для пересылки данных внутри буфера
дисплея. Когда программа передвигает изображение на экране вверх,
команды пересылают данные в меньшие адреса памяти. Когда программа
опускает символы на экране вниз, команды пересылают данные в
большие адреса памяти. В каждом случае программа устанавливает или
сбрасывает флаг направления в соответствии с направлением
пересылаемых данных.
Флаг нуля
Флаг нуля.
Флаг нуля (ZF) показывает, что результат последней операции
равнялся нулю. Этот флаг используется в программах для проверки
двух чисел на равенство. Допустим, в программе вычитаются два
числа. Результат будет нулевым, если значения идентичны и не
нулевым, если они различны.
Флаг переноса
Флаг переноса
Флаг переноса (CF) служит для поддержания процессором многоразряд-
ной арифметики. Обычно при выполнении арифметических операций вроде
сложения или вычитания 8088 может работать с не более чем
16-битовыми числами. Однако в некоторых случаях приходится
манипулировать с числами превышающими 2**16. Например, для сложения
двух 32-битовых чисел программе придется сложить сначала младшие
части чисел, а затем - старшие. На Фиг 3.11 показано сложение
32-битовых чисел 22223333H и 44445555H.
В этом примере складываются сначала младшие 16-битовые згначе-
ния с получением результата 8888H. Затем складываются старшие 16-
второе сложение первое сложение
---------------------------------------
2222 3333
4444 5555
---- ----
6666 8888 Фиг. 3.11
--------------------------------------- 32-битовое сложение
битовые значения с результатом 6666H. 32-битовый результат равен
66668888H. Для получения 32-битового результата требуется два
16-битовых сложения. Для 48-битового числа потребуется уже три
16-битовых сложения и т.д. Для выполнения сложения программа должна
расчленять каждое большое число на 16-бтовые куски.
Однако, приведенный пример достаточно прост. Результат первого
16-битового сложения не влиял на второе. В общем же случае сложения
возможен перенос из одной позиции в другую. Когда процессор
выполняет слоожение двух 16-битовых чисел, он автоматически выпол-
няет переносы. Когда же программа складывает два 32-битовых числа,
как в нашем примере, то ей приходится запоминать перенос в первом
сложении и использовать его при сложении вторых 16-битовых
значений. На Фиг. 3.12 показано сложение чисел 22224444H и
3333EEEEH. В этом примере перенос от первого сложения отражается на
втором сложении.
втоорое сложение первое сложение
--------------------------------------------
2222 4444
3333 EEEE
1 (перенос от первого)
---- -----
5556 13332
--------------------------------------------
Фиг. 3.12 32-битовое сложение с переносом
Первое 16-битовое сложение 4444H и EEEEH дает результат 13332H.
Поскольку результат имеет длину 17 бит, он не может быть помещен в
16-битовый регистр. Флаг переноса регистра состояний примет этот
дополнительный бит арифметической информации. При втором 16-битовом
сложении складываются не только числа 2222H и 3333H, но и значение
флага переноса. Существует две формы команды сложения: команда ADD
складывает два 16-битовых числа, давая 17-битовый результат, а
команда сложения с переносом ADC складывает два 16-битовых числа и
значение флага переноса, давая также 17-битовый результат. В
примере на Рисунок 3.12 для первой операции сложения использовалась
команда ADD, а для второй операции сложения команда ADC.
Оба приведенных примера используют флаг переноса для выполнения
арифметики повышенной точности. В первом примере после сложения
3333H и 5555H получился нулевой перенос; когда команда ADC
прибавляет значение переноса к числам 2222H и 4444H, получается
правильный результат 6666H. Во втором примере флаг переноса был
установлен,так как был перенос из младшей части суммы в старшую.
В случае арифметики еще большей точности программа может снова
и снова использовать в процессе сложения флаг переноса. Каждое
16-битовое сложение устанавливает флаг переноса в соответствии с
его результатом, а программа может сложить следующие по старшинству
части чисел с полученным значением флага переноса. В каждом случае
флаг переноса содержит семнадцатый бит предыдущего результата, и
программа должна использовать это значение при сложении следующих,
более старших, частей чисел.
Флаг переноса служит и для другой важной цели. Когда программа
выполняет вычитание, существует возможность заема из одной позиции
в другую. Флаг переноса показывает при вычитании необходимость
заема из одной части числа в другую.
Вычитать целые числа, в несколько слов длиной программа может
таким же путем, как и складывать. Сначала вычитаются младшие части
чисел, с получением 16-битового результата. Команда вычитания (SUB)
устанавливает флаг переноса, отражая заем. Следующее 16-битовое
вычитание программа выполняет с заемом. Команда вычитания с заемом
(SBB) наряду с обычным вычитанием вычитает из результата флаг
переноса. Как и при сложении программа может осуществлять вычитание
целых чисел произвольной длины, используя флаг переноса в качестве
значения заема.
Микропроцессор 8088 трактует флаг переноса как истинный заем,
то есть если в результате вычитания появляется заем, микропроцессор
устанавливает флаг переноса равным 1. Он показывает, что программа
должна вычесть 1 из результата вычитания старших частей чисел. Если
заема нет, процессор сбрасывает флаг переноса на ноль. Это
означает, что программе не нужно вычитать 1 из старшей части числа.
Микропроцессор устанавливает флаг переноса как индикатор заема
при расширенном вычитании. Наряду с расширением точности программа
может сипользовать флаг переноса для определения соотношения двух
чисел. Если флаг переноса установлен, вычитаемое значение больше
уменьшаемого; если флаг переноса не установлен, вычитаемое значение
меньше или равно уменьшаемому. Это означает, что флаг переноса
становится первичным индикатором при определении соотношения двух
чисел: после того, как программа вычитает два числа, флаг переноса
указывает, какое из них больше. Таким способом программа может
проверять целые числа без знака, включая такие приложения, как
сортировка строк символов. В случае чисел со знаком для определения
соотношения чисел программе нужна дополнительная информация. В
следующей главе в разделе "Условные переходы" обсуждаются все
способы тестирования чисел.
Флаг переполнения
Флаг переполнения
Флаг переполнения OF - единственный флаг в старшем байте регистра
флагов, который устанавливается обычными арифметическими
операциями. Остальные флаги старшего байта находятся под прямым
управлением программиста. Флаг переполнения - еще один
арифметический флаг, как флаг нуля и переноса. Флаг переполнения
необходим для арифметики в дополнительном коде в такой же степени,
как флаг переноса для арифметики повышенной точности.
В арифметике чисел, представленных в дополнительном коде,
старший бит используется для хранения знака числа. Сумматор
микропроцессора работает как со знакопеременными числами, так и
беззнаковыми. Обычно сложение чисел со знаком дает верный
результат. Однако накоторые из чисел, представленных в дополни-
тельном коде, при сложении дают неверный результат. Пример на Фиг.
3.14 - сложение двух 8-битовых чисел, представленных в коде
двоичного дополнения, - иллюстрирует этот случай. Если 72H и 53H -
Шестнадцатеричное Десятичный эквивалент
------------------------------------------------
72H 114
+ 53H + 83
------ ------
0C5H 197
------------------------------------------------
Фиг. 3.14 8-битовое сложение
с переполнением
числа без знака, то результат их сложения верен. Если же это числа,
представленные в дополнительном коде со знаком, то результат
сложения 0C5H неверен, в коде двоичного дополнения он равен -59.
сложение положительных чисел никогда не дает в результате отри-
цательное. Результат сложения оказался непредставиммым в диапазоне
значений 8-битовых чисел в двоичном коде (от 127 до -128). Этот
эффект принято называть переполнением, так как сумма вышла за
пределы диапазона чисел, представимых в дополнительном коде.
Важно заметить, что переполнение и перенос - два различных
флага и имеют разное значение. На Фиг. 3.14 нет переноса, так как
сложение без знака дает правильный результат, а есть переполнение:
что сложение со знаком дает неверный результат. Возможен и случай
одновременно переноса и переполнения, как показано на Фиг. 3.15.
Шестнадцатеричное Двоичное дополнение Беззнаковое
------------------------------------------------------------
8EH -114 142
0ADH - 83 173
----- ----- ----
1 3BH -197 315
------------------------------------------------------------
Фиг. 3.15 8-битовое сложение с переносом и перепонением
Здесь показан пример сложения двух отрицательных чисел. Результат
-197 выходит за пределы диапазона представимости в дополнительном
коде. Это показано тем, что 8-битовый результат 3BH - положительное
число. Кроме того в этом примере устанавливается флаг переноса,
означающий, что сложение без знака дало число, большее
максимального представимого. В случае 8-битовых чисел максимальное
число равно 255.
Вообще говоря, операция сложения в процессоре
выполняется одинаково над числами и со знаком, и без знака, а также
с десятичными числами. Флаги переноса, дополнительного переноса и
переполнения содержат информацию о выполненной операции, позволяя
программе определить верный результат в используемой системе
счисления. Флаг переполнения показывает, что результат
арифметической операции выходит за пределы диапазона чисел,
представленных в дополнительном виде. Переполнение отличается от
переноса, который показывает, что произошел перенос из старшего
бита результата.
Флаг прерываний
Флаг прерываний
Флаг прерываний IF управляет внешними прерываниями. Во время
выполнения тех фрагментов программы пользователя, где внешние
прерывания разрешать нежелательно, программа может сбросить флаг
прерываний. Пока флаг прерываний сброшен в 0, никакие внешние
прерывания не смогут возникнуть. Когда программа устанавливает флаг
прерываний равным 1, внешние устройства могут порождать прерывания.
Управляет флагом прерываний программа пользователя.
IBM PC использует несколько методов обслуживания прерываний.
Флаг прерываний регистра состояния блокирует все внешние
прерывания, за исключением прерываний, вызванных ошибками памяти.
Для тех случаев, когда программе надо заблокировать только
некоторые из прерываний, существует отдельный регистр масок
прерываний. Этот регистр может запретить или разрешить отдельные
внешние прерывания. В гл.8, описывающей аппаратуру IBM PC, будет
рассмотрено использование этого регистра.
Флаг захвата
Флаг захвата
Флаг захвата (специального прерывания) TF помогает при отладке
программ. Этот флаг устанавливается не в результате работы
микропроцессора, а - программой, с помощью специальной команды.
Этот флаг называется также флагом трассировки или шага.
Когда этот флаг установлен, после выполнения каждой команды
возникает прерывание. Эффект при этом такой же, как если бы после
каждой команды некоторое внешнее устройство запрашивало прерывание.
Прерывание по трассировке передает управление в ячейку,
определенную вектором прерывания 4. Во время процедуры прерывания
микропроцессор сбрасывает флаг специального прерывания. Это
позволяет программе обработки прерывания по трассировке избежать
прерывания после каждой команды. Когда обработчик прерывания по
трассировке возвращает управление программе пользователя, он
восстанавливает начальное состояние регистра флагов, в котором флаг
трассировки установлен. Микропроцессор выполняет следующую команду
пользователя, и снова возникает специальное прерывание. Обработчик
прерываний по трассировке получает управление после каждой команды
до тех пор, пока программа пользователя не сбросит флаг захвата.
Отладчик DOS использует флаг трассировки. Одной из функций
отладчика является пошаговое выполнение, при котором перед каждым
возвращением управления к отладчику выполняется одна команда
программы пользователя. Это прерывание инициируется флагом захвата.
Полное описание процедуры прерывания дано в разделе "Векторы
прерываний".
Флаг знака
Флаг знака
Флаг знака (SF) показывает, является результат последней арифмети-
ческой операции положиельным или отрицательным. Установка бита
знака отражает значение старшего бита последнего результата. Если
последний результат отрицателен, то бит знака устанавливается в
еденицу, а если положительный или нулевой, то - в ноль.
специальные ячейки памяти, называемые регистрами.
Модель программирования 8088
Для того, чтобы понять 8088 и научиться программировать для него,
мы начнем с его внутреннего устройства. Внутри процессора имеются
специальные ячейки памяти, называемые регистрами. В регистрах можно
ЪДДДДДДДДДДДДДВДДДДДДДДДДДДДї
AX і AH і AL і
і і і
BX і BH і BL і
і і і Регистры общего
CX і CH і CL і назанчения
і і і
DX і DH і DL і
АДДДДДДДДДДДДДБДДДДДДДДДДДДДЩ
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і SI і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і DI і Адресные регистры
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і BP і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і SP і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і IP і Регистры управления
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і FLAGS і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і CS і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і DS і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ Сегментные регистры
і ES і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і SS і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ Фиг. 3.1 Регистры 8088
сохранять данные-операнды или адреса памяти. Поскольку регистры
расположены внутри самого процессора, он имеет очень быстрый доступ
к находящимся в них данным, намного более быстрый, чем к данным в
памяти. Если в программе требуется быстрыый доступ к какой-либо
переменной, то хранение ее значения в регистре ускоряет выполнение.
Набор регистров 8088 состоит из нескольких групп. Все регистры
8088 показаны по группам на Фиг 3.1.
Оператор Assume
Оператор Assume
После того как сегменты в программе определены, ассемблер должен
узнать, как будут установлены сегментные регистры во время
выполнения программы. В примере на Фиг. 3.9 всего три сегмента, но
болшая программа может иметь намного больше. Располагая всего
четырьмя сегментными регистрами, большая программа может адресо-
ваться одновременно только к части доступных сегмментов. Ассемблеру
необходимо сообщить, к каким именно сегментам происходит адресация
во время выполнения. Это делается с помощью оператора ASSUME
который описывает для ассемблера установки сегментных регистров.
Программист должен связать каждый сегментный регистр с тем
сегментом, на который тот в данный момент указывает.
Фиг. 3.9 иллюстрирует такие сегментные операции. В этом примере
имеется три сегмента: DATA, BUFFER и CODE. Имена для них выбраны
произвольно. Их выбирает программист, а для ассемблера они не имеют
значения. Например, вы можете назвать сегмент именем CODE, а
использовать его только для данных и наооборот. Лучше всего, конеч-
но, называть сегменты так, чтобы их имена имели какой-то смысл в
данной программе. В нашем примере сегменты DATA и BUFFER оба имеют
внутри ячейку данных. Вряд ли реальная программа будет задавать
сегмент лишь с одной ячейкой памяти, но сейчас это служит для
примера. Если программа обращается к данным во многих участках
адресуемого в 8088 пространства, то ей требуется много определений
сегментов. Например, программа управления устройствами доступа IBM
PC может обращаться к памяти в системной области данных,
устанавливать векторы прерываний в начале памяти и выполняться как
программа в любом другом месте. Каждая из этих областей является
сегментом и должна быть определена в программе.
Утверждение ASSUME на Фиг. 3.9 предписывает ассемблеру работать
с учетом следующей установки сегментных регистров: регистр CS со-
держит начальный адрес сегмента CODE, регистр DS указывает на DATA,
а регистр ES определяет сегмент BUFFER. Утверждение ASSUME
(полагать, считать - прим. перев.) означает именно то, что оно
предписывает ассемблеру. Ассемблер обрабатывает исходный текст
программы предполагая, что сегментные регистры установлены как
указано в этом утверждении. Установка сегментных регистров, сделан-
ная в этом утверждении, остается при ассемблировании в силе пока
другое такое же утверждение не определит новые установки. Ассемблер
обрабатывает эти утверждения последовательно, даже если программа
ветвится и закручивается в циклы. Утверждение ASSUME остается в
силе, пока ассемблер не встретит при последовательном просмотре
программы следующее. Заметим, что в утверждении ASSUME не обязано
определять все сегментные регистры. В нашем примере не объявлен
регистр SS. На практике содержимое сегментного ргистра может быть
временами и неизвестно в программе. В этих случаях утверждение
ASSUME должно указывать сегмент NOTHING. Например, утверждение
ASSUME ES:NOTHING
сообщает ассемблеру, что программа не знает, куда указывает допол-
нительный сегментный регистр. Поскольку значение регистра неизвест-
но, ассемблер не должен использовать его в адресных вычислениях.
Важно отметить, что утверждение ASSUME не генерирует команд ма-
шинного языка. Это директива ассемблеру полагать, что сегментные
регистры установлены в соответствии с указанной в этом утверждении
информацией. Добиться правильное установки сегментов - забота
программиста. Аналогично, ассемблер не может проверить, что утверж-
дение ASSUME при выполнении будет соответствовать содержимому
сегментных регистров. Из-за того, что программа может прийти к
любому конкретному ASSUME множеством разных путей, за его
корректность отвечает программист. В нашем примере предполагается,
что сегментные регистры устанавливаются до выполнения данного кус-
ка программы. Если они установленыы неверно, то программа будет вы-
полняться неправильно даже если ассемблирование прошло успешно.
Первая команда увеличивает значение VAR1, находящейся в сегмен-
те данных. Ассемблер полагает, что регистр DS указывает на сегмент
DATA в соответствии с утверждением ASSUME. Поскольку регистр DS
предполагается при использовании данных по умолчанию, то для этой
команды ассемблер не генерирует сегментный префикс. Сформированная
этой инструкцией 4-байтовая машинная команда не содержит сегментно-
го префикса.
Вторая команда определяет переменную VAR2, которая находится в
сегменте названном BUFFER. Программа сообщила ассемблеру, что
дополнительный сегментный регистр указывает на сегмент BUFFER. Для
увеличения VAR2 ассемблер генерирует четырехбайтовую команду машин-
ного языка, но ей предшествует команда с однобайтовым префиксом,
которая отменяет использование регистра DS в этой команде.
Префиксный байт 26H говорит процессору использовать при создании
20-битового адреса памяти регистр ES. В колонке объектных кодов на
листинге ассемблер отмечает префиксную команду двоеточием.
Третья команда изменяет переменную VAR3 в сегменте CODE.
Утверждение ASSUME связывает этот сегмент с регистром CS. Ассемблер
автоматически генерирует соответствующий префикс переназначения
сегмента. В данном случае префикс 2EH предписывает процессору
использовать при вычислении испольнительного адреса регистр CS.
Вначале утверждение ASSUME покажется излишеством. В первое
время при написании программы естественно забывать о его примене-
нии. Ассемблер выдаст массу сообщений об ошибках чтобы помочь вам в
вашей забывчивости. Но при достаточном опыте, утверждение ASSUME
помогает программисту ассемблера сосредоточиться на структурах дан-
ных в программе. Программист должен не забывать устанавливать
сегментные регистры для адресации требуемых для программы данных.
Ассемблер облегчит бремя запоминания для каждой команды, где
располагаются данные и какой сегментный регистр должен быть
использован чтобы попасть к ним.
Программа может использовать утверждение SEGMENT для передачи
информации другим программам. Оператор SEGMENT может задавать
выравнивание сегмента в памяти, способ его комбинирования с други-
ми сегментами и имя его типа. Для программистов IBM PC особый
интерес представляют два вида выравнивания сегментов. Выравнивание
по параграфам (тип PARA) размещает начало сегмента с начала
параграфа - ячейки памяти, адрес которой в памяти кратен 16-ти .
Это означает, что первый байт сегмента будет иметь смещение 0
относительно значения сегментного регистра. Выравнивание по байтам
(тип BYTE), наоборот, размещает сегмент в любом месте памяти. В
этом случае сегментный регистр может и не указывать на первый байт
сегмента. В программе может потребоваться ненулевое смещение для
доступа к началу сегмента.
Различные способы связывания сегментов задает параметр типа
связи. Особенно это полезно при модульном программировании.
Описание PUBLIC приводит к объединению всех сегментов с одинаковыми
именами в один большой сегмент. Например, можно объединить все
сегменты кодов. Это приведет к соединению разных подпрограмм в их
собственных модулях с главной процедурой. Другой полезный тип связи
- AT, при указании которого в сочетании с адресным выражением,
сегмент располагается по заданному абсолютному адресу. Такое
объявление необходимо при работе с данными в фиксированном месте,
например, с векторами прерываний в начале памяти.
Намного более полное описание описание утверждения SEGMENT
можно найти в справочном томе к макроассемблеру IBM PC. Некоторые
из возможностей опертора SEGMENT мы будем использовать далее в
примерах.
Оператор Segment
Оператор Segment
Решить проблему адресации сегментов поможет ассемблер. В одной из
своих частей программа на языке ассемблера должна определить
составляющие ее сегменты. Кроме того специальные команды сообщают
ассемблеру, какие сегменты с каким регистром связаны. Благодаря
этому ассемблер может, когда требуется, определить какой сегментный
префикс нужен в коде команды. Если программист задает ссылку, не
связанную с регистром DS, но доступную через другой сегментный
регистр, то ассемблер сам сформирует правильный префикс. Это
позволяет программисту работать непосредственно с данными и текстом
программы, оставив ассемблеру работу по осуществлению адресации.
Объявление сегментов позволяет ассемблеру следить за тем, какие
сегменты доступны через сегментные регистры и определять возможные
ошибки. Например, в программе могут появиться переменные, которые
недоступны из-за того, что на сегмент этой переменной не указывает
ни один из сегментных регистров. Ассемблер квалифицирует это как
Microsoft (R) Macro Assembler Version 5.00 1/1/80 03:53:05
Фиг. 3.9 Сегменты Page 1-1
PAGE ,132
TITLE Фиг. 3.9 Сегменты
0000 DATA SEGMENT
0000 01 VAR1 DB 1 ; Переменная в сегменте DATA
0001 DATA ENDS
0000 BUFFER SEGMENT
0000 02 VAR2 DB 2 ; Переменная в сегменте BUFFER
0001 BUFFER ENDS
0000 CODE SEGMENT
0000 03 VAR3 DB 3 ; Переменная в сегменте CODE
ASSUME CS:CODE, DS:DATA, ES:BUFFER
0001 FE 06 0000 R INC VAR1 ; Переменная из сегмента DATA
0005 26: FE 06 0000 R INC VAR2 ; Переменная из сегмента BUFFER
000A 2E: FE 06 0000 R INC VAR3 ; Переменная из сегмента CODE
000F CODE ENDS
END
Фиг. 3.9 Сегменты
ошибку. Она возникает из-за того, что в программе не обеспечена
адресуемость. Это ограничение, но лучше обнаружить ошибку при
ассемблировании, чем во время работы программы.
Оператор SEGMENT определяет все сегменты, давая каждому из них
имя. Программа на Фиг. 3.9 демонстрирует определение нескольких
сегментов. В качестве имени сегмента может использоваться любое
допустимое имя переменной. Утверждение SEGMENT сообщает ассемблеру,
что все следующие команды и данные во время выполнения программы
будут находиться в этом сегменте. Оператор ENDS указывает конец
текущего сегмента. В этом утверждении тоже указывается имя сегмен-
та. Каждому утверждению SEGMENT должно соответствовать утверждение
ENDS. В противном случае ассемблер запутается и выдаст сообщение об
ошибке.
Предназначение сегментов
Предназначение сегментов
Каждый из сегментных регистров имеет свое, отмеченное выше назна-
чение. В некоторых случаях, однако, более удобна связь с данными
вне сегмента данных, например, с небольшой областью данных в
программе. В большинстве случаев программа работает с данными в той
области, на которую указывает регистр DS, но иногда программе
требуется ссылка на локальную переменную, находящуюся в кодовом
сегменте программы. Чтобы осуществить эту ссылку, приходится
изменять обычное использование сегментов. Фиг.3.8 показывает такую
органзацию программы.
ГДДДДДДДДДДДДДДДДДДДЕДДДДДД CS
і Программа и і
і і
і локальные данные і
і ГДДДДДД CS:LOCAL_VALUE
ГДДДДДДДДДДДДДДДДДДДґ
і і
і і
і і
ЕДДДДДДДДДДДДДДДДДДДЕДДДДДД DS
і Основная і
і і
і область даных і
і і
ГДДДДДДДДДДДДДДДДДДДґ
і і
Фиг. 3.8 Переназначение CS на локальные данные.
Вместо изменения значения регистра DS, чтобы он указывал на
программный сегмент, команда изменяет ссылку на данные, показывая,
что переменная расположена в сегменте кодов (Code Segment).
INC CS:LOCAL_VARIABLE
Это делается с помощью префикса "CS:". В машинном языке команда
переопределения сегмента выглядит как однобайтовый префикс перед
обычной командой машинного языыка. 8088 понимает этот префикс пере-
назначения сегмента и изменяет обычный способ вычисления адреса.
Вместо регистра DS процессор использует для вычисления физического
адреса данных регистр CS. Одного префикса в команде всегда
достаточно, так как 8088 может адресоваться в ней не более чем к
одной ячейке памяти.
Для нормального обращения к данным команда может использовать
любой из четырех сегментных регистров. Регистр DS используется по
умолчанию, то есть когда в команде не указан другой сегментный
регистр, то используется DS. Помните, что при использовании в
адресных вычислениях регистра BP сегментом по умолчанию становится
стековый сегмент. Команда может определить и любой из трех других
сегментных регистров, указав его в адресном выражении. Некоторые
команды, правда, не могут пользоваться переназначением сегментов.
Это - команды обработки строк. Строковая команда определяет
использование регистров неявным образом, и оно не может быть
изменено. В главе 4 мы обсудим строковые команды и их специфическое
пользование сегментами.
Прямая адресация
Прямая адресация
Простейший способ определить операнд в памяти - даать имя ячейке
памяти. В дальнейшем программа использует это имя в командах для
ссылки на соответствующий участок памяти. Вот как этот метод
используется в команде
INC OPND
В примере команда ассемблера
OPND DW ?
объявляет OPND участком памяти длиной в слово. Когда программа
ипользует OPND в качестве операнда, ассемблер помещает адрес OPND в
машинную команду. В данном примере вы можете видеть адрес 0123 как
часть команды в объектном коде. Этот способ называют прямой
адресацией, поскольку команда непосредственно в себе содержит адрес
операнда.
Регистр флагов
Регистр флагов
Последний управляющий регистр - 16-битовый регистр флагов. Этот
регистр содержит информацию, которая используется побитно, а не в
качестве 16-битового числа. Биты флагового регистра имеют значение
для процессора по-отдельности. Некоторые из этих бит содержат коды
условий, установленные последней выполненой командой. Программа
пользуется этими кодами для управления своим выполнением. Программа
может тестировать коды условий и на основе полученных значений
выбирать последовательность выполнения. Другие биты в регистре
флагов показывают состояние процессора при выполнении текущей
команды. Эти биты управляются специальными командами.
Регистр флагов лучше всего описывать последовательно, по одному
биту. Структура регистра флагов показано на Фиг. 3.10. Заметим,
что здесь определены не все биты. Остальные зарезервированы, то
есть в настоящее время их значение не определено. Однако в даль-
нейших версиях процессора они могут быть использованы для каких-
нибудь специальных целей. Поэтому никогда не следует расчитывать на
неизменность значения зарезервированных бит.
номер бита 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
ЪДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДї
іXXіXXіXXіXXіOFіDFіIFіTFіSFіZFіXXіAFіXXіPFіXXіCFі
АДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДЩ
Фиг. 3.10 Регистр флагов
Все флаги младшего байта регистра устанавливаются арифметичес-
кими или логическими операциями процессора. Например, операция ADD
устанавливает все флаги в младшем байте в соответствии с ее
результатом. За исключением флага переполнения, все флаги старшего
байта устанавливаются специально предназначенными для этого коман-
дами. Флаги старшего байта отражают состояние процессора 8088 и
будут влиять на режим выполнения программы. Флаги в младшем байте -
это коды условия и могут быть испольлзованы в командах условных
переходов для изменения порядка выполнения программы.
Регистры адресации
Регистры адресации
В процессоре 8088 имеется четыре 16-битовых регистров, которые мо-
гут принимать участие в адресации операндов. Один из них является
одновременно регистром общего назначения - регистр базы BX. Другие
Microsoft (R) Macro Assembler Version 5.00 11/17/88 21:42:25
Фиг 3.2 Адресация операндов Page 1-1
PAGE ,132
TITLE Фиг 3.2 Адресация операндов
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE,SS:CODE
0123 ORG 123H
0123 ???? OPND DW ?
0200 ORG 200H
0200 43 INC BX ; Увеличение регистра
0201 FF 06 0123 R INC OPND ; Увеличение ячейки памяти
0205 FF 07 INC WORD PTR [BX] ; Увеличение слова памяти
0207 FF 87 0123 R INC [OPND+BX] ; Смещение плюс индекс
020B FF 84 0123 R INC [OPND+SI]
020F FF 84 0123 R INC OPND[SI] ; Другой способ - тот же результат
0213 FF 85 0123 R INC OPND + [DI]
0217 FF 86 0123 R INC [OPND] + [BP]
021B FF 00 INC WORD PTR [BX + SI] ; База плюс индекс
021D FF 03 INC WORD PTR [BP] + [DI]
021F FF 81 0123 R INC [OPND + BX + DI] ; База + индекс + смещение
0223 FF 82 0123 R INC OPND[BP][SI]
0227 CODE ENDS
END
Фиг 3.2 Адресация операндов
три - это указатель базы (Base Poiner - BP), индекс источника
(Source Index - SI) и индекс назначения (Destination Index - DI).
Программа может использовать регистры BP, SI и DI в качестве
16-битовых операндов, но отдельные байты в них недоступны. Основное
назначение этих регистров - поставлять 16-битовые значения,
используемые в формировании адресов операндов.
Каждая команда в 8088 задает для выполнения некоторую операцию.
Разные операции могут иметь от нуля до двух операндов. Например,
команде разрешения прерываний Set Interrupt (STI) операнды не
нужны. Команда увеличения (INC) требует, чтобы программист опреде-
лил один операнд, - регистр или ячейку памяти, - который должен
быть увеличен на еденицу. Команда ADD (сложение) должна иметь два
операнда - складываемые величины. Некоторые команды неявно задают
расположение операнда, но большинство команд позволяют программисту
выбирать в качестве операнда регистр или ячейку памяти. Если в
качестве операнда выбран регистр, то программисту остается только
указать его имя. Для указания же в качестве операнда участка памяти
у вас есть много различных способов.
Хорошим примером служит команда INC. Она имеет единственный
операнд. На Фиг. 3.2 изображен листинг ассемблера с несколькими
различными вариантами команды INC. Первая команда INC называет в
качестве операнда регистр BX. Заметим, что в поле операндов в этом
случае кроме BX ничего нет. Остальные команды в примере указывают в
качестве операнда ячеку памяти. И хотя в них иногда появляется имя
регистра BX, он не является здесь самим операндом, а используется
для определения его адреса.
Регистры общего назначения
Регистры общего назначения
В первую группу входят регистры, используемые в основном для вы-
числений. Все эти общие регистры имеют размер 16 бит, но программа
может работать и со старшими или младшими 8-ю битами каждого регис-
тра отдельно. Например. регистр AX состоит из 16 бит. Программа мо-
жет обратиться к старшим 8 битам AX как к регистру AH, а младшие 8
бит образуют регистр AL. То же самое верно для регистров BX, CX и
DX. Программа может рассмматривать эту группу регистров как четыре
16-битовых, восемь 8-битовых или некоторую комбинацию 8- и 16-бито-
вых регистров.
Основное назначение группы общих регистров - хранить операнды.
Общие регистры характерны способностью хранить как слово, так байт
данных. Однако эти регистры при выполнении определенных операций
имеют специальное назначение, либо они могут иметь особые
возможности помимо тех, которые имеются у остальных регистров этой
группы. В следующих разделах отмечены некоторые из специальных
функций этих регистров.
Регистр AX соответствует сумматору более ранних процессоров.
Хотя 8088 значительно более универсален, например, в части
арифметических операций, чем ранние машины вроде процессора 8080,
регистр AX имеет несколько специальных функций. Фирма Intel
оптимизировала набор команд 8088, привлекая к выполнению некоторых
операций регистр AX. Например, существуют непосредственные
операции, в которых один из операндов подразумевается самой
командой. Непосредственные операции с регистрами AX и AL (16- и
8-битовый сумматоры соответственно) обычно требуют более короткой
команды, чем аналогичные операции с привлечением других регистров
общего назначения. А меньший размер команды позволяет получать
более компактные и быстродействующие программы.
Регистр BX служит как регистром для вычислений, так и адресным
регистром. При использовании в качестве 16-битового регистра он мо-
жет служить для определения адреса операнда. Способы адресации для
микропроцессора 8088 выделены в следующий пункт.
Набор команд 8088 использует регистр CX в качестве счетчика к
некоторым иструкциям. Эти команды используют находящееся в CX зна-
чение как указатель числа итераций команды или фрагмента программы.
Регистр DX служит как расширение аккумулятора для
многоразрядных операций умножения и деления. В этих 32-битовых
операциях участвут одновременно регистры AX и DX.
Сегментные регистры
Сегментные регистры
INTEL 8088 имеет четыре сегментных регистра: CS,DS,SS и ES - для
кодового, данных-, стекового и дополнительного сегментов
соответственно. Это их обычное использование, но применение этих
регистров может именяться в соответствии с потребностями программы.
8088 использует регистр сегмента программы для идентификации
того сегмента, который содержит выполняемую в данный момент
программу. В сочетании с указателем команд регистр CS используется
для указания текущей команды. Каждая выполняемая команда находится
в ячейке, на которую указывает пара регистров CS:IP.
Комбинация сегментного регистра с регистром смещения для
указания физического адреса записывается в виде сегмент:смещение,
например, CS:IP. Значение сегмента стоит перед двоеточием, смещение
- после. Такая нотация используется и для регистров, и для
абсолютных адресов. Вы можете писать такие адреса как CS:100,
DS:BX, 570:100, или 630:DI.
Регистр сегмента данных (DS) процессор использует для обычного
доступа к данным. Схемы адресации для операндов, которые мы
рассматривали в предыдущем пункте, дают 16-битовое смещение, и в
большинстве случаев для формирования исполнительного адреса процес-
сор комбинирует это смещение с ргеистром DS.
Регистр сегмента стека указывает на системный стек. Команды
PUSH, POP, CALL и RET управляют данными в стеке в позиции по адресу
SS:SP. Регистр SP - указатель стека - служит для определения
смещения в стеке. Кроме того, сегмент стека подразумевается по
умолчанию при адресации с использованием регистра BP. Это дает
доступ к данным в стеке с использованием в качестве указателя
регистра BP. В следующей главе есть пункт о стековых операциях,
который демонстрирует, каким образом адресация через BP упрощает
связь с данными в стеке.
Наконец, регистр дополнительного сегмента используется 8088 для
доступа к данным, когда требуется более одного сенмента. Обычной
операцией такого рода является копирование данных из одной области
памяти в другую. Между областями, находящимися не внутри одного и
того же блока памяти размером 64К, невозможно произвести обмен дан-
ными, используя единственный сегментный регистр. Имея в распоряже-
нии дополнительный сегментный регистр, программа, как показано на
Фиг. 3.7, может указать одновременно исходный и целевой сегменты.
Регистр DS указывает область исходных данных, а регистр ES -
і і
ГДДДДДДДДДДДДДДДДДДДДґ ДДДДД DS
Копировать і Сегмент - і
отсюда ДДДДДДДДґ і
і і источник і
і ГДДДДДДДДДДДДДДДДДДДДґ
і і і
і і і
і ГДДДДДДДДДДДДДДДДДДДДґ ДДДДД ES
і і Сегмент і
і і і
сюда ДДДДДДДДґ назначения і
ГДДДДДДДДДДДДДДДДДДДДґ
Фиг.3.7 Копирование из сегмента в сегмент
сегмент назначения. Для передачи данных существуют специальные
строковые команды, которые автоматически используют регистры DS и
ES для указания исходного и целевого регистров.
Указатель команд
Указатель команд
Указатель команд - это 16-битовый регистр, который содержит смеще-
ние очередной команды. Как показано в предыдущем пункте, Процессор
использует регистр CS совместно с регистром IP для формирования 20-
битового физического адреса. Регистр CS определяет сегмент выполня-
емой программы, а IP задает смещение.
Поскольку в задании адреса очередной команды участвует два
регистра, существует несколько способов задать ход выполнения
программы. Наиболее обычный из этих способов осуществляется при
нормальном выполнении программы. При извлечении процессором команды
из памяти и ее выполнении значение регистра IP увеличивается на
размер команды в байтах. Тепер пара CS:IP указывает на следующую
команду.
Для изменения порядка выполнения команд используются команды
перехода. Команда перехода одного вида изменяет только регистр IP и
дает переход внутри одного сегмента. Этот тип перехода называется
внутрисегментным или близким (NEAR) переходом. Спецификация для
него требует лишь новое значение для регистра IP. Регистр CS
остается без изменений. Близкий переход может передавать управление
только внутри текущего сегмента, поэтому дальность перехода
ограничена сверху 64K байт. Для перехода к боле отдаленному участку
программы требуется переход другого типа.
Переход второго типа называется межсегментным или далеким (FAR)
переходом. При этом переходе процессор назначает новые значения как
для IP так и для CS. Этот переход позволяет выполнять новую прог-
рамму расположенную в любом месте адресного пространства 8088. Но
для выполнения такого перехода команда должна определить новые
значения для обоих регистров - CS и IP. При прямом переходе это
требует пяти-байтовой команды : один байт на код операции и по два байта для регистров CS и IP.
Указатель стека
Указатель стека
Регистр указателя стека (SP) - это 16-битовый регистр, который
определяет текущее смещение вершины стека. Процессор использует
указатель стека совместно с регистром сегмента стека для формирова-
ния физического адреса. Процессор использует пару регистров SS:SP
для всех стековых ссылок, включая PUSH, POP, CALL и RETURN. Пара
SS:SP всегда указывает текущую вершину стека. Когда команда
помещает в стек слово, регистр SP уменьшается на два. При извлече-
нии из стека слова процессор увеличивает регистр SP на два. Стек
растет в направлении меньших адресов памяти. Все стековые опера-
ции используют значения размером в слово. Отдельный байт не может
быть ни помещен в стек ни взят из него.
Процессор изменяет регистр SP для того чтобы отразить действия
над стеком. На регистр SS ни одна из стековых операций не влияет.
Программа устанавливает регистр SS независимо от какой либо опера-
ции PUSH (поместить в стек) или POP (извлечь), после чего он
указывает на сегмент стека. Это означает, что системный стек
ограничен в размере 64 килобайтами. Например, если программа
устанавливает регистр SS в 1000H, а регистр SP - на 0, то первое,
что будет помещено в стек, разместится по адресам 1000:FFFFH и
1000:FFFEH. Последовательно помещаемые в стек данные будут
размещаться в нем по все более младшим адресам, пока последий
объект не будет расположен в 1000:0001H и 1000:0000H. Если
программа поместит в стек в этот момент еще что-нибудь, то оно
разместится в 1000:FFFFH и 1000:FFFEH, то есть в первых байтах
используемых стеком. Поскольку стек теперь "замкнулся", ранее
помещенные в него данные разрушены.
При нормальном использовании длина стека не достигает 64K. Если
программа устанавливает стек в 512 байт, например, то она может
инициализировать пару SS:SP как 1000:2000H. Первая ячейка стека
будет 1000:01FFH, а последняя доступная - 1000:0000H. Если програм-
ма поместит в стек более 256 слов, уменьшение регистра SP вызовет
проход его значения через нуль и приведет к размещению данных
сначала сегмента стека. После этого программа будет помещать
стековые данные в область, которая в данном случае не
предназначалась для стека - начиная с 1000:FFFFH. При этом может
произойти одна из двух неприятных вещей. Стек может перекрыть коды
или данные программы и разрушить их, либо стековые данные будут
направляться в пространство, которое не имеет физической памяти.
Любую из этих вещей очень трудно обнаружить при отладке программы,
поэтому единственный выход - оставлять для стека как можно больше
места. В случае персонального компьютера IBM, использующего
дисковую операционную систему IBM рекомендуется размер стека не
меньше 128 байт. Это дает достаточно места для удовлетворения
потребностей в стеке различных служебных программ DOS и системы
вместе с обычными запросами самой программы.
Управляющие рагистры
Управляющие рагистры
Для операций управления в процессоре 8088 используются главным
образом три 16-битовых регистра. Это указатель стека (SP), указа-
тель команды (IP) и регистр флагов. Два регистра-указателя процес-
сор использует для необходимой при выполнении программы адресации в
памяти. Регистр флагов содержит коды состояний, которые программа
может использовать для управления своим выполнением.
Векторы прерываний
Векторы прерываний
Еще одна важная составная часть микропроцессора 8088 - механизм
прерываний. Эта компонента системы встроена в микропроцессор, и
обеспечивает эффективные методы обработки прерываний.
Когда микропроцессор 8088 получает сигнал о необходимости
прерывания, он определяет, какое из усройств требует обслуживания
посредством аппаратной процедуры, известной как цикл подтверждения
прерывания. В IBM PC для обслуживания внешних прерываний
используется контроллер прерываний 8259 фирмы Intel. Контроллер
прерываний программируется так, чтобы выдавать однобайтовое число в
ответ на цикл подтверждения прерывания микропроцессора 8088. Это
число, находящееся в диапазоне от 0 до 255, - номер прерывания
внешнего усройства, вызвавшего прерывание. В персональной ЭВМ
контроллер прерываний обслуживает восемь внешних прерываний,
которым соответствуют номера от 8 до 15.
Как только микропроцессор 8088 получает номер прерывания, он
должен передать управление соответствующей программе обработки
прерывания. Первые 1024 байт памяти микропроцессора 8088
зарезервированы для векторов прерываний. Каждому из 256 возможных
прерываний отводится четырехбайтовая область. Прерывание 0 имеет
четыре байта по адресам от 0 до 3, прерывание 1 - от 4 до 7, и т.д.
Каждая четырехбайтовая ячейка содержит указатель на соответствующий
обработчик конкретного прерывания. Первые два байта содержат
смещение адреса программы обработки прерывания, а последние два
байта - сегмент. Для задания значения этого поля может
использоваться оператор определения двойного слова DD.
Так же, как и вызов подпрограммы, прерывание должно сохранить в
стеке адрес возврата. Поскольку обработчик прерывания может
находиться в любом месте адресного пространства микропроцессора, он
должен обслужить прерывание, как вызов типа FAR, т.е. перед тем,
как микропроцесоор передаст управление обработчику прерывания, он
сохранит сегмент и смещение текущей команды в программе. Кроме того
возврат из программы обработки прерывания должен вернуть машину в
точности в то состояние, в котором она была в момент возникновения
прерывания. Чтобы помочь в этом, микропроцессор 8088 также
сохраняет регистр флагов в стеке. Это означает, что эти действия
уже не придется выполнять каждой программе обработки прерываний.
Сохранение регистра флагов означает также сохранение и текущего
состояния флага разрешения прерываний. Принятие внешнего прерывания
сбрасывает флаг разрешения прерывания, так что программа обработки
прерывания уже не может быть прервана другим прерыванием. Команда
возврата из прерывания, которая восстанавливает регистр флагов,
автоматически деблокирует систему прерываний восстановлением флага
прерываний в состояние предшествующее возникновению прерывания.
Когда возникает прерывание, микропроцессор помещает в стек
региср флагов, за которым следуют регистры CS и IP. 8088 использует
номер прерывания, чтобы считать указатель на программу обработки
прерывания, и передать ей управление. Теперь уже эта программа
отвечает за сохранение регистров, которые она использует, и
восстановление их перед возвратом управления в прерванную
процедуру. Для возврата из прерывания используется специальная
команда IRET. Она извлекает верхние три слова из стека и помещает
их в регистры IP, CS и регистр флагов. В следующих главах мы
приведем несколько примеров, использующих механизм прерываний.
Программист может использовать механизм прерывания
непосредственно, без запроса внешних прерываний. Существуют
команды, которые заставляют микропроцессор работать так, как будто
при их выполнения возникло внешнее прерывание. Такие действия
называются программными прерываниями, так как они порождаются
программами, но имитируют действия обычных прерываний. Процессор
помещает все три управляющих регистра в стек и выбирает вектор
прерывания по указанному программой однобайтовому значению.
Микропроцессор использует записанный в начале памяти вектор
прерывания в качестве указателя подпрограммы обработки прерывания.
Программные прерывания придают большую гибкость системе 8088.
В случае обычных вызовов подпрограммы программист до ее выполнения
обязан знать, где она находится. Но если программа вызывает под-
программу, используя программное прерывание, то подпрограмма может
находиться в любом месте адресного пространства, и вызывающей
программе нет нужды знать ее местонахождение. Единственным пара-
метром, котрый требуется от программиста, вызывающего подпрограмму,
является номер вектора прерываний. Управляющие программы и опера-
ционная система фирмы IBM очень выгодно используют этот механизм.
Программные прерывания дают доступ к сервисным программам системы.
Программам пользователя не нужно знать точные адреса, которые могут
изменяться в разных версиях системного программного обеспечения.
Кроме того, сервисные подпрограммы могут быть подменены в любой
момент времени простой заменой четырехбайтового вектора,
указывающего на новую программу, без всякой модификации программ,
использующих эти подпрограммы. В гл.10 мы приведем несколько
примеров, которые покажут, использование такого подхода.
Вычисление адресов
Вычисление адресов
В способе прямой адресации памяти привлекательна простота, но во
многих случаях программа вынуждена вычислять действительный адрес в
памяти. Простейший пример - операции с вектором, одномерным
массивом. В программе на языке Фортран такую структуру можно
создать оператором
DIMENTION OPND(20)
В других языках высокого уровня существуют аналогичные способы
создания массивов. При выполнении программа получает доступ к
разным элементам в соответствии со значением индекса, например,
OPND(5). Написание программы на языке ассемблера требует от
программиста вычисления местонахождения пятого элемента в поле
данных OPND. Затем программа может использовать полученное значение
для прямой адресации. Однако, в случае с выражением OPND(I), где I
вычисляется в ходе выполнения программы, способа прямого указания
правильного адреса для программы на языке ассемблера не существует.
Адрес должен вычисляться в ходе выполнения программы.
Набор команд 8088 допускает несколько способов определения ис-
полнительного адреса (Effective Address - EA) операнда. Эти способы
вычисления адреса называют способами адресации. Их количество
предназначено для облегчения задачи определения исполнительных
адресов. Благодаря правильному выбору способа адресации программист
может минимизировать количество вычислений в программе.
Формула для определения I-го элемента массива OPND такова:
EA = адрес базы OPND + (I * длина),
где длина - это длина каждого элемента массива. В данном примере
OPND - массив, состоящий из слов, поэтому каждый элемент в нем
имеет длину 2 байта. Тогда формула выглядит так:
EA = адрес базы + (I * 2)
Для вычисления этого адреса требуется по крайней мере один ре-
гистр, содержащий адрес операнда. Программа может вычислить испол-
нительный адрес, оставив результат в одном из регистров. Тогда,
вместо указния адреса в самой команде INC, можно просто указать,
какой из регистров его содержит.
Для хранения адресов операндов программа может использовать лю-
бой из четырех адресных регистров. Так, в нашем примере программа
добавляет к адресу базы 2*I и помещает результат в регистр BX. Со-
ответствующий элемент вектора в этом случае будет увеличиваться ко-
мандой
INC WORD PTR [BX]
Выражение [BX] сообщает ассемблеру, что регистр BX содержит ад-
рес операнда, а не является операндом сам по себе. Скобки [ и ],
заключающие какое-либо значение, указывают ассемблеру, что это
значение - адрес. Другая часть операндного выражения, WORD PTR,
требуется ассемблеру для информации, что операнд является
переменной типа WORD (слово). Далее мы обсудим оператор PTR более
подробно.
Assembler для начинающих
Адресация переходов
Адресация переходов
Если адрес перехода или вызова подпрограммы является частью самой
коамнды (как данные в командах с непосредственным операндом), это -
непосредственный переход. Если адрес перехода команды содержится в
регистре или ячейке памяти, это - косвенный переход, так как
команда требует загрузки адреса, извлекаемого из некоторого
промежуточного места хранения; программа не может перейти прямо в
необходимое место, и должа идти туда косвенно.
Существует два метода вычисления адреса перехода. Если в
команде указано значение адреса, это абсолютный переход, т.е.
переход по абсолютному адресу. Команда может указать место
перехода, как некоторое расстояние от нее самой. Этот метод
перехода называется относительным переходом.
Преимущество относительных переходов заключается в том, что
программа наиболее часто переходит к близлежащим ячейкам; команда
перехода может использовать однобайтовое смещение. Если смещение
трактуется, как число в дополнительном коде, то двухбайтовая
команда относительного перехода (один байт - код операции, и один
байт - смещение) может выполнить переход на 127 байт вперед или на
128 байт назад внутри программы. Микропроцессор 8088 имеет два типа
относительных переходов: один имеет однобайтовое смещение, другой -
двухбайтовое.
В микропроцессоре 8088 все условные переходы имеют однобайтовое
смещение. Иногда это неудобно, например в случае условного перехода
к ячейке, находящейся на расстоянии в 150 байт от текущего места.
В таких случаях программа должна использовать пару переходов,
условный и безусловный; далее приводится пример такого метода
перехода. В обычных же случаях однобайтовые смещения условных
переходов в микропроцессоре 8088 минимизируют объем программы,
необходимой для реализации любой заданной функции.
При расчете смещения относительного перехода микропроцессор
8088 отсчитывает смещения от значения указателя команд, которое
получится после выполнения команды. Фиг. 4.26 показывает разные
примеры команд относительного перехода. Если точка перехода следует
непосредственно за переходом, смещение равно 0. При переходе к
самой команде перехода смещение равно -2. При двухбайтовом смещении
переход может быть сделан в диапазоне -32768 - 32767 байт от
значения регистра IP после выполнения команды перехода.
Арифметические команды
Арифметические команды
Арифметические команды любого микропроцессора привлекают к себе
наибольшее внимание. Каждый заинтересован в выполнении
арифметических вычислений, и именно эти команды проделывают такую
работу. Хотя их немного, они выполняют большинство преобразований
данных а микропроцессоре. В реальных же условиях арифметические
команды занимают лишь малую часть всех исполняемых команд.
Команды пересылки используют большинство принципов работы
команд микропроцессора 8088, а при изучении арифметических команд
необходимо рассмотреть некоторые тонкости их выполнения.
Арифметический пример
Арифметический пример
Чтобы проиллюстрировать функции, которые мы рассмотрели в
предыдущих разделах, давайте решим арифиетическую задачу на языке
ассемблера. Пример прост, но использует многие команды. Задача
заключается в вычислении частного двух арифметических выражений, в
которых некоторые числа постоянны, а другие переменны. Все числа
являются 16=битовыми целыми числами со знаком.
Формула вычислений:
A * 2 + B * C
X = ------------------
D - 3
Эта задача решается подпрограммой на языке ассемблера,
изображенной на Фиг. 4.16. Подпрограмма сначала выполняет два
умножения. Так как микропроцессор 8088 всегда помещает результат
16=битового умножения в пару регистров DX:AX, в примере результат
первого умножения переносится в пару регистров BX:CX перед
выполнением второго умножения. Когда оба умножения завершены,
программа выполняет сложение числителя. Поскольку умножение дает
32=битовые результаты, в программе требуется сложение повышенной
точности. Это сложение оставляет результат в DX:AX. В примере
знаменатель вычисляется в регистре CX, а затем на него делится
числитель. Программа записывает частное из регистра AX в переменную
результата X. Остаток в этой задаче игнорируется.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:10
Фиг. 4.16 Пример арифметических вычислений Page 1-1
PAGE ,132
TITLE Фиг. 4.16 Пример арифметических вычислений
;-------------------------------------------------------------
; Производятся вычисления по формуле
;
; A * 2 + B * C
; X = -------------------
; D - 3
;
; Все переменные - 16-разрядные целые числа со знаком
;-------------------------------------------------------------
Фиг. 4.16 Арифметический пример (начало)
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 ???? X DW ? ; Память для переменных
0002 ???? A DW ?
0004 ???? B DW ?
0006 ???? C DW ?
0008 ???? D DW ?
000A FIG4_16 PROC NEAR
000A B8 0002 MOV AX, 2 ; Загрузка константы
000D F7 2E 0002 R IMUL A ; DX:AX = A * 2
0011 8B DA MOV BX, DX
0013 8B C8 MOV CX, AX ; BX:CX = A * 2
0015 A1 0004 R MOV AX, B
0018 F7 2E 0006 R IMUL C ; DX:AX = B * C
001C 03 C1 ADD AX, CX
001E 13 D3 ADC DX, BX ; DX:AX = A * 2 + B * C
0020 8B 0E 0008 R MOV CX, D
0024 83 E9 03 SUB CX, 3 ; CX = D - 3
0027 F7 F9 IDIV CX ; AX = (A*2 + B*C) / (D-3)
0029 A3 0000 R MOV X, AX ; Сохранение результата
002C C3 RET
002D FIG4_16 ENDP
002D CODE ENDS
END
Фиг. 4.16 Арифметический пример (продолжение)
Арифметика с одним операндом
Арифметика с одним операндом
Команда отрицания NEG - это оператор смены знака. Она меняет знак
двоичного дополнительного кода операнда=байта или слова. Другие
две команды с одним операндом изменяют значение оперенда на 1.
Команда увеличения INC прибавляет 1 к операнду, а команда
уменьшения DEC вычитает 1 из операнда. С помощью команд увеличения
и уменьшения можно перемещать указатель по массиву ячеек памяти.
Эти команды также могут реализовать счетчик цикла. Каждый проход
по циклу уменьшает счетчик, а когда его значение достигнет 0, цикл
завершается.
Все эти однооперандные команды могут иметь в качестве операнда
как байт, так и слово. Если любая из этих команд указывает ячейку
памяти с помощью одного из косвенных способов адресации, например
[BX+SI], ассемблер нуждается в помощи, так как ему необходимо знать
длину операнда в памяти, чтобы породить правильный код операции.
Команда может использовать модификаторы BYTE PTR или WORD PTR,
чтобы описать операнд.
Эти три команды влияют на регистр состояния точно так же, как
это делают арифметические команды. Прибавление 1, вычитание 1 и
вычитание из 0 идентичны соответственно INC, DEC и NEG; однако
команды с одним операндом более эффективны.
Безусловные переходы
Безусловные переходы
Безусловные переход - это такой переход, который передает
управление всякий раз, когда он выполняется. Наоборот, услловный
переход проверяет текущее состояние машины, чтобы определить,
передавать управление или нет. Существует два вида команд
безусловной передачи управления - команды переходов и вызовов.
Все команды вызова CALL - безусловны. Различные команды CALL
показаны на Фиг. 4.27. Близкий вызов CALL, или NEAR CALL, указывает
новое значение регистра IP и сохраняет старое значение регистра IP
в стеке в качестве адреса возврата. Далекий вызов CALL, или FAR
CALL, задает новые значения сегмента и смещения для дальнейшего
выполнения программы и сохраняет в стеке как регистр IP, так и
регистр CS. Близкий непосредственный вызов CALL - это относительный
переход, использующий двухбайтовое поле смещения. Все остальные
команды вызова - абсолютные переходы. Непосредственный вызов FAR
CALL требует четырехбайтовое поле операнда для указания новых
значений для регистров CS и IP. Косвенные переходы используют байт
адресации mod=r/m для указания операнда=регистра или памяти; этот
операнд содержит адрес подпрограммы. Косвенные вызовы типа NEAR
загружают однословный операнд в регистр IP. Вызовы типа FAR
загружают двойное слово из памяти в пару регистров CS:IP; первое
слово загружается в регистр IP, а второе - в регистр CS. Если
команда указывает регистр в качестве операнда косвенного далекого
вызова, результат непредсказуем; микропроцессор 8088 берет новое
значение регистра CS неизвестно откуда. Ни в коем случае нельзя
использовать эту модификацию команды.
Командам CALL соответствуют команды возврата RET. Все возвраты
- косвенные переходы, поскольку они извлекают адрес перехода из
вершины стека. Близкий возврат извлекает из стека одно слово и
помещает его в регистр IP, а далекий возврат извлекает два слова,
помещая слово из меньшего адреса в регистр IP, а слово из большего
адреса в регистр CS.
Программы могут модифицировать возвраты как типа NEAR, так и
типа FAR, указывая параметр счетчика байтов. Команда возврата
прибавляет его значение к указателю стека после извлечения из него
адреса (адресов) возврата. Такая команда позволяет программе
удалять параметры из стека без использования специальных команд
POP; тем самым подчеркивается, что стек - носитель передаваемых
подпрограмме параметров. Такой стиль работы со стеком мы уже
обсуждали во всех подробностях ранее в разделе "Работа со стеком".
Команды безусловного перехода JMP идентичны командам CALL по их
возможностям адресации. Однако существует дополнительная команда
перехода, указывающая однобайтовое смещение для близкого
относительного перехода (команда короткого перехода).
Соответствующей ей команды CALL не существует, так как вызовы
подпрограмм, расположенных поблизости, происходят очень редко.
Команды переходов используют те же методы генерации адреса, что и
команды вызова.
Сделаем сдесь замечание об оптимизации кода и о том, как
работает ассемблер. По мере того, как ассемблер делает первый
переход по тексту программы и назначает адреса командам, он должен
решить, использовать двух= или трехбайтовую разновидность команды
JMP. Если это переход назад, т.е. на место, уже известное
ассемблеру, он может определить правильное смещение; тем самым
ассемблер знает, находится ли переход в диапазоне короткого
смещения. Однако, если переход делается вперед, на метку, о которой
ассемблер еще не знает, он должен предположить, что метка находится
далее, чем 128 байт от текущего места. Затем ассемблер порождает
длинную форму команды перехода. Худший случай ассемблер обязан
выбирать потому, что потом уже не может возвратиться назад и
увеличить размер команды. Затем ассемблер заместит трехбайтовую
команду перехода двухбайтовой командой JMP и однобайтовой командой
NOP, если обнаружит, что переход делается ближе 128 байт от
текущего места. Так как такой переход выполняется несколько
быстрее, время выполнения в этом случае сокращается, но объектный
код остается больше необходимого.
Если программисту заранее известно, что переход вперед делается
на место, лежащее в диапазоне 128 байт от текущего места, он может
об этом сообщить ассемблеру с помощью следующей строки:
JMP SHORT LABEL
Аттрибут SHORT заставляет ассемблер сформировать короткую форму
SHORT команды перехода, даже если он еще не встречал метку. Если же
программист сделал ошибку и переход в действительности не может
быть коротким, ассемблер выдает сообщение об ошибке. На Фиг. 4.26
дан пример оператора SHORT.
Фиг. 4.28 показывает, как можно устроить таблицу переходов
с помощью команды косвенного перехода. В этом примере делается
выбор среди нескольких программ, основываясь на значении аргумента
в регистре AL. Аналогичная программа могла бы вызвать подпрограмму
по индексу. Это - реализация на языке ассемблера оператора CASE,
который существует в некоторых языках высокого уровня.
Близкие и далекие переходы
Близкие и далекие переходы
Команды перехода модифицируют указатель команды IP, и, возможно,
регистр сегмента кодов CS. Эти регистры показывают, какая
следующая команда должна быть выполнена. Команда перехода является
специальным случаем пересылки MOV данных в регистр или пару
регистров; и некоторые ЭВМ действительно выполняют команду перехода
именно таким способом. Однако способы загрузки пары регистров
CS:IP в микропроцессоре 8088 во многом отличаются от способов,
используемых для других регистров.
Прежде всего мы должны ввести некоторые определения. Если
команда перехода изменяет только регистр IP, это близкий переход
(NEAR=переход), так как переход происходит внутри сегмента. Если
переход изменяет регистр CS, это далекий FAR=переход.
Аттрибуты NEAR и FAR используются при работе ассемблера. Любая
программная метка в программе на языке ассемблера имеет атрибут
либо NEAR, либо FAR, так же, как данные имеют атрибуты BYTE или
WORD. В некоторых примерах этой главы имеются процедуры, которые
используют атрибут NEAR в операторе PROC. Это означает, что метка,
связанная с оператором PROC (имя процедуры) имеет атрибут NEAR.
Ассемблер использует эту информацию для того, чтобы определить,
какой тип команды перехода или вызова породить при переходе к этой
метке. Поскольку большинство процедур - подпрограммы, атрибут NEAR
или FAR оператора PROC также определяет тип порождаемой команды
возврата. Вызов FAR=процедуры сохраняет значения как регистра CS,
так и регистра IP, тогда как вызов NEAR=процедуры оставляет в стеке
только значение регистра IP. Команда возврата должна учитывать,
какой тип вызова юыл сделан, чтобы подпрограмма могла вернуться к
правильному месту.
Десятичная коррекция
Десятичная коррекция
Те же самые команды, что и для чисел в двоичном дополнительном
коде, используются в программе для работы с числами в
двоично=десятичном коде BCD. Однако результат арифметических
операций может оказаться неправильным для двоично=десятичного
представления. Команды десятичной коррекции корректируют
результат, полученный после действий двоичной арифметики.
Десятичная коррекция для сложения DAA и десятичная коррекция
для вычитания DAS используются для работы только с упакованными
десятичными числами. В упакованном десятичном числе каждый байт
содержит две десятичные цифры. Команды DAA и DAS работают только с
байтом данных, содержащимся в регистре AL. В связи с этими
присущими командам ограничени- ями, ни у DAA, ни у DAS операндов
нет.
На Фиг. 4.12 показаны два примера. В первом примере
складываются два упакованных десятичных числа. Оба числа состоят из
двух десятичных цифр, поэтому они представлены единственными
байтами. В примере складываются эти числа, оставляя результат в
регистре AL. Непосредственно за этим следует команда DAA, которая
корректирует результат сложения, преобразуя его в упакованную
десятичную форму. После команды DAA в регистре AL остается
правильное упакованное десятичное число в диапазоне 0 - 99. Если
результат меньше 100, регистр содержит ответ, а флаг переноса
содержит 0. Если результат находится в диапазоне 100 - 198, то в
регистре AL остаются две младшие десятичные цифры, а флаг переноса
установлен равным 1, показывая, что был перенос.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:04
Фиг. 4.12 Пример двоично-десятичной арифметики Page 1-1
PAGE ,132
TITLE Фиг. 4.12 Пример двоично-десятичной арифметики
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 ?? BCD1 DB ? ; Две десятичные цифры упакованного числа
0001 ?? BCD2 DB ?
0002 ???? BCD1L DW ? ; Четыре десятичные цифры упакованного числа
0004 ???? BCD2L DW ?
0006 FIG4_12 PROC NEAR
;----- Сложение двух упакованных чисел
0006 DAA_EXAMPLE:
0006 A0 0000 R MOV AL, BCD1 ; Взять первое упакованное число
0009 02 06 0001 R ADD AL, BCD2 ; Добавить второе
000D 27 DAA ; Преобразование упакованного числа
000E C3 RET
;----- Сложение двух упакованных чисел размером по 4 цифры (2 байта)
000F DAA_LONG:
000F A0 0002 R MOV AL, BYTE PTR BCD1L
0012 02 06 0004 R ADD AL, BYTE PTR BCD2L ; Добавление младшей части числа
0016 27 DAA ; Коррекция упакованного числа
0017 A2 0004 R MOV BYTE PTR BCD2L, AL ; Сохранение младшей части
001A A0 0003 R MOV AL, BYTE PTR BCD1L+1
Фиг. 4.12 Примеры вычислений с BCD (начало)
001D 12 06 0005 R ADC AL, BYTE PTR BCD2L+1 ; Добавление старшей части числа
0021 27 DAA ; Коррекция упакованного числа
0022 A2 0005 R MOV BYTE PTR BCD2L+1, AL ; Сохранение старшей части
0025 C3 RET
;----- Сложение двух упакованных чисел
0026 DAS_EXAMPLE:
0026 A0 0000 R MOV AL, BCD1
0029 2A 06 0001 R SUB AL, BCD2 ; Вычитание значений
002D 2F DAS ; Коррекция упакованного числа
002E C3 RET
002F FIG4_12 ENDP
002F CODE ENDS
END
Фиг. 4.12 Примеры вычислений с BCD (продоложение)
Команда DAA правильно устанавливает регистр флагов. Если в
результате сложения получилось значение в диапазоне 100 - 198, флаг
переноса показывает перенос из старшей десятичной позиции.
Аналогично, нулевой результат оставляет установленным в 1 флаг
нуля. В случае операций с упакованными десятичными числами флаги
знака и переполнения не имеют значения, хотя флаг знака
устанавливается, если установлен старший бит регистра AL. Команда
DAA использует флаг дополнительного переноса для определения вида
коррекции, но после выполнения этой команды флаг дополнительного
переноса неопределен.
Второй пример на Фиг. 4.12 демонстрирует десятичное сложение
повышенной точности. Оно весьма похоже на двоичную арифметику с
повышенной точностью, за исключением того, что после сложения
каждого байта появляется команда DAA. Из=за ограничений, присущих
команде DAA, в примере нельзя было сложить два упакованных
десятичных слова, как слова, а затем применить коррекцию. С
упакованными десятичными числами разрешена только байтовая
арифметика.
Наконец, на Фиг. 4.12 показано, как использовать команду DAS.
Это делается так же, как и при сложении, но команда DAS должна
следовать за вычитанием. Здесь тоже допустимы только байтовые
операции.
Команда деления
Команда деления
Одна из арифметических операций микропроцессора 8088 - деление.
Как и в случае умножения, существует две формы деления - одна для
двоичных чисел без знака DIV, а вторая для чисел в дополнительном
коде IDIV. Любая форма деления может работать с байтами и словами.
Команда деления DIV выполняет деление без знака и дает как
частное, так и остаток. Как и в случае умножения, операнды должны
находиться на специфических местах. Также подобно умножению, для
деления одно из этих чисел в два раза длиннее обычного операнда:
делимое является операндом двойной длины. Байтовые команды делят
16=битовое делимое на 8=битовый делитель. В результате деления
получается два числа. Деление помещает частное в регистр AL, а
остаток в регистр AH. Такое расположение операндов делает команду
деления дополнительной к команде умножения; это означает, что
умножение регистра AL на байтовый операнд, а затем деление регистра
AX на тот же операнд возвращает регистр AL к его первоначальному
состоянию. Регистр AH будет содержать 0, поскольку остатка нет.
Фиг. 4.14 схематически иллюстрирует команду деления.
ЪДДДДДДДДї
і AX і
і BX і
і CX і
і DX і Остаток
ЪДДДДДДДДВДДДДДДДДї АДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї
і DX і AX і * ЪДДДДДДДДї ДДДДД> і DX і AX і
АДДДДДДДДБДДДДДДДДЩ і SI і АДДДДДДДДБДДДДДДДДЩ
і DI і
і BP і
і SP і
АДДДДДДДДЩ
ЪДДДДДДДДї
і Память і
і(слова) і
АДДДДДДДДЩ
(a) Деление слов
ЪДДДДДДДДДї
і AH і
і AL і
і BH і
і BL і
і CH і
і CL і
і DH і
і DL і Остаток
ЪДДДДДДДДї АДДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї
і AL і * ЪДДДДДДДДДї ДДДДД> і AH і AL і
АДДДДДДДДЩ і Память і АДДДДДДДДБДДДДДДДДЩ
і(байты) і
АДДДДДДДДДЩ
(b) Деление байтов
Фиг.4.14 Операции деления
Команда, работающая со словами, делит 32=битовое делимое на
16=битовый делитель. Делимое находится в паре регистров DX:AX,
причем регистр DX содержит старшую значащую часть, а регистр AX -
младшую. Деление слов помещает частное в регистр AX, а остаток в
регистр DX. Здесь опять=таки умножение и деление взаимно
дополнительны: умножение регистра AX на слово, а затем деление его
на то же слово возвращает регистр AX к его первоначальному
состоянию. Теперь регистр DX становится нулевым, так как остатка
нет.
Ни один из флагов состояния не определен после команды деления.
Однако во время деления может возникнуть ошибка значимости. Если
частное больше, чем может быть помещено в регистр результата,
микропроцессор не может дать правильный результат. В случае деления
байтов частное должно быть меньше 256, и меньше 65535 в случае
операции со словами. Микропроцессор не устанавливает никаких флагов
для сигнализации при этой ошибке, вместо этого он выполняет
программное прерывание уровня 0. Как и в случае других программных
прерываний, это прерывание по делению на 0 сохраняет флаги, регистр
кодового сегмента и указатель команды в стеке. Затем микропроцессор
передает управление в ячейку, на которую ссылается указатель по
адресу 0. Подпрограмма деления на 0 должна предпринять
соответствующие действия по обработке этой ошибки. (Прерывание 0
называется делением на 0 даже тогда, когда это прерывание возбудило
деление на число, отличное от нуля. В документации фирмы Intel это
прерывание называется делением на нуль, хотя более точно его надо
было бы назвать прерыванием по переполнению после деления).
Деление целых чисел со знаком IDIV отличается от команды DIV
только тем, что оно учитывает знаки обоих операндов. Если результат
положителен, все происходит так же, как было описано для команды
DIV, за исключением того, что максимальное значение частного
соответственно равно 127 и 32767 для байтов и слов. Если результат
отрицателен, частное усекается, а остаток имеет тот же знак, что и
делимое. Минимальные значения частных для отрицательных результатов
-128 и -32768 для байтов и слов.
Делимое (AX) Делитель(MOD-R/M) Частное (AL) Остаток(AH)
------------------------------------------------------------------
7 2 3 1
7 -2 -3 1
-7 2 -3 -1
-7 -2 3 -1
------------------------------------------------------------------
Фиг. 4.15 Примеры деления со знаком
На Фиг. 4.15 показаны четыре примера деления, а также
полученные в них результаты. Все примеры, приведенные здесь,
байтовые, т.е. делимое находится в регистре AX, а делитель
указывается байтом mod=r/m. Деление помещает частное в регистр AL,
а остаток в регистр AH. Заметим, что знак остатка всегда тот же,
что и у делимого. Значение частного всегда усекается в направлении
нуля.
Команда пересылки
Команда пересылки
Команда MOV - основная команда пересылки данных, которая пересылает
байт или слово данных из памяти в регистр, из регисрта в память,
или из регистра в регистр. Команда MOV может также занести число,
определенное программистом, в регистр или в память.
В действительности команда MOV - это целое семейство машинных
команд микропроцессора 8088. Таблица, в которую сведены варианты
всех машинных команд микропроцессора 8088, приведена в приложении
А. Беглый просмотр этой таблицы показывает, что существует семь
различных вариантов команды MOV, но программист использует каждую
из этих команд с помощью единого названия операции MOV. Ассемблер
порождает правильную машинную команду, основываясь на типах
операндов, которые написал программист; и это одна из причин, по
которой ассемблер требует для операндов назначения типов, т.е.
ассемблер должен знать, что представляет собой каждый операнд -
регистр, байт памяти, слово памяти, сегментный регистр, или
что=нибудь еще. Такое назначение типов позволяет ассемблеру
построить правильную машинную команду. В случае использования
команды MOV ассемблер должен решить, какой из семи вариантов
является подходящим, основываясь на операндах, написанных
программистом.
На Фиг.4.1 представлены различные способы, которыми в
микропроцессоре 8088 можно переслать данные из одного места в
другое. Каждый прямоугольник означает здесь регистр или ячейку
памяти. Стрелки показывают пути пересылки данных, которые допускает
набор команд микропроцессора 8088. Основной путь - из памяти в
регистры и наоборот. С данными, помещенными в регистры, можно
работать с большей эффективностью, чем с данными в памяти, так как
микропроцессор не делает обращения к памяти всякий раз, когда нужны
данные. Кроме того, все команды микропроцессора 8088 могут указать
только один операнд памяти. Поэтому, например, команда сложения ADD
требует, чтобы по крайней мере один из операндов был в регистре.
Микропроцессор 8088 не имеет возможности сложить одну ячейку памяти
с другой с помощью одной команды.
ЪДДДДДДДДДДДДДДДДДДДДї
і і
і Непосредственные і
АДДДДДДВДДДДДДВДДДДДДЩ
ЪДДДДДДДДДДї і і ЪДДДДДДДДДДДї
і і і і і Регистры і
і Г<ДДДДДДДДДДДЩ АДДДДДДДДДДД>ґ AX і
і і і BX і
і Память Г<ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД>ґ CX і
і і і DX і
і Г<ДДДДДДДДДДДї ЪДДДДДДДДДДД>ґ SI і
і і і і і DI і
і і і і і BP і
і і і і і SP і
АДДДДДДДДДДЩ v v АДДДДДДДДДДДЩ
ЪДДДДДДБДДДДДДБДДДДДДДї
і Сегментные регистры і
і CS DS ES SS і
АДДДДДДДДДДДДДДДДДДДДДЩ
Фиг.4.1 Операции пересылки данных
В самой команде MOV может содежаться новое содержимое регистра.
Такая форма операнда называется непосредственным оперндом; данные
находятся в самой команде и не требуют вычисления адреса. Вы можете
рассматривать эту форму адресации как специальный тип, при котором
операнд находится в самой команде, а не где=то в другом месте
памяти или в регистре. Кроме команд пересылки, у микропроцессора
8088 есть и команды обработки данных с непосредственным операндом.
Из Фиг.4.1 также ясно, что команда может переслать
непосредственнйе данные в регистр или ячейку памяти. Записывать
информацию в команду бессмысленно, так что поток данных для команды
с непосредственным операндом имеет одно направление.
Наконец, команда MOV может записать сегментный регистр в память
или регистр. Она может также загрузить сегментный регистр из памяти
или из другого регистра. Однако не существует команды загрузки
сегментного регистра данными с непосредственным операндом; это
означает, что загружать сегментный регистр такими данными
непроизводительно. Если в программе необходимо поместить известное
значение в сегментный регистр, нужно сначала записать это значение
в один из регистров или в ячейку памяти, а затем можно уже
пересылать это значение в сегментный регистр. На Фиг. 4.2 показано,
как это сделать.
icrosoft (R) Macro Assembler Version 5.00 1/1/80 04:00:28
Фиг. 4.2 Команда пересылки Page 1-1
PAGE ,132
TITLE Фиг. 4.2 Команда пересылки
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 EXWORD LABEL WORD
0000 EXBYTE LABEL BYTE
0000 8B C3 MOV AX,BX ; Регистр BX --> Регистр AX
0002 8B D8 MOV BX,AX ; Регистр AX --> Регистр BX
0004 8B 0E 0000 R MOV CX,EXWORD ; Память --> Регистр
0008 89 16 0000 R MOV EXWORD,DX ; Регистр --> Память
000C 8A 2E 0000 R MOV CH,EXBYTE ; Память --> Регистр (байт)
0010 88 36 0000 R MOV EXBYTE,DH ; Регистр --> Память (байт)
0014 BE 03E8 MOV SI,1000 ; Непосредственное --> Регистр
0017 B3 17 MOV BL,23 ; Непосредственное --> Регистр (байт)
0019 C7 06 0000 R 07D0 MOV EXWORD,2000 ; Непосредственное --> Память
001F C6 06 0000 R 2E MOV EXBYTE,46 ; Непосредственное --> Память (байт)
0024 A1 0000 R MOV AX,EXWORD ; Память --> Аккумулятор
0027 A0 0000 R MOV AL,EXBYTE ; Память --> Аккумулятор (байт)
002A A3 0000 R MOV EXWORD,AX ; Аккумулятор --> Память
002D A2 0000 R MOV EXBYTE,AL ; Аккумулятор --> Память (байт)
0030 8E 1E 0000 R MOV DS,EXWORD ; Память --> Сегментный регистр
0034 8E D8 MOV DS,AX ; Регистр --> Сегментный регистр
0036 8C 1E 0000 R MOV EXWORD,DS ; Сегментный регистр --> Память
003A 8C C0 MOV AX,ES ; Сегментный регистр --> Регистр
;----- Непосредственное значение в сегментный регистр
003C B8 ---- R MOV AX,CODE ; Взять непосредственное значение
003F 8E D8 MOV DS,AX ; Загрузить его в сегментный регистр
0041 CODE ENDS
END
Фиг. 4.2 Команды пересылки
На Фиг. 4.2 изображен листинг ассемблера некоторых возможных
вариантов команды MOV. Единственная команда ассемблера MOV
порождает несколько различных машинных команд.
Рассматривая Фиг.4.2, обратите внимание на сантаксис команды
MOV. Команда MOV имеет два операнда: источник и результат. В
команде они следуют друг за другом, источник следует за
результатом. Первая команда на рисунке MOV AX, BX пересылает
содержимое регистра BX в регистр AX. Следующая команда обратна
предыдущей, содержимое регистра AX пересылается в регистр BX.
Команда MOV не меняет источник, т.е. команда
MOV AX, BX
меняет регистр AX, результат, но не меняет регистр BX,
источник.
Никакие из команд MOV не меняют флагов состояния. Хотя иногда
это кажется неудобным, но является наилучшим способом работы с
флагами. Как мы увидим далее, микропроцессор 8088 имеет команды,
которые могут эффективно проверить любую ячейку памяти так, что
команда пересылки не потребуется. В качестве примера случая, когда
установка флагов при пересылке не нужна, рассмотрим арифметику
повышенной точности. Когда программа выполняет вычисления
повышенной точности, она должна переслать части операндов в
регистры, чтобы расположить их там для выполнения операции. Такая
пересылка не меняет ни одного флага, а это позволяет флагам
обслуживать арифметику повышенной точности.
Как было замечено, существует несколько различных вариантов
команд пересылки на машинном языке. Объектный код на Фиг. 4.2
иллюстрирует эти варианты. Если вас интересует структура машинного
языка, вы можете сравнить объектный код с описанием машинного языка
в приложении А. Такое сравнение поможет выяснить значение отдельных
битов в машинном коде. Например, вы сможете увидеть значения данных
с непосредственным операндом в командах. К счастью, для того, чтобы
писать программы на ассемблере, вам не требуется точно знать, как
работает ассемблер.
Если вы хотите достичь наибольшей возможной эффективности
программ, вам надо изучить объектный код на Фиг. 4.2. Число байтов
команды непосредственно связано с количеством времени, необходимого
для выполнения этой команды. Например, команда пересылки, которая
берет непосредственное значение и посылает его в память, занимает 6
байт. Набор команд микропроцессора 8088 содержит несколько команд,
оптимизированных для работы с аккумулятором AX либо AL.
Использование этих команд поможет вам сэкономить время и место в
программах, где это важно.
Последние две команды на Фиг. 4.2 показывают, как занести
непосредственное значение в сегментный регистр. Любой другой
регистр, в примере это регистр AX, может временно содержать
непосредственное значение перед его записью в сегментный регистр.
Есть и другеи команды, которые переносят данные. Пример на
Фиг. 4.3 иллюстрирует эти команды.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:33
Фиг. 4.3 Команды пересылки данных Page 1-1
PAGE ,132
TITLE Фиг. 4.3 Команды пересылки данных
0000 CODE SEGMENT
ASSUME CS:CODE, DS:CODE
0000 EXDWORD LABEL DWORD
0000 EXWORD LABEL WORD
0000 EXBYTE LABEL BYTE
0000 87 D9 XCHG BX,CX ; Регистр BX <--> Регистр CX
0002 87 1E 0000 R XCHG BX,EXWORD ; Регистр BX <--> Память
0006 93 XCHG AX,BX ; Регистр AX <--> Регистр BX
0007 E4 20 IN AL,020H ; Порт 20H --> AL
0009 EC IN AL,DX ; Порт (DX) --> AL
000A E6 21 OUT 021H,AL ; AL --> Порт 021H
000C EE OUT DX,AL ; AL --> Порт (DX)
000D 8D 36 0000 R LEA SI,EXWORD ; Адрес(EXWORD) --> SI
0011 C5 36 0000 R LDS SI,EXDWORD ; M(EXDWORD) --> SI
; M(EXDWORD+2) --> DS
0015 C4 3E 0000 R LES DI,EXDWORD ; M(EXDWORD) --> DI
; M(EXDWORD+2) --> ES
0019 9F LAHF ; Флаги --> AH
001A 9E SAHF ; AH --> Флаги
001B D7 XLAT EXBYTE ; M(BX+AL) --> AL
001C CODE ENDS
END
Фиг. 4.3 Команды пересылки данных
Команда преобразования
Команда преобразования
Когда программа выполняет целое деление со знаком, возникает
проблема, если делимое - байтовый операнд. Иногда нужно разделить
байтовое значение на байтовое, но команда деления требует, чтобы
делимое занимало регистр AX. В случае деления со знаком
необходимо, чтобы значение в регистре AX было правильной копией
числа, представленного в дополнительном коде. Команда
преобразования байта в слово CBW решает эту задачу; она берет число
из регистра AL и расширяет его знак в регистр AH. Таким образом,
если значение в регистре AL положительно, команда заполняет регистр
AH нулями, если же значение в регистре AL отрицательно, она
устанавливает в регистре AH все единицы. Команда CBW загружает в
регистр AX 16=битовое число, равное значению исходного байта в
регистре AL. В случае деления слов команда преобразования слова в
двойное слово CWD выполняет идентичную функцию. Команда CWD
расширяет знак слова из регистра AX в регистр DX. Эти две команды
расширяют операнды до выполнения целого деления со знаком.
В случае целого деления без знака при тех же условиях знака уже
не существует, и его не надо расширять в старшую часть делимого. В
этом случае правильным является заполнение регистра AH (или
регистра DX) нулями перед делением. Существует много команд,
которые могут выполнить эту задачу, включая команду MOV с
непосредственным операндом, или просто
SUB AH,AH
что гарантирует обнуление регистра AH.
Команда замены
Команда замены
Команда замены XCHG просто меняет местами содержимое двух ячеек.
Эта команда может поменять местами содержимое двух регистров, или
регистра и памяти. При этом в качестве операндов не могут
использоваться сегментные регистры.
Команда XCHG заменяет три команды пересылки и не требует
промежуточной ячейки памяти. Если бы команда замены не
существовола, программе потребовалось бы три пересылки, чтобы
обменять значения в регистре AX и в регистре BX. Сначала она должна
была бы переслать содержимое регистра AX в рабочую ячейку, затем
переслать содержимое регистра BX в регистр AX, и наконец, переслать
содержимое рабочей ячейки в регистр BX. Команда XCHG одна выполняет
эту операцию.
Команды обработки строк
Команды обработки строк
Одной из функций, в которой в наборе команд микропроцессора 8088
уделено особое внимание, является обработка строк. Строка символов
или чисел, с которыми программа работает, как с группой, является
обычным типом данных. Программа пересылает строку из одного места
в другое, сравнивает ее с другими строками, а также ищет в ней
заданное значение. Обычным типом данных является строка символов.
Программа представляет каждое слово, предложение либо другую
структуру строкой символов в памяти. Функции редактирования,
например, в большой степени используют операции поиска и пересылки.
Строковые команды микропроцессора 8088 выполняют эти операции с
минимальными программными затратами, а также при минимальном
времени исполнения.
Сначала давайте обсудим принципы работы со строками. Программа
может выполнять строковые операции как над байтами, так и над
словами; отдельные элементы строк могут иметь 8 либо 16 бит.
Строковые команды не используют способы адресации, используемые
остальными командами обработки. Фактически строковые команды очень
конкретны в адресации и не допускают каких=либо вариаций. Строковые
команды адресуют операнды комбинациями регистров DS:SI либо ES:DI.
Операнды источника используют регистровую пару DS:SI, а операнды
результата регистровую пару ES:DI, откуда и названия
индекс=регистров источника и результата. Все строковые команды
имеют встроенную коррекцию адреса после выполнения операции. Строка
состоит из многих элементов, но строковые команды обработки строк
могут работать только с одним элементом в каждый момент времени,
поэтому программа тоже работает со строкой по одному элементу в
момент времени. Автоматическое увеличение или уменьшение адреса
дает возможность быстрой обработки строковых данных. Флаг
направления в регистре состояния управляет направлением обработки.
Когда он установлен равным 1, адрес уменьшается, если флаг сброшен
в 0, то увеличивается. Размер операнда определяет количество
увеличений=уменьшений. Байтовые команды обработки строк изменяют
адрес на 1 после каждой операции, а команды обработки строк над
словами изменяют адрес на 2. Тем самым после выполнения операции
указатель ссылается на следующий элемент строки.
Команды передачи управления
Команды передачи управления
Команды передачи управления нужны для того, чтобы передавать
выполнение программы в различные секции команд. В их число входят
также команды вызова подпрограмм. Команды вызова подпрограмм
вызывают подпрограммы, а команды перехода передают управление
поименованной ячейке без сохранения адреса возврата. Команда
условного перехода позволяет ЭВМ думать. Условные команды могут
проверить результат предыдущих действий и изменить течение
программы на основе полученного результата. Если бы команды
условного перехода не сеществовали, программирование для ЭВМ было
бы много проще, но и менне продуктивно.
Первое, что нужно рассмотреть при обсуждении команд передачи
управления - это методы адресации, используемые для определения
адреса ячейки, куда передается управление. Хотя операнд команды
перехода - такая же ссылка к памяти, как и ссылка к данным,
программы используют адреса перехода иначе, чем адреса данных.
Поэтому для адресации точки перехода существуют лучшие способы.
Команды сканирования и сравнения
Команды сканирования и сравнения
Две оставшиеся строковые команды используются в программах для
сравнения строковой информации. Первая из них - команда,
сканирование строки SCAS. Эта команда сравнивает значение в
регистре AL или регистре AX с операндом в памяти, на который
ссылается пара регистров ES:DI. Команда SCAS устанавливает флаги
нуля, переноса и переполнения, показывая результат сравнения
аккумулятора и ячейки памяти, и изменяет регистр DI так, чтобы он
указывал на следующий операнд в строке.
Команда SCAS не может использовать обычный префикс REP для
сканирования длинной строки информации. Точно так же, как команда
REP LODS не имеет смысла, команда REP SCAS не позволяет программе
контролировать каждое сравнение. Вместо этого существует два
варианта префикса REP - "повторять пока равно" REPE и "повторять
пока не равно" REPNE. Как и в случае обычного префикса REP,
программа загружает в регистр CX длину строки. Если указан префикс
REPE, команда выполняется ло тех пор, пока содержимое регистра AL
(или AX) не перестанет совпадать с ячейками памяти, или пока
содержимое регистра CX не станет равно 0. Пока аккумулятор
совпадает с ячейкой памяти, сканирование продолжается. Команда
REPNE в точности противоположна команде REPE. Сканирование
продолжается до тех пор, пока аккумулятор не совпадает с ячейкой
памяти.
Комбинация команд SCAS и REPNE позволяет программе выполнять
быстрый поиск по таблице. Чтобы найти объект в таблице, программа
должна перебрать каждую ячейку для сравнения с аргументом. На
Фиг. 4.24 показано, как команда SCAS выполняет эту функцию. В
регистре AL содержится аргумент сравнения. Таблица SCAN_TABLE
содержит значения, среди которых ведется поиск, а в регистре CX
находится длина таблицы. Команда REPNE SCASB сканирует таблицу до
тех пор, пока содержимое аккумулятора не станет равно элементу
строки. В этом месте регистр DI указывает на байт таблицы,
непосредственно следующий за сравнением. Вы можете определить
смещение совпавшего объекта, вычитая единицу из регистра DI после
метки FOUND. Программа может использовать эту информацию для
доступа к другой таблице, или таблицам, которые содержат
информацию, соответствующую этим исходным данным. Нужно обратить
особое внимание на команду JE после команды сканирования.
Существуют два случая, в которых управление передается этой
команде: байт в строке совпал с регистром AL и условие, задаваемое
префиксом REPNE, больше не выполняется; либо регистр CX достиг
нулевого значения без нахождения соответствующего числа в таблице.
В некоторых случаях создаются ситуации, исключающие появление
второго условия. Но в большинстве программ, необходимо учитывать
возможность неверных исходных данных. Программа перейдет на метку
FOUND после команды сканирования, если команда установила флаг нуля
(или равенства). Тем самым гарантируется, что сравнение найдено.
Если же регистр CX достиг нуля, последняя итерация сканирования
сбросила флаг нуля, показывая, что соответствия нет.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:36
Фиг. 4.24 Поиск в таблице Page 1-1
PAGE ,132
TITLE Фиг. 4.24 Поиск в таблице
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE,ES:CODE
;--------------------------------------
; Поиск значения AL в таблице
;--------------------------------------
0000 8D 3E 000C R LEA DI, SCAN_TABLE ; Адрес таблицы
0004 B9 000B 90 MOV CX, SCAN_TABLE_LENGTH ; Длина таблицы
0008 F2/ AE REPNE SCASB ; Поиск
000A 74 00 JE FOUND ; Если равно, то значение найдено
; ... ; Иначе значение не найдено
000C FOUND:
;----- продолжение программы
000C 89 96 93 8A 85 8D 83 SCAN_TABLE DB 'ЙЦУКЕНГШЩЗХ'
98 99 87 95
= 000B SCAN_TABLE_LENGTH EQU $-SCAN_TABLE
0017 CODE ENDS
END
Фиг. 4.24 Сканирование таблицы
Последняя строковая команда - сравнение строк CMPS. Подобно
сканированию строки, это - команда сравнения. Подобно команде MOVS,
она работает с двумя операндами памяти. Команда CMPS сравнивает
строку по адресу DS:SI со строкой по адресу ES:DI, и соответственно
устанавливает флаги. Как и для команды SCAS, в данном случае
использовать префикс REP нельзя, а префиксы REPE и REPNE можно
использовать беспрепятственно.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:41
Фиг. 4.25 Сравнение строк Page 1-1
PAGE ,132
TITLE Фиг. 4.25 Сравнение строк
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE,ES:CODE
;--------------------------------------
; Сравнивается 5-символьная строка с таблицей
; таких 5-символьных строк. Выход из программы
; если найдена искомая строка в таблице строк.
;--------------------------------------
0000 FIG4_25 PROC NEAR
0000 8D 36 001D R LEA SI, ARGUMENT ; Адрес строки
0004 8D 3E 0022 R LEA DI, COMPARE_TABLE ; Адрес таблицы
0008 BB 0000 MOV BX, 0 ; В BX cчетчик просмотренных строк
000B COMPARE_LOOP:
000B 56 PUSH SI ; Сохранение адреса строки
000C 57 PUSH DI ; Сохранение адреса таблицы
000D B9 0005 MOV CX, 5 ; Сравниваются 5 байт
0010 F3/ A6 REPE CMPS ARGUMENT,COMPARE_TABLE ; Сравнение
0012 5F POP DI ; Восстановление
0013 5E POP SI ; регистров
0014 74 06 JE FOUND ; Искомая строка найдена
0016 83 C7 05 ADD DI, 5 ; Сдвиг указателя на следующую
; строку в таблице
0019 43 INC BX ; Номер текущей строки в таблице
001A EB EF JMP COMPARE_LOOP ; Цикл
001C FOUND:
001C C3 RET
001D FIG4_25 ENDP
001D 41 42 43 44 45 ARGUMENT DB 'ABCDE'
0022 COMPARE_TABLE LABEL BYTE
0022 51 57 45 52 54 50 4F DB 'QWERT','POIUY','ASDFG','LKJHG'
49 55 59 41 53 44 46
47 4C 4B 4A 48 47
0036 5A 58 43 56 42 4D 4E DB 'ZXCVB','MNBVC','VWXYZ','ABCDE'
42 56 43 56 57 58 59
5A 41 42 43 44 45
004A CODE ENDS
END
Фиг. 4.25 Сравнение строк
Фиг. 4.25 демонстрирует пример использования команды CMPS.
Этот пример сравнивает пятисимвольную исходную строку с таблицей
строк символов. Программа пытается найти соответствие исходной
строки с элементом таблицы. Когда строка найдена, в регистре BX
нахолится индекс строки. В программе используется префикс REPE, так
что команда сравненния строк выполняется до тех пор, пока один из
символов аргумента не совпадает с символом таблицы. Если все пять
символов совпали, программа находит правильный элемент. Команда JE
("переход, если равно") проверяет результат команды CMPS. Если
сравнение завершилось из=за несоответствия символов, флаг нуля
показывает ненулевое состояние. Если же команда CMPS завершилась
потому, что счетчик CX стал нулевым, флаг нуля покажет совпадение и
произойдет переход на метку FOUND. Вы можете заметить, что в этом
примере отсутствуют некоторые необходимые детали, которые смогли бы
сделать его хорошей программой. Например, он никак не обрабатывает
случай, когда исходная строка не совпала ни с одним элементм
таблицы. Любой хороший программист скажет вам, что исключительные
ситуации нужно обрабатывать всегда.
Команды управления микропроцессором
Команды управления микропроцессором
Оставшиеся команды микропроцессора 8088 управляют его работой.
Многие из них устанавливают или сбрасывают некоторые флаги
состояния.
Команды ввода и вывода
Команды ввода и вывода
Для выполнения операций ввода и вывода микропроцессор 8088 имеет
команды IN и OUT соответственно. Каждое устройство ввода=вывода
IBM PC имеет один или больше встроенных регистров, с которыми могут
работать эти команды. Каждое устройство ввода=вывода имеет адрес
для каждого регистра в устройстве. Это адресное пространство
отличается от адресного пространтва памяти; всего существует 216,
или 65536 адресов ввода=вывода, доступных микропроцессору 8088. В
IBM PC 512 из этих адресов назначены системному каналу ввода=вывода
и могут использоваться различными адаптерами. Другие 256 адресов
исполбзуются на системной плате для управления подключенными туда
устройствами ввода=вывода.
Команда IN пересылает данные из устройства ввода=вывода в
регистр AL. Эта команда может указать адрес устройства ввода=вывода
двумя различными способами. Если адрес устройства находится в
пределах 0 - 255, он может содержаться в команде как
непосредственное значение. Если адрес больше 255, команда сообщает
это косвенно. В случае косвенной команды адрес устройства
ввода=вывода содержится в регистре DX. Регистр DX может содержать
адреса всех устройств ввода=вывода, включая те, номера которых
меньше 256.
Аналогично работает команда OUT, за исключением того, что она
записывает регистр AL в регистр устройства ввода=вывода. Адреса в
команде OUT указываются так же, как и в команде IN.
Команды IN и OUT также могут пересылать слова в устройства
ввода=вывода и из них. В случае работы со словами источником и
приемником является регистр AX. Так как у микропроцессора 8088
однобайтовая внешняя шина, устройства ввода=вывода IBM PC работают
только с байтами при любых операциях ввода=вывода. Это означает,
что операции ввода=вывода слов не используются в персональной ЭВМ.
Однако пословные операции ввода=вывода имеют смысл в системе с
микропроцессором 8086, который имеет тот же набор команд.
Логические операции
Логические операции
Следующий класс команд - логические команды. Эти команды, точно
так же, как и арифметические команды, преобразуют данные, но делают
это не арифметически. В то время как команды сложения и вычитания
связаны со школьной арифметикой, логические команды работают со
значениями 0 и 1, которые использует ЭВМ. В общем случае, эти
команды позволяют программе выполнять битовые операции.
Четырьмя основными логическими командами являются AND (и), OR
(или), XOR (исключающее или), NOT (не). Существуют и другие
логические функции, состоящие из этих четырех функций, но в
микропроцессоре 8088 для них нет соответствующих команд. Эти четыре
команды работают непосредственно с нулями и единицами двоичного
кода.
Простейшая функция выполняется командой NOT. Эта команда
основывается на определении единицы и нуля, как истины (TRUE) и лжи
(FALSE) соответственно. Предложение NOT TRUE (не истина) - это
FALSE (ложь), а предложение NOT FALSE (не ложь) - это TRUE
(истина). Команда NOT инвертирует все биты числа данных. Иначе
говоря, команда NOT эквивалентна вычитанию данных из величины,
состоящей из всех единиц. Фиг. 4.17 показывает, как оператор NOT
действует на единственный бит.
Значение NOT(Значение)
-------------------------------------
0 1
1 0
------------------------------------- Фиг. 4.17 Операция NOT
Остальные три логические функции имеют два операнда. На
Фиг.4.18 показаны результаты действий, произведенных каждой
функцией над парой бит.
X Y X AND Y X OR Y X XOR Y
-----------------------------------------------------
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0
-----------------------------------------------------
Фиг. 4.18 Логические операции
Поскольку микропроцессор 8088 работает с байтами или словами,
он повторяет результаты таблицы с каждым битом операнда. Например,
байтовая команда выполняет логическое И со значениями нулевого бита
обеих операндов а помещает результат в бит 0 результата. Затем эта
команда повторяет функцию И с битами от первого до седьмого. В
результате получается побитовая функция И над отдельными битами
операндов.
Функция AND равна 1 только тогда, когда оба операнда равны 1. В
терминах истинности, результат есть истина только тогда, когда и X,
и Y истинны. Функция OR дает 1, если хотя бы один из операндов
равен 1. Результат есть истина, если либо X, либо Y являются
истинными. Результат функции XOR равен 1, только если один из
операндов равен 1, а другой равен 0. Если же оба операнда равны 0,
или оба равны 1, то результат равен 0. Функция исключающее ИЛИ в
точности соответствует сложению, у которого игнорируется перенос.
Фиг. 4.19 иллюстрирует логические команды микропроцессора
8088. Команде NOT требуется один операнд, а ее форма идентична
команде NEG. Остальные логические команды копируют синтаксис команд
сложения и вычитания.
Когда микропроцессор 8088 делает логическую операцию, он
устанавливает флаги в соответствии с результатом. Так как операция
не арифметическая, флаги переноса и переполнения всегда
устанавливаются равными 0. Флаг дополнительного переноса после
логических операций остается неопределенным, в то время как другие
флаги (знак, нуль) правильно отражают результат операции.
Исключение представляет команда NOT, которая не изменяет ни одного
флага.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:15
Фиг. 4.19 Логические команды Page 1-1
PAGE ,132
TITLE Фиг. 4.19 Логические команды
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 EXBYTE LABEL BYTE
0000 EXWORD LABEL WORD
0000 22 06 0000 R AND AL, EXBYTE ; AL <- AL and [EXBYTE]
0004 81 E3 9FEF AND BX, 1001111111101111B ; BX <- BX and 9FEFH
0008 80 26 0000 R 03 AND EXBYTE, 00000011B ; [EXBYTE] <- [EXBYTE] and 3
000D 08 2E 0000 R OR EXBYTE, CH ; [EXBYTE] <- [EXBYTE] or CH
0011 0B 16 0000 R OR DX, EXWORD ; DX <- DX or [EXWORD]
0015 0D FFF9 OR AX, 0FFF9H ; AX <- AX or 0FFF9H
0018 33 1E 0000 R XOR BX, EXWORD ; BX <- BX xor [EXWORD]
001C 30 1E 0000 R XOR EXBYTE, BL ; [EXBYTE] <- [EXBYTE] xor BL
0020 34 EF XOR AL, 0EFH ; AL <- AL xor 0EFH
0022 F7 D1 NOT CX ; CX <- not CX
0024 F6 16 0000 R NOT EXBYTE ; [EXBYTE] <- not [EXBYTE]
0028 F7 06 0000 R 0003 TEST EXWORD, 0003H ; Установка флагов по [EXWORD] and 3
002E 84 E0 TEST AH, AL ; Установка флагов по (AH and AL)
0030 A9 0002 TEST AX, 02H ; Установка флагов по (AX and 2)
0033 D1 C1 ROL CX, 1 ; Циклический сдвиг влево на 1
0035 D3 0E 0000 R ROR EXWORD, CL ; Циклический сдвиг вправо на CL
0039 D0 16 0000 R RCL EXBYTE, 1 ; Циклический сдвиг с переносом
; влево на 1
003D D3 DB RCR BX, CL ; Циклический сдвиг с переносом
; вправо на CL
003F D1 E0 SHL AX, 1 ; Сдвиг логический влево на 1
0041 D1 E0 SAL AX, 1 ; Сдвиг арифметический влево на 1
0043 D3 EB SHR BX, CL ; Сдвиг логический вправо на CL
0045 D0 3E 0000 R SAR EXBYTE, 1 ; Сдвиг арифметический вправо на 1
0049 CODE ENDS
END
Фиг. 4.19 Логические команды
Первоочередное назначение логических операций в микропроцессоре
8088 - работа с битами. Самой малой единицей данных, с которой
может работать этот микропроцессор, является байт. Ни одна из
арифметических команд не может непосредственно выделить или
изменить единственный бит, а логические команды позволяют программе
обрабатывать отдельные биты.
Почему интересны однобитовые операции? Во многих случаях
программа должна хранить значение индикатора - истина - ложь. Этот
бит может означать, что печатающее устройство занято, что нажата
регистровая клавиша, или что инициализация программы выполнена. В
таких случаях расточительно отводить байт для хранения
единственного бита информации. Программа может объединить несколько
таких битов в одном байте, если у нее есть способ выделения
отдельных битов для их проверки и установки. Такое объединение
однобитовых флагов очень широко используется в устройствах
ввода=вывода, которые имеют различные адреса. Устройству
ввода=вывода гораздо проще работать с разными битами по одному
адресу, чем распознавать многие адреса.
Логические команды могут выделить отдельные биты в байте или
слове так, что они могут быть установлены, сброшены, проверены. Для
выделения битов эти команды используют маску. Значение маски
используется командой побитно. Чтобы установить какой=либо один
бит, нужно использовать команду OR. В этом случае все значения
маски - нули, кроме единицы на месте устанавливаемого бита. Команда
OR над маской и другим операндом устанавливает 1 в выбранном бите,
а другие биты результата оставляют неизменными. Аналогично,
оператор AND может сбросить единственный бит. В маске все разряды
единичные, кроме сбрасываемого бита. Этот бит сбросится в 0, а
остальные останутся без изменений.
Программисты не используют функцию исключающее или столь же
часто, как команды AND и OR, но она тоже бывает полезна. Команда
может выполнить взаимное дополнение одного бита с данными. Запишите
маску для команды XOR так, чтобы на месте инвертируемого бита была
1, а на всех других местах 0. Когда команда XOR выполнится, биты,
соответствовавшие нулям, останутся без изменений, а биты,
соответствовавшие единицам маски, инвертируются. Если начальное
значение бита было 0, 1 XOR 0 дает 1, дополнение к 0, а если
начальное значение было 1, 1 XOR 1 дает 0, дополнение к 1.
Последняя логическая команда - TEST (проверка). Эта команда
идентична команде AND, за исключением того, что она не записывает
результат, но устанавливает флаги в соответствии с ним, т.е.
команда TEST соответствует команде AND, как команда CMP
соответствует команде SUB. Эта команда проверяет заданный бит, или
набор битов внутри байта или слова.
Как работает команда проверки? Предположим, программа хочет
проверить младший значащий бит байта, бит 0. Программа порождает
маску 01H либо в регистре, либо как непосредственное значение.
Команда TEST (или AND) дает результат с гарантированными нулями по
всем позициям, за исключением бита 0; значение бита 0 отражает
значение оригинала. Если нулевой бит оригинала содержит 0, то бит
остается нулевым. Если он сначала единичен, результат ненулевой, и
флаг нуля сбрасывается; если же бит 0, результат нулевой, и флаг
нуля устанавливается. Таким образом, программа может проверить
единственный бит, выполняя команды TEST и AND с маской, которая
имеет единственную единицу на месте проверяемого бита; регистр
флагов отразит состояние этого единственного бита. Команда TEST
проверяет заданный бит без разрушения других битов, поскольку эта
команда не изменяет поле результата.
Операции сдвига и поворота
Операции сдвига и поворота
Остальные логические команды на Фиг. 4.19 выполняют сдвиги данных.
Команда сдвига перемещает все биты в поле данных либо вправо, либо
влево. Это можно проиллюстрировать церковной скамьей, на которой
сидят мужчины и женщины. Каждый раз, когда приходит новый человек
и садится на край скамьи, остальные сидящие на ней сдвигаются на
одно место. Если скамья уже заполнена, то крайний в результате
такого сдвига вытесняется с нее. Команда сдвига делает в точности
то же самое, только вместо женщин и мужчин здесь выступают нули и
еденицы.
На Фиг.4.20 показаны восемь различных команд сдвига; у этих
команд имеются некоторые вариации. Сначала мы рассмотрим общие для
этих команд черты.
Как и другие логические команды, сдвиги работают с байтами и
словами. Каждая команда указывает единственный операнд. Этот
операнд может быть либо регистром, либо ячейкой памяти. Все эти
команды используют байт mod=r/m для описания операнда.
ЪДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДї
ЪДДДДї і ЪДДДДДДДДДї і і ЪДДДДДДДДДДї і ЪДДДДї
і CY Г<ДБДґ ДАННЫЕ Г<Щ АД>ґ ДАННЫЕ ГДБД>ґ CY і
АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ
ROL ROR
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і ЪДДДДї ЪДДДДДДДДДї і і ЪДДДДДДДДДДї ЪДДДДї і
АДДґ CY Г<ДДДґ ДАННЫЕ Г<ДДЩ АДДД>ґ ДАННЫЕ ГДДД>ґ CY ГДЩ
АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ
RCL RCR
ЪДДДДї ЪДДДДДДДДДї ЪДДДДДДДДДДї ЪДДДДї
і CY Г<ДДДґ ДАННЫЕ Г<ДДД 0 0 ДДД>ґ ДАННЫЕ ГДДД>ґ CY і
АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ
SHL SHR
ЪДДДДДДДї
і і
ЪДДДДї ЪДДДДДДДДДї і ЪДДДБДДДДДДї ЪДДДДї
і CY Г<ДДДґ ДАННЫЕ Г<ДДД 0 АДД>ґ ДАННЫЕ ГДДД>ґ CY і
АДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДДЩ АДДДДЩ
SAL SAR
Фиг. 4.20 Операции сдвига.
Во всех командах сдвига определяется счетчик сдвигов, т.е.
программа указывает число битов, на которое надо сделать сдвиг; это
число и есть счетчик сдвигов. Его наиболее распространенное
значение - единица. Такой счетчик сдвигает биты операнда на одну
позицию. Однако команда может задать произвольный счетчик сдвигов,
занося его значение в регистр CL пред сдвигом. Если в команде
указано, что счетчик сдвигов надо взять из регистра CL, значение
этого регистра определяет число сдвигов битов. Число в регистре CL
может быть любым от 0 до 255, но его практически имеющие смысл
значения лежат в пределах 0 - 16. Значение 0 не вызывает сдвига, а
любое значение больше 16 сдвигает битов больше, чем содержит
операнд.
Другая общая черта команд сдвига - это установка флага
переноса. Бит, попадающий за пределы операнда, имеет специальное
место. Команды сдвига помещают последний выдвинутый из операнда бит
в флаг переноса. Если сдвиг был на один бит, то бит из дальнего
конца операнда становится новым значением флага переноса. В случае
многобитового сдвига, вдвигаемый в перенос бит появляется изнутри
операнда. Флаг переноса имеет значение для операций повышенной
точности. Поскольку операнд операции сдвига может иметь максимум 16
бит, программа может организовать работу с данными большего размера
с помощью нескольких сдвигов и флага переноса. Программа
"разрезает" операнд на 16=битовые куски, а затем сдвигает каждую
часть на один бит каждый раз. Флаг переноса используется программой
для передачи выдвинутой информации в следующую часть сдвигаемого
операнда.
Верхние четыре команды на Фиг.4.20 - команды циклического
сдвига. На рисунке схематически представлена работа каждой команды.
Циклические сдвиги переносят появляющийся в конце операнда бит в
другой конец. Циклический сдвиг влево ROL и циклический сдвиг
вправо ROR различаются лишь направлением сдвига данных. Аналогично,
циклический сдвиг влево с переносом RCL и циклический сдвиг вправо
с переносом RCR являются зеркальным отражением друг друга. Команды
ROL и RCL различаются в трактовке флага переноса. Байтовая команда
RCL рассматривает данные как 9=битовые, причем роль девятого бита
играет флаг переноса. Если операнд - слово, команда ROL циклически
сдвигает 16 бит, а команда RCL циклически сдвигает 17 бит.
Команды снизу Фиг.4.20 не возвращают выдвигаемые из операнда
биты в свой операнд. Эти биты попадают в флаг переноса, а затем
просто исчезают. Значение, вдвигаемое в операнд, определяется типом
сдвига. В случае логического сдвига вдвигаемый бит всегда 0;
арифметический сдвиг выбирает вдвигаемый бит таким, чтобы сохранить
знак операнда.
Почему сдвиг называется арифметическим, если он входит в группу
логических команд? Сдвиг числа на одну позицию (бит) эквивалентен
умножению или делению этого числа на 2. В десятичной системе
счисления, добавление нуля в конце числа умножает его на 10. В
двоичной арифметике добавление 0 в конце умножает число на 2. Так
как ЭВМ не может добавить другой бит в конце операнда, операция
сдвига действует аналогично. Команда сдвига влево перемещает все
биты влево на одну позицию, а в младшую позицию помещает 0. Таким
образом, сдвиг влево умножает число на 2. Если величина сдвига
больше единицы, число умножается на 2, возведенное в степень,
равную содержимому счетчика сдвигов. Например, сдвиг влево на 3
бита эквивалентен умножению на 8.
Сдвиг числа вправо - это то же самое деление на 2. Сдвинутый
операнд - частное, а флаг переноса - остаток. Если счетчик сдвигов
больше 1, операнд по=прежнему есть частное, а остаток теряется.
Таким образом, команды сдвига делают эффективным умножение и
деление на степень 2. Фактически, воэможность замены умножения
сдвигом становится хорошим выходом в ситуациях, когда необходимо
исключить умножение, даже если множитель не есть степень 2.
При арифметическом сдвиге вместо деления на 2 отрицательного
числа возникает следующая проблема. Если команда вдвигает 0 в
старший бит, результат становится положительным. Команда
арифметического сдвига вправо SAR решает эту проблему путем
восстановления значения старшего бита во время сдвига. Поэтому
отрицательное число остается отрицательным, а положительное -
положительным. Эта проблема не возникает в случае сдвига влево,
поскольку бит знака находится у операнда слева. Из=за этого команды
логического сдвига влево SHL и арифметического сдвига влево SAL
идентичны.
В связи с арифметической природой, все команды сдвогов влияют
на флаг переполнения так же, как и на флаг переноса. Флаг
переполнения не определен в случае счетчиков сдвига больших
единицы, но при единичных сдвигах команды устанавливают флаг
переполнения только в случае, если в результате операции изменился
знак числа. Если старший бит не изменился, флаг переполнения
сбрасывается, т.е. флаг переполнения показывает, дает ли
подразумеваемое сдвигом умножение или деление правильный результат
в дополнительном коде.
На Фиг. 4.21 приведены два примера команд сдвига. Первый пример
демонстрирует умножение на число с помощью команд сдвига влево. В
примере выполняется умножение на 9, не являющееся степенью 2.
Сначала в примере данные сдвигаются влево на три позиции, чтобы
умножить число на 8. Затем программа складывает полученное значение
с первоначальным, давая результат, равный первоначальному числу,
умноженному на 9.
Недостатки этого метода очевидны. Он требует много больше
команд, чем простое умножение - которое выглядело бы примерно так:
PUSH DX
MOV DX,9
IMUL DX
POP DX
Кроме того, умножение на 9 с помощью сдвига дает 16=битовый
результат, а не 32=битовый, как команда IMUL.
Все же в программе умножение с помощью сдвига может оказаться
желательным в некоторых случаях. В первую очередь, его преимущество
- скорость выполнения. Команда IMUL требует много времени, тогда
как команда сдвига выполняется гораздо быстрее. В случае примера на
Фиг. 4.21, метод сдвига работает примерно на 25% быстрее. Выигрыш
небольшой, но может оказаться решающим для приложения, зависящего
от умножения целых чисел на 9. Умножения на степень 2 могут дать и
больший выигрыш в скорости выполнения.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:21
Фиг. 4.21 Примеры инструкций сдвига Page 1-1
PAGE ,132
TITLE Фиг. 4.21 Примеры инструкций сдвига
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
;--------------------------------------------------
; Эта программа умножает число, заданное в регистре AX
; на 9 без использования команды умножения
;--------------------------------------------------
0000 MUL9 PROC NEAR
0000 51 PUSH CX ; Сохранение регистра CX в стеке
0001 50 PUSH AX ; Временное сохранение AX
0002 B1 03 MOV CL, 3 ; Будем сдвигать регистр AX на 3 разряда,
0004 D3 F8 SAR AX, CL ; тем самым уножая на 8
0006 8B C8 MOV CX, AX ; CX <- AX * 8
0008 58 POP AX ; Восстановление AX
0009 03 C1 ADD AX, CX ; AX <- исходное значение * 9
000B 59 POP CX ;
000C C3 RET
000D MUL9 ENDP
;--------------------------------------------------
; Эта программа программа выделяет один бит в
; регистре AX, номер которого задан в регистре CL
;--------------------------------------------------
000D 53 PUSH BX ; Сохранение регистра BX в стеке
000E BB 0001 MOV BX, 1 ; Создание маски (1 в разряде 0 регистра BX)
0011 D3 C3 ROL BX, CL ; Сдвиг маски
0013 23 C3 AND AX, BX ; Выделение требуемого разряда
0015 5B POP BX ; Восстановление регистра BX
0016 C3 RET
0017 CODE ENDS
END
Фиг. 4.21 Примеры сдвига
Второй пример на Фиг. 4.21 показывает, как использовать сдвиг
на переменное число разрядов для выборки отдельного бита. Этот
фрагмент предполагает, что исходная информация находится в регистре
AX, а регистр CL содержит номер бита, выбираемого из регистра AX:
если содержимое регистра CL равно 8, из регистра AX выбирается бит
8. Программа сдвигает маску в регистре BX на указанную в регистре
CL позицию, а команда AND изолирует выбранный бит.
Для того чтобы этот пример работал правильно, число в регистре
CL должно быть в диапазоне 0 - 15. Можно было бы использовать
команду AND, чтобы выделить младшие четыре бита значения сдвига в
регистре CL; команда AND CL, 0FH гарантирует, что число в регистре
CL находится в пределах 0 - 15. Вы можете изменить этот пример так,
чтобы выделить более одного бита из слова. Можно было бы выделить
тетраду из 16=битового слова, заменив значение маски в регистре BX.
Операции со стеком
Операции со стеком
В гл.3 обсуждалось, как реализован стек в микропроцессоре 8088.
Микропроцессор 8088 адресует стек с помощью регистровой пары SS:SP.
Помещение объектов в стек приводит к тому, что он растет в сторону
меньших адресов памяти. Стек, кроме всего прочего, служит и для
запоминания адресов возврата из подпрограмм. В этом разделе
рассматриваются некоторые команды, которые непосредственно работают
со стеком.
Фиг.4.7 иллюстрирует ассемблированные стековые команды.
Мнемоника команд очевидна; за кодами операций PUSH и POP следует
имя регистра для указания операнда. Единственным исключением
является помещение и извлечение из стека регистра флагов, которые
используют мнемонику PUSHF и POPF соответственно. Содержимое любой
ячейки памяти, которую программа может адресовать, используя
возможные способы адресации, также может быть помещено или
извлечено из стека.
При любых действиях со стеком в микропроцессоре 8088 базовой
единицей информации является 16=битовое слово. Длина любого
объекта, помещаемого в стек либо извлекаемого из стека, составляет
одно или несколько слов. Байтовых команд, связанных с засылкой
данных или извлечением их из стека, не существует. Если, например,
программе необходимо сохранить содержимое регистра AL а стеке, она
должна поместить содержимое регистра AX, так как не существует
способа сохранения только содержимого регистра AL.
Основное назначение стека - временное хранение информации. Как
мы уже видели, стек используется для сохранения адреса возврата;
программа также может сохранять данные. Если программа хочет
использовать регистр, пусть даже сохранить текущие данные, она
может послать значение этого регистра в стек. Эти данные
сохраняются в стеке и позже могут быть восстановлены. Например,
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:43
Фиг. 4.7 Операции со стеком Page 1-1
PAGE ,132
TITLE Фиг. 4.7 Операции со стеком
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 EXWORD LABEL WORD
0000 50 PUSH AX ; Поместить регистр в стек
0001 56 PUSH SI
0002 0E PUSH CS ; Можно поместить в стек сегментный регистр
0003 FF 36 0000 R PUSH EXWORD ; Можно также поместить в стек ячейку памяти
0007 8F 06 0000 R POP EXWORD ; Можно извлечь то, что в помещено в стек
000B 07 POP ES ; Можно извлечь в другое место
000C 5F POP DI
000D 5B POP BX
000E 9C PUSHF ; Другая мнемоника для флагов
000F 9D POPF
;----- Пример, демонстрирующий передачу параметров
0010 50 PUSH AX
0011 53 PUSH BX
0012 51 PUSH CX
0013 52 PUSH DX
0014 E8 0017 R CALL SUBROUTINE ; Передача управления
; ... ; Продолжение программы
0017 SUBROUTINE PROC NEAR
0017 8B EC MOV BP, SP ; Занесение в BP адреса стека
0019 8B 46 02 MOV AX, [BP+2] ; Выборка последнего параметра (DX)
001C 8B 5E 04 MOV BX, [BP+4] ; Выборка третьего параметра (CX)
001F 8B 4E 06 MOV CX, [BP+6] ; Выборка второго параметра (BX)
0022 8B 56 08 MOV DX, [BP+8] ; Выборка первого параметра (AX)
; ...
0025 C2 0008 RET 8 ; Возврат с уничтожением поля параметров
0028 SUBROUTINE ENDP
0028 CODE ENDS
END
Фиг. 4.7 Операции со стеком
программе нужно ввести код из порта ввода=вывода 3DAH, а в регистре
DX находятся важные данные. Следующая последовательность команд
PUSH DX
MOV DX, 3DAH
IN AL, DX
POP DX
сохраняет регистр DX в стеке на то время, пока он нужен в
программе для выполнения команды IN.
Операции сохранения регистров в стеке обычно используется в
начале программы. В большинстве случаев подпрограмма старается
избегать изменения содержимого любого регистра. Поэтому
подпрограмма, которой нужны регистры для вычислений и для хранения
адресов, помещает все необходимые ей регистры в стек до выполнения
команд обработки. Затем, после выполнения, подпрограмма
восстанавливает регистры из стека с помощью команд POP.
Помните о том, что стек - это структура типа LIFO. Если в вашей
программе выполняется последовательность команд
PUSH BX
PUSH CX
POP BX
POP CX
то результирующим эффектом будет обмен значений в регистрах BX
и CX. Только тот факт, что в команде PUSH был указан регистр BX, не
означает, что команда POP, указывающая на тот же регистр,
восстанавливает первоначальное содержимое регистра BX. Еще одним
важным моментом является то, что команды PUSH и POP должны быть
сбалансированы, т.е. каждой команде PUSH должна соответствовать
команда POP. Точно так же, как и в случае скобок в арифметическом
выражении, если посылки и извлечения из стека не сбалансированы,
результаты будут неверны. Более того, несбалансированные команды
PUSH/POP обычно приводят к возврату из подпрограмм по адресу
значения данных, а не значения указателя команд из=за того, что
микропроцессор 8088 записывает в стек адрес возврата. Обычно это
вынуждает микропроцессор выполнять программу, которую программист
никогда не писал. Поэтому баланс стековых команд обязателен. Будьте
особенно внимательны в тех случаях, когда в программе есть условный
переход вокруг стековых операций; можно легко выпустить из виду
один из вариантов выполнения, что оставит стек несбалансированным.
Наряду с сохранением данных, программа может использовать стек
в качестве буфера при некоторых пересылках; в частности, не
существует команды пересылки, которая бы переносила данные из
одного сегментного регистра в другой. В обычном случае загрузка
одного сегментного регистра из другого требует сначала загрузки его
значения а промежуточный регистр. Это достигается следующей
последовательностью из двух команд:
MOV AX,CS ;переслать значение регистра
;CS в регистр AX
MOV DS,AX ;загрузить это значение в
; регистр DS
Каждая из этих команд имеет длину несколько байт, и эта
последовательность разрушает содержимое регистра AX. Альтернативным
подходом может быть
PUSH CS ; регистр CS поместить в стек
POP DS ; поместить это значение в регистр DS
Результирующий эффект этой последовательности команд тот же,
регистр DS загружается из регистра CS. Здесь длина программы -
всего два байта, и к тому же не требуется промежуточный регистр.
Однако эти две команды занимают больше времени, так как нужны
дополнительные циклы чтения и записи в стек. Это - метод потери в
скорости выполнения ради уменьшения размера объектного кода.
Передача параметров
Передача параметров
Стек также служит удобным местом для передачи информации в
подпрограммы и из них. Обычно программа передает параметры в
подпрограмму, помещая их в регистры, однако в некоторых случаях
число параметров превышает размеры регистрового пространства. В
таких случаях програииа может поместить параметры в стек до
выполнения команды CALL (вызов подпрограммы). Как мы увидим в
гл.10, стек является единственным средством передачи параметров в
подпрограммы, написанные на языке ассемблера, из языков высокого
уровня Бейсик и Фортран.
Подпрограмма может очень эффективно загружать эти параметры из
стека. В обычных случаях программа читает информацию из стека
единственным способом - извлекая ее оттуда. Вместо этого
подпрограмма может использовать регистр BP, как указатель на
область стека. Когда программа передает параметры через стек, одной
из первых команд в подпрограмме выполняется команда
MOV BP, SP
которая загружвет регистр BP текущим значением указателя стека.
Поскольку регистр BP - адресный регистр, подпрограмма может
использовать его при адресных вычислениях, а это означает, что все
параметры доступны как смещения относительно регистра BP.
Конструкторы микропроцессора 8088 определенно помнили об
описанном выше методе передачи параметров, так как при доступе к
данным регистр BP использует по умолчанию регистр стекового
сегмента SS в качестве сегментного регистра. Во всех других
нормальных случаях доступа к данным микропроцессор использует
регистр DS. Поскольку стек находится в стековом сегменте,
регистровую пару SS:BP очень естественно использовать для адресации
информации в стеке.
На Фиг. 4.7 изображен пример, демонстрирующий использование
регистра BP для доступа к параметрам, переданным через стек. В этом
примере головная программа перед выполнением команды CALL поместила
четыре слова в стек. Подпрограмма загружает в BP указатель данных в
стеке. Заметим, что смещения, используемые для доступа к данным в
стеке, учитывают тот факт, что адрес возврата также был записан в
стек в результате выполнения команды CALL.
В подпрограмме этого примера в вершине стека лежит адрес
возврата, и регистр BP содержит смещение этой ячейки. Двумя байтами
ниже в стеке лежит помещенный последним параметр, регистр DX;
далее, через двухбайтовые интервалы - регистры CX, BX и AX. Таким
образом, правильным адресом для чтения параметра, содержащегося в
регистре DX, будет [BP+2], а другие адреса следуют через
двухбайтовые интервалы. В данном примере значение, находившееся в
регистре DX, попадает в регистр AX, CX в BX и т.д.
Подпрограмма может использовать регистр BP для адресации стека
не только при передаче параметров. Подпрограмма может оказаться
длинной и запутанной настолько, что хранить все необходимые ей во
время выполнения значения в регистрах трудно. Помещение этих
значений в стек и загрузка указателя этой области в регистр BP
решает проблему.
Многим подпрограммам в течение их выполнения также необходима
локальная память, и подпрограммы могут динамически расположить ее в
стеке. Всякий раз, когда программа вызывается, она может вычесть
размер этой области памяти из содержимого указателя стека. Так как
стек растет по направлению к младшим адресам, вычитание числа из
регистра SP идентично помещению в стек такого же количества данных
- за исключением тех данных, которые не инициализированы. После
этого подпрограмма может использовать регистр BP для адресации
такой области памяти. Когда наступает момент возврата, подпрограмма
может прибавить соответствующее значение к указателю стека, и тем
самым восстановить его прежнее значение. Динамическая организация
данных означает, что программа использует область памяти только
тогда, когда она необходима для работы, и не занимает эту память
все остальное время, поэтому программу можно выполнять на машине с
малым объемом памяти, что невозможно при другой организации данных.
Но лучшим является то, что программист не должен создавать сложную
подсистему управления памятью, так как все находится под
управлением стековой структуры.
Оператор возврата из подпрограммы на Фиг. 4.7 демонстрирует еще
одну возможность набора команд микропроцессора 8088. Команда
возврата из подпрограммы RET может иметь операнд, который
представляет собой значение, прибавляемое микропроцессором к
содержимому указателя стека после извлечения адреса возврата. В
примере используется значение 8; это означает, что восемь байт, или
четыре слова данных должны быть удалены из стека после возврата.
Эти значения исчезают навсегда. Результат тот же, какой был бы в
итоге извлечения значений из стека, чтобы уничтожить их; команда
возврата уже сделала это автоматически.
Такой метод удаления информации из стека срабатывает только в
случае параметров, которые вызывающая программа помешает в стек.
Подпрограмма обязана удалить все динамически распределенные области
памяти из стека перед выполнением возврата. Она должна сделать это
явно, а не с помощью команды возврата, так как область данных лежит
между текущей вершиной стека и адресом возврата.
Подпрограмма может возвратить в стеке некоторую информацию
вызывающей программе. Если вызывающая программа помешает параметры
в стек, подпрограмма может изменить их значения и оставить в стеке,
а вызывающая программа может извлечь их после возврата. Если
подпрограмма возвращает только один параметр, но вызывалась с тремя
параметрами в стеке, то выполнить возврат она может с помощью
команды RET 4. При этом последние два параметра извлекаются из
стека и только возвращаемый параметр остается в стеке.
В гл.10, где мы используем подпрограммы на языке ассемблера с
языками высокого уровня, головная программа помещает параметры в
стек. Но эти параметры - адреса данных, а не собственно данные. Это
означает, что ассемблерная подпрограмма не должна возвращать
параметры в стеке и обязана извлечь все параметры из стека при
возврате.
Переходы по условию
Переходы по условию
Условные переходы делятся на две группы: проверяющие результаты
предыдущей арифметической или логической команды, и управляющие
итерациями фрагмента программы. Все условные преходы имеют
однобайтовое смещение. Если условный переход осуществляется на
место, находящееся дальше 128 байт, нужно использовать специальную
конструкцию. Например, допустим, что программе надо перейти к
метке ZERO, если установлен флаг нуля; эта метка находится дальше
128 байт от текущего места. Программа в этом случае выглядит
примерно так:
JNZ CONTINUE
JMP ZERO
CONTINUE:
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:56
Фиг. 4.28 Таблица переходов Page 1-1
PAGE ,132
TITLE Фиг. 4.28 Таблица переходов
0000 CODE SEGMENT
ASSUME CS:CODE
;----------------------------------------
; В этом примере демонстрируется программа,
; осуществляющая переход в зависимости от
; значения регистра AL. В регистре находится
; индекс в таблице переходов необходимой программы
;----------------------------------------
0000 2A FF SUB BH, BH ; BH <- 0
0002 8A D8 MOV BL, AL ; Индекс загружается в регистр BL
0004 D1 E3 SHL BX, 1 ; * 2 для получения смещения
0006 2E: FF A7 000B R JMP CS:[BX + BRANCH_TABLE] ; Косвенный близкий переход
000B BRANCH_TABLE LABEL WORD
000B 0011 R DW ROUTINE_ONE
000D 0011 R DW ROUTINE_TWO
000F 0011 R DW ROUTINE_THREE
; ...
0011 ROUTINE_ONE LABEL NEAR
0011 ROUTINE_TWO LABEL NEAR
0011 ROUTINE_THREE LABEL NEAR
0011 CODE ENDS
END
Фиг. 4.28 Таблица переходов
Здесь используется условный переход с противоположным условием.
На метку ZERO управление передает команда безусловного перехода,
которая может использовать смещение вплоть до 32768 байт, а в
условном переходе используется метка CONTINUE.
Если целью является минимизация программ, этого метода нужно
избегать, так как он превращает команду условного перехода в
пятибайтовую последовательность. Иногда реорганизация программы
приводит к тому, что место перехода попадает в нужный диапазон.
Однако не стоит особенно стараться минимизировать программу. В
большинстве случаев не имеет особого значения, насколько у
программы большой объем, лишь бы он не превышал заданного. Это
имело бы смысл, если бы вы пытались сделать нечто помещающееся в
модуль ПЗУ постоянного объема, но обычно усилия, затрачиваемые на
изменения программы, не дают ощутимого выигрыша.
Перекодировка
Перекодировка
Команда перекодировки XLAT преобразует информацию из одного
представления в другое. Команда XLAT преобразует значение в
регистре AL в другое значение, выбираемое из таблицы, на которую
указывает регистр BX. На Фиг.4.4 схематически показано, как
работает эта команда. Регистр BX вместе с выбранным сегментным
регистром определяет точку начала таблицы перекодировки в памяти.
К этому адресу таблицы команда прибавляет содержимое регистра AL,
значение между 0 и 255. Данные, расположенные по этому адресу,
команда XLAT пересылает в регистр AL. Команда XLAT выполняет
операцию просмотра таблицы.
Команду XLAT хорошо использовать при кодировании и
декодировании текстовых данных. С помощью этой команды программа
может организовать простую замену кодов символов. В следующем
примере десять символов кода ASCII от 0 до 9 перекодируются в целях
передачи. Этот метод может использоваться в системе для
перекодировки информации, передаваемой из одной машины в другую.
Когда данные принимаются, другая программа возвращает
закодированные символы к их первоначальному виду. На Фиг. 4.5
демонстрируется кодировка и декодировка.
На Фиг.4.5 изображены две таблицы перекодировки, одна для
передачи, а другая для приема. Чтобы передать значение 5, программа
находит значение 5 в таблице передачи (а), из которой извлекает
значение 6, которое передает. Когда это значение принимается,
программа декодирования ищет 6 в таблице приема (b), чтобы
перекодировать его в истинное значение 5.
На Фиг. 4.6 показана подпрограмма, которая производит это
декодирование. Подпрограмма перекодирования читает начальное
значение из порта ввода=вывода, и возвращает кодированное или
декодированное значение в вызывающую программу через регистр AL.
Одна и та же программа выполняет как кодирова- ние, так и
декодирование, меняя таблицы кодировки.
Сначала подпрограмма читает данные из вводного порта 40H в
регистр AL. Затем она вычитает значение "0" в коде ASCII из
значения данных, чтобы получить цифровое значение. Это означает,
что символ "0" дает значение 0 в регистре AL, символ "1" дает 1, и
т.д. Команда LDS загружает указатель нужной таблицы в пару
регистров DS:BX. Загружая этот указатель из ячейки памяти - в
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:38
Фиг. 4.6 Пример перекодировки Page 1-1
PAGE ,132
TITLE Фиг. 4.6 Пример перекодировки
0000 CODE SEGMENT
ASSUME CS:CODE, DS:CODE
; Эта программа вводит значение из порта 040H и
; декодирует его, используя таблицу перекодировки.
; Так как одна и та же программа используется как для
; кодировки, так и декодировки, указатель TABLE_POINTER
; указывает на соответсвующую таблицу перекодировки.
; Вызывая подпрограмму, необходимо установить
; этот указатель на соответствующую таблицу.
0000 TRANSLATE PROC NEAR ; Подпрограмма TRANSLATE
0000 E4 40 IN AL,040H ; Ввод значения из порта
0002 2C 30 SUB AL,"0" ; Значение относительно символа "0",
; т.е. относительно начала таблицы
0004 C5 1E 000A R LDS BX,TABLE_POINTER ; (DS,BX) указывает на таблицу
0008 D7 XLAT XMIT_TABLE ; Перекодировка числа
0009 C3 RET
000A 000E ---- R TABLE_POINTER DD XMIT_TABLE
000E 35 37 39 31 33 36 38 XMIT_TABLE DB '5791368024'
30 32 34
0018 37 33 38 34 39 30 35 RECV_TABLE DB '7384905162'
31 36 32
0022 TRANSLATE ENDP
0022 CODE ENDS
END
Фиг. 4.6 Пример перекодировки
примере TABLE_POINTER - подпрограмма может использовать любую
таблицу перекодировки. В этой программе имеются две табдицы, одна
из них для передачи, названная XMIT_TABLE, которая соответствует
Фиг.4.5(а), другая - таблица приема, названная RECV_TABLE, -
соответствует Фиг.4.5(б). Перед вызовом подпрограммы головная
программа должна записать нужный адресный указатель в переменную
TABLE_POINTER. Если головная программа принимает коды, она должна
поместить адрес таблицы RECV_TABLE в переменную TABLE_POINTER.
Заметим, что эта подпрограмма может проделать любую перекодировку,
поскольку таблицу перекодировки назначает вызывающая программа.
Команда XLAT выполняет перекодировку по таблице, на которую
указывает пара регистров DS:BX. В регистре AL находится значение
между 0 и 9. Команда XLAT складывает это значение с содержимым
указателя и загружает перекодированное значение в регистр AL.
Команда RET возвращает управление в вызывающую программу.
Другим обычным случаем использования команды XLAT является
смена кода представления символов в одной машине на код
представления в другой машине. IBM PC, работает в коде ASCII, а
большинство машин фирмы IBM используют код EBCDIC (Extended
Binary=Coded=Decimal Interchange Code - расширенный
двоично=кодированный десятичный код обмена информации). Чтобы
связываться с такими машинами, в программе надо перекодировать
символы, и команда XLAT естественным образом подходит для этой
функции.
Итак, команда XLAT является весьма мощным средством
перекодировки байтовой или символьной информации. Мощность этой
команды делает ее редко используемой, так как возможность
использовать ее преимущества возникает не часто. Однако помните об
этой команде на тот случай, когда она окажется полностью
оправданной.
Пересылка данных
Пересылка данных
Команды пересылки данных обычно наиболее часто используются из
всего набора команд любой ЭВМ, и микропроцессор 8088 - не
исключение. Большая часть каждой задачи по обработке данных
заключается в переносе информации из одного места в другое.
Программа может выполнять некоторую обработку информации по мере
того, как она пересылается, но громадная часть работы сводится
только к пересылке. В этом разделе рассматривается основные
команды пересылки данных микропроцессора 8088, а в разделе,
посвященном обработке строк - остальная часть таких команд.
Пересылка флагов
Пересылка флагов
Набор команд микропроцессора 8088 имеет команды LAHF и SAHF в
первую очередь для совместимости с набором команд микропроцессора
8080. Команда LAHF берет 8 младших бит регистра флагов - а эти
флаги совпадают с флагами микропроцессора 8080 - и засылает их в
регистр AH. Команда SAHF действует наоборот, младший байт регистра
флагов загружает из регистра AH.
Вам потребуюся эти две команды, если вы переводите программу из
системы команд микропроцессора 8080 в команды микропроцессора 8088.
Они необходимы, чтобы отобразить стековые операции с аккумулятором
микропроцессора 8080 в стековые операции микропроцессора 8088.
Пересылка строки
Пересылка строки
Может показаться удобным использовать команды LODS и STOS для
пересылки данных из одного места в другое, но для этой цели
существует другая команда, пересылка строки MOVS. Эта команда
подобна комбинации команд LODS и STOS. Она берет данные из пары
регистров [DS:SI], помещает их в пару регистров [ES:DI], и изменяет
как регистр SI, так и регистр DI, чтобы они указывали на следующую
ячейку в каждой строке. Команда MOVS делает это одна, и не
загружает аккумулятор во время пересылки. Команда MOVS делает
сочетание LODS и STOS более быстрым и дающим меньше побочных
эффектов.
Команда MOVS указывает два операнда памяти. Только MOVS и еще
одна строковая команда CMPS работают с двумя операндами памяти. Все
остальные команды требуют, чтобы один или оба операнда находились в
регистре микропроцессора. Как и команды LODS и STOS, команда MOVS
работает как с байтами, так и со словами. Поскольку строковые
команды имеют дело с жестко заданными адресами, для определения
типов служат только операнды, написанные программистом. Команда
должна иметь оба операнда, и оба они должны быть одинаковых типов,
иначе программист может указать тип пересылки частью кода операции,
т.е. команда MOVSB и случае байтовых строк или команда MOVSW для
строк, состоящих из слов. Если в программе используется основная
форма, команда MOVS, ассемблер проверяет переменные на правильность
сегментной адресации, а также проверяет их типы.
Комбинация команды MOVS с префиксом REP дает эффективную
команду пересылки блока. Имея счетчик в регистре CX и показывающий
направление пересылки флаг направления, команда REP MOVS пересылает
данные из одного места памяти в другое очень быстро.
Микропроцессор, выполняющий команду REP MOVS, пересылает данные с
максимально возможной скоростью. Он больше не выбирает никакие
команды, поскольку единственное, что делается во время такой
пересылки - это пересылка.
Установка флага направления критична для правильной работы
команды REP MOVS. Различные виды установки флага направления
обсуждались в гл.3 именно на примере команды пересылки, и в
программе необходимо придерживаться рекомендаций, данных в этой
главе, особенно, если поля источника и результата перекрываются.
Префикс REP
Префикс REP
Существует специальный случай использования строковых команд. Есть
префикс, специально предназначенный для строковых команд. Также
как префикс подавления сегментации, используемый для порождения
специальной сегментной адресации, он предшествует обычной команде и
модифицирует ее работу. А именно, этот префикс вводит строковую
команду в цикл. Мнемоника префикса REP происходит от английского
слова Repeat - повторить. Микропроцессор 8088 использует этот
префикс в тесной связи с регистром CX, который указывает число
повторений команды.
Примером является команда STOSB. Команда
REP STOSB
есть специальная форма команды записи байта. Эта команда
повторяется до тех пор, пока содержимое регистра CX не уменьшится
до 0. Команда STOSB записывает байт из регистра AL в ячейку памяти,
которая указывается парой регистров ES:DI, а затем увеличивает или
уменьшает регистр DI на единицу так же, как и обычная команда
STOSB. Затем префикс REP уменьшает регистр CX, и если он теперь не
нуль, повторяет всю команду целиком. Запись строки повторяется до
тех пор, пока регистр CX не достигнет нуля.
Такая возможность превращает команду STOS в команду заполнения.
Программа помещает заполнитель в регистр AL, счетчик байта в
регистр CX, адрес блока в пару регистров ES:DI и сбрасывает флаг
направления. Затем команда REP STOSB заполняет блок памяти
значением из регистра AL. Такой фрагмент кода показан на Фиг. 4.23.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:31
Фиг. 4.23 Заполнение области памяти Page 1-1
PAGE ,132
TITLE Фиг. 4.23 Заполнение области памяти
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE,ES:CODE
;--------------------------------------
; В этом примере область данных BYTE_BLOCK
; заполняется значением 01H
;--------------------------------------
0000 8D 3E 000C R LEA DI, BYTE_BLOCK ; DI <- адрес области данных
0004 B9 0032 90 MOV CX, BYTE_BLOCK_LENGTH ; CX <- размер заполняемой области
0008 B0 01 MOV AL, 01H ; Символ для заполнения
000A F3/ AA REP STOS BYTE_BLOCK ; Заполнение
000C 0032[ BYTE_BLOCK DB 50 DUP(?)
??
]
= 0032 BYTE_BLOCK_LENGTH EQU $-BYTE_BLOCK
003E CODE ENDS
END
Фиг. 4.23 Заполнение блока
В случае команды LODS префикс REP не имеет смысла. Загрузка
непрерывной строки данных в аккумулятор не дает программе
возможности иметь дело с данными по мере их поступления. Однако
префикс REP весьма полезен для работы с другими командами обработки
строк.
Проверки кода условия
Проверки кода условия
Первая группа команд условного перехода проверяет текущее состояние
регистра флагов. Затем в зависимости от кодов условия команда
делает переход (или не делает). Команды условного перехода не
устанавливают флаги, а только проверяют их текущее состояние.
Ранее рассмотренные арифметические и логические команды
устанавливают флаги. В последующих примерах предположим, что
команда сравнения CMP уже установила флаги.
На Фиг.4.29 показаны команды условного перехода и проверяемые
ими флаги. В строках рисунка перечислены команды условного
перехода, а в пяти колонках показано состояние флагов. Буква X в
любой позиции означает, что команда не проверяет флаг. Цифра 0
означает, что этот флаг должен быть сброшен, чтобы условие было
выполнено и переход произошел. Цифра 1 означает, что флаг должен
быть установлен, чтобы переход произошел. Некоторые элементы
таблицы показывают выражение, которое должно быть истинно, чтобы
переход произошел. Так сделано для арифметических переходов, и мы
обсудим их далее более подробно.
На Фиг.4.29 условные переходы разделены на три группы:
непосредственно проверяющие один из флагов; делающие арифметическое
сравнение без знака; делающие арифметическое сравнение со знаком.
На Фиг.4.29а показана проверка отдельных флагов. Условный
переход может проверить каждый из этих пяти флагов непосредственно
на 0 или 1. Проверка флага переноса показана на рисунке в группе
арифметики без знака, поскольку она имеет еще и арифметический
смысл. Заметим, что многие команды условного перехода имеют более
одной мнемоники, если даже выполняется одна и та же проверка.
Например, проверку флага нуля осуществляет команда JZ (переход,
если результат операции равен нулю). Однако команда JE (переход,
если равно) также порождает ту же команду. Следующая
последовательность поясняет смысл этого:
CMP AX, BX
JE LABEL
Команда CMP вычитает содержимое регистра BX из содержимого
регистра AX, устанавливая флаги в соответствии с результатом. Так
как результат нулевой, если оба операнда равны, флаг нуля
показывает равенство. Аналогично, команда JNZ (переход, если не
нуль) идентичен команде JNE (переход, если не равно). Команда JP
(переход по четности) - то же самое, что и команда JPE (переход при
наличии четности); команда JNP (переход по нечетности) - то же
самое, что команда JPO (переход при отсутствии четности).
Команды Флаги
условного
перехода OF CY Z P S Комментарий
-------------------------------------------------------------------
JE/JZ X X 1 X X
JP/JPE X X X 1 X
JO 1 X X X X
JS X X X X 1
JNE/JNZ X X 0 X X
JNP/JPO X X X 0 X
JNO 0 X X X X
JNS X X X X 0
(a)
JL/JNGE a X X X b a NEQ b
JLE/JNG a X 1 X b Z OR (A NEQ B)
JNL/JGE a X X X b a = b
JNLE/JG a X 0 X b (NOT Z) AND (a=b)
(b)
JB/JNAE/JC X 1 X X X
JBE/JNA X 1 1 X X CY OR Z
JNB/JAE/JN X 0 X X X
JNBE/JA X 0 0 X X (NOT CY) AND (NOT Z)
(c)
------------------------------------------------------------------
Фиг. 4.29 Проверка флагов перехода по условию. (a) Проверка флага
(b) Арифметика со знаком; (c) беззнаковая арифметика.
Следующую группу команд условного перехода на Фиг.4.29б
составляют арифметические сравнения со знаком. Существуют четыре
условия, которые могут быть проверены: меньше (JL), меньше или
равно (JLE), больше (JG), больше или равно (JGE). Другие четыре
мнемоники - отрицания этих четырех. В случае арифметики со знаком
ассемблер использует мнемонику в названии команд "меньше" (less) и
"больше" (greater). Далее мы увидим, что для арифметики без знака
ассемблер использует мнемонику "выше" (above) и "ниже" (below).
Арифметические выражения можно понять, используя их вместе с
командой CMP. Например,
CMP AX,BX
JL LABEL
Преход произойдет, если содержимое регистра AX меньше
содержимого регистра BX. Вы можете читать комбинацию команд
сравнения и условного перехода вместе, как один оператор: операнд
результата встречается первым, затем идет условный оператор, а за
ним следует исходный операнд. Другой пример:
CMP CX,WORD_IN_MEMORY
JNLE LABEL
- можно прочитать так: "переход, если содержимое регистра CX не
меньше, чем, или равен содержимому ячейки памяти WORD_IN_MEMORY.
Этот прием можно использовать для определения значения любой команды
арифметического перехода, учитывающей знак или не учитывающей.
Как показано на Фиг.4.29б арифметические сравнения со знаком
проверяют одновременно несколько флагов. Фактически каждая из этих
команд проверяет некоторую комбинацию флагов переполнения, знака,
и, возможно, флага нуля. Например, в команде JL требуется, чтобы
флаги переполнения и знака имели разные значения. Если они имеют
одинаковое значение, первый операнд не был меньше второго.
Рассмотрим эту операцию несколько подробнее, чтобы понять работу
арифметических сравнений.
Когда сравниваются два числа со знаком, возможны четыре
комбинации флагов знака и переполнения. Рассмотрим каждую из
четырех комбинаций, чтобы определить какое состояние операндов
привело к данному результату. Предположим, что в каждом случае
флаги установила команда CMP, вычитавшая два операнда.
Знак S = 0, переполнение O = 0.
Условие S=0 означает, что результат вычитания положителен.
Условие O=0 означает, что переполнения не было, т.е. результат,
представленный в дополнительном коде, правильный. Вычитание двух
чисел, дающее положительный результат, показывает, что первое число
больше второго, и поэтому имеет место соотношение "больше". Однако
вычитание двух равных чисел также дает положительный результат, так
что условие S=0, O=0 означает "больше или равно".
S=1, O=0
В этом случае O=0 означает, что результат верен, а S=1 говорит
о том, что он отрицателен. Чтобы получить отрицательный результат,
большее число должно вычитаться из меньшего, и соотношение означает
"меньше".
S = 0, O = 1
Здесь O=1 показывает, что результат неверен, т.е. вышел за
пределы возможностей разрядной сетки. Это значит, что сложение двух
положительных чисел дало отрицательный результат или наоборот. В
данном случае это сравнение показывает, что знак результата
неверен; поэтому результат этого сравнения идентичен случаю, когда
S=1, O=0, что означает "меньше".
S = 1, O = 1
Снова O=1 говорит о том, что знак результата неверен. Поэтому
вычитание должно было привести к очень большому положительному
числу, и соотношение будет "больше или равно".
В некоторых случаях также учитывается флаг нуля. Например,
команда JLE выполняется, если условие есть "меньше" (знак и
переполнение разные) или "равно" (флаг нуля равен 1). Эти три флага
позволяют микропроцессору 8088 проверить все возможные комбинации
чисел со знаком.
Последняя часть таблицы (Фиг.4.29в) показывает условия,
проверяемые для арифметики без знака. Как и в случае арифметики со
знаком, существуют четыре возможные соотношения между операндами,
которые может проверить микропроцессор. Для того чтобы отличить
команды условного перехода ориентированные на беззнаковую
арифметику от знаковой арифметики, используются слова "выше" и
"ниже" в названии команд. Вероятно, этим выражается точка зрения
создателей набора команд, заключающаяся в том, что арифметика без
знака будет использоваться в программах для вычисления адресов, а
отрицательных адресов не бывает. "Выше" и "ниже" показывают
расположение значения адресов внутри адресного пространства, в то
время как "больше" и "меньше" говорит о соотношении чисел со
знаком. Здесь важно, что выполняется именно та команда, которая
указана в программе на языке ассемблера, независимо от типов
сравниваемых операндов. Например, программа сравнивает два числа со
знаком, а использует команду JA (переход, если выше).
Микропроцессор выполняет условный переход в зависимости от
соотношения двух чисел, считая их числами без знака, т.е. именно
программист обязан выбрать правильную команду условного перехода.
Микропроцессор 8088 при сравнении двух чисел без знака
учитывает только два флага. Флаг переноса показывает, какое из
чисел больше. Сравнение устанавливает флаг переноса, если первый
операнд ниже второго или сбрасывает флаг переноса, если первый
операнд либо выше, либо равен второму операнду, и флаг нуля
определяет, что в данном случае верно.
Сравнения без знака можно читать так же, как и сравнения со
знаком. Например,
CMP AX,BX
JA LABEL
- переход на метку LABEL происходит, если регистр AX выше
регистра BX. Условный переход выполняется всегда, если объявленное
соотношение существует между первым и вторым операндами
предшествовавшей команды сравнения.
Симовльная коррекция сложение и вычитание
Симовльная коррекция: сложение и вычитание
Команды символьной коррекции очень похожи на команды десятичной
коррекции. Они следуют за сложением или вычитанием распакованных
десятичных чисел. В тех же случаях, в которых программа использует
команды десятичной коррекции DAA и DAS для упакованных десятичных
чисел, она использует символьную коррекцию для распакованных
десятичных чисел. В распакованных десятичных числах цифры от 0 до
9 представляются одним байтом. Такая конструкция числа называется
символьной десятичной из=за того, что такие числа просто
преобразовывать в символьный вид и наоборот (прибавлять и вычитать
30H, соответственно).
После сложения двух распакованных десятичных чисел программа
обычно выполняет команду символьной коррекции при сложении AAA,
которая преобразует результат в правильное распакованное
представление десятичного числа. Правила сложения идентичны
правилам для упакованных десятичних чисел. Поскольку сложение двух
распакованных десятичных чисел может дать в результате число,
большее 9, командам AAA и AAS требуется для работы не только
регистр AL. В случае команды AAA младшая цифра скорректированного
результата остается в регистре AL. Если десятичное сложение привело
к переносу из младшей цифры, команда AAA устанавливает равными 1
флаги переноса и дополнительного переноса. В других случаях она
сбрасывает их в 0. Содержимое других флагов не определено после
команды коррекции. Команды символьной коррекции отличаются от
десятичных команд тем, что они влияют на содержимое регистра AH, а
также устанавливают флаг переноса, если есть перенос из младшей
значащей цифры.
Символьная коррекция вычитания AAS используется в программе
после вычитания одного распакованного десятичного числа из другого,
и результат этой байтовой операции должен быть помещен в регистр
AL. Результат команды символьной коррекции остается в регистре AL,
и если вычитание привело к появлению заема, команда AAS уменьшает
регистр AH, а также устанавливает флаги переноса и дополнительного
переноса. В противном случае флаги сбрасываются. Другие флаги после
команды не определены.
Символьная коррекция деление
Символьная коррекция: деление
Так же, как и другие арифметические операции, деление имеет
соответствующую команду для обслуживания распакованных десятичных
чисел. Однако в отличие от других команд, програииа должна
выполнять команду символьной коррекции деления AAD до выполнения
команды деления. Команда AAD берет две цифры распакованного
десятичного числа из регистра AX (старшая значащая цифра адресуется
в регистре AH) и преобразует его в двоичное число в регистре AL,
оставляя в регистре AH нуль. После этого в регистре AX оказывается
значение, готовое для деления на десятичное распакованное число,
состоящее из одной цифры. Команда AAD устанавливает коды условия в
соответствии с результатом в регистре AL. Флаги нечетности, знака
и нуля соответствуют значению AL, а остальные неизвестны.
Есть случаи, когда после деления может оказаться, что частное -
это не одна десятичная цифра. Так получается потому, что в этом
случае переполнение после деления не регистрируется. В худшем
случае 99 делится на 1, давая частное 99, число, меньшее
максимального как для команды DIV, так и для команды IDIV, так что
переполнение не возникает. Однако это число больше максимального
распакованного десятичного числа из одной цифры, которое равно 9.
Существует два метода борьбы с таким случаем. Во=первых, после
каждой последовательности команд AAD=DIV можно проверять, не
превысило ли частное 9, и вызывать соответствующую обработку
переполнения. Или программа может использовать команду AAM после
деления, чтобы преобразовать частное в распакованное десятичное
число из двух цифр. Но в этом случае программа должна где=либо
сохранить остаток до выполнения команды AAM, так как она разрушит
содержимое регистра AH. Этот способ порождает десятичный результат,
состоящий из двух цифр, после деления значения из двух цифр на
число из одной цифры. Но если распакованный десятичный делитель
нулевой, то деление вызовет прерывание по делению на нуль,
показывая, что произошло переполнение при делении.
Символьная коррекция умножение
Символьная коррекция: умножение
Когда в программе перемножаются два неупакованных десятичных числа,
результат в регистре AL является двоичным числом. Поскольку
наибольшее неупакованное десятичное в двоичном представлении число
равно 9, то максимальный результат при BCD-умножении без упаковки
равен 81. Однако, этот результат не является значимым неупакованным
BCD-представлением этого числа. Команда символьной коррекции для
умножения (AAM - от ASCII Adjust for Multiply) переводит такой
двоичный результат в неупакованный десятичный. Командой AAM старшая
десятичная цифра результата помещается в регистр AH, а в AL
остается младшая десятичная цифра. Например, если программа
перемножает значения 6 и 7 и результат в AL равен 2AH, то команда
AAM преобразует результат, помещая в AH 04H, а в AL - 02H, что
соответсвует неупакованному десятичному числу 42 в регистрах AH:AL.
Команда AAM вычисляет распакованный десятичный результат с
помощью деления числа в регистре AL на 10. Она помещает частное в
регистр AH, оставляя остаток в регистре AL. Команда AAM
устанавливает флаги нуля и знака в соответствии с результатом в
регистре AL. Так как результат - распакованное десятичное число,
знак всегда положителен, а знак нуля устанавливается равным 1,
только если исходное число кратно 10 - т.е. если младшая значащая
десятичная цифра равна 0. Остальные флаги после команды AAM
остаются неопределенными. Флаг переноса теперь не имеет смысла,
потому что умножение двух распакованных десятичных чисел никогда не
дает результата, превосходящего число, представимое двумя
десятичными цифрами.
Программа также всегда может использовать команду AAM для
деления двоичного числа в регистре AL на 10. В таком виде она может
рассматриваться, как специальный случай команды деления, которая
делит однобайтовое число в регистре AL на 10. Частное помещается в
регистр AH, остаток - в регистр AL.
Сложение
Сложение
Команда ADD выполняет сложение указанных операндов, представленных
в двоичном дополнительном коде. Микропроцессор помещает результат
на место первого операнда после того, как сложит оба операнда.
Второй операнд не изменяется. Команда корректирует регистр флагов
в соответствии с результатом сложения. Например, команда
ADD AX,BX
складывает содержимое регистра BX с содержимым регистра AX, и
оставляет результат в регистре AX. Регистр флагов сообщает о том,
был ли результат нулевым, отрицательным, имел ли четность, перенос
или переполнение.
Фиг. 4.8 кратко иллюстрирует варианты команды ADD.
Существуют две формы сложения, 8=битовое и 16=битовое. В различных
формах сложения принимают участие различные регистры. Ассемблер
следит за тем, чтобы операнды соответствовали друг другу.
Содержимое байтового регистра (например, CH) не может быть
прибавлено к ячейке памяти, которая не имеет тип BYTE. Если ячейка
памяти является одним из операндов, она может быть либо
операндом=результатом, либо неизменяемым операндом. Тем самым
команда может прибавить содержимое регистра к ячейке памяти и
возвратить результат в память. Одним из операндов может также быть
непосредственное значение. На Фиг. 4.9 показан листинг ассемблера с
накоторыми арифметическими командами.
Команда сложения с переносом ADC - это та же команда ADD, за
исключением того, что в сумму включается флаг переноса. Для любой
формы команды ADD существует сравнимая с ней команда ADC.
ЪДДДДДДДДї ЪДДДДДДДДї ЪДДДДДДДДї
і AX і і AX і і AX і
і BX і і BX і і BX і
і CX і і CX і ДДДДДДД> і CX і
і DX і і DX і і DX і
АДДДДДДДДЩ АДДДДДДДДЩ АДДДДДДДДЩ
ЪДДДДДДДДї + ЪДДДДДДДДї ЪДДДДДДДДї
і SI і і SI і і SI і
і DI і і DI і і DI і
і BP і і BP і ДДДДДДД> і BP і
і SP і і SP і і SP і
АДДДДДДДДЩ АДДДДДДДДЩ АДДДДДДДДЩ
ЪДДДДДДДДї
і Память і
і(слова) і
АДДДДДДДДЩ
ЪДДДДДДДДДї
іНепосред-і
іственный і
АДДДДДДДДДЩ
ЪДДДДДДДДДї ЪДДДДДДДДДї ЪДДДДДДДДДї
і AH і і AH і і AH і
і AL і і AL і і AL і
і BH і і BH і і BH і
і BL і і BL і і BL і
і CH і і CH і і CH і
і CL і + і CL і і CL і
і DH і і DH і і DH і
і DL і і DL і і DL і
АДДДДДДДДДЩ АДДДДДДДДДЩ АДДДДДДДДДЩ
ЪДДДДДДДДДї ЪДДДДДДДДДї
і Память і ДДДДДДД> і Память і
і(слова) і і(слова) і
АДДДДДДДДДЩ АДДДДДДДДДЩ
ЪДДДДДДДДДї
іНепосред-і
іственный і
АДДДДДДДДДЩ
Фиг. 4.8 Операции сложения
Обе команды сложения, как ADD, так и ADC, устанавливают равным
1 флаг переноса, если произошел перенос из старшего разряда
результата. Команда ADD складывает два операнда, не обращая
внимания на флаг переноса, а команда ADC учитывает и флаг переноса.
Если флаг переноса равен 0, результат равен результату выполнения
команды ADD. Если же флаг переноса равен 1, то результат на 1
больше результата команды ADD. Таким образом, программа может
использовать флаг переноса для операций повышенной точности.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:49
Фиг. 4.9 Арифметические команды Page 1-1
PAGE ,132
TITLE Фиг. 4.9 Арифметические команды
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 EXBYTE LABEL BYTE
0000 EXWORD LABEL WORD
0000 03 1E 0000 R ADD BX,EXWORD ; BX <- BX + [EXWORD]
0004 29 0E 0000 R SUB EXWORD,CX ; [EXWORD] <- [EXWORD] - CX
0008 12 3E 0000 R ADC BH,EXBYTE ; BH <- BH + [EXBYTE] + Carry
000C 18 0E 0000 R SBB EXBYTE,CL ; [EXBYTE] <- [EXBYTE] - CL - Carry
0010 F7 1E 0000 R NEG EXWORD ; [EXWORD] <- -[EXWORD]
0014 FE 06 0000 R INC EXBYTE ; [EXBYTE] <- [EXBYTE] + 1
0018 4E DEC SI ; SI <- SI - 1
0019 81 C7 00C8 ADD DI,200 ; DI <- DI + 200
001D 83 EC 64 SUB SP,100 ; SP <- SP - 100
0020 83 D1 0A ADC CX,10 ; CX <- CX + 10 + Carry
0023 83 1E 0000 R 14 SBB EXWORD,20 ; [EXWORD] <- [EXWORD] - 20 - Carry
0028 3B C3 CMP AX,BX ; Установка флагов по AX - BX
002A 81 FE 01F4 CMP SI,500 ; Установка флагов по SI - 500
002E F6 26 0000 R MUL EXBYTE ; AX <- AL * [EXBYTE]
0032 F7 EB IMUL BX ; DX:AX <- AX * BX
0034 F7 36 0000 R DIV EXWORD ; AX <- DX:AX / [EXWORD]
0038 F6 FD IDIV CH ; AL <- AX / CH
003A 27 DAA ; Десятичное коррекция для сложения
003B 2F DAS ; Десятичное коррекция для вычитания
003C 37 AAA ; ASCII коррекция для сложения
003D 3F AAS ; ASCII коррекция для вычитания
003E D4 0A AAM ; ASCII коррекция для умножения
0040 D5 0A AAD ; ASCII коррекция для деления
0042 98 CBW ; AX <- расширенное по знаку AL
0043 99 CWD ; DX:AX <- расширенное по знаку AX
0044 CODE ENDS
END
Фиг. 4.9 Арифметические команды
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:54
Фиг. 4.10 Пример вычислений с повышенной точностью Page 1-1
PAGE ,132
TITLE Фиг. 4.10 Пример вычислений с повышенной точностью
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 ???????? VALUE1 DD ? ; Область данных размером 32 разряда
0004 ???????? VALUE2 DD ?
;----- Сложение двух 32-разрядных чисел
0008 A1 0000 R MOV AX,WORD PTR VALUE1
000B 01 06 0004 R ADD WORD PTR VALUE2,AX ; Сложение младших 16 разрядов
000F A1 0002 R MOV AX,WORD PTR VALUE1+2
0012 11 06 0006 R ADC WORD PTR VALUE2+2,AX ; Сложение старших 16 разрядов
;----- Вычитание двух 32-разрядных чисел
0016 A1 0000 R MOV AX,WORD PTR VALUE1
0019 29 06 0004 R SUB WORD PTR VALUE2,AX ; Вычитание младшей части
001D A1 0002 R MOV AX,WORD PTR VALUE1+2
0020 19 06 0006 R SBB WORD PTR VALUE2+2,AX ; Вычитание старшей части
0024 CODE ENDS
END
Фиг. 4.10 Пример с повышенной точностью
Фиг. 4.10 иллюстрирует сложение пары 32=битовых чисел; в
примере складываются 32=битовые числа поля VALUE1 и поля VALUE2, а
результат помещается в поле VALUE2. Заметим, что один из операндов
должен быть помещен в регистр. В первом сложении используеся
команда ADD, так как текущее значение флага переноса несущественно
для первого сложения. После соответствующего размещения операндов
программа на Фиг. 4.10 выполняет второе сложение с помощью команды
ADC, с учетом флага переноса, установленного предыдущим сложением.
Это также хороший пример показывающий, почему команда MOV не
устанавливает никаких флагов. Если бы команда MOV изменяла флаги,
выполнить правильно второе сложение было бы гораздо труднее.
Специальные команды
Специальные команды
Команда NOP - еще одна удобная команда микропроцессора 8088. Она
не делает ничего - "нет операции". Тщательный анализ машинных
команд показывает, что это в действительности команда XCHG. А
именно, это
XCHG AX,AX
что эквивалентно "ничего не выполнять", и бывают ситуации,
когда такую команду желательно использовать, чтобы выждать
некоторое время. В небольшом цикле, предназначенном для
определенной временной задержки, можно использовать команды NOP для
заполнения тела цикла, достигая тем самым точной выдержки времени
выполнения цикла (хотя цикл - не лучший способ временной задержки,
если интервал не очень маленький). Разработчики IBM PC требуют
использовать NOP в некоторых местах, чтобы удовлетворять
определенным временным требованиям. Например, программа не может
иметь доступ в схему таймера чаще, чем раз в одну микросекунду. Две
последовательно идущие команды IN нарушают это требование, так что
между командами IN должно быть выполнено несколько команд NOP.
Команда HLP останавливает ЭВМ; после выполнеиня этой команды
микропроцессор останавливается. Если прерывания заблокированы во
время останова, ЭВМ полностью "замирает". В этой сиутации
единственная возможность запустить ЭВМ заново - выключить питание и
включить его снова. Однако, если прерывания были разрешены в момент
останова микропроцессора, они продолжают восприниматься и
управление будет передаваться обработчику прерываний. После
выполнеиня команды IRET в обработчике программа продолжает
выполнение с ячейки, следующей за командой HLT. Команду HLT можно
использовать в мультизадачных системах, чтобы завершить текущую
активную задачу, но это не всегда лучший способ такого завершения.
Разработчики персональной ЭВМ используют команду останова только
тогда, когда возникает катастрофическая ошибка оборудования и
дальнейшая работа бессмысленна.
Команда LOCK - это командный префикс, такой же, как подавление
сегментации или REP-префикс. Она предназначена для
мультипроцессорных систем, в которых несколько микропроцессоров
могут одновременно работать с одними и теми же ячейками памяти.
Префикс LOCK вынуждает микропроцессор 8088 захватить линии
управления, и тем самым получить исключительное право достура в
память на время обработки команды с префиксом. Лучший пример этого
- установка- проверка флага в общей памяти.
MOV AL,1
LOCK XCHG AL,FLAG_BYTE
CMP AL,1
В этом примере байт FLAG_BYTE содержит нулевой или единичный
индикатор. Микропроцессор устанавливает флаг равным единице, когда
входит в "критическую" область программы, где он выполняет
некоторые системные действия, которые может выполнять в данный
момент времени лишь один микропроцессор. Перед входом в
"охраняемую" область микропроцессор должен проверить, не работает
ли в ней другой микропроцессор. Если это так, он должен подождать
перед входом; иначе он может войти в область. В примере перед
командой XCHG используется префикс LOCK. Префикс LOCK дает
микропроцессору право исключительного доступа в течение выполнения
команды XCHG, которая читает содержимое ячейки памяти, а затем
записывает данные в эту же ячейку. Команда XCHG записывает 1 из
регистра AL в поле FLAG_BYTE, засылая его текущее значение в
регистр AL. Теперь, если регистр AL содержит 1, то в "охраняемой"
области находится другой микропроцессор, и проверяющий
микропроцессор обязан ждать. Если регистр AL нулевой,
микропроцессор может войти в "охраняемую" область, а команда XCHG
уже установила поле FLAG_BYTE равным 1, чтобы больше ни один
микропроцессор не смог войти туда. Префикс LOCK препятствует любому
другому микропроцессору проверять поле FLAG_BYTE в течение
короткого интервала времени между проверкой и установкой ячейки
флага.
К сожалению, описание работы префикса LOCK носит чисто
теоретический характер. IBM PC не реализует аппаратные средства,
необходимые для работы LOCK.
Команда WAIT останавливает выполнение программы
микропроцессором, аналогично команде HLT. Но в случае команды WAIT
выполнение программы возобновляется, когда один из внешних выводов
микропроцессора 8088, вывод TEST, становится активен. Если вывод
TEST активен во время выполнения команды WAIT, остановки не
возникает вообще. Если вывод TEST неактивен, микропроцессор ждет до
тех пор, пока он не станет активен. Микропроцессор 8088 использует
эту команду вместе с командой ESC, чтобы работать с арифметическим
сопроцессором 8087.
Команда ESC дает возможность расширить набор команд
микропроцессора 8088 без изменений самого микропроцессора. Команда
содержит поле режима адресации и может указать любую ячейку памяти
с помощью обычных способов адресации микропроцессора 8088. Однако
микропроцессор ничего не делает в случае этой команды, кроме того,
что читает данные из соответствующей ячейки и просто их
отбрасывает.
Команда ESC позволяет другому микропроцессору, или так
называемому сопроцессору, наблюдать за работой микропроцессора
8088. Команда ESC активизирует сопроцессор, и он выполняет ее, как
собственную. Если сопроцессору нужен адрес памяти, микропроцессор
8088 выдает этот адрес в цикле фиктивного чтения. Затем сопроцессор
может выполнять запись или чтение по этому адресу в зависимости от
того, что ему нужно. Эффективность команды ESC станет очевидна в
гл.7, где рассматривается арифметический сопроцессор 8087,
сопроцессор микропроцессора 8088.
Сравнение
Сравнение
Команда сравнения CMP сравнивает два числа, вычитая одно из
другого. Она не записывает результат, но флаги состояния
устанавливает в соответствии с результатом. Эта команда изменяет
только флаги. В программе команда сравнения используется так же,
как и команда вычитания; однако команды сравнения с заемом не
существует.
Сравнение с повышенной точностью требует чуть больше усилий,
чем сравнение байтов или слов. Фактически в этих случаях много
проще использовать команду вычитания вместо команды сравнения. На
Фиг. 4.11 показано сравнение пары 32=битовых чисел в памяти с
использованием регистра AX в качестве области временного хранения.
Это сравнение определяет, какое из чисел больше. Программа в
результате своего выполнения устанавливает коды условия. Флаг
переноса определяет, какое из чисел больше: если флаг равен 1,
число VALUE больше.
Вторая программа на Фиг. 4.11 проверяет два 32=битовых числа на
равенство. Программа сохраняет младший результат, а затем
комбинирует его со старшим, и таким образом выясняет
эквивалентность результата нулю. Команда OR описана в следующем
разделе, а здесь существенно то, что она комбинирует два значения
так, что окончательное значение равно 0 тогда и только тогда, когда
оба исходных значения равны 0. Результат этой подпрограммы
сравнения - значение флага нуля; если он равен 1, числа равны.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:00:59
Фиг. 4.11 Сравнение чисел заданных с повышенной точностью Page 1-1
PAGE ,132
TITLE Фиг. 4.11 Сравнение чисел заданных с повышенной точностью
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 ???????? VALUE1 DD ? ; Область данных размером 32 разряда
0004 ???????? VALUE2 DD ?
0008 FIG4_11 PROC NEAR
;----- Сравниваются по неравенству два 32-разрядных числа
0008 COMPARE_UNEQUAL:
0008 A1 0000 R MOV AX, WORD PTR VALUE1
000B 2B 06 0004 R SUB AX, WORD PTR VALUE2 ; Вычитание младшей части
000F A1 0002 R MOV AX, WORD PTR VALUE1+2
0012 1B 06 0006 R SBB AX, WORD PTR VALUE2+2 ; Вычитание старшей части
0016 C3 RET ; Возврат с установленными флагами
;----- Сравниваются по равенству два 32-разрядных числа
0017 COMPARE_EQUAL:
0017 A1 0000 R MOV AX, WORD PTR VALUE1
001A 2B 06 0004 R SUB AX, WORD PTR VALUE2 ; Вычитание младшей части
001E 8B D8 MOV BX, AX ; В BX младшая часть результата
0020 A1 0002 R MOV AX, WORD PTR VALUE1+2
0023 1B 06 0006 R SBB AX, WORD PTR VALUE2+2 ; Вычитание старшей части
0027 0B C3 OR AX, BX ; Объединение результатов
0029 C3 RET ; Флаг Z показывает равенство
002A FIG4_11 ENDP
002A CODE ENDS
END
Фиг. 4.11 Сравнение с повышенной точностью
Умножение
Умножение
Микропроцессор 8088 значительно мощнее предшествовавших ему
8=битовых устройств. Одна из причин увеличения мощности -
добавление команд умножения и деления к набору команд
микропроцессора. В прежних микропроцессорах выполнение операций
умножения и деления требовало вызова подпрограмм на языке
ассемблера.
Существует две команды умножения. Покоманде MUL умножаются два
целых числа без знака и дает результат без знака. По команде IMUL
умножаются целые числа со знаком. При умножении целых чисел в
качестве операндов используются числа, представленные в
дополнительном коде и получается результат, имеющий правильный знак
и значение.
Обе команды умножения работают как с байтами, так и со словами.
Однако диапазон форм представления операндов гораздо уже, чем для
команд сложения и вычитания. Фиг. 4.13 иллюстрирует варианты
команды умножения. Чтобы умножить 8 бит на 8 бит, один из операндов
должен быть в регистре AL, а результат всегда оказывается в
регистре AX. Результат может иметь длину вплоть до 16 бит
(максимальное получаемое значение равно 255 * 255 = 65025). Чтобы
умножить 16 бит на 16 бит, один из операндов нужно поместить в
регистр AX. Результат, который может быть длиной до 32 бит
(максимальное значение 65535 * 65535 < 2+32) помещается в пару
регистров; в регистре DX содержатся старшие 16 бит результата, а
врегистре AX - младшие 16 бит. Умножение не допускает
непосредственного операнда.
Установка флагов командой умножения несколько отличается от
других арифметических команд. Единственные имеющие смысл два флага
- это флаги переноса и переполнения, и они по=разному
устанавливаются двумя командами.
Команда умножения без знака MUL устанавливает оба флага, если
старшая половина резул в регистре AL получится 2AH; команда AAM
преобразует этот результат, оставляя в регистре AH число 04H, и
02H в регистре AL - или распакованное десятичное число 42 в паре
регистров AH:AL.
ЪДДДДДДДДї
і AX і
і BX і
і CX і
і DX і
ЪДДДДДДДДї АДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї
і AX і * ЪДДДДДДДДї ДДДДД> і DX і AX і
АДДДДДДДДЩ і SI і АДДДДДДДДБДДДДДДДДЩ
і DI і
і BP і
і SP і
АДДДДДДДДЩ
ЪДДДДДДДДї
і Память і
і(слова) і
АДДДДДДДДЩ
(a) Умножение слов
ЪДДДДДДДДДї
і AH і
і AL і
і BH і
і BL і
і CH і
і CL і
і DH і
і DL і
ЪДДДДДДДДї АДДДДДДДДДЩ ЪДДДДДДДДВДДДДДДДДї
і AL і * ЪДДДДДДДДДї ДДДДД> і AH і AL і
АДДДДДДДДЩ і Память і АДДДДДДДДБДДДДДДДДЩ
і(байты) і
АДДДДДДДДДЩ
(b) умножение байтов
Фиг. 4.13 Операции умножения
Целое умножение со знаком (IMUL) устанавливает флаги переноса и
переполнения в соответствии с тем же критерием, т.е. эти флаги
устанавливаются в случае, когда результат не может быть представлен
тоько своей младшей половиной. Однако, поскольку число имеет знак,
то задача не сводится только к сравнению старшей половины
результата с нулем. Команда IMUL устанавливает флаги, если старшая
половина результата не является распространением знака младшей. Это
значит, что в случае положительного результата проверка будет такой
же, как для команды MUL - установка флага происходит при ненулевой
старшей половине результата (но самый старший бит равен нулю,
указывая на положительность результата). В случае отрицательного
результата IMUL устанавливает флаги, если старшая половина
результата состоит не только из едениц (но старший бит равен 1,
указывая на отрицательность результата). Например, перемножение
байтов с отрицательным результатом устанавливает флаги когда
результат менше -128 - наименьшего числа, представимого в одном
байте. Другой пример, перемножение слов с положительным
результатом, устанавливает флаги, если результат превышает 32 767 -
наиболшее представимое одним словом число.
Управление циклами
Управление циклами
Существует несколько команд условного перехода, предназначенных для
управления циклами в программах. Поскольку программые циклы
используются часто, желательно эффективное управление циклом. На
Фиг. 4.30 показаны четыре команды, созданные для того, чтобы
облегчить программирование циклов на языке ассемблера
микропроцессора 8088.
Так же, как строковые команды используют регистр CX в качестве
счетчика, команды цикла LOOP используют регистр CX в качестве
счетчика цикла. Все эти команды неявно рассматривают регистр CX как
счетчик итераций цикла. Простейшая команда среди них - команда
LOOP. Команда LOOP уменьшает регистр CX и передает управление на
метку, если содержимое регистра CX не равно 0. Если вычитание
единицы из регистра CX не привело к нулевому результату, команда
LOOP не делает перехода, и выполняется следующая команда.
Приведенный ниже программный фрагмент демонстрирует обычное
использование команды LOOP.
MOV CX,LOOP_COUNT
BEGIN_LOOP:
; ... тело цикла
LOOP BEGIN_LOOP
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:01
Фиг. 4.30 Команды цикла Page 1-1
PAGE ,132
TITLE Фиг. 4.30 Команды цикла
0000 CODE SEGMENT
ASSUME CS:CODE
;----------------------------------------
; В этом примере демонстрируются команды цикла.
; Команды в примере не являются законченной программой.
;----------------------------------------
0000 E3 06 JCXZ END_OF_LOOP ; Конец цикла, если CX равно 0
0002 BEGIN_LOOP:
; .... Тело цикла
0002 E2 FE LOOP BEGIN_LOOP ; Переход пока регистр CX не станет равен 0
; .... Если проверяется какое-либо условие, то
0004 E1 FC LOOPE BEGIN_LOOP ; Переход по равенству в условии и
; значение регистра CX не равно 0
; .... Или
0006 E0 FA LOOPNE BEGIN_LOOP ; Переход по неравенству в условиии и
; значение регистра CX не равно 0
0008 END_OF_LOOP:
0008 CODE ENDS
END
Фиг. 4.30 Команда цикла
Программа помещает число итераций цикла в регистр CX перед
выполнением цикла. Затем выполняется тело цикла, а следом за ним
команда LOOP. Она уменьшает счетчик на единицу, что соответствует
единственной, только что выполненной итерации цикла. Если теперь
счетчик в регистре CX равен 0, программа продолжает выполняться
после команды LOOP. Если счетчик не равен 0, управление
возвращается к началу цикла, чтобы совершить еще один проход по
телу цикла. Тело цикла выполняется столько раз, сколько было
сначала задано содержимым регистра CX. Единственное важное
замечание: если программа внутри цикла изменяет регистр CX, число
итераций цикла не будет соответствовать начальному значению в
регистре CX.
Описанный метод одинаково хорошо работает, когда число циклов
известно во время ассемблирования (как в примере, где LOOP_COUNT -
непосредственно заносимое значение), и когда число циклов
определяется во время выполнения. Если вычисленное число оказалось
равным 0, цикл выполнится 65536 раз. Когда микропроцессор 8088
выполняет первую команду LOOP, он уменьшает CX от 0 до 0FFFFH, и
поскольку теперь регистр CX ненулевой, повторяет цикл. Таким
образом, загрузка нулевого значения счетчика циклов - специальный
случай. Этот специальный случай обрабатывается командой JCXZ
(переход, если содержимое регистра CX равно 0). Эта команда
проверяет текущее содержимое регистра CX, и делает переход, если
оно равно нулю. Команда не проверяет ни одного флага, и не влияет
ни на один из них. Следующий пример аналогичен предыдущему, за
исключением того, что он загружает регистр CX из ячейки памяти,
содержимое которой вычисляется во время выполнения программы. По
этой причине может оказаться, что счетчик циклов нулевой, и пример
использует команду JCXZ, чтобы проверить, нужно ли полностью
пропустить тело цикла.
MOV CX,LOOP_COUNT_WORD
JCXZ END_OF_LOOP
BEGIN_LOOP:
; ... тело цикла
LOOP BEGIN_LOOP
END_OF_LOOP:
В программе не нужно использовать команду JCXZ в каждом цикле с
вычисляемым счетчиком. Если программист знает, что счетчик циклов
никогда не будет равен нулю, проверка не нужна. Однако опыт
показывает, что значение, которое "никогда" не должно появиться,
обычно появляется в первую очередь, как только вы начинаете
выполнять программу.
Оставшиеся две команды цикла предоставляют еще большие
возможностей при управлении циклами. Эти команды аналогичны
префиксам REPE и REPNE. Если команда LOOP выходит из цикла, только
когда в регистре CX оказывается нуль, то команда LOOPE (цикл, пока
равно) выходит из цикла, если установлен флаг нуля, или если в
регистре CX получился 0. Тем самым становится возможным
двойственное завершение цикла. Программа может загрузить в регистр
CX максимальное число итераций цикла, а затем проверять флаг нуля в
конце каждого цикла на условие завершения. Команда LOOPNE (цикл,
пока не равно) выполняет обратную к описанной проверку флага нуля:
цикл здесь завершается, если регистр достиг нуля, или если
установлен флаг нуля.
Следующий пример показывает использование команды LOOPNE. В
примере складываются два списка чисел, чтобы найти пару элементов,
сумма которых точно равна 100. Так как в каждой итерации перед
проверкой складываются два чила, команду REPNE CMPSB использовать
нельзя.
В примере предполагается, что пары регистров DS:SI и ES:DI
инициализированы так, чтобы указывать на эти списки.
MOV CX,MAX_LOOP_COUNT ;максимальное число заходов
BEGIN_LOOP:
LODSB ;чтение числа из первого списка
ADD AL,ES:[DI] ;прибавить из второго списка
INC DI ;указатель на следующий элемент
CMP AL,100 ;проверка на нужное значение
LOOPNE BEGIN_LOOP ;снова, если не равно и не все
JE MATCH_FOUND ;переход сюда, чтобы определить конец
Установка флагов
Установка флагов
Есть три команды, которые непосредственно управляют состоянием
флага переноса. Команды STC, CLC, CMC соответственно могут
устанавлмвать, сбрасывать и изменять флаг переноса. Этот флаг -
единственный, которому уделено такое внимание, и в первую очередь,
благодаря важности флага переноса при операциях с повышенной
точностью. Флаг переноса критичен на промежуточных шагах любых
многословных операций. Возможность сбрасывать или устанавливать
флаг переноса может помочь при циклической обработке с повышенной
точностью. На Фиг. 4.31 показан пример использования команды CLC.
Цикл внутри примера складывает отдельные байты двух 10-разрядных
упакованных десятичных чисел. Программа выполняет цикл пять раз,
так как за каждую итерацию она обрабатывает две цифры. Информация
о переносе из одного оборота цикла в другой передается через флаг
переноса. Команда CLC сбрасывает флаг переноса перед первым циклом
для того, чтобы перед первым сложением не было переноса. Флаг
переноса также важен в операциях сдвига, где он становится девятым
или семнадцатым битом регистра во время выполнения операции.
Два флага состояния микропроцессора имеют специальные команды
работающие с ними. Программа может установить или сбросить маску
прерываний соответственно командами STI и CLI. Команда STI включает
систему прерываний микропроцессора 8088, позволяя ему реагировать
на внешние прерывания. Команда CLI блокирует систему внешних
прерываний.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:06
Фиг. 4.31 Десятичная арифметика повышенной точности Page 1-1
PAGE ,132
TITLE Фиг. 4.31 Десятичная арифметика повышенной точности
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
= 0005 NUMBER_LENGTH EQU 5 ; 5 байт для упакованного числа
0000 0005[ NUMBER_ONE DB NUMBER_LENGTH DUP (?)
??
]
0005 0005[ NUMBER_TWO DB NUMBER_LENGTH DUP (?)
??
]
;----------------------------------------
; Эта программа складывает два десятичных упакованных
; числа (NUMBER_ONE и NUMBER_TWO) и заносит результат
; в NUMBER_TWO.
;----------------------------------------
000A START_ADD:
000A B9 0005 MOV CX, NUMBER_LENGTH ; Определение длины чисел
;----- Установка индексных регистров на младший байт складываемых чисел
000D 8D 36 0004 R LEA SI, NUMBER_ONE + NUMBER_LENGTH - 1
0011 8D 3E 0009 R LEA DI, NUMBER_TWO + NUMBER_LENGTH - 1
0015 F8 CLC ; Нет младших разрядов
0016 ADD_LOOP:
0016 8A 04 MOV AL, [SI] ; Взять байт из первого числа
0018 12 05 ADC AL, [DI] ; Добавить из второго с учетом переноса
001A 27 DAA ; Коррекция до упакованного формата
001B 88 05 MOV [DI], AL ; Занесение байта результата
001D 9C PUSHF ; Сохранение флага переноса (CF)
001E 4E DEC SI ; Сдвиг указателя первого числа
001F 4F DEC DI ; Сдвиг указателя второго числа
0020 9D POPF ; Восстановление флагов
0021 E2 F3 LOOP ADD_LOOP ; Обработка следующего байта
0023 CODE ENDS
END
Фиг. 4.31 Операции с BCD повышенной точности
Программа может установить или сбросить флаг направления с
помощью команд STD и CLD. Команда CLD сбрасывает флаг направления,
приводя к тому, что строковые команды ведут обработку при
возрастающих адресах памяти. Команда STD устанавливает флаг, в
результате чего строковые команды уменьшают адресный указатель
после каждого выполнения.
Вычитание
Вычитание
Команды вычитания SUB и SBB идентичны командам сложения, за
исключением того, что они выполняют вычитание, а не сложение. Вы
можете скорректировать Фиг.4.8 для вычитания, изменив знак "+" на
знак "-". Вычитание устанавливает флаги состояния в соответствии с
результатом операции, причем флаг переноса теперь означает заем.
Например, команда
SUB AX, BX
вычитает значение регистра BX из значения регистра AX, а затем
помещает результат в регистр AX. Флаги состояния изменяются так,
чтобы отражать результат выполнения команды.
Команда вычитания с заемом SBB решает задачи вычитания
повышенной точности. Команда SBB учитывает флаг заема при
вычитании, т.е. значение заема вычитается из результата,
полученного при нормальном вычитании. На Фиг. 4.10 показано
вычитание повышенной точности, выполненное с теми же значениями,
что и сложение. В этом примере значение поля VALUE1 вычитается из
значения поля VALUE2, помещая результат в поле VALUE2.
Загрузка и запись
Загрузка и запись
Листинг ассемблера на Фиг. 4.22 показывает различные строковые
команды. Загрузка строки LODS и запись строки STOS являются
простейшими строковыми командами. Если программа указывает
байтовый операнд в команде LODS, то она загружает в регистр AL
байт, на который указывает пара регистров DS:SI. Затем она
изменяет регистр SI на единицу; он увеличивается, либо уменьшается,
в зависимости от состояния флага направления. Если команда LODS
указывает на слово, то она загружает регистр AX и изменяет регистр
SI на 2. Команда STOS строго противоположна, и записывает байт из
регистра AL либо слово из регистра AX в ячейку памяти. В случае
записи ячейка определяется парой регистров ES:DI. Команда записи
изменяет регистр DI либо на единицу, либо на 2, в зависимости от
типа операнда.
Программист может писать на ассемблере команду LODS (а также и
все другие строковые команды) различными способами. Тип операнда
можно указать частью кода операции, либо ассемдлер может определить
тип элемента строки, основываясь на операнде, присутствующем в
команде. Как показано на Фиг. 4.22, команда
LODS EXBYTE
порождает команду загрузки строк байтов, так же как и команда
LODSB.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:01:26
Фиг. 4.22 Команды обработки строк Page 1-1
PAGE ,132
TITLE Фиг. 4.22 Команды обработки строк
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE,ES:CODE
0000 EXBYTE LABEL BYTE
0000 EXWORD LABEL WORD
0000 EXBYTE1 LABEL BYTE
0000 EXWORD1 LABEL WORD
0000 AC LODS EXBYTE ; Загрузка AL из DS:SI
0001 AD LODS EXWORD ; Загрузка AX из DS:SI
0002 AC LODSB ; Загрузка AL из DS:SI
0003 AA STOS EXBYTE ; Сохранение AL в ES:DI
0004 AB STOS EXWORD ; Сохранение AX в ES:DI
0005 AB STOSW ; Сохранение AX в ES:DI
0006 F3/ AA REP STOSB ; Сохранение AL в ES:DI в цикле CX раз
0008 A4 MOVS EXBYTE1, EXBYTE ; Пересылка байта [ES:DI] <- [DS:SI]
0009 A5 MOVS EXWORD1, EXWORD ; Пересылка слова [ES:DI] <- [DS:SI]
000A A4 MOVSB ; Пересылка байта [ES:DI] <- [DS:SI]
000B F3/ A5 REP MOVSW ; Пересылка CX слов [ES:DI] <- [DS:SI]
000D AE SCAS EXBYTE1 ; Сравнение AL с [ES:DI]
000E F3/ AE REPE SCASB ; Сравнение AL с [ES:DI] пока равно
0010 F2/ AF REPNE SCASW ; Сравнение AX с [ES:DI] пока не равно
0012 A7 CMPS EXWORD, EXWORD1 ; Сравнение слова [DS:SI] с [ES:DI]
0013 F3/ A7 REPE CMPSW ; Сравнение слов [DS:SI] с [ES:DI] пока
; равно в цикле CX раз
0015 F2/ A6 REPNE CMPSB ; Сравнение байт [DS:SI] с [ES:DI] пока
; не равно в цикле CX раз
0017 CODE ENDS
END
Фиг.4.22 Строковые команды
В первом случае ассемблер определяет, что строка состоит из
байта, поскольку EXBYTE - переменная типа BYTE. Во втором случае
программист непосредственно указывает, что работает с байтами.
Собственно ассемблер не требует поля операнда. Программисты чаще
используют вторую форму, так как не имеют имени переменной,
связанной со строкой. Программа динамически располагает строку в
памяти, для нее не существует фиксированного места, и,
следовательно, нет и имени переменной. Команда STOS аналогична.
Чтобы непосредственно указать строку слов, а не байтов,
используются коды операций LODSW и STOSW. Ассемблер должен знать,
для байтовой строки или строки слов написана команда, поскольку
машинные команды различны для различных типов строк. Эта разница
определяет значение, на которое надо изменить индексный регистр.
Операнд в команде должен быть указан в том случае, если в
программе используются основные формы команд LODS и STOS. Если
программа не имеет удобной метки для строки, она может использовать
формы LODSB и STOSB. Преимущество использования основной формы LODS
и указания операнда заключается в том, что ассемблер при этом
проверяет не только тип операнда, но и возможность его адресации.
Так как команда LODS работает с объектами только в сегменте DS,
оператор ASSUME должен соответственно описывать расположение
сегмента поименованной переменной. Аналогично, ассемблер проверяет
основную форму команды STOS на адресацию сегмента ES. Любая форма
приемлема для ассемблера, но лучше использовать основную форму,
чтобы позволить ассемблеру наилучшим способом проверить наличие
ошибок в программе до выполнения.
Загрузка исполнительного адреса
Загрузка исполнительного адреса
Команда загрузки действительного адреса LEA очень похожа на команду
MOV. Но вместо пересылки данных из ячейки памяти в регистр команда
LEA загружает в регистр адрес двнных. Так как набор команд
микропроцессора 8088 разрешает иметь в команде только один адрес
памяти, в качестве приемника результата всегда указывается регистр.
Команда LEA может ссылаться на операнд источника с помощью любого
типа адресации, который можно указать байтом mod=r/m.
Во многих случаях команда LEA идентична команде MOV с
непосредственным операндом. Команды
MOV BX, OFFSET EXWORD
LEA BX, EXWORD
делают одно и то же. Первая команда - это непосредственная
пересылка, которая использует смещение переменной EXWORD. Оператор
OFFSET говорит ассемблеру о том, что в регистр BX надо загрузить
смещение адресного значения (все адресные значения имеют две части
- сегмент и смещение) переменной EXWORD. Команда LEA вычисляет
действительный адрес переменной EXWORD и помещает его в регистр BX.
В этом случае команды выполняют одинаковые действия.
Но если бы программа загружала в регистр BX адрес десятого
байта массива, на который указывает регистр DI, команда LEA
выглядела бы следующим образом
LEA BX, 10[DI]
Микропроцессор выполнил бы вычисление адреса, используя
информацию из байта mod=r/m в точности, как в случае команды MOV.
Затем он поместил бы вычисленное смещение, а не данные, по этому
адресу в регистр BX. Аналогичной команде с непосредственным
операндом MOV, которая могла бы выполнять ту же функцию, нет. У
ассемблера здесь нет способа определения непосредственного
значения, так как адрес неизвестен во время ассемблирования.
Загрузка указателя
Загрузка указателя
Поскольку механизм адресации микропроцессора 8088 требует
определения как сегмента, так и смещения каждой переменной,
желательно загрузить всю эту адресную информацию единственной
командой. Эту работу выполняют команды LDS и LES. Команда
LDS SI, EXDWORD
загружает регистровую пару DS:SI значениями сегмента и
смещения, содержащимися в переменной EXDWORD. Команда LDS загружает
в регистр SI значение смещения, расположенное по адресу EXDWORD, а
в регистр DS - значение сегмента, расположенное по адресу
EXDWORD+2. Команда LDS одна загружает два 16=битовых регистра
значением указателя, взятого из некоторой ячейки памяти. Так как
эта команда устанавливает и сегментный регистр, и регистр смещения,
программа может сразу адресоваться к объекту, на который этот адрес
указывает. Программа может организовать указатель из сегмента и
смещения во время ассемблирования с помощью оператора DD, который
порождает 32=битовое поле данных. Если операндом DD является
адресное выражение, двухсловное поле будет содержать сегмент и
смещение адресного значения в том же самом формате, который
используется в командах LDS и LES.
Команда LES идентична LDS, за исключением того, что она
загружает регистр ES. С помощью одной команды записать значения
сегмента и смещения нельзя. Программа должна записывать значение
указателя двумя командами пересылки слов, а не одной командой
записи указателя. Это приемлемо, так как программа обычно читает
указатель гораздо чаще, чем записывает его. Обычно программа
записывает указатель один раз, во время инициализации, и может
быть, иногда меняет его во время смены режимов работы системы. А
вот читается указатель, вероятно, достаточно часто. В последующих
главах есть примеры, в которых значения указателей и читаются, и
записываются.
Assembler для начинающих
Ассемблер и макроассемблер
Ассемблер и макроассемблер
После того, как исходный файл уже создан, можно применить
ассемблер. Существуют две версии ассемблера: полная версия,
называемая Макроассемблером, которая на специальной, отведенной для
программы ассемблера дискете, именуется MASM.EXE, и менее полная
версия - Ассемблер, или ASM.EXE - без возможности работать с
макроопределениями. Если для эффективного использования программы
MASM требуется объем памяти 96К, то для программы ASM достаточно
64К. Указанные значения никак не связаны с объемом памяти,
занимаемой исходной программой. Они относятся к объему памяти,
необходимой для ассемблирования, а не выполнения уже
оттранслированной программы. Поэтому возможна ситуация, когда для
разработки пользовательской программы, требующей для своего
выполнения только 4К, необходима ЭВМ с объемом памяти не менее 64К.
Входной информацией для ассемблера является исходный файл,
созданный редактором EDLIN, либо аналогичным редактором. Исходный
файл - это текстовый файл в кодах ASCII. В результате работы
ассемблера может получиться до трех выводных файлов. Объектный файл
представляет собой вариант исходной программы, записанной на
машинном языке. Объектный файл - это еще не совсем готовая для
выполнения программа, однако она близка к реальномй машинному
языку. Листинговый файл является текстовым явйлом в кодах ASCII,
включающим как исходную информацию, так и информацию, полученную в
результате работы ассемблера. Приведенные в данной книге примеры
являются ассемблерными листинговыми файлами. И наконец, ассемблер
может сформировать файл перекрестных ссылок. Этот файл, не
являющийся ни программой на машинном языке, ни текстовым файорм,
содержит информацию об использовании символов и меток в
ассемблерной программе. Как и в случае объектного файла, перед
использованием файла перекрестных ссылок требуется его
дополнительная обработка.
Запуск ассемблера осуществляется командой DOS
A> ASM
или
A>MASM
Команда ASM запускает усеченный вариант ассемблера, а команда
MASM - Макроассемблер. После того, как ассемблер начинает
выполняться, он просит указать: какие файлы будут использованы при
ассемблировании. На Фиг. 5.10 приводится последовательность команд
для запуска ассемблера.
После того, как команда ASM введена, DOS загружает в память
ассемблер. Ассемблер выводит на экран название версии и переходит
к выдаче запросов к оператору. Если в вашей системе имеется только
один дисковод с гибким диском, то на этом этапе вы можете вынуть
дискету с ассемблером и вставить дискету с данными. Ассемблер
запрашивает: какой файл будет транслироваться, при этом достаточно
ввести только имя этого файла без указания типа .ASM. Кроме этого,
ассемблер запрашивает имена выходных файлов. Объектному файлу
ассемблер присваивает то же имя, что и у исходного файла, но с
расширением .OBJ, если только вы не захотите его изменить. В
данном примере ответ оператора "B:" является указанием ассемблеру,
чтобы он записал объектный яайл на диск в дисковод B:. Аналогичные
ответы оператора на запросы по поводу листингового файла и файла
перекрестных ссылок сообщают ассемблеру, чтобы эти файлы он также
записал на диск в дисководе B:. В справочнике диска,
установленного в дисководе B:, можно увидеть все полученные в
результате ассемблирования файлы.
Для всех запросов имеются режимы, применяемые по умолчанию.
Если в ответ на любой из этих запросов нажать клавишу возврата, то
ассемблер использует режим по умолчанию. Для листингового файла и
файла перекрестных ссылок по умолчанию принимается значение NUL.
Для DOS файл типа NUL - это специальный файл: все, что записывается
в файл NUL не доступно и не может быть восстановлено. Файл
NUL относится к файлам типа WOF (write=only file - файл только для
-------------------------------------------------------
A>ASM
The IBM Personal Computer Assembler
Version 1.00 (C)Copyright IBM Corp 1981
Source filename [.ASM]: B:FIG5_10
Object filename [FIG5_10.obj] B:
Source lisying [NUL.LST] B:
Cross reference [NUL.CRF] B:
Warning Severe
Errors Errors
0 0
A>DIR B:FIG5_10.*
FIG5_10 ASM 44 1-01-83 12:00a
FIG5_10 LST 426 1-01-83 12:00a
FIG5_10 OBJ 40 1-01-83 12:00a
FIG5_10 CRF 19 1-01-83 12:00a
A>
A>B:
A>A:ASM FIG5_10,,,,
The IBM Personal Computer Assembler
Version 1.00 (C)Copyright IBM Corp 1981
Warning Severe
Errors Errors
0 0
B>
----------------------------------------------------
Фиг. 5.10 Выполнение ассемблирования
записи).
Если ассемблер во время ассемблирования обнаружит какие- нибудь
ошибки, он записывает их в листинговый файл. Кроме того, он выводит
их на дисплей, в результате чего можно непосредственно исправить
любые из обнаруженных ощибок и нет необходимости выискивать их в
листинговом файле. Если вы пользуетесь сокращенной версией
ассемблера, ASM, то информация об ошибках содержит только их коды.
В случае Макроассемблера, MSAM, выводится и код, и информация об
ошибке. В уменьшенном варианте ассемблера не остается места для
текстового сообщения об ошибке. В нижней части Фиг. 5.10 показан
более простой способ запуска Макроассемблера. Этот метод удобен,
когда в системе имеется два дисковода с гибкими дисками. В этом
случае дискета с ассемблером устанавливается в дисковод A:, а
дискета с информацией, содержащий исходный файл, - в дисковод B:.
Устанавливается режим выбора по умолчанию дисковода B:. Ассемблер
запускается с помощью команды A:ASM. В оставшейся части команды -
FIG5=10,,,; - содержится вся информация для ассемблера, которая
запрашивалась у оператора в предыдущем примере. FIG5=10 определяет
имя файла, который нужно ассемблировать, а последовательность
запятых служит указанием ассемблеру сгенерировать объектный,
листинговый файл и файл перекрестных ссылок в соответствии со
стандартным соглашением об именах файлов. При этом методе
ассемблирования получаются точно такие же результаты, что и в
первом случае.
Существует много способов того, как указать ассемблеру имена
файлов. Дап рассмотренных выше примера соответствуют крайним
случаям. В первом примере в ответ на запрос указывалось имя каждого
из файлов. Во втором случае не было необходимости ни в каких
запросах. Более подробно различные варианты, возможные в команде
ASM (или MASM), описываются в справочном руководстве по
Макроассемблеру.
После окончания ассемблирования можно воспользоваться
полученными выходными файлами. Объектный файл является исходным для
следующего этапа формирования выполняемой программы на машинном
языке. Это - этап редактирования связей LINK, который описывается в
следующем разделе.
В листинговом файле соединяются исходный файл и читабельный
вариант программы на машинном языке. Этот файл может быть выведен
на дисплей с помощью команды дисковой операционной системы TYPE,
например,
A> TYPE B:FIG5=11.LST
Команда TYPE загружает в память содержимое файла и выводит его
на дисплей. Одновременно этот же файл может быть выведен на печать,
если перед выполнением команды TYPE нажать клавиши Ctrl=PrtSc.
Нажатие Ctrl=PrtSc предписывает DOS осуществлять вывод как на
экран, так и на принтер. В результате листинг выводится и на
дисплей, и на печать. Ширину листинга следует задать равной 132
символам. Это выполняется с помощью команды ассемблера PAGE,
которая, как вы могли заметить, присутствует почти во всех
приводимых примерах программ. Команда
PAGE ,132
предписывает ассемблеру установить ширину листингового файла
равной 132 символам. Кроме того, перед выводом на принтер нужно
установить ширину печатаемой колонки. Это можно выполнить с помощью
команды MODE дисковой операционной системы.
A> MODE LPT1:132.
Данная команда устанавливает режим, при котором на принтере
фирмы IBM печатается 132 колонки. В этом случае листинговый файл
распечатывается без смещения строк, которого нельзя избежать на
дисплее.
Блок управления файлом (FCB)
Блок управления файлом (FCB)
Прежде, чем перейти к упомянутому примеру, необходимо рассмотреть
формируемую DOS структуру данных - блок управления файлом FCB (File
Control Block), который является существенным элементом файловой
системы и участвует во всех файловых операциях.
Блок управления файлом обеспечивает связь пользовательской
программы с функциями DOS. При любой файловой операции происходит
обращение к блоку FCB. На Фиг.5.5 показан состав стандартного
блока FCB. Имеется модификация блока FCB, называемая расширенным
блоком FCB, которая применяется в специальных случаях, когда нужно
"скрыть" файл. Скрытый файл защищен от записи. Это значит, что
программа не может модифицировать содержимое этого файла, не
изменив предварительно его блока FCB. Скрытый файл не фигурирует в
листинге справочника. Скрыть файл - один из простейших способов
защиты файла от неумелого пользователя. В приводимых примерах
используются только стандартные блоки FCB.
Поля данных блока FCB охватывают все атрибуты файла. Номер
дисковода, имя и тип файла составляют идентификатор файла. Размер
файла и дата яаляются атрибутами файла, которые приводятся в
листинге справочника. Оставшиеся поля - текущий номер блока, длина
записи и номер записи при произвольном доступе - служат для
определения местоположения внутри файла при операциях чтения и
записи. Длина записи указывает на число байтов в определяемой
пользователем записи. Так как все операции чтения и записи в файл
начинаются с границы записи, то длина записи определяет количество
данных, обрабатываемых во время каждой из этих операций.
Существуют два способа определения текущей записи при обращении
к файлу. При первом, последовательном, способе записи
обрабатываются по порядку. При этом текущий номер блока и
относительный номер записи определяют запись, которая будет
обрабатываться следующей. По мере того, как программа выполняет
операции чтения или записи, DOS увеличивает на 1 относительный
номер записи, чтобы он указывал на следующую запись. Выполнение
ЪДДДДї
0 і і Номер носителя
ГДДДДЕДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДї
1 і і і і і і і і і Имя файла
ГДДДДЕДДДДЕДДДДЕДДДДБДДДДБДДДДБДДДДБДДДДЩ
9 і і і і Расширение
ГДДДДЕДДДДЕДДДДЩ
0С і і і Текущий блок
ГДДДДЕДДДДґ
0E і і і Размер записи
ГДДДДЕДДДДЕДДДДВДДДДї
10 і і і і і Размер файла
ГДДДДЕДДДДЕДДДДБДДДДЩ
14 і і і Дата
ГДДДДЕДДДДЕДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДДї
16 і і і і і і і і і і і Зарезервировано
ГДДДДЕДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДДЩ
20 і і Относительная запись
ГДДДДЕДДДДВДДДДВДДДДї (нумерация в блоке)
21 і і і і і Номер записи
АДДДДБДДДДБДДДДБДДДДЩ прямого доступа
Фиг. 5.5 Блок управления файлом
последовательных операций идет в направлении от начала к концу
файла. Примером последовательного файла служит программный файл на
языке ассемблера. Ассемблер осуществляет чтение записей от начала
до конца этого файла.
Программа может обращаться к файлу, используя также и
произвольный доступ. Произвольный в данном случае не означает, что
запись выбирается случайным образом. Наоборот, это значит, что
программа может выбрать в качестве следующей любую запись в файле.
По-другому такой файл называют файлом с прямым доступом, так как у
программы имеется непосредственный доступ к любой записи.
Программа может обращаться к файлу любым из двух указанных
способов. При последовательных операциях DOS автоматически изменяет
в блоке FCB значение поля относительного номера записи. При
произвольном доступе к файлу номер записи должен определяться
программой. В файловой системе файл с произвольным доступом
является аналогом вектора. Точно так же, как для обращения к любому
элементу вектора программа должна задать значение соответствующего
индекса, так и при обращение к записи в файле с произвольным
доступом номер записи должен определяться программой.
Теперь можно рассмотреть пример программы, использующей блок
FCB и функции DOS. На Фиг. 5.6 приведена длинная программа, которая
реально мало что делает. Ее цель - продемонстрировать использование
блока FCB и функци DOS, связанных с прерыванием 21H. Однако
содержащиеся в этой программе операции могут применяться в реальных
задачах.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:12
Фиг. 5.6 Пример использования функций ДОС Page 1-1
PAGE ,132
TITLE Фиг. 5.6 Пример использования функций ДОС
0000 CODE SEGMENT
005C ORG 05CH ; Положение первого поля FCB
005C FCB LABEL BYTE ; Метка всего поля FCB
005C ?? FCB_DRIVE DB ? ; Номер устройства
005D 0008[ FCB_NAME DB 8 DUP (?) ; Имя файла
??
]
0065 0003[ FCB_EXT DB 3 DUP (?) ; Тип файла
??
]
0068 ???? FCB_BLOCK DW ? ; Номер текущего блока
006A ???? FCB_RECORD_SIZE DW ? ; Размер записи
006C ???????? FCB_FILE_SIZE DD ? ; Размер файла
0070 ???? FCB_DATE DW ? ; Дата последнего изменения
0072 000A[ FCB_RESV DB 10 DUP (?) ; Зарезервировано ДОС
??
]
007C ?? FCB_CURRENT_RECORD DB ? ; Номер текущей записи
007D ???????? FCB_RANDOM_RECORD DD ? ; Номер записи при прямом
; доступе
0090 ORG 090H
0090 DISK_TRANSFER_ADDRESS LABEL BYTE ; Буфер для данных
0100 ORG 100H
ASSUME CS:CODE,DS:CODE,ES:CODE
0100 E9 01CD R JMP PROGRAM_START ; Переход на начало программы
= 0020 RECORD_SIZE EQU 32 ; Размер записи
0103 03 00 00 00 00 KEYBOARD_BUFFER DB 3, 0, 0, 0, 0 ; Буфер ввода с клавиатуры
0108 94 A0 A9 AB 20 E3 A6 FILE_ERROR_MSG DB 'Файл уже существует', 10, 13, '$'
A5 20 E1 E3 E9 A5 E1
E2 A2 E3 A5 E2 0A 0D
24
011E 8D A5 A2 AE A7 AC AE BAD_OPEN_MSG DB 'Невозможно открыть файл', 10, 13, '$'
A6 AD AE 20 AE E2 AA
E0 EB E2 EC 20 E4 A0
A9 AB 0A 0D 24
0138 8E E8 A8 A1 AA A0 20 BAD_WRITE_MSG DB 'Ошибка при записи в файл', 10, 13, '$'
AF E0 A8 20 A7 A0 AF
A8 E1 A8 20 A2 20 E4
A0 A9 AB 0A 0D 24
0153 8E E8 A8 A1 AA A0 20 BAD_READ_MSG DB 'Ошибка при чтении файла', 10, 13, '$'
AF E0 A8 20 E7 E2 A5
AD A8 A8 20 E4 A0 A9
AB A0 0A 0D 24
016D 8E E8 A8 A1 AA A0 20 BAD_CLOSE_MSG DB 'Ошибка при закрытии файла', 10, 13, '$'
Фиг. 5.6 Пример использования функциямй DOS (начало)
AF E0 A8 20 A7 A0 AA
E0 EB E2 A8 A8 20 E4
A0 A9 AB A0 0A 0D 24
0189 8F E0 A8 20 A2 A2 AE INPUT_BAD_MSG DB 'При вводе требуется два символа', 10, 13, '$'
A4 A5 20 E2 E0 A5 A1
E3 A5 E2 E1 EF 20 A4
A2 A0 20 E1 A8 AC A2
AE AB A0 0A 0D 24
01AB 82 A2 AE A4 20 A4 AE CHAR_BAD_MSG DB 'Ввод должен быть: символ|символ', 10, 13, '$'
AB A6 A5 AD 20 A1 EB
E2 EC 3A 20 E1 A8 AC
A2 AE AB 7C E1 A8 AC
A2 AE AB 0A 0D 24
;----- Установка буфера (Области связи с диском)
01CD PROGRAM_START:
01CD B4 1A MOV AH, 1AH ; Установка буфера
01CF 8D 16 0090 R LEA DX, DISK_TRANSFER_ADDRESS
01D3 CD 21 INT 21H
;----- Поиск файла
01D5 B4 11 MOV AH, 11H ; Поиск файла с заданным
01D7 8D 16 005C R LEA DX, FCB ; именем
01DB CD 21 INT 21H
01DD 0A C0 OR AL, AL
01DF 75 0A JNZ NO_FILE ; Переход если файл новый
01E1 8D 16 0108 R LEA DX, FILE_ERROR_MSG ; Сообщение о неправильном
01E5 ERROR_EXIT: ; имени файла
01E5 B4 09 MOV AH, 9H ; Вывод сообщения на экран
01E7 CD 21 INT 21H ; и выход из программы
01E9 CD 20 INT 20H
;----- Создание нового файла
01EB NO_FILE:
01EB B4 16 MOV AH, 16H ; Создание файла
01ED 8D 16 005C R LEA DX, FCB
01F1 CD 21 INT 21H
01F3 0A C0 OR AL, AL ; Проверка на успех
01F5 74 06 JZ CREATE_OK
01F7 8D 16 011E R LEA DX, BAD_OPEN_MSG ; Сообщение об ошибке при
01FB EB E8 JMP ERROR_EXIT ; создании файла
;----- Установка параметров FCB
01FD CREATE_OK:
01FD C6 06 007C R 00 MOV FCB_CURRENT_RECORD, 0 ; Инициализация номера
0202 C7 06 007D R 0000 MOV WORD PTR FCB_RANDOM_RECORD, 0 ; записи
0208 C7 06 007F R 0000 MOV WORD PTR FCB_RANDOM_RECORD+2, 0
020E C7 06 006A R 0020 MOV WORD PTR FCB_RECORD_SIZE, RECORD_SIZE
Фиг. 5.6 Пример использования функциямй DOS (продолжение)
;----- Вывод в файл
0214 B0 41 MOV AL, 'A' ; В этом цикле выводятся
0216 CHARACTER_LOOP: ; символы алфавита
0216 8D 3E 0090 R LEA DI, DISK_TRANSFER_ADDRESS
021A B9 0020 MOV CX, RECORD_SIZE
021D F3/ AA REP STOSB ; Заполнение буфера символами
021F 50 PUSH AX ; Сохранение символа
0220 8D 16 005C R LEA DX, FCB
0224 B4 15 MOV AH, 15H ; Последовательный вывод
0226 CD 21 INT 21H ; блока символов
0228 0A C0 OR AL, AL ; Проверка на ошибку
022A 58 POP AX ; Восстановление символа
022B 74 06 JZ WRITE_OK
022D 8D 16 0138 R LEA DX, BAD_WRITE_MSG ; Ошибка записи
0231 EB B2 JMP ERROR_EXIT
0233 WRITE_OK:
0233 FE C0 INC AL ; Следующий символ
0235 3C 5B CMP AL, 'Z'+1 ; Проверка на окончание вывода
0237 75 DD JNE CHARACTER_LOOP ; Вывод следующего символа
;----- Изменение файла
0239 KEYBOARD_LOOP: ; Цикл ввода символов
0239 8D 16 0103 R LEA DX, KEYBOARD_BUFFER ; с клавиатуры и
023D B4 0A MOV AH, 0AH ; изменение файла
023F CD 21 INT 21H ; Ввод с клавиатуры
0241 80 3E 0104 R 02 CMP KEYBOARD_BUFFER+1, 2 ; Было введено два символа?
0246 74 0A JE KEY_INPUT_OK
0248 8D 16 0189 R LEA DX, INPUT_BAD_MSG ; Сообщение об ошибке
024C KEYBOARD_ERROR:
024C B4 09 MOV AH, 9H
024E CD 21 INT 21H ; Печать сообщения об ошибке
0250 EB E7 JMP KEYBOARD_LOOP ; Повторный ввод
0252 KEY_INPUT_OK:
0252 8D 16 01AB R LEA DX, CHAR_BAD_MSG ; Установка указателя на
; сообщение об ошибке
0256 A0 0105 R MOV AL, KEYBOARD_BUFFER+2 ; Выборка первого символа
0259 3C 24 CMP AL, '$' ; Сравнение с символом конца
025B 75 03 JNE CHANGE_RECORD ; Изменение записи
025D EB 5C 90 JMP PROGRAM_EXIT
;----- Чтение старой записи из файла
0260 CHANGE_RECORD:
0260 3C 41 CMP AL, 'A' ; Проверка на то, что символ
0262 7C E8 JL KEYBOARD_ERROR ; находится в диапазоне A-Z
0264 3C 5A CMP AL, 'Z'
0266 77 E4 JA KEYBOARD_ERROR
0268 2A E4 SUB AH, AH ; Преобразование символа в
026A 2C 41 SUB AL, 'A' ; номер записи
026C A3 007D R MOV word ptr FCB_RANDOM_RECORD, AX ; Занесение номера в FCB
026F 8D 16 005C R LEA DX, FCB
0273 B4 21 MOV AH, 21H ; Прямое чтение записи
0275 CD 21 INT 21H
Фиг. 5.6 Пример использования функциямй DOS (продолжение)
0277 0A C0 OR AL, AL ; Проверка на ошибку
0279 74 07 JE RANDOM_RECORD_OK
027B 8D 16 0153 R LEA DX, BAD_READ_MSG ; Сообщение об ошибке
027F E9 01E5 R JMP ERROR_EXIT
;----- Вывод старой записи на экран
0282 RANDOM_RECORD_OK:
0282 C6 06 00B0 R 0A MOV DISK_TRANSFER_ADDRESS+32, 10 ; Занесение символов конца
0287 C6 06 00B1 R 0D MOV DISK_TRANSFER_ADDRESS+33, 13 ; строки и конца вывода
028C C6 06 00B2 R 24 MOV DISK_TRANSFER_ADDRESS+34, '$' ; в буфер
0291 B4 09 MOV AH, 9H
0293 8D 16 0090 R LEA DX, DISK_TRANSFER_ADDRESS
0297 CD 21 INT 21H ; Вывод записи на экран
;----- Изменение файла
0299 A0 0106 R MOV AL, KEYBOARD_BUFFER+3 ; Выборка символа, на который будет
029C B9 001F MOV CX, RECORD_SIZE-1 ; заменяться содержимое записи
029F 8D 3E 0091 R LEA DI, DISK_TRANSFER_ADDRESS+1
02A3 F3/ AA REP STOSB ; Замена 31 символа
02A5 B4 22 MOV AH, 22H
02A7 8D 16 005C R LEA DX, FCB
02AB CD 21 INT 21H ; Прямая запись на файла на
; старое место
02AD 0A C0 OR AL, AL ; Проверка на ошибку
02AF 74 07 JZ RANDOM_WRITE_OK
02B1 8D 16 0138 R LEA DX, BAD_WRITE_MSG ; Сообщение об ошибке
02B5 E9 01E5 R JMP ERROR_EXIT
02B8 RANDOM_WRITE_OK:
02B8 E9 0239 R JMP KEYBOARD_LOOP ; Обработка следующей записи
;----- Конец программы
02BB PROGRAM_EXIT:
02BB B4 10 MOV AH, 10H ; Закрытие файла
02BD 8D 16 005C R LEA DX, FCB
02C1 CD 21 INT 21H
02C3 0A C0 OR AL, AL ; Проверка на ошибку
02C5 74 07 JZ CLOSE_OK
02C7 8D 16 016D R LEA DX, BAD_CLOSE_MSG ; Сообщение об ошибке
02CB E9 01E5 R JMP ERROR_EXIT
02CE CLOSE_OK:
02CE CD 20 INT 20H ; Возврат в ДОС
02D0 CODE ENDS
END
Фиг. 5.6 Пример использования функциямй DOS (продолжение)
Приведенная программа состоит из двух частей. В первой части
создается файл из 26 записей длиной по 32 байта. Каждая запись
соответствует одной букве алфавита: запись 1 - это "AAAA...A",
запись 2 - "BBBB...B" и т.д. Данный файл формируется
последовательно. Во второй части программы этот файл Mincho"'> рассматривается как файл с произвольным доступом. В ответ на
введенный пользователем с клавиатуры запрос программа считывает и
выводит на дисплей одну из 26 записей. С помощью этого же ввода
осущкствляется редактирование записи: 31 символ записи, начиная со
второго, заменяется на значение, которое вводится с клавиатуры.
Выполнение программы завершается при вводе с клавиатуры символа
"$".
В этом примере моделируется справочная база данных. На первом
этапе программа осуществляет формирование этой базы данных. На
втором этапе выполняется обработка произвольных запросов к базе
данных и редактирование ее содержимого. Хотя любая реальная
программа управления базой данных гораздо сложнее, чем приведенный
здесь пример, однако он является хорошей иллюстрацией основных
функций, относящихся к работе с файлами.
Программа, приведенная на Фиг. 5.6, - это файл типа .COM. В
следующем разделе мы обсудим различие между этим файлом и файлом
типа .EXE. Использование в данном примере файла типа .COM позволяет
для завершения работы программы воспользоваться прерыванием INT
20H. В случае файла типа .COM начало программы в сегменте должно
иметь смещение 100H. Первые 100H байт программного сегмента
называются программным префиксом PSP и предназначены для хранения
некоторых специальных данных, которые используются программной,
написанной на языке ассемблера.
Смещение блока FCB в сегменте равно 05CH, и использование в
программе этого на первый взгляд произвольного адреса имеет
определенные основания. Запись информации в данный блок FCB
осуществляется командным процессором, входящим в состав DOS. После
того, как пользователь вызвал программу, указав ее имя, DOS
просматривает оставшуюся часть командной строки в поисках имен
файлов. Первое встретившееся в команде имя помещается в блок FCB,
смещение которого равно 05CH. Если в командной строке будет еще
одно имя, то оно запишется в блок FCB со смещением 06CH. Так как в
нашеи примере фигурирует только один файл, то используют только
блок FCB со смещением 05CH. Запуск описываемой программы
осуществляется с помощью следующей синтаксической конструкции:
A> FIG5=6 TEST.FIL
FIG5=6 - имя программы. В каталоге программа фигурирует под
именем FIG5=6.COM. Файл TEST.FIL является файлом, который программа
создает и затем модифицирует. Интерпретатор командных строк
выбирает имя "TEST.FIL" и помещает его в соответствующее поле блока
FCB со смещением 05CH. Рассматривая имя файла в качестве параметра
командной строки, можно с помощью описываемой программы создавать и
модифицировать любой файл. Если бы имя файла входило в ассемблерную
программу, мы могли бы работать только с единственным файлом.
Начиная со смещения 05CH, текст программы соответствует
структуре блока FCB. Первая ячейка идентифицируется меткой блока
FCB. Каждое из полей FCB имеет свою метку и длину, так что
программа может обрабатывать их непосредственно. Например, чтобы
задать длину записи, программа модифицирует переменную
FCB_RECORD_SIZE.
Начиная с адреса 080H помещается еще одно специальное поле
программного префикса. Эта область памяти размером 128 байт
отводится по умолчанию под область связи с диском DTA (Disk
Transfer Area) и используется DOS в качестве буфера для всех
файловых записей. Всякий раз, записывая или читая запись, DOS
использует буфер области DTA. Во время инициализации DOS
устанавливает смещение области DTA в программном сегменте равным
080H. Программа может изменить это значение, используя функцию 1AH
прерывания 21H. Это смещение должно быть изменено, еслм длина
записи больше, чем 128 байт. В рассматриваемом примере область
связи с диском сдвигается до смещения в сегменте, равного 90H. Это
связано с тем, что блок FCB со смещением 05CH выходит за границу с
адресом 80H. Если бы граница области DTA определялась соглашением,
принятым по умолчанию, то при передаче файлов информация в
последнем байте блока FCB была бы разрушена. В этом байте
записывается номер записи при произвольном доступе к файлу. Так как
в данной программе для чтения-записи данных используется
произвольный доступ, упомянутое наложение информации необходимо
исключить.
Первая команда программы имеет смещение 100H и является
переходом на фактическое начало программы. Такая структура
программы может показаться неэффективной, однако ассемблирование
происходит гораздо успешней, если все данные помещаются перед
командами, которые на них ссылаются. Фактически в программе могут
быть ошибки, если она содержит ссылки на данные вперед. Поэтому для
большей надежности данные помещаются в начале программы.
В первой части программы устанавливается область связи с
диском. В данной программе эта буферная область имеет смещение 90H.
Так как длина записи равняется только 32 байтам, то впереди
программы имется достаточно места.
Далее в программе используется прерывание 21H и функция DOS для
поиска файла с именем, совпадающим с именем, записанным в блоке
FCB. Обратите внимание, что пара регистров DC:DX указывает на блок
FCB, как и должно быть при любых файловых операциях. Если система
обнаружит файл с идентичным именем, программа завершит работу,
выдав сообщение об ошибке и сохранив существующий уже файл. В
данной программе обрабатываются только новые файлы. Существующие
файлы здесь никак не используются, однако гарантируется, что их
содержимое программой не сотрется. Конечно, в реальной практике
програма была бы составлена так, чтобы охватывать случай как новых,
так и уже существующих файлов.
Создание файла обеспечивается участком программы, помещенным
NO_FILE. Формировать блок FCB до этого данной программе не нужно,
так как он был сформирован командным процессором. Если описываемая
файловая операция не сможет быть выполнена, например, из-за
отсутствия свободного места на диске или отсутствия места в
каталоге диска, то программа завершит выполнение и выдаст
соответствующее сообщение об ошибке.
При любом обращении DOS к файлу программа должна, прежде всего,
открыть файл. Процедура открытия файла устанавливает связь между
операционной системой и пользовательской программой. В процессе
этой операции DOS просматривает справочник диска, находит нужный
файл (или не находит, что соответствует сбойной ситуации) и
заполняет поле блока FCB, относящееся к длине файла. После того,
как файл открыт, DOS не должна просматривать справочник диска
всякий раз, когда происходит обращение к файлу. Система DOS
сохраняет в блоке FCB информацию о файле до тех пор, пока она этот
файл не "закроет". Термины "закрыть" и "открыть" файл еще раз
указывают на связь с традиционным делопроизводством. Папку с файлом
документов нужно открыть прежде, чем можно будет ознакомиться с
содержащимся в ней бумагами, и закрыть до того, как ее уберут снова
в бюро.
В рассматриваемом примере открытие файла происходит при его
создании. Если бы такой файл уже существовал, связь с ним была бы
установлена с помощью функции открытия файла (AH=0FH). Если
открытие файла произошло успешно, программма изменяет значения
некоторых полей блока FCB. В частности, длина записи должна быть
установлена равной 32 байт, так как по умолчанию DOS считает ее
равной 128 байт.
Часть программы с именем CHARACTER_LOOP передает в файл 26
записей. Определяющий символ каждой записи передается в буфер
области DTA оператором REP STOSB. Отдельные записи передаются на
диск с помощью функции последовательной записи (AH=0AH). Программа
также выполняет проверку на отсутствие ошибок.
Начиная с метки KEYBOARD_LOOP программа переходит от
формирования файла к запросу=коррекции записей в нем. В данном
примере используется буферированный ввод с клавиатуры,
поддерживаемый DOS. Это позволяет пользователю вводить строку из
двух символов и, если необходимо, осуществлять редактирование.
После ввода двух символов должна быть нажата клавиша окончания
ввода. Программа выполняет проверку правильности ввода, и если
последний не удовлетворяет требованиям, то он отвергается и
выдается сообщение об ошибке.
При вводе пользователем символа "$" программа заканчивает
работу. В случае же ввода символов от "A" до "Z" программа
осуществляет чтение соответствующей записи и выводит на дисплей ее
содержимое. В оставшиеся 31 байт буфера программа записывает второй
из введенных с клавиатуры символов. Модицифированная таким образом
запись передается на диск в режиме произвольного доступа.
В заключительной части программы осуществляется закрытие файла.
Аналогично тому, как функция открытия файла устанавливала связь
между DOS и пользовательской программой,
??????
файла существенно для гарантии того, что DOS записала на диск все
модифицированные записи. Во время нормального выполнения программы
DOS может оставить несколько последних записей в буфере. Это
ускоряет выполнение, так как DOS не должна обращаться к диску по
поводу каждой записи. Функция закрытия файла осуществляет запись на
диск содержимого буфера.
Приведенный на Фиг. 5.6 пример программы иллюстрирует основные
способы обращения к файлу с помощью DOS. Эта программа ничего
полезного не выполняет, однако приближение ее к реальным задачам
потребовало бы намного большего числа команд, которые мало что
прояснили бы в отношении использования функций DOS. В данной
программе важно обратить внимание на необходимость проверки ошибок
после каждой операции, выполняемой DOS. В то время, как DOS с
помощью прерывания 24H обрабатывает аппаратные ошибки,
пользовательская программа должна разрешать такие ситуации, как
совпадение имен файлов или отсутствие свободного места на дискете.
В данном примере обработка ошибок проста и состоит из вывода
соответствующего сообщения и завершения работ программы. Обработка
ошибок в реальных программах значительно сложнее и более актуальна,
так как в этом случае возможность потери важной информации должна
быть исключена.
И наконец, возможно, что проработав с данной программой, вы
будете не очень удовлетворены ею как пользователь: в ней
отсутствует запрос на ввод данных, сообщения об ошибках лаконичны и
некоторые из выводимых сообщений частично накладываются на
предыдущие, затирая их. Данная программа нуждается в доработке
прежде, чем ею сможет воспользоваться кто-либо, не участвовавший в
ее написании или тщательно в ней не разобравшийся.
Дисковая операционная система
Дисковая операционная система
Поскольку для подготовки и выполнения программ мы собираемся
использовать DOS, то начнем с рассмотрения того, что такое DOS, и
каковы ее функции. DOS обеспечивает операционную среду, в которой
выполняются другие программы. В больших ЭВМ операционная система
представляет собой программу, которая управляет работой всей
машины. В большинстве случаев у таких ЭВМ имеется несколько
пользователей, конкурирующих между собой из-за доступа к ресурсам
машины. Операционная система играет роль рефери, который решает,
кто из пользователей, когда и к каким ресурсам имеет доступ.
Операционная система позволяет пользователям работать, не мешая
друг другу, а также предлагает им выбор сервисных программ, которые
освобождают их от всех трудностей, связанных с учетом аппаратных
особенностей ЭВМ. Эти программы исключают выполнение пользователем
операций, которые портят данные и программы других пользователей и,
тем самым, нарушают правильную работу аппаратных средств.
Значительная часть операционных систем больших ЭВМ устроена так,
что пользовательская программа не имеет прямого доступа к
аппаратным средствам. Коллективные интересы пользователей ставятся
здесь выше индивидуальной свободы пользовательских программ.
В небольших компьютерных системах, подобных IBM PC,
операционная система служит иной цели. В каждый момент времени с
персональной ЭВМ работает только один пользователь, и управление
ресурсами машины осуществляется извне: ими распоряжается тот, кто
сидит за клавиатурой. На программном уровне разрешены любые
последовательности команд. Выполнение программы может привести к
неприятностям только для того пользоватея, который в этот момент
работает с ЭВМ, и то только в том смысле, что его программа или
данные будут для него потеряны.
Назначение операционной системы IBM PC состоит в том, чтобы
обеспечить операционную среду и набор сервисных программ для
пользователей. В качестве пользователя может быть программист или
какая-нибудь прикладная программа. Например, когда вы садитесь за
клавиатуру и начинаете работать с системой, то вы - пользователь
DOS. Однако, когда текстовый редактор записывает файл на дискету,
то пользователем DOS является текстовый редактор. Вместо того,
чтобы самому осуществить запись файла, текстовый редактор
пользуется услугами DOS.
Основная функция DOS IBM PC - это обеспечить файловую систему и
операционную среду для программ. Файловая система представляет
собой набор программных средств записи и считывания данных с
дискеты или жесткого диска. Если все прикладные программы для
записи данных используют DOS, то они могут коллективно пользоваться
этими данными, и кроме того, в каждом случае при разработке
приложений не надо будет заново переписывать файловую систему.
EXTRN и PUBLIC
EXTRN и PUBLIC
Редактор связей не может, однако, выполнить все, о чем говорилось
выше, самостоятельно. Ассемблер должен получить от программиста
информацию о подпрограммах, относящихся к другому программному
модулю. Это ввполняется с помощью оператора PUBLIC, извещающего
ассемблер о том, что данное символическое имя доступно другим
программам. Кроме того, программист указывает ассмеблеру, какие из
символических имен является внешними для данного программного
модуля. В языке ассемблера это реализуется оператором EXTRN,
который объявляет соответствующее имя внешним для текущего
ассемблирования, чтобы оно могло быть правильно обработано.
Ассемблер помечает данную команду таким образом, чтобы редактор
связей мог впоследствии найти ее и вставить туда правильное
значение адреса.
Оператор EXTRN выполняет две фуекции. Во-первых, он сообщает
ассемблеру, что указанное символическое имя является внешним для
текущего ассемблирования. Конечно, на этом этапе ассемблер мог бы
считать, что любое имя, не идентифицированное им в процессе
ассемблирования, является внешним. Однако, если когда-нибудь вы
ошиблись в указании имен, то ассемблер решит, что имеется в виду
внешнее имя, и не выдает сообщения об ошибке. Это отложит индикацию
ошибки до этапа редактирования связей. Для большинства
программистов это слишком поздно, особенно, если речь идет о чем-то
простом, вроде описки. Таким образом, ассемблер индицирует ошибку в
случае любого не определенного им символического имени.
Вторая функция оператора EXTRN состоит в том, что он указывает
ассемблеру тип соответствующего символического имени. Так как
ассемблирование является очень формальной процедурой, то ассемблер
должен знать, что представляет из себя каждый символ. Это позволяет
ему генерировать правильные команды. В случае данных оператор EXTRN
может указывать на байт, двойное слово или другой типовой элемент.
Тип имени подпрограммы или другой программной метки может быть либо
NEAR, либо FAR, в зависимости от того, в каком сегменте она
находится. От программиста требуется указать в операторе EXTRN тип
символического имени. Так как кроме того ассемблером осуществляется
посегментная адресация программы, то оператор EXTERN указывает на
сегмент, в котором появляется данный идентификатор. Это не входит в
синтаксис оператора EXTRN, а определяется местоположением этого
оператора в программе. Ассемблер считает, что внешнее имя относится
к тому же сегменту, в котором появляется оператор EXTERN для этого
символического имени.
На Фиг. 5.13 приведен пример ассемблерной программы,
иллюстрирующей использование оператора EXTRN. Здесь имеются два
имени, являющиеся внешними для данной программы. OUTPUT_CHARACTER
обозначает однобайтовую переменную. Соответствующий этой переменной
атрибут ":BYTE" указывается после имени переменной. Указатель NEAR
программной метки OUTPUT_ROUTINE говорит о том, что она находится в
том же сегменте. Хотя приведенная на Фиг. 5.13 прогграмма содержит
ссылки на эти символические имена, при трансляции ассемблер знает,
как ему сегментировать правильные команды. Если бы оператор EXTRN
отсутствовал в программе, то в этом случае ассемблер
инициализировал бы ошибки. Из ассемблерного листинга видно, что
после поля адреса в командах, ссылающихся на внешние имена, стоит
символ E.
icrosoft (R) Macro Assembler Version 5.00 4/2/89 16:06:36
Фиг. 5.13 Основная программа Page 1-1
PAGE ,132
TITLE Фиг. 5.13 Основная программа
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?) ; Резервирование места для стека
????
]
0080 STACK ENDS
0000 CODE SEGMENT PUBLIC
EXTRN OUTPUT_ROUTINE:NEAR, OUTPUT_CHARACTER:BYTE
ASSUME CS:CODE
0000 START PROC FAR
0000 1E PUSH DS ; Сегмент адреса возврата
0001 B8 0000 MOV AX, 0
0004 50 PUSH AX ; Смещение адреса возврата
0005 FC CLD ; Установка направления
0006 8C C8 MOV AX, CS ; Установка сегментного регистра
0008 8E D8 MOV DS, AX
ASSUME DS:CODE ; Индикация состояния регистра
000A 8D 36 001D R LEA SI, MESSAGE ; Адрес строки сообщения
000E CLOOP:
000E AC LODSB ; Выборка следующего байта сообщения
000F A2 0000 E MOV OUTPUT_CHARACTER, AL ; Сохранение в памяти символа
0012 E8 0000 E CALL OUTPUT_ROUTINE ; Вывод символа
0015 80 3E 0000 E 0A CMP OUTPUT_CHARACTER, 10 ; Проверка на символ конца сообщения
001A 75 F2 JNE CLOOP ; Обработка следующего символа
001C CB RET ; Возврат в ДОС
001D 9D E2 A0 20 AF E0 AE MESSAGE DB 'Эта программа - тест', 13, 10
A3 E0 A0 AC AC A0 20
2D 20 E2 A5 E1 E2 0D
0A
0033 START ENDP
0033 CODE ENDS
END START
Фиг. 5.13 Главная процедура
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:28
Фиг. 5.14 Подпрограмма вывода Page 1-1
PAGE ,132
TITLE Фиг. 5.14 Подпрограмма вывода
0000 CODE SEGMENT PUBLIC
ASSUME CS:CODE,DS:CODE ; Это должно быть так при вызове
PUBLIC OUTPUT_CHARACTER, OUTPUT_ROUTINE
0000 ?? OUTPUT_CHARACTER DB ?
0001 OUTPUT_ROUTINE PROC NEAR
0001 A0 0000 R MOV AL, OUTPUT_CHARACTER ; Выборка выводимого символа
0004 B4 0E MOV AH, 14 ; Функция вывода в BIOS
0006 BB 0000 MOV BX, 0 ; Установка номера страницы
0009 BA 0000 MOV DX, 0
000C CD 10 INT 10H ; Вызов подпрограммы вывода на экран
000E C3 RET ; Возврат в вызывающую программу
000F OUTPUT_ROUTINE ENDP
000F CODE ENDS
END
Фиг. 5.14 Процедура вывода
Рассмотрим эту же задачу с другой стороны. Каким образом
редактор связей узнает о местоположении внешних имен? На Фиг. 5.14
приведена подпрограмма, на которую ссылается другая программа,
относящаяся к Фиг. 5.13. Переменные и программные метки, на которые
имеются ссылки в программе, на Фиг. 5.13, объявлены в подпрограмме
с помощью оператора PUBLIC. Это означает, что их имена доступны для
другого программного модуля. Ни на какие другие переменные или
программные метки в этой программе, не указанные в операторе
PUBLIC, ссылки в других программах невозможны. Хотя это может
показаться неудобным, однако, если все имена имели бы атрибут
PUBLIC, то возникла бы другая трудность. Это означало бы, что
каждое имя в любом из модулей, которые вы могли бы связать между
собой, должны быть уникальными, т.е. вы никогда бы не смогли
использовать одно и то же символическое имя дважды в разных
модулях. Это может быть серьезным препятствием для повторного
использования некоторых подпрограмм, так как такое использование
возможно и через несколько лет, а помнить все символические имена и
следить за тем, чтобы ни одно из них не повторялось дважды довольно
сложно. Заметьте, что в операторе PUBLIC не требуется указывать
атрибуты имен: об этом заботятся обычные операторы языка
ассемблера.
Программа LINK устанавливает соответствие между всеми внешними
именами и соответствующими операторами PUBLIC, которые их
объявляют. После этого редактор связей записывает правильные
значения адресов в команды, гдк есть ссылки на внешние имена.
Обрабатываются те поля в командах, рядом с которыми в ассемблерном
листинге стоял символ "E".
Кроме того, ассемблер осуществляет объединение любых сегментов
с одними тем же именем. В случае программ на Фиг. 5.13 и П5.14
основная программа и подпрограмма принадлежат одному и тому же
сегменту с именем CODE. Так как в операторе EXTRN основной
программы для программы OUTPUT_ROUTINE указан атрибут NEAR, то
желательно, чтобы эта программа была в том же сегменте. Атрибут
PUBLIC в операторе SEGMENT указывает редактору связей объединить
оба программных модуля в один выполняемый сегмент.
В программе на Фиг. 5.13 есть еще один сегмент, который следует
рассмотреть. Данная программа выполняется как программа типа .EXE.
При передаче управления программе типа .EXE система DOS организует
для этой программы стек. Информация для стека поступает от
редактора связей, который записывает ее в головную метку файла типа
.EXE. Подготовить все для стека обязан программист. Если он этого
не сделает, то редактор связей выдает соответствующее сообщение. В
обычной ситуации это не может служить препятствием для выполнения
программы. Однако в таком случае параметры стека для программы
выбираются по умолчанию, т.е. местоположение и размер стека могут
оказаться неподходящими. За подготовку стека отвечает сегмент
STACK, входящий в программу на Фиг.5.13. Его имя STACK и задание
соответствующего атрибута равным STACK говорят о том, что это
область памяти предназначена для стека. Редактор связей, кроме
того, проверяет, правильно ли установлен указатель стека в момент,
когда управление передается программе.
Файловая система
Файловая система
На каждой дискете, используемой в IBM PC, может храниться от 160 до
360 Кбайт информации, а на жестком диске - более 107 байт.
Очевидным образом возникает задача ведения архива. При таких
объемах информации необходим способ полного упорядочения хранимой
информации. В качестве пользователя DOS вы заинтересованы в том,
чтобы данные представляли отдельную совокупность, как, например,
программа на языке ассемблера. Вас не интересует, где эти данные
будут располагаться на дискете. Физическое распределение этих
данных на поверхности дискеты - это заботы системы.
Основной единицей хранения данных является файл. Файл - это
совокупность данных, интерпретируемых некоторым образом. Владелец,
или создатель файла присваивает ему имя. Это имя может быть
использовано при любых ссылках на эти данные для того, чтобы
обеспечить к ним доступ. Ссылка на данные не требует никаких
указаний в программе на то, где они физически располагаются.
Любой файл состоит из записей. Каждая запись - это отдельный
элемент данных, но не обязательно один байт. Чтобы понять, что
такое файлы и записи, лучше всего вспомнить, в каком значении эти
слова употребляются в делопроизводстве.
Файл - это большой ящик или папка, в которой хранится множество
документов. На файловой папке обычно указано ее название - имя
файла. В папке собраны отдельные записи. Например, в файле
преподаватели могут хранить контрольные работы, написанные
студентами. Каждая отдельная контрольная работа, входящая в файл,
соответствует одной записи. Собранные и храняшиеся у преподавателя
записи содержательно именуются, например "первая контрольная
работа". Чтобы отыскать чью-то контрольную работу, преподаватель
сначала находит нужный файл, а затем просматривает этот файл в
поисках требуемой записи.
Как все это соотносится теперь с файлами, обрабатываемыми ЭВМ?
Файл представляет собой совокупность связанных между собой данных,
и у файла есть имя. Записи - это то, что составляет файл, Размер и
содержание записей определяются программистом, DOS не проверяет
формат записей, а просто помещает их в файл. Для системы DOS любая
запись - это совокупность байтов в файле. Содержание байтов,
составляющих запись, определяется программистом.
Рассмотрим тепрь программу на языке ассемблера как пример
файла. У программы есть имя, и это имя станет именем
соответствующего файла. Файл состоит из записей, где каждая запись
представляет собой один оператор языка ассемблера. Формат любой
записи ни о чем не говорит DOS, он понятен только ассемблеру.
Отдельные части одной записи соответствуют полям оператора языка
ассемблера. Для DOS не важно, как записи разбиваются на поля, это
дело прикладной программы, в данном случае - ассемблера.
Файлы com и exe
Файлы .com и .exe
В предыдущем примере рассматривался файл типа .COM. Однако
результатом процесса ассемблирования-редактирования связей является
обычно файл типа .EXE. Зачем нужен файл типа .COM, если проще
получить файл типа .COM?
У каждого из обоих типов файлов есть свои преимущества. И чтобы
принять обоснованное решение о том, какой из них предпочесть в
каждом конкретном случае, нужно представлять их отличия.
Главное различие между файлами типа .COM и типа .EXE связано с
форматом записи соответствующего объектного файла на дискете. Оба
типа файлов являются программами, записанными на машинном языке.
Программа, записанная в файле типа .COM может сразу выполняться.
DOS может непосредственно загрузить его в память машины с дискеты.
После этого DOS передает управление в сегмент памяти, отведенный
для команд, в точку со смещением 100H. Файл типа .EXE
непосредственно выполнен быть не может. У соответствующего
объектного файла, хранящегося на дискете, имеется заголовок. В нем
содержится информация, сгенерированная редактором связей. Наиболее
важная ее часть относится к информации, связанной с перемещением. В
то время, как у файла типа .COM перемещаем один сегмент команд, у
файла типа .EXE могут быть перемещены многие различные сегменты.
Это ограничивает максимальный размер файла .COM 64 кбайтами, если
только программа не подгружает еще и другие сегменты. Файл типа
.EXE может содержать ряд сегментов, которые динамически
перемещаются в пределах программной области.
В чем состоит перемещаемость? Во время ассемблирования
программа расположена в каком-то определенном месте памяти. Как
было ранее установлено, ассемблер автоматически начинает каждый
сегмент со смещением 0. В ассемблерных листингах рядом с некоторыми
адресами стоят символы R. Это означает, что данный адрес является
перемещаемым. Если программа сдвигается так, что ее начало будет
иметь смещение, отличное от 0, то упомянутый адрес должен быть
изменен. Обычно перемещением занимается редактор связей. Однако
пересчет значений некоторых адресов не может быть выполнен до
загрузки программы. В каждом файле типа .EXE имеется информация о
таких адресах.
Файл типа .COM не является перемещаемым. У такого файла
отсутствует информация, необходимая для перемещения. Вместо этого у
программы, составляющей файл типа .COM, должен быть перемещаем
сегмент команд. Это означает, что хотя сам сегмент команд можно
модифицировать, начальное смещение всегда должно быть одним и тем
же. В такой программе все смещения должны оставаться неизмененными.
Кроме того, от программиста требуется предусмотреть, чтобы при
любой операции с сегментными регистрами (например, запись в
сегментный регистр полученного значения) всегда производилось
обращение к регистру текущего сегмента команд. Примером правильной
программной последовательностью для записи в регистр DS текущего
значения сегмента команд будет:
PUSH CS
POP DS
Иногда может показаться заманчивым реализовать ту же операцию с
помощью следующей последовательности команд (предположим, что как и
в приведенной на Фиг. 5.6 программе имя сегмента команд - "CODE")
MOV AX,CODE
MOV DS,AX
Для программного файла типа .COM эта запись будет неправильной.
В момент ассемблирования и редактирования связей сегментное
значение для сегмента CODE неизвестно. Оно определяется только при
загрузке программы. Поскольку файл типа .COM не может предоставить
загрузчику перечня всех сегментных ссылок (информация для
перемещения), то в данном случае программа будет выполняться
неправильно.
Между описываемыми типами файлов имеются различия в отношении
установки значений сегметных регистров и расположения стека. Для
файла типа .COM значения регистров CS, DS, ES и SS устанавливаются
DOS, равными такими, что они указывают на тот сегмент, в который
они загружают программу. Значение регистра SP устанавливается так,
чтобы он указывал на последнюю доступную в сегменте ячейку памяти.
Таким образом программа занимает начало, а стек - конец сегмента.
В головной метке файла типа .EXE задаются значения регистров
CS, IP, SS и SP. Значения регистров DS и ES DOS устанавливает таким
образом, чтобы они указывали на тот сегмент, в который загружается
программа. Регистр CS указывает на сегмент, который был
идентифицирован как сегмент содержащий стартовый адрес программы.
Если в файле типа .COM программа должна иметь начальное смещение в
сегменте команд равным 100H, то в программном файле типа .EXE
начальный адрес может иметь другое значение. Как показано ниже,
значение этого адреса может содержаться в операторе END:
END START_LOCATION
Это является указанием ассемблеру-редактору связей, что после
загрузки программы управление следует передать на метку
START_LOCATION.
В обоих типах файлов используется программный префикс PSP,
который образуют первые 100H байтов того сегмента, куда загружается
программа. В этой области памяти хранится специальная информация, о
которой говорилось при рассмотрении программы, приведенной на
Фиг. 5.6. В случае файла типа .EXE регистры DS и ES указывают на
эту область данных, тогда как значения регистров CS и SS
устанавливаются на соответствующих этапах
ассемблирования-редактирования связей. В случае файла типа .COM все
регистры указывают на PSP. Это обеспечивает обоим типам файлов
непосредственный доступ к информации, хранящейся в порядке
программного сегмента.
Преимущества файла типа .COM состоит в том, что в этом случае
регистр CS указывает на PSP, а в файле типа .EXE - нет. Прерывания
20H и 27H, связанные с завершением выполнения программы и передачей
управления DOS, требуют, чтобы во время прерывания регистр указывал
на PSP. В случае файла типа .EXE это осуществить сложно. К
счастью, следующая последовательность команд позволяет в
программном файле типа .EXE передать управление обратно DOS.
PROGRAM PROG FAR
PUSH DS ; запись сегмента с PSP
MOV AX,0
PUSH AX ; запись в стек смещения 0
...
RET
PROGRAM ENDP
В относящейся к PSP ячейке со смещением 0 содержится команда
INT 20H. Запись в стек состояния регистра DS и нуля устанавливает
значение адреса длинного возврата, равное адресу PSP со смещением
0. Выполнив команду возврата, программа перейдет к команде INT 20H.
Но к этому моменту в регистре CS уже будет храниться значение PSP,
и команда прерывания INT 20H возвратит управление DOS.
Для прерывания 27H, при котором завершается работа программы и
управление передается DOS с сохранением программы в памяти,
аналогичного способа реализации нет. Хотя имеются методы записи в
регистр CS правильного значения перед прерыванием 27H, обычно более
простым является организация программы в виде файла типа .COM.
И наконец, файл типа .COM занимает на диске меньше места, чес
файл типа .EXE с такой же как в файле типа .COM программой. Так как
у файла типа .COM отсутствует заголовок, то и места для него на
диске не требуется. При рассмотрении в следующем разделе программы
DEBUG будет изложен метод, позволяющий преобразовывать файл типа
.EXE в файл типа .COM.
Функции DOS
Функции DOS
Интерпретатор командных строк обеспечивает средства, необходимые
для того, чтобы программа, написанная на языке ассемблера, начала
выполняться. Кроме того, DOS уже во время выполнения программы
обеспечивает доступ к файловой системе с помощью механизма функций
DOS. В данном разделе объясняется, что представляют собой эти
функции и как они могут быть использованы в программе.
Программа использует функции DOS посредством программного
прерывания. Благодаря этому, программа может вызывать
соответствующую служебную программу, не зная ее адреса. Нужное
прерывание задается программистом. А во время инициализации DOS
векторы прерывания для функций системы определяются таким образом,
чтобы они указывали на соответствующие подпрограммы. Следовательно,
по мере получения других версий DOS нет необходимости вносить
изменения в программы. На Фиг.5.3 приводятся векторы прерывания
DOS.
Прерывание Действие
----------------------------------------------
20H Окончание программы
21H Вызов функции (см. Фиг. 5.5)
22H Адрес завершения
23H Адрес CTRL-BREAK
24H Обработчик кртической ошибки
25H Чтение с диска по абсолютному адресу
26H Запись на диск по абсолютному адресу
27H Завершить, но остаться резидентно
----------------------------------------------
Фиг. 5.3 Прерывания DOS
Некоторые прерывания фактически предназначены для
пользовательских подпрограмм. Прерывания 22H, 23H и 24H являются
указателями на подпрограммы, которые могут быть в программе
пользователя. Эти векторы определяют программу, которая должна
выполняться при наступлении соответствующей ситуации. Например, при
нажатии клавиши CTRL-BREAK DOS выполняет прерывание 23H. Обычно
нажатие этой клавиши вызывает останов программы. Обычно DOS
выполняет стандартную обработку соответствующих ситуаций. Если же
программе нужно обрабатывать их иначе, то она может изменить вектор
прерывания.
В качестве примера рассмотрим прерывание 24H - обработку
неустранимой ошибки. Всякий раз обнаруживая ошибку система
инициирует прерывание. Обычно соответствующий вектор указывает на
программу в DOS, которая выводит на дисплей сообщение об ошибке. В
случае ошибки, связанной с диском, DOS выводит сообщение о дисковой
ошибке. Пользователь может по своему усмотрению сделать повторную
попытку выполнения операции, вызвавшей ошибку, либо закончить
выполнение, либо пропустить операцию, вызвавшую ошибку.
Предположим, например, что вы пишите программу форматирования
дискет. Это операция, в ходе которой на поверхности дискеты
происходит физическая разметка дорожек и секторов. Вместе с
форматированием дискеты проверяется отсутствие сбойных участков.
Последняя операция называется верификацией дискеты. Так как при
верификации допускается присутствие на дискете одно-двух сбойных
участков, то вы специальным образом помечаете эти участки, чтобы в
дальнейшем их случайно не использовать. Данная программа
форматирования заменяет обработку неустранимых ошибок. Вам не
нужно, чтобы DOS выводила для пользователя сообщение об ошибке.
Вместо этого вы хотите, чтобы обработка ошибки верификации
происходила в самой программе, и соответствующий участок на диске
помечался как сбойный. Для этого вы заменяете вектор прерывания в
ячейке 0:0090H(24H*4) указателем на свою подпрограмму обработки
ошибок. Когда DOS зафиксирует дисковую ошибку, ваша подпрограмма
может пометить это место как сбойный участок, никак не уведомляя об
этом пользователя.
Прерывания 25H и 26H связывают между собой две части системы. С
точки зрения своей файловой структуры DOS фактически состоит из
двух компонент. Одна из них, IBMBIO.COM, включает программы
непосредственного доступа к аппаратным средствам. Другая,
IBMDOS.COM реализует файловую систему. Упомянутые два прерывания
используются, когда компонента DOS, относящаяся к файловой системе,
обращается к другой компоненте - BIO. Хотя эти прерывания и могут
использоваться программистом, основное их назначение - разделить
две части DOS. Прерывание 25H осуществляет чтение, а прерывание 26H
- запись информации с абсолютной адресацией диска. На этом уровне
обеспечивается доступ к определенным участкам диска, а не записям в
файле.
Прерывания 20H и 27H обеспечивают механизм возврата управления
к DOS после выполнения программы. Прерывание 20H соответствует
нормальному завершению работы программы. Прерывание 27H интересно
тем, что хотя оно связано с завершением программы, занимаемая
программой область памяти не возвращается обратно в распоряжение
DOS. Все содержимое данной области сохраняется неизменным до тех
пор, пока не будет выключено питание или выполнена переустановка
системы в начальное состояние. Это удобно в тех случаях, когда
нужно ввести специальную обработку прерываний, либо аналогичную
функцию, которая должна сохраниться как часть системного
программного обеспечения. В гл.10 будет приведен пример
использования прерывания INT 27H для расширения системы. В DOS
имеется особенность работы с прерываниями 20H и 27H. Их
Значение
в AH Функция
----------------------------------------------------------
0 Завершение программы
1 Ввод с клавиатуры
2 Вывод на экран
3 Дополнительный ввод (асинхронная коммуникация)
4 дополнительный вывод
5 Вывод на принтер
6 Прямой ввод/вывод с консоли
7 Прямой ввод с консоли без эха
8 Ввод с консоли без эха
9 вывод строки
OAH Буферизованный ввод с клавиатуры
0BH Проверка состояния клавиатуры
0CH Очистка буфера клавиатуры и функция # в AL
0DH Переустановка диска
0EH Выбор диска
0FH Открытие файла
10H Закрытие файла
11H Поиск первого
12H Поиск следующего
13H Уничтожение файла
14H Последовательное чтение
15H Последовательная запись
16H Создание файйла
17H Переименование файла
19H Текущий диск
1AH Установка адреса обмена с диском
1BH Адрес таблицы размещения
21H Прямое чтение
22H Прямая запись
23H Размер файла
24H Установка записи для прямого обращения
25H Установка вектора прерывания
26H Создание нового програмного сегмента
27H Прямое чтение блока
28H Прямая запись блока
29H Анализ имени файла
2AH Чтение даты
2BH Установка даты
2CH Получение времени
2DH Установка времени
-------------------------------------------------------
Фиг. 5.4 Функции прерывания DOS 21H
использование предпочтительней в файле типа .COM, так как файл типа
.EXE имеет существенно другой формат и использование в нем этих
функций DOS немного сложнее. В следующем разделе будут рассмотрены
различия между файлами типа .COM и типа .EXE и то, почему
упомянутые прерывания, связанные с завершением работы программы,
выполняются в них по-разному.
Прерывание 21H является прерыванием, через которое происходи
обращение к основным функциям DOS. Это прерывание обеспечивает
доступ к системе ввода-вывода, управляемой DOS. На Фиг.5.4
представлены все возможные функции, использующие это прерывание.
Выбор функции в программе осуществляется с помощью записи в регистр
AH нужного значения перед выполнением прерывания 21H.
Параметры этих функций приводятся в приложении D руководства по
DOS. Вместо подробного ознакомления мы разберем пример, в котором
использованы некоторые из них. В частности, этот пример включает
функции DOS, связанные с диском.
Имена файлов
Имена файлов
Рассмотрим сначала, как система DOS формирует имена файлов. Каждое
сформированное DOS имя файла состоит из двух частей. Первая часть
имени файла имеет длину от 1 до 8 символов. Эта часть определяется
пользователем и соответствует присвоенному им "имени" файла.
Вторая часть имени, называемая расширением, имеет длину от 1 до 3
символов. Эта часть, определяющая "тип" файла, обычно задается
прикладной программой. Имя и его расширение разделяются точкой.
Например, в имени файла "COMMAND.COM" COMMAND является именем, а
COM - расширением.
В некоторых случаях расширение имени файла определяется самим
пользователем. При этои DOS или прикладная программа используют это
расширение для идентификации типа файла. В имени файла
"COMMAND.COM" .COM определяет файл команд. С ассемблированием
связан один входной файл и от одного до трех выходных файлов.
Расширение входного ассемблерного файла есть .ASM, а выходных
файлов: объектного - .OBJ, листинга - .LST и файла перекрестных
ссылок - .CRF. Во многих случаях в прикладных программах требуется,
чтобы у имени файлов были определенные расширения.
ДИРЕКТОРИИ
Так как на любой дискете может поместиться большое количество
информации, то использовать всю дискету для записи только одного
файла было бы неэкономно. Операционная система позволяет хранить
на фиксированном диске или дискете одновременно несколько файлов.
На магнитком носителе может храниться более одного файла
благодаря тому, что система организует каталог содержимого диска.
Этот каталог напоминает оглавление книги. В нем перечислены все
файлы, имеющиеся на диске или дискете. Кроме имени файла DOS
помещает в каталоге и другую необходимую и полезную информацию.
Прежде всего в нем имеются указатели, которые нужны для определения
фактического местонахождения данных на магнитном носителе. Кроме
этого, каталог удобен тем, что имеет временные ярлыки для каждого
файла. Когда какая-нибудь программа создает либо обновляет файл,
DOS производит запись соответствующей даты и времени. Это очень
удобно в тех случаях, когда существует множество копий некоторой
информации и вам требуется самая последняя версия.
Организация каталога на диске решает задачу хранения нескольких
файлов на одном диске или дискете. Однако в каждый момент времени
DOS может обращаться только к одному дисководу. Если в системе
имеется больше, чем один дисковод с гибким или жестким диском, то
нужно сообщить DOS, на каком из них расположен файл. В этом случае
наименование дисковода указывается в качестве префикса имени файла.
Например, у файла COMMAND.COM в дисководе A уточненное полное имя
будет A:COMMAND.COM.
Карта связей
Карта связей
На Фиг. 5.16 приведена полученная в результате редактирования карта
связей. Так как рассматриваемый пример прост, то и карта не очень
содержательна. Каждому сегменту в исполняемом файле соответствует
отдельная строка карты. В строке последовательно указаны значения
начального и конечного адресов каждого сегмента в том виде, в каком
они будут загружены в память. Заметьте, что размер сегмента CODE
равен 3CH. Это является суммарным размером сегментов CODE в двух
программных модулях. Другой сегмент в нашем примере - это сегмент
STACK размером 80H байт.
По поводу адресов, приведенных в карте связи, нужно сделать
следующие замечания. Во-первых, все они являются 20- битовыми
адресами и отсчитываются от ячейки 0. Так как загрузка программы
будет осуществляться DOS, то занрузчик изменит значения этих
адресов. Однако относительно друг друга их значения останутся
такими же. В отношении сегментов следует еще отметить, что в памяти
они не располагаются последовательно. Хотя длина сегмента CODE
равняется только 3CH, сегмент STACK начинается с адреса 40H.
Сегменты должны располагаться на границах параграфов для того,
чтобы адреса смещения сохраняли правильные значения. Благодаря
привязке к границам параграфов, сегментные регистры указывают точно
на первую ячейку сегмента. Следовательно, в нашем случае редактор
связей расположил сегмент STACK на первой границе параграфа сразу
же после конечного адреса сегмента CODE. С учетом того, что конец
сегмента CODE равен 3BH, следующий адрес ячейки, который делится на
16, будет равен 40H.
----------------------------------------------------
Start Stop Length Name Class
00000H 0007FH 00080H STACK
00080H 000CEH 0004FH CODE
Program entry point at 0008:0000
----------------------------------------------------
Фиг. 5_16 Карта связей для Фиг. 5.13 и 5.14
Возможно вы заметили, что общая длина сегментов CODE в
действительности не равна 3CH. В отсутствие других спецификаций
редактор связей поместил начало каждой части сегмента CODE на
границах параграфов. Длина первого модуля FIG5=13 равняется 2BH.
Следующий модуль, FIG5=14, редактор связей поместил на следующей
границе параграфа, в данном случае по адресу 30H. Так как длина
второго модуля равна 0CH, общий размер сегмента CODE будет равен
03CH. Выравнивание по границам параграфов выполняется по умолчанию
совместно с ассемблированием и редактированием связей. Оператор
языка ассемблера SEGMENT может, если нужно, изменить это
выравнивание на побайтовое (BYTE), либо пословно (WORD). При
выравнивании типа BYTE программные программные упаковываются в один
сегмент. В смысле экономии памяти это наиболее эффективный способ
объединения модулей. Однако выравнивание по границе параграфа
гарантирует, что во время выполнения программы не возникает никаких
трудностей с адресацией сегментов. Если в программе производятся
вычисления адресов и выравнивание производилось не по границам
параграфов, то возможно возникновение ошибок.
В конце карты связей указывается входная точка исполняемого
файла. Этот адрес, как и другие, вычисляется по отношению к началу
исполняемого модуля и перераспределяется загрузчиком. Имеется
несколько способов указания стартового адреса для программы типа
.EXE. При одном из способов программа выполняется, начиная с
первого байта программного модуля. При этом вы должны следить за
тем, чтобы в первом байте первого сегмента рабочего файла
содержалась команда, с которой вы хотите начать выполнение. Более
предпочтительным является способ задания входной точки в операторе
END головной программы. На Фиг. 5.13 последним в программе является
оператор
END START
где START - это метка первой выполняемой команды. Так как
модули связаны друг с другом в определенном порядке, то эта же
команда будет первой и в программе. Однако если модули связать в
обратном порядке, то входная точка будет располодена правильным
образом. В этом можно убедиться на практике.
При любой операции связывания должен быть только один оператор
END с указанием стартового адреса. Обратите внимание, что в
подпрограмме FIG5=14 в операторе END входная точка не указана. Если
имеется более одной входной точки, то редактор связей обычно
выбирает ту, которая указана последней. Лучше непосредственно
задавать входную точку, чем допускать вероятность того, что
редактор связей выберет не ту входную точку. Имейте в виду, что
этот способ задания входной точки программы пригоден только в
случае файла типа .EXE. Программа типа .COM всегда выполняется,
начиная со смещения 100H сегмента команд.
Командный процессор
Командный процессор
Наряду с файловой системой DOS обеспечивает операционную среду для
прикладных программ. Первый компонент DOS с которым сталкивается
пользователь, - это командный процессор. Эта часть DOS берет на
себя обработку вводимых пользователем команд и запуск прикладных
программ.
В первый момент после включения питания "интеллектуаль- ность"
IBM PC невысока: возможно и имеется большой потенциал, но
способностей хоть как-то реализовать его немного. В постоянной
памяти ЭВМ хранятся программы тестирования компонентов системы -
POST (Power=On Self=Test - самопроверка при включении) и установки
в начальное состояние устройств ввода-вывода. Остальная часть
записанной в ПЗУ базовой системы ввода-вывода BIOS (Basic Input
Output System) предоставляет программисту, работающему с языком
ассемблера, набор средств, которые помогают ему обращаться к
аппаратным средствам, не беспокоясь о том, как технически они
реализованы. Но этого не достаточно, чтобы обеспечить среду для
выполнения серьезных прикладных программ.
Последнее является функцией DOS. После установки ЭВМ в
начальное состояние программа POST выполныет загрузку DOS с диска
или дискеты в оперативную память. Этот процесс называется загрузкой
системы. Первое, что делает DOS, - загружает минимальную программу,
необходимую для загрузки остальной части DOS. По окончании процесса
загрузки на дисплей выводтися название операционной системы и
указание на авторские права. Помимо всего прочего в названии указан
номер соответствующей версии операционной системы. Иногда этот
номер играет важную роль, так как каждая новая версия означает
дополнительные функциональные возможности операционной системы.
После операции загрузки система готова к приему команд от
оператора (за исключением специального случая, о котором будет
сказано ниже). На этом этапе управление передается командному
процессору - DOS произвела загрузку в память командного процессора,
файловой системы и других служебных программ, и все они готовы
выполнять свои функции. Передача управления командному процессору
индицируется им следующим запросом к оператору
A>
Этот запрос содержит двоякий смысл. Символ ">" означает
готовность командного процессора к приему команды. Префикс "A"
указывает на выбранный по умолчанию дисковод с гибким диском. Так
как DOS может обрабатывать файлы, расположенные только на каком-то
одном диске, то в случае одновременной работы с несколькими дисками
или дискетами пользователь должен указать, к какому из дисководов
следует обратиться системе. В персональной ЭВМ дисководы
обозначаются буквами латинского алфавита. У вычислительной системы
с двумя накопителями на гибких дисках имеется дисковод A: и
дисковод B:. Жесткий диск обычно обозначается C: (После
наименования дисковода следует двоеточие.) Обычно файлы,
используемые системой DOS, считываются с дисковода, выбранного
системой по умолчанию, если только оператор не изменит этот
порядок. Для того чтобы считать файл с выбранного по умолчанию
дисковода, системе требуется только имя этого файла. Для считывания
файла с любого другого дисковода нужно кроме имени файла указать
DOS наименование дисковода.
Только в ответ на команды, которые поступают от пользователя
DOS выполняет какие-то действия. Все команды, относящиеся к DOS,
вводятся в ответ на запрос с ее стороны, индицируемый символом ">".
Пользователь вводит имя нужной ему команды, после чего поступившая
заявка обрабатывается командным процессором. Как он обрабатывает
эту заявку, зависит от команды, которую ввел пользователь. Имеются
встроенные, или резидентные команды, которые всегда доступны. Либо
команда может инициировать нерезидентное, или транзитное
выполнение. Для выполнения таких команд должен существовать
определенный файл на диске.
Встроенные команды обеспечивают поддержку файловой системы. Они
сделаны резидентными в DOS, поскольку часто используются при работе
с данными, хранящимися на дисках. После того, как пользователь ввел
команду, интерпретатор командных строк передает управление
соответствующей программе DOS. Программа реализует свою функцию,
соответствующую спецификации введенной команды, затем возвращает
управление DOS. На Фиг.5.1 приведены резидентные команды дисковой
операционной системы.
Команда Действие
--------------------------------------------------------
COPY Копирует файл с одного места в другое
DATE Вывод/модификация текущей даты
DIR Вывод каталога дискеты
ERASE Удаление файла с дискеты
PAUSE Система ждет нажатия клавиши
REM Вывод комментария
RENAME Изменить имя файла
TIME Вывод/модификация текущего времени
TYPE Вывод содержимого файла
--------------------------------------------------------
Фиг. 5.1 Резидентные команды DOS
Примером резидентной команды может служить команда DIR, которая
выводит на дисплей справочник дискеты. На Фиг.5.2 показаны выходные
данные, полученные в результате выполнения команды чтения каталога.
Обратите внимание, что в справочнике дискеты для каждого
записанного на ней файла уазываются его имя и тип, а также длина в
байтах, дата и время его создания. Так как для загрузки любой
программы DOS необходимо выполнить команду чтения каталога, то эта
команда являеися встроенной.
----------------------------------------
A>dir
Volume in drive A has no label
Directory of A:\
COMMAND COM 37637 17/06/88 12:00
AUTOEXEC BAT 38 4/03/89 17:33
CONFIG SYS 96 17/06/88 12:00
COUNTRY SYS 12838 17/06/88 12:00
DISKCOPY COM 10428 17/06/88 12:00
DISPLAY SYS 15741 17/06/88 12:00
FDISK COM 70151 17/06/88 12:00
FORMAT COM 22923 17/06/88 12:00
KEYB COM 14759 17/06/88 12:00
KEYBOARD SYS 23360 3/08/88 12:00
REPLACE EXE 17199 17/06/88 12:00
SELECT COM 3674 3/08/88 12:00
SELECT HLP 27562 3/08/88 12:00
SELECT PRT 1594 3/08/88 12:00
SYS COM 11472 17/06/88 12:00
012345 678 109 17/06/88 12:00
16 File(s) 17408 bytes free
------------------------------------------
Фиг. 5.2 Каталог дискеты
Если пользователь введет нерезидентную команду, то командный
процессор попытается загрузить ее с диска или дискеты. В этом
случае он выступает в роли загрузчика программы. Предполагая, что
имя файла совпадает с именем команды, интерпретатор просматривает
справочник в поисках такого файла, а когда находит, загружает его в
память. Затем интерпретатор передает управление загруженной
программе, которая теперь может реализовывать свои функции.
Но не каждый файл может быть загружен с помощью командного
процессора. Тип файла должен быть либо .COM, либо .EXE, что
соответствует либо файлу команды, либо выполняемому файлу. Конечным
продуктом выполнения операций ассемблирования и редактирования
связей является файл типа .EXE. Отсюда вытекает возможность
написания собственной системной команды. Если написали,
странслировали и скомпоновали программу на языке ассемблера, а
затем оставили эту программу на дискете, то ее можно загружать и
выполнять так же, как и любую другую программу DOS. Именно это
позволяет писать программы, которые будут выполняться под
управлением DOS.
Существуют различия между файлами типа .COM и типа .EXE. Они
имеют различные структуры и управление им передается по- разному.
Хотя обычно после этапа редактирования связей получаются файлы типа
.EXE, существуют некоторые причины для использования и файлов типа
.COM. В следующем разделе будут обсуждаться различия между типами
файлов и тем, как преобразовывать файл типа .EXE в файл типа .COM.
Рассмотрим теперь пример вызова программы. Хорошей иллюстрацией
здесь может служить ассемблер. Чтобы вызвать ассемблер, нужно
ввести команду
A>ASM
Каталог дискеты содержит файл с именем ASM.EXE. Это и есть
ассемблер. После ввода команды ASM, он просматривает дискету в
дисководе A: (выбранном по умолчанию). Найдя файл с именем ASM.EXE,
командный процессор загружает и передает управление ассемблеру.
Теперь вычислительная система находится под управлением ассемблера.
При благополучном завершении трансляции ассемблер вернет управление
командному процессору. Заметьте, что файл, содержащий ассемблер -
это файл типа .EXE, поэтому он может быть загружен командным
процессором.
Если ассемблер находится на дискете, установленной в дисководе
A:, то пользователь может обратиться к другому дисководу следующим
образом:
A>B:ASM
Префикс B: указывает DOS, что файл находится на дискете в
дисководе B:. Файл полностью определяется не только указанием его
имени, но и дисковода, с которого он будет считан. Одного имени
файла достаточно только при считывании файла с дисковода,
выбранного по умолчанию. Для того чтобы оттранслировать файл,
расположенный на носителе в дисководе B: с помощью ассемблера,
который находится на дискете в дисководе A:, нужно ввести следующую
команду:
A>ASM B:FILE.ASM
В этой команде одновременно задаются программы ASM, считываемые
с дисковода, который выбран по умолчанию, и исходный файл FILE.ASM,
считываемый с дисковода B:.
Это можно сделать и другим способом: задать команду B:, которая
предписывает DOS сделать дисководом, выбираемым по умолчанию,
дисковод B:.
A>B:
B>A:ASM FILE.ASM
Заметьте, что после этого запрос со стороны системы меняется на
B>. Приведенная в этом примере команда по своему действию полностью
идентична команде из предыдущего примера.
Кроме того, интерпретатор командных строк может работать с
файлом, называемом файлом с пакетом команд, с расширением имени
.BAT. Этот тип файла совершенно отличен лт файлов типа .COM и типа
.EXE. Файл типа .BAT не содержит выполняемого машинного кода, а
состоит из нетранслированных команд, которые интерпретируются
командным процессором. Все содержащиеся в этом файле команды
выполняются DOS по очереди. Можно считать, что файл типа .BAT
заменяет процедуру ввода команд с клавиатуры, так как они
содержатся непосредственно в соответствующем файле. После того,
как система закончила обработку пакетного файла, она обращается за
следующей командой к клавиатуре. Все эти особенности делают файл
типа .BAT удобным средством выполнения повторяющихся заданий. После
того, как такой файл уже создан, единственная команда обращения к
нему заменяет ввод всех содержащихся в нем команд.
В системе допускается специальный файл с именем AUTOEXEC.BAT.
Если такой файл имеется на диске, то сразу же после своей загрузки
DOS обращается к нему, передавая управление командам, составляющим
пакет команд этого файла. Это позволяет автоматически загружать с
диска нужную пользователю программу. Предположим, что вы написали
прикладную программу, использующую DOS. (При этом говорят, что
программа была написана с "привязкой к DOS".) В случае создания
файла AUTOEXEC.BAT, инициирующего выполнение прикладной программы,
оператору, работающему с этой прикладной программой, совершенно не
нужно будет знать, как работает интерпретатор командных строк. Он
знает, что управление осуществляет программа и это главное.
Многомодульность
Многомодульность
Как указывает само имя программы LINK, ее основное назначение
"связать", или объединить, несколько объектных модулей в один
выполняемый модуль. Все рассмотренные до сих пор примеры
относились к одномодульным программам, т.е. все, что они должны
были выполнять, реализовывалось в одном исходном модуле. Однако
этот путь не всегда возможен или желателен.
Имеется ряд причин, в силу которых программа программа сожет
быть разбита на несколько модулей. Во-первых, размер программы.
Редактирование очень большой программы становится весьма громоздким
и трудным делом, а ее ассемблирование занимает много времени.
Предположим, что вы допустили ошибку в единственной строке
ассемблерной программы, состоящей из 5000 строк. Чтобы изменить
одну эту строку, необходимо выполнить редактирование всей
программы. После этого нужно оттранслировать всю программу из 5000
строк, что занимает довольно много времени. После относительно
быстрого этапа редактирования связей программа готова к выполнению.
Предположим, теперь, что вместо того, чтобы иметь дело с
программой, состоящей из 5000 строк, вы разбиваете ее на десять
программых модулей, по 500 строк в каждом. Для внесения изменений в
единственную строку вам необходимо выполнить редактирование только
исходного файла из 500 строк. Ассемблирование программы из 500
строк занимает значительно меньше времени, чем программы из 5000
строк. Этап редактирования связей все так же будет занимать
относительно немного времени, особенно по сравнению с
ассемблированием большой программы. Уменьшение размеров отдельных
модулей позволяет более быстро осуществить процесс
редактирования-ассемблирования.
Другая причина разбиения программы на меньшие модули связана с
этапом ее разработки. В случае больших программных средств в их
разработке, как правило, участвуют несколько человек. Если имеется
один исходный файл, то отдельные программисты вынуждены работать с
ним по очереди. Такой подход очень быстро становится неудобным.
И последняя из причин создания модульных программ заключается в
возможности их универсального использования. Предположим, что
созданная вами программа включает несколько подпрограмм. Если вы
хорошо справились с этим, то каждая из подпрограмм выполняет у вас
особую функцию с хорошо документированной спецификацией входа и
выхода. Через некоторое время при написании новой программы вы
захотите использовать в ней ту же самую подпрограмму. Если эта
подпрограмма оформлена в виде отдельного программного модуля, то
включить ее в новую программу не составляет труда. В противном
случае вам придется как-то выделить этот фрагмент из исходной
программы и перенести его в новую программу. После одной-двух
попыток вам, возможно, захочется поискать другой способ.
Разбиение программы на модули требует от программиста ряда
действий. Во-первых, нужно тщательно продумать, как ваша программа
будет строиться из меньших компонентов. Во-вторых, нужно определить
как входные, так и выходные параметры этих меньших программ. И
наконец, должна быть возможность обмена данными между программными
модулями. Первые два пункта относятся к основам программирования и
мы их здесь касаться не будем. Последний же пункт связан с
ассемблированием и редактором связей, поэтому его мы рассмотрим.
Если разработанная вами программа состоит из нескольких
модулей, то в этом случае у ведущей, или основной программы должна
быть возможность вызывать эти подпрограммы. Это реализуется
командой CALL, имеющей один операнд - метку соответствующей
подпрограммы. Во всех рассмотренных до сих пор примерах
подпрограмма была частью того же самого программного модуля, так
что ассемблеру было точно известно, каким будет адрес подпрограммы
в момент ее выполнения. Это позволяло ассемблеру определять
правильное значение смещения для поля адреса в команде.
Теперь мы сталкиваемся с ситуацией, когда ассемблирование
подпрограммы осуществляется отдельно от ассемблирования команды
CALL. Это означает, что ассемблер не может определить правильный
адрес для процедуры вызова, т.е. из-за того, что ассемблирование
подпрограммы и основной программы осуществляется раздельно, у
ассемблера нет способа предсказать правильное значение адреса.
Однако эту задачу может выполнить программа LINK. Определение
адресов производится на этапе редактирования связей. Так как при
работе программы LINK ее входом являются все программные модули, то
редактору связей известно, где будет конец каждой из подпрограмм.
После этого редактор связей может определить значения тех адресов,
которые были известны ассемблеру.
Операция редактирования связей
Операция редактирования связей
Рассмотрим теперь операции, с помощью которых описанные выше
программные модули были объединены в один исполняемый модуль.
Ассемблирование программ выполняется с помощью команд, описанных в
предыдущем разделе:
B:>A:MAS FIG5=13,,,;
B:>A:MASM FIG5=14,,,;
При этом получается два объектных модуля FIG5=13.OBJ и
FIG5=14.OBJ. Для объединения этих модулей вызывается программа
LINK. На Фиг. 5.15 приведены операции, с которых начинается работа
программы LINK.
В данном примере предполагается, что дискета с DOS установлена
в дисководе A:, рабочая дискета - в дисководе B:, и дисковод B:
выбирается по умолчанию. После запуска программа LINK запрашивает
пользователя, для каких объектных файлов следует выполнить
редактирование связей. Имена файлов вводятся без указания типа
.OBJ. Если связываемых модулей больше одного, то их имена вводятся
через разделитель "+". В данном примере выполняется редактирование
связей для модулей FIG5=13 и FIG5=14.
-------------------------------------------
A>LINK
IBM Personal Computer Linker
Version 1.10 (c)Copyright IBM Corp 1982
Object Modules [.OBJ]: B:FIG5_13+B:FIG5_14
RunFile [A:FIG5_13.EXE]: B:
List File [NUL.MAP]: B:
Libraries {.LIB]:
A>
-------------------------------------------
Фиг. 5.15 Выполнение редактирования связей
Модули связываются в том же порядке, в каком их имена
передаются программе LINK. В данном случае программа FIG5=13
предшествует программе FIG5=14. Перечисление модулей в обратном
порядке привело бы к такому же обратному порядку их расположения в
итоговом модуле. Как правило, порядок формирования программы
безразличен. Единственное исключение делается для входной точки
программы.
Следующий запрос редактора связей относится к имени
исполняемого, или рабочего, файла. По умолчанию ему присваивается
имя первого из объектных модулей с расширением .EXE. Вводя другое
имя, можно изменить имя файла, но не его расширение .EXE.
Далее запрашивается имя файла для хранения карты связей.
Допускается любое имя, однако в режиме по умолчанию формирование
карты не производится. В нашем примере ввод символа B: является
указанием редактору связей записать карту связей на дисководе B:.
Редактор связей выбрал для этого файла имя FIG5=13.MAP. Полученный
в результате операции связывания файл FIG5=13.MAP приводится на
Фиг. 5.16 и будет рассмотрен в следующем параграфе.
Последний запрос редактора связей касается библиотек программ,
которые могут быть включены в полученный при связывании модуль. В
случае некоторых языков высокого уровня на этом шаге может
возникнуть необходимость указать имя библиотеки рабочих программ.
Для программ на языке ассемблера такой необходимости нет.
Отладчик DEBUG
Отладчик DEBUG
Программа DEBUG (отладчик) дает средство обнаружения ошибок при
работе с программой, транслированной в машинный язык. Программа
DEBUG обеспечивает возможность пошагово выполнять программу и
следить за тем, что при этом происходит. Программа DEBUG - это еще
одно программное средство, поставляемое как часть DOS. Вы
загружаете ее так же, как и любую другую программу, и работаете в
диалоге, используя клавиатуру и экран. Когда программа DEBUG
ожидает каких-либо действий со стороны пользователя, то свой запрос
она обозначает символом "-".
Вместо перечисления всех команд, которые входят в программу
DEBUG, используем данный отладчик для проверки работы только что
составленной программы, приведенной на Фиг. 5.13 и П5.14. На
Фиг. 5.17 приведен соответствующий листинг.
В данном примере сначала вызывается программа DEBUG и
указывается та программа, которую предполагается отлаживать - в
нашем случае программа FIG5=13.EXE. После того, как программа DEBUG
загружена, она производит загрузку отлаживаемой программы.
Управление теперь принадлежит отладчику, и он с помощью символа "-"
показывает, что ожидает ввода. До тех пор, пока вы не введете для
него указаний, с программой ничего происходить не будет.
Команда R выводит содержимое всех регистров в момент,
соответствующий загрузке программы FIG5=13 и передаче ей управления.
Содержимое регистров не требует пояснений, за исключением, быть
может, значений флагов. Флаг NV указывает на отсутствие
переполнения, флаг UP - флаг направления и т.д. При выводе
содержимого регистров в последней строке приводится следующая
выполняемая команда. В ячейке 04C5:0000 записана команда PUSH DS.
B>A:DEBUG FIG5_13.EXE
-R
AX=0000 BX=0000 CX=0120 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000
DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=0000 NV UP DI PL NZ NA PO NC
2C26:0000 1E PUSH DS
-U
2C26:0000 1E PUSH DS
2C26:0001 B80000 MOV AX,0000
2C26:0004 50 PUSH AX
2C26:0005 FC CLD
2C26:0006 8CC8 MOV AX,CS
2C26:0008 8ED8 MOV DS,AX
2C26:000A 8D361D00 LEA SI,[001D]
2C26:000E AC LODSB
2C26:000F A24000 MOV [0040],AL
2C26:0012 E82C00 CALL 0041
2C26:0015 803E40000A CMP [0040],0A
2C26:001A 75F2 JNZ 000E
2C26:001C CB RET Far
2C26:001D 9D POPF
2C26:001E E2A0 LOOP FFC0
2C26:0020 20AFE0AE AND [BX+AEE0],CH
2C26:0024 A3E0A0 MOV [A0E0],AX
-D2C26:0
2C0E:0000 CD 20 00 A0 00 9A EE FE 1D F0 ED 04 04 1C 3C 01 . ............<.
2C0E:0010 22 1B EB 04 22 1B 04 1C 01 01 01 00 02 06 FF FF "..."...........
2C0E:0020 FF FF FF FF FF FF FF FF FF FF FF FF 08 2C D0 FF .............,..
2C0E:0030 04 1C 14 00 18 00 0E 2C FF FF FF FF 00 00 00 00 .......,........
2C0E:0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
2C0E:0050 CD 21 CB 00 00 00 00 00 00 00 00 00 00 20 20 20 .!...........
2C0E:0060 20 20 20 20 20 20 20 20 00 00 00 00 00 20 20 20 .....
2C0E:0070 20 20 20 20 20 20 20 20 00 00 00 00 00 00 00 00 ........
-RAX
AX 0000
:1234
-E 2C0E:21
2C0E:0021 69. 73. 20. 61.20 20.
-G 3C
AX=0E54 BX=0000 CX=003F DX=0000 SP=FFEA BP=0000 SI=001D DI=0000
DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=003C NV UP DI PL NZ NA PO NC
2C26:003C CD10 INT 10
-T
AX=0E54 BX=0000 CX=003F DX=0000 SP=FFEA BP=0000 SI=001D DI=0000
DS=2C26 ES=2C26 SS=2C26 CS=F000 IP=0165 NV UP DI PL NZ NA PO NC
F000:F065 FB STI
-T
Фиг. 5.17 Листинг отладчика для Фиг 5.13 и 4.14 (начало)
AX=0E54 BX=0000 CX=003F DX=0000 SP=FFEA BP=0000 SI=001D DI=0000
DS=2C26 ES=2C26 SS=2C26 CS=F000 IP=0166 NV UP DI PL NZ NA PE NC
F000:F066 FC CLD
-G 2C26:3E
T
AX=0E54 BX=0000 CX=003F DX=0000 SP=FFEA BP=0000 SI=001D DI=0000
DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=013E NV UP DI PL NZ NA PO NC
2C26:003E RET
-G
Эта программа - тест
Program terminated normally
-R
AX=0754 BX=0000 CX=003F DX=0000 SP=FFEA BP=0000 SI=001D DI=0000
DS=2C26 ES=2C26 SS=2C26 CS=2C26 IP=003E NV UP DI PL NZ NA PO NC
2C26:003E C3 RET
-Q
B>
Фиг. 5.17 Листинг отладчика для Фиг 5.13 и 4.14 (продолжение)
Здесь следует немного задержаться и проанализировать
информацию, которая записывается в регистры. Содержимое регистров
соответствует моменту, когда программа FIG5=13 получает управление
от командного процессора. Обратите внимание, что пара регистров
CS:IP указывает на первую команду, определяемую оператором END
программы. Регистры DS и ES указывают на префикс программного
сегмента. И наконец, пара регистров SS:SP указывает на сегмент
STACK. Описанное состояние регистров будет сравниваться позднее с
аналогичным состоянием регистров для файла типа .COM.
Чтобы просмотреть большее число команд, надо ввести символ "U"
(дизассемблировать), и на дисплей выводится около двадцати
следующих команд. Это удобно при обладке программы, для которой нет
листинга. Дизассемблирование программы позволяет просмотреть ее
команды. Это может сэкономить вам бумагу и время в случае, когда в
программу внесены небольшие изменения. Так как ваш листинг больше
не соответствует той программе, которая находится в памяти, то ее
дизассемблирование позволяет вам узнать правильные адреса для
каждой команды.
Однако у дизассемблирования с помощью программы DEBUG имеется
ряд недостатков по сравнению с использованием листинга. Отсутствуют
комментарии (может быть очень важные для понимания программы), и
ячейки памяти идентифицируются только по адресу, а не по имени
переменной. Например, хранящаяся в ячейке 04C5:000E команда имеет,
как показано на Фиг.5.13, следующий вид:
MOV OUTPUT_CHARACTER,AL
а в дизассемблированном виде
MOV [0030],AL
Это одна и та же команда. Для программиста, выполняющего
отладку, имя переменной OUTPUT_CHARACTER говорит больше, чем адрес
ячейки [0030]. Однако программе DEBUG не известны имена переменных,
и она вынуждена оперировать фактическими адресами.
Кроме того, программа, DEBUG не обеспечивает той же самой
ассемблерной мнемоники, которую воспринимает ассемблер. Это значит,
что некоторые команды будут выглядеть по-разному. Команда из ячейки
04C5:0014 будет при дмзассемблировании иметь вид
CMP B,[3000],0A
но та же саммая команда на Фиг.5.13 представлена в виде:
CMP OUTPUT_CHARACTER,10
Программа дизассемблирования как на входе, так и на выходе
работает только с шестнадцатеричными значениями. Этим объясняется
появление значения 0A. Мы уже выяснили, почему получается значение
[0030], вместо имени OUTPUT_CHARACTER. Что же такое символ "B,"?
Ассемблер оперирует переменными вполне определенного типа. Это
значит, что во время ассемблирования тип переменных может иметь
значение: байт, слово или какой-нибудь другой. Таким образом, когда
программист вводит команду, содержащую ссылку на область памяти, то
ассемблеру известен размер этой области. На программа DEBUG не
имеет представления о длине переменной, записанной по адресу
[0030]. Однако программе дизассемблирования точно известно, что
данная команда пересылает ровно один байт данных, указанных
непосредственно в команде, по адресу [0030]. Таким образом, символ
"B," указывает на то, что непосредственная операция состоит в
пересылке одного байта. Для получения того же самого результата с
помощью ассемблера соответствующая команда должна иметь вид:
CMP BYTE PTR [0030],10
Вы можете рассматривать символ "B," как сокращение BYTE PTR.
Аналогично символ W используется для WORD PTR,L - для длинного
(far) возврата и т.д.
Вместе с командой в дизассемблированном виде вводится и ее
объектный код. Как вы можете заметить, по адресу 04C5:001C записаны
какие-то команды, которых нет на Фиг. 5.13. Эта область данных,
содержащая строку "Это тест". Однако команде, осуществляющей
дизассемблирование, не известно, где в программе кончаются команды
и начинаются данные. Таким образом она все интерпретирует как
команды. (Кстати, именно эта последовательность команд выполнялась
бы, если в вашей программе был сделан переход в рассматриваемую
область данных.)
Команда вывода на экран D позволяет просмотреть на дисплее
области данных. Отображение на экране состоит из двух частей.
Вместе с листингом содержимого ячеек памяти в шестнадцатеричном
представлении приводятся символы в коде ASCII, которые
соответствуют этим значениям. Если отображение команд подобным
образом не имеет смысла, то область данных представляется очень
ясно. Когда вам будет нечем особенно заняться, вы можете
попробовать написать такую программу, команды которой в коде ASCII
соответствуют инициалам вашего имени. С помощью отладчика можно
изменять содержимое регистров и ячеек памяти. Если ввести символ R
(регистр) и затем тип регистра, то на дисплей будет выведено
содержимое этого регистра с возможностью его коррекции. Если нажать
клавишу "возврат", то содержимое регистра останется прежним. Оно
будет изменено, если ввести новое значение.
Можно также модифицировать содержимое ячеек памяти. Ввод
символа E (редактирование) позволяет это сделать. При этом
программа DEBUG выводит на дисплей значения отдельных ячеек памяти,
после которых следует символ . Вы можете изменить содержимое
ячейки, вводя новое значение, либо нажать клавишу пробела, чтобы
перейти к следующей ячейке, или - клавишу "возврат", чтобы
вернуться к режиму запроса следующей команды отладчика. В
рассматриваемом примере значения в первых трех ячейках остаются
прежними. Содержимое ячейки 04C5:0024 изменено со значения 61H на
значенее 20H. Так как эта ячейка входит в область данных, то
выводимое сообщение будет отличаться от тог, которое было в
транслированной программе.
В любой команде, обращающейся к ячейкам памяти, предполагается,
что адрес является частью команды. Команда E, как и команда
отображения, выводит на дисплей содержимое ячейки по указанному в
ней адресу. Точно так же можно было использовать адрес в команде
дизассемблирования. Можно ввести адрес в виде сегмента и смещения,
или только смещения. Если вы указали только смещение, то
соответствующий сегментный регистр будет выбран программой DEBUG. В
случае команды U используется регистр CS, а для команд D и E по
умолчанию сегмент будет определяться регистром DS.
Теперь попытаемся выполнить эту программу. Ее можно просто
запустить и посмотреть, что будет происходить. На для этого не
нужна программа DEBUG. Программа DEBUG позволяет задать точки
останова, называемые "точками прерывания" программы. Благодаря
введению в программу таких точек можно возвращать управление
программе DEBUG. Это дает еще одну возможность проверки состояния
памяти и регистров для того, чтобы контролировать ход выполнения
программы.
Команда G, (выполнять) передает управление от программы DEBUG
пользовательской программе. Выполнение команд начинается с ячейки,
задаваемой парой регистров CS:IP (так же, как в реальном
микропроцессоре). Тестируемая программа продолжает выполняться до
тех пор, пока она не пройдет точку прерывания. В нашем примере мы
задали точку прерывания по адресу 3CH. Так как мы указали только
смещение, то для определения сегмента программа DEBUG использует
содержимое регистра CS. Из листинга, приведенного на Фиг. 5.14,
видно, что смещение 3CH соответствует команде INT 10H. В
рассматриваемом примере программы было выбрано именно это место,
потому что это - та точка, где управление передается подпрограмме
BIOS, вызываемой из ПЗУ. Проверка программы в этой точке
гарантирует, что мя установили регистры в нужное состояние перед
выполнением подпрограммы BIOS.
Как только встречается точка прерывания, управление
возвращается программе DEBUG. При этом, так же как и в случае
команды R, на дисплей выводятся содержимое регистров и следующая
выполняемая команда. Так как управление опять передано программе
DEBUG, вы можете использовать любую из команд отладки.
Имеются ограничения на использование точек прерывания.
Фактически точка прерывания реализуется кодом операции 0CCH.
Соответствующая этому коду команда вызывает прерывание INT 3.
Данное математическое прерывание возвращает управление программе
DEBUG. Если какая-то команда возвращает управление отладчику, то
точка прерывания должна находиться в начале этой команды. Если же
точка прерывания выбрана где-то в другом месте, то управление не
будет возвращено отладчику и будет выполняться не та команда
программы, которая предполагалась. Например, если бы было задано
"=G 3D", то по адресу 3CH была бы команда INT 0CCH, и дальнейшее
выполнение программы предсказать трудно.
Если точка прерывания выбрана осмотрительно, то никаких
осложнений не будет. Командв "G" позволяет задать до десяти точек
прерывания. После прохождения любой из них происходит
восстановление исходных значений точек прерывания. Выполнение
команды отладки "G" без указания точек прерывания никогда не выйдет
ни на какую из ранее заданных точек прерывания, потому что все они
были удалены. Если вы запустили программу, и произошел ее останов и
она зациклилась, то возможно, что вернуть управление, которое
передано программе, удастся только с помощью клавиши системного
сброса Ctrl=Alt=Del, т.е. вам придется начать все с начала.
Запуская незнакомую программу, следует быть осторожным.
Если вы захотите ввести пермаментную точку прерывания,
воспользуйтесь командой E для изменения значения первого байта
команды на значение 0CCH. Эта точка прерывания будет там постоянно
или, по крайней мере, до тех пор, пока вы ее не измените. Возможно
вы захотите использовать это прерывание в точке входа в
подпрограмму обработки ошибки и проанализируете возникающие ошибки
более тщательно, чем при передаче их обработки программе.
Существует еще одно обстоятельство, связанное с точками
прерывания, о котором следует помнить. Если вы попытаетесь задать
точку прерывания в области, относящейся к ПЗУ, то ничего не
получится. Так как вы не можете менять содержимое ПЗУ, то команда
0CCH никогда туда не запишется.
Рассмотрим следующую команду отладчика - команда трассировки T.
Данная команда инициирует выполнение одной команды отлаженной
программы. В нашем примере команда T выполнена несколько раз. Как
вы можете убедиться, выполняется несколько первых команд BIOS,
вызванной по прерыванию INT 10H. Подпрограмма BIOS, естественно,
находится в ПЗУ. Команда трассировки позволяет "приостановить"
программу при ее выполнении в ПЗУ.
Перед передачей управления пользовательской программе команда
трассировки выставляет в регистре флагов соответствующий бит
трассировки. Этот бит инициирует прерывание INT 1 после выполнения
каждой команды. Вектор прерывания INT 1 возвращает управление
программе DEBUG. Выполнение прерывания INT 1 автоматически
сбрасывает бит трассировки в исходное состояние. Это значит, что
программа DEBUG не прерывается после выполнения каждой ее команды.
Команда трассировки служит прекрасным средством "пробиться" через
трудный участок программы. При этом программа DEBUG выводит на
экран каждую команду вместе с содержимым регистров как раз в
момент, предшествующий выполнению этой команды. Так как в данном
режиме используются не точки прерывания, а собственно прерывания,
то можно выполнять трассировку даже программ ПЗУ.
Вернемся к нашему примеру. Команда =G 4C5:3E обеспечивает
полное выполнение подпрограммы BIOS. Обратите внимание, что
программа вывела на дисплей символ "Э". Вызванная по прерыванию 10H
подпрограмма BIOS выводит символы на дисплей. В данном случае это
первый символ выводимого сообщения. Так как теперь можно быть
уверенными, что наша программа выполняется правильно, то, введя
символ "G", мы обеспечим выполнение программы до конца без точек
прерывания.
В данном примере рассматривался файл типа .EXE, и потому для
возврата управления системе DOS мы не могли использовать прерывание
INT 20H. Вместо этого программа записала в стек состояние регистра
DS и значение 0. Управление передается обратно системе DOS в конце
основной программы с помощью команды возврата типа FAR. Программа
DEBUG распознает это и фиксирует состояние машины в конце
тестируемой программы. Если бы это был файл типа .EXE, то
прерывание INT 20H аналогичным образом вернуло бы управление
программе DEBUG. Теперь, уделив достаточно времени этому примеру,
мы можем выйти из программы DEBUG и вернуться в систему DOS с
помощью команды завершения Q.
Перекрестные ссылки
Перекрестные ссылки
Чтобы воспользоваться файлом перекрестных ссылок, сформированным
ассемблером, требуется дополнительная обработка. Для перевода
файла типа .CRF в текстовый файл в коде ASCII нужно выполнить
программу CREF. Программа CREF запускается точно так же, как и
ассемблер, за исключением того, что при запуске задаются только два
файла: входной файл типа .CRF и выходной файл типа .REF. При
вводе команды DOS A> CREF запрашиваются имена двух файлов.
Альтернативой может служить команда A> CREF B:FIG5=10, B, которая в
качестве входного файла использует файл B:FIG5=10.CRF и формирует
файл B:FIG5=10.REF. Как и случае с ассемблером имеются и другие
варианты данной команды, описанные в справочном руководстве по
Макроассемблеру.
На Фиг. 5.12 показан выход формирователя перекрестных ссылок.
Данная конкретная таблица перекрестных ссылок получена для
программы, приведенной на Фиг. 5.6. В левом столбце перечислены все
символические имена, определенные в программе. Напротив каждого
символичсекого имени приводится последовательность целочисленных
параметров. Числа соответствуют номерам строк, в которых появляется
это имя. Если за номером строки следует символ #, то имя юыло
определено в этой строке. Если же символ # отсутствует, то значит в
этой строке содержится ссылка на имя.
Фиг. 5.6 Пример использования функций ДОС
Symbol Cross Reference (# is definition) Cref-1
BAD_CLOSE_MSG . . . . . . . . . 50# 169
BAD_OPEN_MSG . . . . . . . . . . 37# 86
BAD_READ_MSG . . . . . . . . . . 46# 141
BAD_WRITE_MSG . . . . . . . . . 41# 105 159
CHANGE_RECORD . . . . . . . . . 126 128#
CHARACTER_LOOP . . . . . . . . . 94# 110
CHAR_BAD_MSG . . . . . . . . . . 60# 123
CLOSE_OK . . . . . . . . . . . . 168 171#
CREATE_OK . . . . . . . . . . . 3# 28 28 173
DISK_TRANSFER_ADDRESS . . . . . 26# 68 95 144 145 146 148 152
ERROR_EXIT . . . . . . . . . . . 76# 87 106 142 160 170
FCB . . . . . . . . . . . . . . 5# 71 82 99 136 155 165
FCB_BLOCK . . . . . . . . . . . 15#
FCB_CURRENT_RECORD . . . . . . . 23# 89
FCB_DATE . . . . . . . . . . . . 18#
FCB_DRIVE . . . . . . . . . . . 6#
FCB_EXT . . . . . . . . . . . . 11#
FCB_FILE_SIZE . . . . . . . . . 17#
FCB_NAME . . . . . . . . . . . . 7#
FCB_RANDOM_RECORD . . . . . . . 24# 90 91 135
FCB_RECORD_SIZE . . . . . . . . 16# 92
FCB_RESV . . . . . . . . . . . . 19#
FILE_ERROR_MSG . . . . . . . . . 33# 75
INPUT_BAD_MSG . . . . . . . . . 54# 117
KEYBOARD_BUFFER . . . . . . . . 32# 112 115 124 150
KEYBOARD_ERROR . . . . . . . . . 118# 130 132
KEYBOARD_LOOP . . . . . . . . . 111# 121 162
KEY_INPUT_OK . . . . . . . . . . 116# 122#
NO_FILE . . . . . . . . . . . . 74 80#
PROGRAM_EXIT . . . . . . . . . . 127 163#
PROGRAM_START . . . . . . . . . 29 66#
RANDOM_RECORD_OK . . . . . . . . 140 143#
RANDOM_WRITE_OK . . . . . . . . 158 161#
RECORD_SIZE . . . . . . . . . . 31# 92 96 151
WRITE_OK . . . . . . . . . . . . 104 107#
Фиг. 5.12 Таблица перекрестных ссылок для
программы Фиг. 5.6
Как может быть использован листинг перекрестных ссылок?
Перекрестные ссылки позволяют определить, в каком месте
используется каждое имя. Например, если переменная получает Mincho"'> неверное значение, то листинг перекрестных ссылок показывает все
команды, где имеются ссылки на данное символическое имя. Это
поможет определить те команды, с которыми связано неправильное
выполнение программы. Возможно также, что вы занимаетесь
модификацией уже существующей программы, написанной кем-то другим
или же вами, но так давно, что вы успели забыть, как эта программа
работает. Если вы захотели изменить одну из подпрограмм, то вам
должно быть известно, какие части программы эту подпрограмму
используют. Листинг перекрестных ссылок показывает все команды CALL
(а так же другие команды, имеющие к этому отношение), в которых
есть ссылки на это символическое имя. Проанализировав
соответствующие места программы, вы можете решить: допустимо ли
вносимое изменение для всех тех участков, откуда вызввается данная
подпрограмма. Листинг перекрестных ссылок значительно облегчает
задачу определения всех участков программы, содержащих ссылки.
Преодразование файла типа exe в файл типа com
Преодразование файла типа .exe в файл типа .com.
На дискете с DOS имеется сервисная программа под именем EXE2BIN.
Эта программа преобразует файл типа .EXE в файл типа .COM. Однако
программа EXE2BIN работает не со всеми файлами. Далее излагается
метод использования программы DEBUG, с помощью которого любая
программа преобразуется в файл типа .COM.
На Фиг. 5.18 приведена программа, которую мы будем
преобразовывать. Эта программа выполняет точно такие же функции,
что и программа предыдущего примера, а именно - выводит на дисплей
фразу : "Это тест". Однако в данной программе эта строка выводится
на дисплей по прерыванию INT 21H с помощью функции 9 DOS.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:33
Фиг. 5.18 Пример преобразования файла типа .EXE в тип .COM Page 1-1
PAGE ,132
TITLE Фиг. 5.18 Пример преобразования файла типа .EXE в тип .COM
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0100 ORG 100H
0100 8D 16 010A R LEA DX, MESSAGE
0104 B4 09 MOV AH, 9H ; Функция вывода строки ДОС
0106 CD 21 INT 21H ; Вывод строки на экран
0108 CD 20 INT 20H ; Возврат в ДОС
010A 9D E2 A0 20 AF E0 AE MESSAGE DB 'Эта программа - тест', 10, 13, '$'
A3 E0 A0 AC AC A0 20
2D 20 E2 A5 E1 E2 0A
0D 24
0121 CODE ENDS
END
Фиг. 5.18 Пример перевода .EXE в .COM
Обратите внимание, что данная программа записана как файл типа
.COM. На это указывает оператор ORG 100H, предшествующий первой
команде. Остальная часть программы должна быть перемещаемым
сегментом команд, и об этом не нужно забывать при написании
программы, которая будет преобразовываться в файл типа .COM.
Ассемблирование и редактирование связей этой программы
осуществляется обычным способом. Однако до запуска программы DEBUG
нужно изменить в имени файла тип .EXE на тип .COM. Это необходимо
сделать, так как программа DEBUG не позволяет записывать файл типа
.EXE. На Фиг. 5.19 показана последовательность шагов, которую нужно
выполнить. В данном примере вводится команда программы DEBUG без
имени файла. В качестве имени можно было бы в данной строке указать
FIG5=18.COM, зато его отсутствие позволило продемонстрировать
некоторые другие функции программы DEBUG. Команда N отладчика
позволяет задать имя файла. После этого команда L выполняет
загрузку файла в память. Если указать имя файла в команде DEBUG, то
последняя выполнит все то же самое, что и команды N и L.
Теперь, когда файл загружен, вы обнаружите, что в
дествительности программа загрузилась, начиная со смещения 400H.
Команда M сдвигает содержимое области памяти с 400H на 100H. Длина
области, равная 1000H, была выбрана для гарантии того, что там
поместится вся программа. Теперь программа соответствует формату
файла типа .COM и может быть опять записана на дискету. Однако
прежде, чем это сделать, вы изменяете содержимое регистра CX, чтобы
он указал фактическую длину программы. При любом считывании и
записи файлов на дискету, осуществляемых программой DEBUG, длина
файла хранится в регистре CX. Так как файл типа .COM теперь намного
короче, чем был файл типа .EXE, то мы можем сэкономить дисковую
B>A:ASM FIG5_18,,,;
The IBM Personal Computer Assembler
Version 1.00 (c)Copyright IBM Corp 1981
Warnings Severe
Errors Errors
0 0
B>A:LINK FIG5_18,,,;
Ibm Personal Computer Linker
Version 1.10 (C)Copyright IBM Corp 1982
Warning: No STACK segment
There was 1 error detected.
B>RENAME FIGS5_18.EXE FIGS5_18.COM
B>A:DEBUG
-NFIGS_18.COM
-L
-M 400 1000 100
-U100 10F
06D7:0100 BA091 MOV DX,0109
06D7:0103 B409 MOV AH,09
06D7:0105 CD21 INT 21
06D7:0107 CD20 INT 20
06D7:0109 54 PUSH SP
06D7:010A 68 DB 68
06D7:010B 69 DB 69
06D7:010C 7320 JNC 012E
06D7:010E 69 DB 69
06D7:010F 7320 JNC 0131
-D100
06D7:0100 BA 09 01 B4 09 CD 21 CD-20 54 68 69 73 20 69 73 :..4.M!M' This is
06D7:0110 20 61 20 74 65 73 74 DA-0D 24 00 00 00 00 00 00 a test..$.......
06D7:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
06D7:0130 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
06D7:0140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
06D7:0150 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
06D7:0160 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
06D7:0170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
-RCX
CX 0380
:120
-W
Writing 0120 bytes
-Q
Фиг. 5.19 Пример преобразования из.EXE в .COM (начало)
>BDEBUG FIG5_18.COM
-R
AX=0000 BX=0000 CX=0120 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000
DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI NZ NA PO NC
-Q
B>FIG5_18
Эта программа - тест
Фиг. 5.19 Пример преобразования из.EXE в .COM (продолжение)
память, задав в регистре CX правильное значение для программы.
Команда W записывает файл обратно на дискету. Кстати, это еще одно
преимущество использования файлов типа .COM. Программа DEBUG не
будет записывать файл типа .EXE на дискету, потому что в памяти
отсутствует информация головной метки. В то же время файл типа .COM
может быть записан на дискету программой DEBUG. Если вы отлаживаете
программу и вам нужно изменить в ней один или два байта без ее
повторного ассемблирования (это называется "латанием" программы),
то это можно сделать. Просто внесите в программу изменения,
убедитесь, что регистр CX установлен правильно, и с помощью команды
W запишите программу на дискету.
Команда Описание
-------------------------------------------------
D Вывод содержимого памяти
E Изменить содержимое памяти
F Заполнить блок памяти
G Выполнять программу
H Шестнадцатеричное сложение и вычитание
I Считать и показать значение из порта
L Загрузить с диска
M Переслать блок памяти
N Назначить имя файла
O Вывести значение в порт
Q Выход из отладчика
R Вывести значения регистров
S Поиск строки байт
T Выполнить одну команду
U Дизассемблировать блок кода
W Записать данные на диск
--------------------------------------------------
Фиг. 5.20 Команды DEBUG
В результате работы отладчика получился новый вариант программы
FIG5=18.COM. Обращая внимание на состояние регистров, мы видим, как
они устанавливаются в случае файла типа .COM. Сравните это с
показанным на Фиг.5.17 состоянием регистров для файла типа .EXE.
Разница между ними поможет уяснить некоторые различия между файлами
типа .COM и типа .EXE.
Имеются и другие команды, используемые при работе с отладчиком
DEBUG. На Фиг. 5.20 приведен полный набор команд для работы с
программой DEBUG. В руководстве по DOS подробно описаны эти
команды.
Редактор связей
Редактор связей
Программа, полученная на выходе ассемблера, еще не готова к
выполнению. Прежде, чем сформированный ассемблером объектный код
может выполняться, должна быть выполнена его "привязка".
Программа редактирования связей LINK (LINK.EXE), записанная на
той же дискете, что и другие распространенные в DOS программы,
фактически реализует две различные функции. Во-первых, она может
связать много различных объектных модулей в одну программу.
Во-вторых, на основе ассемблерного объектного модуля редактор связи
формирует выполняемый загружаемый модуль. Рассмотрим по отдельности
обе эти функции программы LINK.
Создание программы на языке Ассемблера
Создание программы на языке Ассемблера
Путь от идеи к готовой для выполнения программы состоит из
нескольких этапов. В данном разделе рассматриваются этапы создания
программы на языке ассемблера для IBM PC. К обсуждаемым здесь
средствам относятся: строковый редактор, ассмеблер, редактор
связей (компоновщик) и отладчик. С помощью редактора создаются
исходные программы на языке ассемблера. Ассемблер преобразует
исходную программу в объектный код, который очень близок к
машинному языку. Редактор связей трансформирует объектный код в
файл типа .EXE, содержащий программу, которая готова к выполнению.
И наконец, программа-отладчик может оказать помощь в выявлении
ошибок в программе.
СТРОКОВЫЙ РЕДАКТОР ДОС
Редактор строк формирует текстовые файлы. Содержимое текстового
файла представлено в коде ASCII. Редактор позволяет вводить нужный
вам текст в файл. Если затем понадобится изменить файл, то для
этого опять используется редактор.
Редактор строк (EDLIN) входит в состав DOS IBM PC. Файл
EDLIN.COM является внешней командой, т.е. загружается в память
только по запросу. Для запуска редактора должна быть выполнена
следующая команда:
A>EDLIN FILE.ASM
где FILE.ASM - имя текстового файла, с которым будет работать
редактор. Файл FILE.ASM уже может существовать, как в случае, когда
вы вносите изменения в сформированную программу. Если же такого
файла еще нет, то он будет создан с помощью редактора EDLIN. По
окончании редактирования результирующий текст помещается в файл
FILE.ASM. Если в данном редактировании файл не создавался, то
редактор EDLIN выполняет переименование старого варианта файла
FILE.ASM в файл FILE.BAK (backup - копия). Создание копии файла
позволяет с ее помощью устранить любые серьезные ошибки, допущенные
при редактировании файла. Вы ликвидируете файл со внесенными при
редактировании ошибками и переименовываете файл .BAK в файл .ASM.
Таким образом, это служит подстраховкой при редактировании текста.
Редактор EDLIN является редактором строк. Каждую строку текста
он обрабатывает отдельно и этим отличается от экранного редактора,
который выводит на экран целый фрагмент редактируемого текстового
файла. При работе с экранным редактором, таким как Персональный
Редактор (Personal Editor), разработанный фирмой IBM, вы
перемещаете курсор и осуществляете редактирование в любом месте
экрана в то время, как с помошью редактора EDLIN каждый раз
редактируется только одна строчка текста. Если вы собираетесь
основательно заняться программированием на языке ассемблера, то вам
лучше пользоваться экранным редактором. Поставляемый фирмой IBM
Персональный Редактор является очень хорошим экранным редактором
текста. Именно с его помощью были подготовлены все примеры в этой
книге. Редактор EDLIN рассматривается здесь только потому, что он
входит в состав DOS. Однако учмтывая, что это не лучший вариант
редактора, описание редактора EDLIN включает только краткое
упоминание его команд и соответствующий пример. Для того, чтобы
подробней ознакомиться с командами редактора EDLIN, следует
обратиться к руководству по DOS.
На Фиг.5.7 подытожены сведения о командах редактора EDLIN. При
разборе примера мы будеи на них ссылаться.
Действие Синтаксис
--------------------------------------------------------------------------------
Изменение строки [n] A
Уничтожение строки [строка][,строка] D
Редактировать строку [строка]
Конец редактирования E
Вставка строки [строка] I
Просмотр строк [строка][,строка] L
Выход из редактора Q
Замена контекста [строка][,строка] [?] ЗаменяемаяСтрока [
Поиск контекста [строка][,строка] [?] СтрокаПоиска
Записать строки [n] W
--------------------------------------------------------------------------------
Фиг. 5.7 Команды EDLIN (Copyright IBM 1981)
Фиг. 5.8 иллюстрирует выполнение двух этапов редактирования
с использованием редактора EDLIN. В первой части редактирования
создается файл с именем FIG5=8.ASM. Сообщение "New file" ("новый
файл") указывает на то, что файла с таким именем до этого не было.
Символом "*" обозначается запрос со стороны редактора EDLIN. Всякий
раз, когда на дисплее появляется этот символ, можно вводить любую
из команд, приведенных на Фиг.5.7. Команда "I" позволяет вводить
текст в файл. По мере ввода текста редактор EDLIN указывает на
экране номера строк. Ввод порции текста завершается нажатием
клавиши Control=Z (на листинге - ^Z). Команда E завершает этап
редактирования, записывает файл и возвращает управление DOS.
На второй части Фиг. 5.8 показано редактирование файла,
созданного в первой чати. Редактор вызывается той же самой
командной строкой, а сообщение "End of input file" ("конец ввода
файла") указывает на то, что данный файл уже существует. Команда L
выводит на экран текущее содержимое файла. В данном примере
вносятся изменения в некоторые строки программы при выполнении
команды редактирования. Если ввести символ "3", то на дисплей будет
A>EGLIN FIG5_8.ASM
New file
*I
1:* PAGE ,132
2:*CODE SEGMENT
3:* ASSUME CS:CODE
4:*
5:*START: PROC NEAR
6:* MOV AX,CODE
8:* MOV DS,AX
8:* RET
9:*START: ENDP
10:*END
11:*^C
*E
A>
A>EDLIN FIG5_8.ASM
End of input file
*L
1:* PAGE ,132
2: CODE SEGMENT
3: ASSUME CS:CODE
4:
5: START PROC NEAR
6: MOV DS,AX
8: MOV DS,AX
8: RET
9: START ENDP
10: END
*3
2:*CODE SEGMENT
2:*CODE SEGMENT,DS:CODE
*5
5:*START: PROC NEAR
5:*START: PROC NEAR
*6
6:* MOV AX,CODE
6:* MOV AX,CS
*9I
9:*
10:*^C
*L
1:* PAGE ,132
2: CODE SEGMENT
3: ASSUME CS:CODE
4:
5: START PROC NEAR
6: MOV DS,AX
8: MOV DS,AX
8: RET
9:
10: START ENDP
11: END
*E
A>
--------------------------------------------
Фиг. 5.8 Пример работы с EDLIN
выведена третья строка, готовая для редактирования. У системы
имеется набор встроенных команд построчного редактирования, которые
приведены на Фиг.5.9. Эти команды редактирования можно использовать
как при работе с редактором EDLIN, как и всегда, когда
осуществляется ввод символов с клавиатуры под управлением DOS, в
частности, при вводе команд, относящизся к командному процессору.
Вернемся к примеру, приведенному на Фиг. 5.8. После того, как
строка с указанным номером выведена на дисплей, ее можно
отредактировать с помощью команд DOS. Клавиша F3 позволяет повторно
вывести строку на дисплей и вносить изменения, начиная с конца
строки. На примере редактирования строки 5 показано использование
клавиша DEL для удаления из поля метки символа ":". При
редактировании строки 6 используется клавиша (курсор вправо) для
установки курсора после ",". Вслед за этим код "CODE" заменяется на
символ "CS". Кроме того, для наглядности текста в данном примере
производится вставка пустой строки перед строчкой 9. После этого
опять выдается листинг файла для того, чтобы были видны внесенные
при редактировании изменения. Команда E - окончание редактирования
- возвращает управление DOS.
----------------------------------------------------------
DEL - Уничтожение следующего символа в хранимой строке
ESC - Отмена редактирования хранимой строки
F1 или -> Копировать символ из хранимой строки
F2 - Копировать все символы до заданного
F3 - Копировать оставшиеся символы на экран
F4 - Пропустить все символы до заданного
F5 - Принять отредактированную строку для продолжения
INS - Вставить символ в строку (отмена - INS повторно)
----------------------------------------------------------
Фиг. 5.9 Клавиши редактирования в DOS
Имеется и ряд других операций, которые можно выполнять с
помощью редактора EDLIN и команж редактирования, вхадящих в DOS.
Лучший способ изучить их - это просмотреть руководство по DOS и
затем все операции попробовать. Однако в случае серьезных
программных разработок лучше обратиться к экранному редактору.
Таблица символических имен
Таблица символических имен
В листинговом файле содержится дополнительная информация, о которой
до сих пор в этой книге нигде не говорилось. После листинга
программы выводится таблица символических имен. На Фиг. 5.11
показан пример такой таблицы, которая является таблицей имен для
программы, приведенной на Фиг. 5.6. В этой таблице перечислены все
символические имена, определенные в программе, и кроме того,
указаны атрибуты каждого из них. Будучи очень формальной системой,
ассемблер хранит эту информацию и для удобства пользователя
приводит ее в листинговом файле. Символические имена
подразделяются при этом на метки, переменные и константы. В
таблице приведено значение каждого имени, если оно известно, и
кроме того, безотносительно к типу данных - его длина.
Microsoft (R) Macro Assembler Version 4.00 4/15/89 23:14:35
Фиг. 5.6 Пример использования функций ДОС Symbols-1
Segments and Groups:
N a m e Size Align Combine Class
CODE . . . . . . . . . . . . . . 02D0 PARA NONE
Symbols:
N a m e Type Value Attr
BAD_CLOSE_MSG . . . . . . . . . L BYTE 016D CODE
BAD_OPEN_MSG . . . . . . . . . . L BYTE 011E CODE
BAD_READ_MSG . . . . . . . . . . L BYTE 0153 CODE
BAD_WRITE_MSG . . . . . . . . . L BYTE 0138 CODE
CHANGE_RECORD . . . . . . . . . L NEAR 0260 CODE
CHARACTER_LOOP . . . . . . . . . L NEAR 0216 CODE
CHAR_BAD_MSG . . . . . . . . . . L BYTE 01AB CODE
CLOSE_OK . . . . . . . . . . . . L NEAR 02CE CODE
CREATE_OK . . . . . . . . . . . L NEAR 01FD CODE
DISK_TRANSFER_ADDRESS . . . . . L BYTE 0090 CODE
ERROR_EXIT . . . . . . . . . . . L NEAR 01E5 CODE
FCB . . . . . . . . . . . . . . L BYTE 005C CODE
FCB_BLOCK . . . . . . . . . . . L WORD 0068 CODE
FCB_CURRENT_RECORD . . . . . . . L BYTE 007C CODE
FCB_DATE . . . . . . . . . . . . L WORD 0070 CODE
FCB_DRIVE . . . . . . . . . . . L BYTE 005C CODE
FCB_EXT . . . . . . . . . . . . L BYTE 0065 CODE Length = 0003
FCB_FILE_SIZE . . . . . . . . . L DWORD 006C CODE
FCB_NAME . . . . . . . . . . . . L BYTE 005D CODE Length = 0008
FCB_RANDOM_RECORD . . . . . . . L DWORD 007D CODE
FCB_RECORD_SIZE . . . . . . . . L WORD 006A CODE
FCB_RESV . . . . . . . . . . . . L BYTE 0072 CODE Length = 000A
FILE_ERROR_MSG . . . . . . . . . L BYTE 0108 CODE
INPUT_BAD_MSG . . . . . . . . . L BYTE 0189 CODE
Фиг. 5.11 Таблица символических имен
для программы Фиг. 5.6 (начало)
KEYBOARD_BUFFER . . . . . . . . L BYTE 0103 CODE
KEYBOARD_ERROR . . . . . . . . . L NEAR 024C CODE
KEYBOARD_LOOP . . . . . . . . . L NEAR 0239 CODE
KEY_INPUT_OK . . . . . . . . . . L NEAR 0252 CODE
NO_FILE . . . . . . . . . . . . L NEAR 01EB CODE
PROGRAM_EXIT . . . . . . . . . . L NEAR 02BB CODE
PROGRAM_START . . . . . . . . . L NEAR 01CD CODE
RANDOM_RECORD_OK . . . . . . . . L NEAR 0282 CODE
RANDOM_WRITE_OK . . . . . . . . L NEAR 02B8 CODE
RECORD_SIZE . . . . . . . . . . Number 0020
WRITE_OK . . . . . . . . . . . . L NEAR 0233 CODE
173 Source Lines
173 Total Lines
57 Symbols
48738 Bytes symbol space free
0 Warning Errors
0 Severe Errors
Фиг. 5.11 Таблица символических имен
для программы Фиг. 5.6 (продолжение)
Assembler для начинающих
Аргументы макрокоманд
Аргументы макрокоманд
В обработке макрокоманд применяется одно из ценных свойств
процедур: в генерацию макрокоманды можно вносить изменения с
помощью параметров. Точно так же, как параметры подпрограммы могут
влиять на ее выполнение, параметры макрокоманды определяют
фактически генерируемые команды. И так же, как и процедуры,
макрокоманды без параметров встречаются относительно редко.
Рассмотрим еще один простой пример. Мы составили программу так,
что во многих ее местах содержимое определенной ячейки памяти
складывается с различными константами. Вместо того, чтобы много раз
писать команду
ADD MEMORY_BYTE,5
или
ADD MEMORY_BYTE,7
нам хотелось бы воспользоваться для нее соответствующей
макрокомандой. Однако во всех приведенных командах константы
разные. Поэтому мы сделаем константу параметром макрокоманды. На
Фиг. 6.2 показаны определение и применение макрокоманды ADDBYTE. В
этом примере в качестве параметра в определении макрокоманды
используется символическое имя CONSTANT. Любые символичесике имена,
появляющиеся в поле операнда оператора MACRO, интерпретируются как
параметры. В момент определения макрокоманды у имени CONSTANT нет
никакого значения: оно просто резервирует место в тексте
макрокоманды. Позднее, при вызове и обработке текста макрокоманды,
вместо символического имени в определении макрокоманды
подставляется определенное значение параметра.
Важно отметить, что параметр макрокоманды - это текстовый
параметр. Так как макропроцессор фактически является текстовым
процессорорм, то он не отличает цифры от букв и наоборот. Это
позволяет при вызове макрокоманды использовать вместо чисел
символические имена. Любой смысл приписывается символьной строке не
макропроцессором, а ассемблером. Макропроцессор подставляет
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:43
Фиг. 6.2 Макрокоманда с аргументом Page 1-1
PAGE ,132
TITLE Фиг. 6.2 Макрокоманда с аргументом
ADDBYTE MACRO CONSTANT
ADD MEMORY_BYTE, CONSTANT
ENDM
0000 CODE SEGMENT
ASSUME CS:CODE
0000 ?? MEMORY_BYTE DB ?
= 0004 FOUR EQU 4 ; Симвользое изображение константы
ADDBYTE 2
0001 2E: 80 06 0000 R 02 1 ADD MEMORY_BYTE, 2
ADDBYTE 4
0007 2E: 80 06 0000 R 04 1 ADD MEMORY_BYTE, 4
ADDBYTE FOUR
000D 2E: 80 06 0000 R 04 1 ADD MEMORY_BYTE, FOUR
0013 CODE ENDS
END
Фиг. 6.2 Аргументы макрокоманды
текстовую строку из вызова макрокоманды на место символического
имени в определении макрокоманды. Таким образом программа может
использовать константное значение "FOUR" с тем же успехом, что и
константу "4".
Возможность использовать символические имена в качестве
параметров макрокоманд принципиально важна для следующего примера
макрокоманды. Этой макрокоманда, одной из команд сопроцессора 8087,
требуется параметр, который при обычном ее использовании почти
всегда бывает символическим именем. Макрокоманда FLDCW - это
команда сопроцессора 8087, которая задает ячейку памяти. Так как в
программах на языке ассемблера в большинстве случаев обращаются к
ячейкам памяти с помощью символических имен, то желательно
сохранить этот способ и для программирования сопроцессора 8087.
На Фиг.6.3 приводится макрокоманда FLDCW и несколько обрашений
к ней. Заметьте, что макрокоманда FLDCW использует в качестве
параметра символическое имя "SOURCE". Параметр SOURCE является
адресом, с которого сопроцессор 8087 загружает управляющее слово.
Для генерации требуемого машинного кода макрокоманда FLDCW
использует команду 8088 ESC. Однако для того, чтобы определить байт
mod=r/m команды, команде ESC требуется значение адреса. Как раз для
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:47
Фиг. 6.3 Макрокоманда для команды FLDCW Page 1-1
PAGE ,132
TITLE Фиг. 6.3 Макрокоманда для команды FLDCW
FLDCW MACRO SOURCE
DB 09BH
ESC 0DH, SOURCE
ENDM
0000 CODE SEGMENT
ASSUME CS:CODE
0000 ???? MEMORY_LOCATION DW ?
FLDCW MEMORY_LOCATION
0002 9B 1 DB 09BH
0003 2E: D9 2E 0000 R 1 ESC 0DH, MEMORY_LOCATION
FLDCW ES:[DI]
0008 9B 1 DB 09BH
0009 26: D9 2D 1 ESC 0DH, ES:[DI]
FLDCW MEMORY_LOCATION[BX+SI]
000C 9B 1 DB 09BH
000D 2E: D9 A8 0000 R 1 ESC 0DH, MEMORY_LOCATION[BX+SI]
0012 CODE ENDS
END
Фиг. 6.3. Макрокомнда FLDCW
этого макрокоманда и использует параметр SOURCE. Такая организация
макрокоманды FLDCW позволяет программировать весьма естественным
способом. Точно так же как пишется
INC MEMORY_LOCATION
вы можно написать команду для сопроцессора 8087
FLDCW MEMORY_LOCATION
Это справедливыо не только для адресов, заданных символическими
именами, но и для других способов адресации. На Фиг. 6.3 показано
несколько примеров задания операнда с помощью адресации по базе и
индексу. Так как макропроцессор воспринимает параметр как какой-то
фрагмент текста, то параметр может быть образован любой символьной
строкой, какую вы пожелаете.
Можно задать макрокоманду и с несколькими параметрами.
Единственное, что ограничивает число параметров макрокоманды, это
длина ассемблерной строки. Все, что следует за оператором MACRO
интерпретируется макропроцессором как параметр. Для разделения
символических имен в определении макрокоманды пользуются запятыми.
Оператор MACRO с тремя параметрами будет выглядеть следующим
образом:
EXAMPLE MACRO ARG1, ARG2, ARG3
Аналогично, при вызове макрокоманды вы должны задать значение
каждого из параметров. Если вы хотите пропустить какой-то параметр,
то ассемблер подставит вместо него символьную строку нулевой длины.
Иногда это полезно, но часто приводит к неправильной трансляции.
Если макрокоманда имеет более одного параметра, то при вызове
макрокоманды относящийся к параметрам текст разделяется запятыми.
Это в точности совпадает со способом задания нескольких параметров
к любой из команд микропроцессора 8088, поэтому будет вполне
естественным для вас. Вызов макрокоманды с тремя параметрами может
выглядеть так:
EXAMPLE 5, [BX], MEMORY_BYTE
В следующем примере вы увидите некоторые возможности множествен-
ности параметров.
Ассемблирование по условию
Ассемблирование по условию
До сих пор макрокоманды не отличались от подпрограмм с точки зрения
как их функционирования, так и использования параметров. Далее, нам
требуется возмоность ассемблирования в зависимости от условия. Так
же как ход выполнения подпрограммы может меняться в зависимости от
некоторых условицй в момент выполнения, так и у макрокоманды должна
быть возможность изменять в момент транслирования генерацию
соответствующего машинного кода в зависимости от удовлетворения
условий.
Макроассемблер фирмы IBM допускает условное ассемблирование. На
самом деле, условное ассемблирование не обязательно входит только в
макрокоманду. Программа может использовать условное транслирование
в любом месте ассемблерного текста. Однако наиболее часто оно
встречается в макрокомандах. В IBM PC условное транслирование
поддерживается только Макроассемблером MASM.
Так же, как и выполнение макрокоманд, условное ассемблирование
происходит во время трансляции, а не выполнения программы. Условное
транслирование позволяет программисту "запрограммировать" ассемблер
на транслирование различных последовательостей кодов. Ассемблер
определяет, что ему транслировать по параметру, известному во время
ассемблирования. Хотя эта возможность может использоваться
программой в любой момент ассемблирования, мы изучим прежде всего,
как она влияет на ассемблирование макрокоманд.
Фиг. 6.4 иллюстрирует условное транслирование при расширении
макрокоманды FIDIVR сопроцессора 8087. Условное тарнслирование
требуетя данной макрокоманде из-за разделения переменных в
ассемблере по типам. Как мы увидим в гл.7, команда, обозначенная
FIDIVR, может применяться к операндам двух типов. Операнд может
быть двух- или четырехбайтовым целым числом. Мы хотим, чтобы
ассемблер выбрал правильный машинный код в зависимости от типа
операнда. Как мы вмдели, у команды ADD в действительности имеется
несколько форм, в зависимости от того, какие операнды представлены
ассемблеру, который выбирает верную форму машинной команды в
зависиммости от этих операндов. Мы хотим делать то же самое для
команды FIDIVR Но в теперь макропроцессор должен определить тип
операнда и сгенерировать правильную команду.
У команды FIDIVR может быть один из двух типов операндов, и в
зависимости от этого будут различаться результирующие команды.
Таким образом, расширение макрокоманды FIDIVR должно
соответствовать нужному операнду. Это обеспечивается двумя
средствами языка: условным транслированием и оператором TYPE.
В языке ассемблера имеется оператор TYPE, который возвращает
Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:42
Фиг. 6.4 Условное транслирование Page 1-1
PAGE ,132
TITLE Фиг. 6.4 Условное транслирование
FIDIVR MACRO SOURCE
IFE 2 - TYPE SOURCE
DB 09BH ;; FWAIT
ESC 037H,SOURCE ;; FIDIVR слово
ENDIF
IFE 4 - TYPE SOURCE
DB 09BH ;; FWAIT
ESC 017H,SOURCE ;; FIDIVR короткое целое
ENDIF
ENDM
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 ???? TWO_BYTE DW ?
0002 ???????? FOUR_BYTE DD ?
0006 ?? ONE_BYTE DB ?
FIDIVR TWO_BYTE
0007 9B 1 DB 09BH ;
0008 DE 3E 0000 R 1 ESC 037H,TWO_BYTE ;
FIDIVR FOUR_BYTE
000C 9B 1 DB 09BH ;
000D DA 3E 0002 R 1 ESC 017H,FOUR_BYTE ;
FIDIVR ONE_BYTE
0011 CODE ENDS
END
Фиг. 6.4. Ассемблирование макрокоманд по условию
значение, равное длине операнда. В случае FIDIVR мы ожидаем, что
операнд будет двух- или четырехбайтовым целым числом.
Выражение
IFE 2-TYPE SOURCE
в макрокоманде FIDIVR проверяет длину операнда SOURCE.
Арифметическое выражение 2=TYPE SOURCE, равняется 0, если операнд
SOURCE является двухбайтовым целым числом, и принимает ненулевое
значение при любом другом типе операнда. Оператор IFE
(транслировать, если равно) сообщает ассемблеру, что нужно
транслировать все последующие команды, если выражение в поле
операнда равно 0. Таким образом, оператор IFE вырабатывает значение
истина, если операнд SOURCE является двухбайтовым целым числом. В
этом случае происходит трансляция всех команд, следующих за
оператором IFE до тех пор, пока не встретится оператор ENDIF. В
нашем примере это означает, что если операнд является двухбайтовым
целым числом, то ассемблируется участок программы
DB 09BH
ESC 37H,SOURCE
В первом вызове макрокоманды на Фиг. 6.4 в качестве операнда
используется двухбайтовое целое число. Поэтому, для расширения этой
макрокоманды ассемблер выбирает команду ESC 37H.
Так как команда FIDIVR имеет два варианта, соответствующие двум
разным типам операндов, то в при выполнении другого условия,
макрокоманда использует второй оператор IFE. Когда операнд является
четырехбайтовым целым числом, макрокоманда генерирует код ESC 17H.
На Фиг. 6.4 показаны два разных расширения одной макрокоманды.
Обратите внимание, что в последнем из показанных на Фиг. 6.4
вызовов макрокоманды операнд не удовлетворяет ни одному из условий.
Так как ни один из операторов IFE не вырабатывает значения
"истина", то и ассемблироваться не будет ни один из них. В этом
случае макропроцессор не генерирует никакого кода.
С помощью оператора IFE ассемблер может проверять выполнение
различных условий. Эти условия приведены в таблице на Фиг. 6.5.
Общая форма оператора IF имеет вид:
IFхх выражение
...
ELSE
...
ENDIF
Если значение условия "истина", то ассемблер обрабатывает
участок программы, следующий за оператором IFхх. Этот транслируемый
участок программы заканчивается либо оператором ELSE или ENDIF.
Оператор ELSE не является обязательным. Если он имеется, то
следующий за ним участок программы будет транслирован при
невыполнении условия в операторе IF. Оператор ENDIF завершает
условное ассемблирование и является обязательным.
IF-операция Ассемблировать если:
---------------------------------------------------------------
IF выражение Выражение не равно 0
IFE выражение Выражение равно 0
IFDEF имя Имя уже было описано как внешнее
IFNDEF имя Имя еще не описывалось
IFB <аргумент> Аргумент пуст
IFNB <аргумент> Аргумент не пуст
IFIDN <арг1>,<арг2> Строка арг1 идентична строке арг2
IFDIF <арг1>,<арг2> Строка арг1 отличается от строки арг2
IF1 Первый поход ассемблера
IF2 Второй проход ассемблера
---------------------------------------------------------------
Фиг. 6.5 Операторы IF для условного ассемблирования
Рассмотрим еще один пример, чтобы познакомиться с некоторыми
другими вариантами использования ассемблирования по условию. На
Фиг. 6.6 показано применение другого условного оператора - IFB, а
также использование вложенных условных операторов. Макрокомандой
здесь является FLD - команда загрузки сопроцессора 8087. Для
транслирования этой команды требуется несколько условных
операторов, так как она может применяться в следующих вариантах:
FLD
FLD 1
FLD короткое_вещественное
FLD длинное_вещественное
FLD временное_вещественное
Поле операнда макрокоманды FLD может быть пустым, содержать
константу, или четырехбайтовую, восьмибайтовую либо десятибайтовую
переменную. Макрокоманда должна распознать каждый из перечисленныз
случаев и сгенерировать правильный программный код. (все эти типы
данных рассмотрены очень подробно в гл.7.)
Оператор IFB проверяет наличие операнда. Если операнд
отсутствует, то ассемблер генерирует соответствующий этому случаю
программный код, так как оператор IFB вырабатывает значение
"истина". Это иллюстрирует первый вызов макрокоманды, когда
генерируется код
DB 09BH,0D9H,0C 1H
Оператор EXITM, содержащийся в этой части условного оператора
IF, реализует выход из макрокоманды. Каждый раз, когда при
расширении макрокоманды ассемблеру встречается этот оператор,
расширение заканчивается, как если бы встретился ENDM. В данном
случае ассемблер пропускает оставшуюся часть макроопределения. При
таком выходе из макрокоманды в ассемблерном листинге появляется
предупреждающее сообщение "Open conditionals:1" ("Незавершенные
условные операторы: 1"). Оно предупреждает вас, что ассемблер не Mincho"'> Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:47
Фиг. 6.6 Вложенные условные макрокоманды Page 1-1
PAGE ,132
TITLE Фиг. 6.6 Вложенные условные макрокоманды
FLD MACRO SOURCE
IFB
DB 09BH,0D9H,0C1H ;; FLD ST(1)
EXITM
ELSE
IFE TYPE SOURCE
DB 09BH,0D9H,0C0H+SOURCE
;; FLD ST(i)
ENDIF
IFE 4 - TYPE SOURCE
DB 09BH
ESC 8,SOURCE ;; FLD короткое плавающее
ENDIF
IFE 8 - TYPE SOURCE
DB 09BH
ESC 40,SOURCE ;; FLD длинное плавающее
ENDIF
IFE 4 - TYPE SOURCE
DB 09BH
ESC 01DH,SOURCE ;; FLD временное плавающее
ENDIF
ENDIF
ENDM
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 ???????? FOUR_BYTE DD ?
0004 ???????????????? EIGHT_BYTE DQ ?
000C ??????????????????? TEN_BYTE DT ?
?
FLD
0016 9B D9 C1 1 DB 09BH,0D9H,0C1H ;
FLD 1
0019 9B D9 C1 1 DB 09BH,0D9H,0C0H+1
FLD FOUR_BYTE
001C 9B 1 DB 09BH
001D D9 06 0000 R 1 ESC 8,FOUR_BYTE ;
0021 9B 1 DB 09BH
0022 DB 2E 0000 R 1 ESC 01DH,FOUR_BYTE ;
FLD EIGHT_BYTE
0026 9B 1 DB 09BH
0027 DD 06 0004 R 1 ESC 40,EIGHT_BYTE ;
FLD TEN_BYTE
002B CODE ENDS
END
Фиг. 6.6 Вложенное условное ассемблирование
встретил оператора ENDIF, закрывающего условный оператор. Это
происходит в результате раннего входа из макрокоманды. Хотя это и
нежелательно, но ни к каким разрушительным последствиям не
приводит. Если оператор EXITM расположен вне условного оператора,
то предупреждение не выводится.
Оператор EXITM необходим в данной макрокоманде, так как
ассемблер проверяет все условные операторы, даже если они не
транслируются. В нашем случае, если операнд SOURCE пуст, оператор
ELSE предотвращает генерацию всех других вариантов команды FLD.
Однако, продолжая просмотр, ассемблер проверяет оператора
IFE TYPE SOURCE
хотя и не может сгенерировать никакого кода. Если SOURCE пусто,
то ассемблер фиксирует синтаксическую ошибку. Вы можете не обращать
внимания на эту ошибку, но принимать трансляцию с сообщениями об
ошибках идет в разрез с нашими правилами. С другой стороны,
использование оператора EXITM приводит к предупреждению "Открытое
условие". Оно нежелательно, но это меньшее из двух зол.
Заметим, что макрокоманда FLD использует ветвь ELSE чтобы
указать на необходимость вычисления выражения в поле операнда
только в случае непустоты этого поля. IF-выражения, содержащие
оператор TYPE, определяют, какой тип операнда использовался при
вызове макрокоманды. Хотя это не упоминается в Руководстве по
Макроассемблеру, оператор TYPE возвращает значение 0, если операнд
является не символическим именем, а константой. Выбор такого
способа в данной макрокоманде вызван скорее соображениями
работоспособности, чем стремлением к элегантности.
Команды INCLUDE
Команды INCLUDE
Оператор ассемблера INCLUDE осуществляет вставку текста из другого
файла в транслируемую программу. Оператор INCLUDE особенно удобен в
случае работы с набором макрокоманд, например, макрокомандами
сопроцессора 8087. Все команды сопроцессора 8087 представлены
макрокомандами. Весь такой набор макрокоманд или какое-то его под-
множемтво вам приходится включать в любую транслируемую программу,
где применяется сопроцессор 8087. Однако копировать эти макроко-
манды в каждый исходный файл неудобно. Кроме того, они занимают
столько места, что исходные файлы быстро заполнили бы всю вашу дис-
кету, если бы вы попытались копировать макрокоманды в каждый файл.
Эта проблема решается в языке ассемблера с помощью оператора
INCLUDE. Оператор
ICLUDE имяфайла
считывает указанный файл и включает его как часть в ассемблируемую
программу. Ассемблер помещает нужный файл в то место, где
расположен оператор INCLUDE. Оператор INCLUDE естественно
использовать по отношению к библиотекам макрокоманд, например,
макрокоманд сопроцессора 8087. Вы помещаете оператор INCLUDE в
начало программы - и любая команда сопроцессора 8087 в вашей
программе будет ассемблироваться правильно.
Аналогично, вы можете использовать оператор INCLUDE для
подключения других частей программы. Если вы захотите разбить вашу
программу на меньшие исходные файлы, но ассемблировать ее как один
файл, то главный исходный файл может состоять из операторов INCLUDE
для всех вспомогательных исходных файлов. Однако как было показано
в гл.5, ассемблирование небольших модулей и связывание их с помощью
программы LINK в большинстве случаев предпочтительнее.
Другой вариант для использования оператора INCLUDE - структура
данных. Вы можете использовать некоторую структуру данных в
нескольких программах. Определение этой структуры данных можно
хранить в виде отдельного файла, и с помощью оператора INCLUDE
обращаться к ней из любой программы, где они требуются. Далее в
этой главе мы рассмотрим способы, с помощью которых программа может
задавать и использовать структуры данных.
Если включаемый файл является файлом макроопределений, то не
имеет смысла, чтобы он появлялся в ассемблерном листинге каждый раз
при трансляции программы. Чтобы исключить макроопределения из
листинга, но сохранить их для генерации программного кода, вы
можете воспользоваться условным оператором IF1. Последовательность
IF1
INCLUDE 87MAC.LIB
ENDM
приводит к включению в программу файла 87MAC.LIB при первом
проходе ассемблера. Именно во время первого прохода ассемблер
осуществляет расширение всех макрокоманд до их окончательной формы.
При втором проходе макроопределения ассемблеру не нужны. Это
ускоряет процесс ассемблирования, так как во время второго прохода
файл макрокоманд не считывается. Ассемблер не печатает файл
макрокоманд, так как листинговый файл формируется при втором
проходе. На Фиг. 6.11 показано использование в программе
Microsoft (R) Macro Assembler Version 5.00 1/1/80 01:21:40
Фиг. 6.11 Вставка макроопределений для 8087 Page 1-1
PAGE ,132
TITLE Фиг. 6.11 Вставка макроопределений для 8087
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 ???? TWO_BYTE DW ?
0002 9B DB E0 FENI
0005 9B DE 3E 0000 R FIDIVR TWO_BYTE
000A 9B D9 C1 FLD
000D CODE ENDS
END
Фиг. 6.11 Вставка макроопределений для 8087
последовательности IF1 ... INCLUDE ... ENDM для макрокоманд
сопроцессора 8087. Приведены как исходный, так и листинговый файлы
данной программы. Ассемблер правильно обрабатывает команды
сопроцессора 8087, опуская печать определений соответствующих
макрокоманд.
Макрокоманды повторения
Макрокоманды повторения
Для тех случаев, когда нужно несколько раз повторять один и тот же
фрагмент программы в макроассемблере имеется несколько специальных
макрокоманд. Это операторы REPT, IRT и IRPC. Каждый из них
действует как макрокоманда в макрокоманде и приводит к генерации
следующего за ним участка программы, пока ассемблеру не встретится
операнд ENDM.
Для простого повторения последрвательности команд используется
макрокоманда REPT. Последовательность
REPT выражение
;... тело макрокоманды REPT
ENDM
дублирует команды, составляющих тело данной макрокоманды. Значение
выражения определяет число повторений текста.
С помощью макрокоманды IRP можно при каждом повторении
использовать разные параметры. При использовании конструкции
IRP фиктивный параметр,<список>
;... тело макрокоманды IRP
ENDM
ассемблер осуществляет столько проходов тела макрокоманды,
сколько указано элементов в списке. При каждом проходе ассемблер
подставляет вместо фиктивного параметра следующий по порядку
элемент списка. Элементы списка должны быть числовыми выражениями.
Если вы хотите использовать в списке символьные значения,
применяйте макрокоманду IRPC. Последователность
IRPC фиктивный параметр, строка символов
;... тело макрокоманды IRPC
ENDM
осуществляет по одному проходу тела макрокоманды для каждого
символа. При этом фиктивный параметр ассемблер каждый раз заменяет
следующим по порядку символом из строки. На Фиг. 6.7 показаны
примеры применения описанных макрокоманд повтора.
Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:54
Фиг. 6.7 Макроповторения Page 1-1
PAGE ,132
TITLE Фиг. 6.7 Макроповторения
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
REPT 3 ; Повторить команду 3 раза
INC AX
ENDM
0000 40 1 INC AX
0001 40 1 INC AX
0002 40 1 INC AX
IRP VALUE,<5,10,15,20>
ADD AX,VALUE
ENDM
0003 05 0005 1 ADD AX,5
0006 05 000A 1 ADD AX,10
0009 05 000F 1 ADD AX,15
000C 05 0014 1 ADD AX,20
IRPC CHAR, ABCD
ADD AX,CHAR&X
ENDM
000F 03 C0 1 ADD AX,AX
0011 03 C3 1 ADD AX,BX
0013 03 C1 1 ADD AX,CX
0015 03 C2 1 ADD AX,DX
0017 CODE ENDS
END
Фиг. 6.7 Макрокоманды повтора
Макрооператоры
Макрооператоры
Пример макрокоманды IRPC на Фиг. 6.7 иллюстрирует, в частности,
применение символа "&". Это - оператор макрокоманд, который служит
для соединения двух элементов. В данном примере оператор "&"
соединяет параметр CHAR со строкой-константой "X". Как вы видите,
при этом формируется действительное имя регистра.
Еще одним удобным средством при программировании макрокоманд
является оператор LOCAL. Оператор LOCAL задает метку, которая
используется только в данной макрокоманде. Эта метка должна быть
уникальной для каждого вызова данной макрокоманды. Предположим, что
вы хотите написать макрокоманду, в которой должен быть кусок
программы следующего вида:
AAAAA: ADD AL,[BX]
INC BX
LOOP AAAAA
При первом вызове этой макрокоманды никаких сложностей не
Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:59
Фиг. 6.8 Использование команды LOCAL Page 1-1
PAGE ,132
TITLE Фиг. 6.8 Использование команды LOCAL
PAUSE MACRO TIME
LOCAL LABEL
MOV CX,TIME
LABEL: LOOP LABEL
ENDM
0000 CODE SEGMENT
ASSUME CS:CODE
PAUSE 100
0000 B9 0064 1 MOV CX,100
0003 E2 FE 1 ??0000: LOOP ??0000
PAUSE 1000
0005 B9 03E8 1 MOV CX,1000
0008 E2 FE 1 ??0001: LOOP ??0001
000A CODE ENDS
END
Фиг. 6.8 Использование команды LOCAL
возникает. Однако при ее повторном использовании в той же
программе, метка AAAAA появляется второй раз. Ассемблер не может
допустить двух меток с одним и тем же именем в одной программе и
отмечает это как ошибку.
Проблема будет разрешена, если объявить метку AAAAA локальной
(LOCAL) для данной макрокоманды. Тогда ассемблер установит сформи-
рованное им уникальное имя для каждого случая применения метки
AAAAA. Встретив имя типа LOCAL впервые, ассемблер меняет его на имя
"??0000". Во второй раз - на имя "??0001" и т.д. Каждое имя в
транслируемой программе будет уникальным, поэтому никаких ошибок не
возникнет. На Фиг. 6.8 показано использование оператора LOCAL.
Здесь макрокоманда PAUSE устанавливает счетчик цикла и затем орга-
низует цикл с помощью метки, объявленной LOCAL. Данная макрокоманда
позволяет организовывать в выполнении программы паузу переменной
длительности. Если в макрокоманде необходимо использовать описатель
LOCAL, то он должен быть первым оператором макрокоманды, сразу же
следуя за оператором MACRO.
Символ Значение
---------------------------------------------------------------
;; Комментарий, имспользуемый только в макроопределении
& Соединение текста с параметром
! Вводить следующий символ без интерпретации
% Преобразовать следующее выражение в значение
---------------------------------------------------------------
Фиг. 6.9 Макросимволы
Существуют специальные символы, которые помогут вам управлять
макрокомандами и их параметрами. В таблице на Фиг. 6.9 показаны
четыре таких символов с объяснением их значений.
На Фиг.6.10 приведен ассемблерный листинг программы, в которой
эти символы применяются. Мы уже видели символ ";;" в некоторых
макрокомандах сопроцессора 8087. Этот специальный указатель поля
комментариев указывает макропроцессору на необходимость исключить
поле комментариев при расширении данной макрокоманды. Это позволит
включать в макрокоманду комментарии, не допуская их появления при
каждом расширении данной макрокоманды. Применение символа "&" мы
также встречали в программе на Фиг. 6.7.
Символ "!" позволяет вам в качестве следующего за ним знака
поставить любой символ. Это понадобится, если вы захотите включить
в макрокоманду какой-либо специальный символ, например, "%", чтобы
он не вызывал при этом никакой макрооперации. И наконец, оператор
"%" преобразует символическое имя в числовое значение, которое это-
му имени в данный момент соответствует. Вы можете воспользоваться
этой возможностью для ведения нумерации при генерации макрокоманды.
В нашем примере на Фиг. 6.10 макрокоманда нумерует выводимые
сообщения в соответствии со значением символического имени VALUE.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:15
Фиг. 6.10 Специальные символы в макрокомандах Page 1-1
PAGE ,132
TITLE Фиг. 6.10 Специальные символы в макрокомандах
= 0000 VALUE EQU 0
EXAMPLE MACRO PARAMETER
DB 'MSG&PARAMETER' ;; Комментарий появится только в определении
INC AX
ENDM
0000 CODE SEGMENT
ASSUME CS:CODE
EXAMPLE %VALUE
0000 4D 53 47 30 1 DB 'MSG0' ;
0004 40 1 INC AX
0005 CODE ENDS
END
Фиг. 6.10 Специальные символы в макрокомандах
Макроопределения
Макроопределения
Макрокоманда - это программный инструмент, который позволяет вам
создавать собственные операции ассемблера. На самом деле макро-
определения относятся к механизму препроцессора. Макропроцессор
позволяет определять новые коды операций для процессора. В этом
определении вы, в частности, сообщаете ассемблеру текст выполняемой
операции. Когда ассемблер встречает этот вновь определенный код
операции, он обращается к сохраненному определению макрокоманды и
помещает в транслируемый участок программы текст из этого
определения. Например, в программе могут быть определены в качестве
макрокоманд часто используемые последовательности команд. Каждый
раз, когда эти команды должны быть вставлены в текст программы,
программист может вместо этого воспользоваться макрокомандой.
В использовании макрокоманды можно выделить два шага. На первом
шаге макрокоманда определяется в программе. Программист присваивает
ей имя и определение. Определение состоит из из операций ассемблера
и команд, которые будут генерироваться каждый раз при появлении
имени макрокоманды. Второй шаг - применение макрокоманды. Это
происходит когда ассемблер встречает ее имя в качестве кода
операции. Ассемблер заменяет это имя указанными в определении
командами.
Возьмем в качестве примера команды сопроцессора 8087, который
мы обсудим в глве 7. В написании программ с использованием команд
числового процессора 8087 возникают некоторые трудности. В
макроассемблере отсутствуют коды операций 8087. Для использования
8087 вы должны сформировать его команды с помощью либо оператора
определения данных, либо кодов операций WAIT и ESC. Лучше всего это
делать через определение макрокоманды, что позволит вам писать
команды 8087. После этого программа может пользоваться командами
8087, хотя они и не входят в язык ассемблера.
В программировании на языке ассемблера макрокоманды исполь-
зуются наиболее часто. Хотя видимых причин не применять макропро-
цессор в языках высокого уровня нет, там макрокоманды встречаются
довольно редко. Макроассемблер для IBM PC поддерживает
макрокоманды. Как мы уже отмечали, существует две версии
ассемблера. Малый ассемблер, ASM, не поддерживает это средство.
Полный ассемблер, MASM, допускает все макрооперации, которые обсуж-
даются в этой главе. Для использования MASM ваш персональный
компьютер должен иметь как миимум 96K оперативной памяти.
Простейшая макрокоманда, которую можно использовать как код
операции 8087, - FENI. Макроассеблер 8088 не распознает ключевого
слова FENI, которое в действительности является командой для 8087.
Фиг. 6.1 показывает два шага макро-процесса: определение
макрокоманды FENI и ее последующий вызов в программе. Фиг.6.1
состоит из двух частей: часть (a) - это исходный файл для
программы, а часть (b) содержит листинг ассемблера для нее. Два
варианта на Фиг. 6.1 разделены, чтобы показать, какой из них
написан программистом, а какой сгенерирован макропроцессором.
Программа определяет макрокоманду с помощью ключевого слова
MACRO. На Фиг. 6.1 макроопределение выглядит так:
FENI MACRO
;---- Тело макрокоманды
ENDM
Оператор MACRO является кодом псевдооперации. Эта конкретная псев-
дооперация сообщает ассемблеру, что начинается определение макроко-
манды. В поле имени операции указано это имя, которое программа
приписывает определяемой макрокоманде, в нашем случае FENI. Команды
PAGE ,132
TITLE Фиг. 6.1 Макрокоманда
FENI MACRO
DB 0DBH, 0E0H
ENDM
CODE SEGMENT
ASSUME CS:CODE
FENI
CODE ENDS
END
Фиг. 6.1 (a) Исходный файл для программы
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:02:38
Фиг. 6.1 Макрокоманда Page 1-1
PAGE ,132
TITLE Фиг. 6.1 Макрокоманда
FENI MACRO
DB 0DBH, 0E0H
ENDM
0000 CODE SEGMENT
ASSUME CS:CODE
FENI
0000 DB E0 1 DB 0DBH, 0E0H
0002 CODE ENDS
END
Фиг. 6.1 (b) Листинг ассмблера программы
Фиг.6.1 Макроопределение.(a) исходный файл; (b) листинг ассемблера.
(или действия ассемблера), которые будут заменять имя макрокоманды,
следуют за строкой заголовка. Наконец, ключевое слово ENDM
указывает ассемблеру на конец определения. Текст между операторами
MSCRO и ENDM называется телом макрокоманды. На Фиг. 6.1 телом
макрокоманды FENI является оператор определения байтов. Поскольку в
8088 нет команды, которая соответствовала бы команде FENI код
машинного языка для этой команды должен состять из операторов DB.
Важно заметить, что во время определения макрокоманды код
машинного языка еще не генерируется. Это можно утверждать, потому
что колонки адреса и данных в листинге ассемблера пусты. Когда
ассемблер впервые встречае макроопределение, он сохраняет его для
дальнейшего использования. Затем программа на Фиг. 6.1 привлекает
макрокоманду FENI. Программист использует имя макрокоманды FENI как
если бы это был код оперции ассемблера типа CLD или DAA, а
ассемблер обращается к сохраненному определению макрокоманды FENI.
Ассемблер берет текст из тела макроопределения и помещает его в той
же позиции транслируемой программы. Знак "+", появляющийся слева от
оператора DB в распечатке ассемблера, указывает на то, что эта
строка вставлена макропроцессором. Если сравнить исходный текст с
ассемблируемым, вы увидите в исходном тексте только команду FENI, в
то время как на листинге ассемблера за командой FENI следует тело
макрокоманды. В данном случае оно представлено одним оператором DB.
Этот простой пример демонстрирует большие возможности
макропроцессора. Возникла необходимость в коде операции FENI,
который не предусмотрен ассемблером. При отсутствии механизма
макрокоманд программист был бы вынужден вместо операции FENI каждый
раз записывать ее код:
DB 0DBH, 0E0H
Имея же в распоряжении такой механизм, можно определить
макрокоманду FENI и в дальнейшем в этой же программе использовать
как код операции только ее. Для использования подобных макрокоманд
есть две серьезные причины. Во-первых, они облегчают написание
программы. Во-вторых, при чтении текста программы оператор FENI,
выглядит гораздо более осмысленным, чем DB 0DBH,0E0H.
Макрокоманду можно сравнить с подпрограммой. Подпрограмма - это
участок программы, определяемый в единственном месте программы,
Программа может передать управление подпрограмме из любой своей
точки. Использование подпрограмм экономит время написания и объем
памяти, занимаемый программой. Вместо того, чтобы каждый раз
переписывать команды подпрограммы, когда необходимо ее выполнение,
вы вставляете ее вызов. Подпрограмма выполняет свою определенную
функцию, и управление возвращантся в точку вызова.
Макрокоманда точно также определяется в ассемблируемой
программе в единственном месте. После того, как макрокоманда
определена, ее можно привлечь ("вызвать") в любой точке
транслируемой программы. Использование макрокоманды экономит время
составления программ и место, занимаемое исходным файлом. Вместо
того, чтобы всякий раз, когда в них возникает необходимость,
переписывать входящие в макрокоманду команды, в программу
вставляется вызов макрокоманды. Ассемблер генерирует заданные в
определении команды и переходит к обработке следующего кода
операции.
Разница между макрокомандой и подпрограммой заключается в
моменте их использования. Макрокоманда является операцией текстовой
обработки. Она определяется и "выполняется" во время
ассемблирования. Выполнение макрокоманды состоит в замене имени
макрокоманды текстом, составляющим тело макрокоманды. Подпрограмма
же хотя и определяется при ассемблировании, но выполняется не
раньше, чем сама программа. Мы будем говорить, что макрокоманда
выполняется при ассемблировании, а подпрограмма - во время
выполнения программы.
Лучший способ отличить макрокоманду от подпрограммы - это
запомнить, когда они проявляются. На самом деле, макропроцессор не
является атрибутом языка программирования. Допустим, вы - адвокат,
и составляете завещания для множества разных людей. Так как
завещания часто похожи, то вы могли бы задать набор
"макрозавещаний", которые будут содержать совпадающие части
составляемых завещаний. Первая часть завещания, где перечислены
участвующие в нем стороны, будет уникальной. Оставшаяся часть будет
состоять из различных макрозавещаний, охватывающих стандартные
куски завещаний. Результатом работы "процессора завещаний" будет
текстовый документ. Макрозавещания раскрываются и образуют
стандартную часть завещания. Вам остается только заполнить
переменные фрагменты документа между макрозавещаниями.
Но если макрокоманды и подпрограммы во многих отношениях так
похожи, то зачем использовать вместо процедуры макрокоманду?
Действительно, во многих случаях применима любая из них.
Последовательность команд может быть определена и как макрокоманда,
и как подпрограмма. Когда вам потребуется эта последовательность,
вы можете вызвать соответственно макрокоманду или подпрограмму.
Какую именно - зависит от того, как вы определили данную
последовательность команд. Выбор последнего задается соображениями
времени выполнения программы и объема занимаемой ею памяти. В
большинстве случаев использование макрокоманд приводит к более
длинным программа, т.е. для реализации одной и той же функции
требуется больше байтов объектного кода. Однако такая программа
выполняется быстрее так как отсутствуют временные издержки,
связанные с вызовом подпрограммы и возвратом в программу каждый
раз, когда требуется данная последовательность команд. Для
минимизации размера программы слудет использовать подпрограммы.
Чтобы иметь программу с максимальным быстродействием, вы
пользуетесь макрокомандами.
В случае макрокоманды FENI на Фиг. 6.1, выбор в пользу
макрокоманды очевиден. Здесь соответствующий участок программы в
качестве макрокоманды не только выполняется быстрее нежели, в
качестве подпрограммы, но и занимает меньше памяти. Команда CALL
для близкой процедуры требует три байта. Макрокоманды FENI - только
два байта. В случае макрокоманд для процессора 8087 для реализации
тех же функций через процедуры потребовалось бы больше байтов
объектного кода. Кроме того использование макрокоманд сокращает
время выполнения программы.
Сегменты
Сегменты
Ранее уже рассматривался оператор 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.00 1/1/80 04:03:25
Фиг. 6.12 Использование сегментов Page 1-1
PAGE ,132
TITLE Фиг. 6.12 Использование сегментов
0000 DATA SEGMENT AT 40H
0017 ORG 17H
0017 ?? KB_FLAG DB ?
= 0040 CAPS_STATE EQU 40H
0018 DATA ENDS
0000 VIDEO SEGMENT AT 0B800H
009E ORG 158
009E ?? INDICATOR DB ?
009F VIDEO ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 CAPS PROC FAR
0000 1E START: PUSH DS ; Адрес возврата
0001 B8 0000 MOV AX,0
0004 50 PUSH AX
0005 B8 ---- R MOV AX,DATA ; Адрес сегмента DATA
0008 8E D8 MOV DS,AX
ASSUME DS:DATA
000A B8 ---- R MOV AX,VIDEO ; Адрес сегмента VIDEO
000D 8E C0 MOV ES,AX
Фиг. 6.12 Расположение сегмента (начало)
ASSUME ES:VIDEO
000F DISPLAY_CAPS:
000F B0 18 MOV AL,18H ; Символ "стрелка вверх" имеет код 18H
0011 F6 06 0017 R 40 TEST KB_FLAG,CAPS_STATE ; Определение состояния клавиши CAPS
0016 75 02 JNZ CAPS_LOCK
0018 B0 19 MOV AL,19H ; Символ "стрелка вниз" имеет код 19H
001A CAPS_LOCK:
001A 26: A2 009E R MOV INDICATOR,AL ; Вывод в верхний левый угол экрана
001E B4 06 MOV AH,6 ; Функция ДОС ввода с клавиатуры
; и вывода на дисплей
0020 B2 FF MOV DL,0FFH ; Направление - ввод с клавиатуры
0022 CD 21 INT 21H
0024 3C 00 CMP AL,0 ; Проверка на наличие символа
0026 74 E7 JZ DISPLAY_CAPS ; Нет символа
0028 3C 25 CMP AL,'%' ; Проверка на символ конца
002A 74 08 JE RETURN
002C B4 02 MOV AH,2 ; Функция вывода на дисплей
002E 8A D0 MOV DL,AL ; Выводимый символ
0030 CD 21 INT 21H
0032 EB DB JMP DISPLAY_CAPS ; Повторение
0034 RETURN:
0034 CB RET ; Возврат в ДОС
0035 CAPS ENDP
0035 CODE ENDS
END START
Фиг. 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.00 1/1/80 04:03:31
Фиг. 6.13 Структура Программного Префикса Page 1-1
PAGE ,132
TITLE Фиг. 6.13 Структура Программного Префикса
0000 PROGRAM_SEGMENT_PREFIX SEGMENT
0000 0002[ INT_20 DB 2 DUP (?)
??
]
0002 ???? MEMORY_SIZE DW ?
0004 0005[ LONG_CALL DB 5 DUP (?)
??
]
0009 ???????? TERMINATE_ADDR DD ?
000D ???????? CTRL_BREAK DD ?
005C ORG 05CH
005C 0010[ FCB1 DB 16 DUP (?)
??
]
006C ORG 06CH
006C 0010[ FCB2 DB 16 DUP (?)
??
]
0080 ORG 080H
0080 0080[ DTA DB 128 DUP (?)
??
]
0100 PROGRAM_SEGMENT_PREFIX ENDS
0000 CODE SEGMENT
ASSUME CS:CODE,DS:PROGRAM_SEGMENT_PREFIX
0000 A1 0002 R MOV AX,MEMORY_SIZE
0003 CODE ENDS
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. Если же хотите использовать первый метод распределения
памяти, то вам потребуется другой способ определения структур
данных. Такой способ объявления данных как раз рассматривается в
следующем разделе.
Структуры
Структуры
Структура данных - это организация данных, которая имеет для
программиста определенный смысл. Как показывает опыт, мы определяем
структуры данных когда одна и та же совокупность данных
используется более чем одной программой или программистами.
Благодаря определению, обе стороны имеют четкий образ этих данных.
Если программа A передает некотрые данные программе B, то
определение структуры данных гарантирует, что каждая из программ
ищет данные в одном и том же месте.
У нас уже был хороший пример структуры данных. Блок управления
файлом FCB является структурой данных. Блок FCB используется
программами для обмена информацией о файле с DOS. В блоке FCB
содержатся такие важные данные об обрабатываемом файле, как номер
текущей записи, длина файла и т.д. Кроме того, в блоке FCB имеется
зарезервированное поле, которое содержит информацию, используемую
только DOS. В блоке FCB находится вся информация, необходимая для
DOS и прикладных программ. Эта структура данных служит для передачи
параметров файла между DOS и прикладной программой.
Теперь нужно найти такой способ определения структур данных,
чтобы программа могла с удобством ими пользоваться. В
Макроассемблере фирмы IBM имеется оператор STRUC, позволяющий
определять структуру данных. С точки зрения программиста структура
данных выглядит как еще один сегмент. Определение данных
ассемблируется так же, как и обычные операторы данных, и описание
структуры, как и описание сегмента, заканчивается оператором ENDS.
Однако в действительности структура не генерирует данные. Оператор
STRUC определяет структуру данных для ассемблера. В дальнейшем имя
этой структуры данных используется в ассемблируемой программе для
генерации соответствующей области данных.
Если рассматривать оператор STRUC описанным выше образом, то он
больше похож на оператор MACRO. Программа определяет структуру
данных в одном месте, а ее вызов осуществляет позднее. Фактическая
генерация данных происходит при вызове структуры. Фиг. 6.14 поможет
понять работу оператора STRUC.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:36
Фиг. 6.14 Структуры Page 1-1
PAGE ,132
TITLE Фиг. 6.14 Структуры
FCB STRUC
0000 00 DRIVE DB 0 ; Номер устройства
0001 20 20 20 20 20 20 20 FILE_NAME DB ' ' ; Имя файла
20
0009 20 20 20 FILE_EXT DB ' ' ; Тип файла
000C 0000 CURRENT_BLOCK DW 0 ; Номер текущего блока
000E 0080 RECORD_SIZE DW 80H ; Логический размер записи
0010 00000000 FILE_SIZE DD 0 ; Размер файла в байтах
0014 0000 DATE DW 0 ; Дата последнего изменения
Фиг. 6.14 Структуры (начало)
0016 000A[ RESERVED DB 10 DUP (?) ; Зарезервировано ДОС
??
]
0020 00 SEQ_NUMBER DB 0 ; Номер текущей записи
0021 00000000 RANDOM_NUMBER DD 0 ; Номер записи при прямом
; доступе
0025 FCB ENDS
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 01 INPUT FCB <1,'FIG6-14','INP'>
0001 464947362D313420
0009 494E50
000C 0000
000E 0080
0010 00000000
0014 0000
0016 000A[
??
]
0020 00
0021 00000000
0025 02 OUTPUT FCB <2,'EXAMPLE','TST'>
0026 4558414D504C4520
002E 545354
0031 0000
0033 0080
0035 00000000
0039 0000
003B 000A[
??
]
0045 00
0046 00000000
004A STRUCTURES PROC FAR
004A 1E PUSH DS ; Установка адреса возврата
004B B8 0000 MOV AX,0
004E 50 PUSH AX
004F 0E PUSH CS ; Установка DS на сегмент CODE
0050 1F POP DS
ASSUME DS:CODE
0051 8D 16 0000 R LEA DX,INPUT ; Открытие вводимого файл
Фиг. 6.14 Структуры (продолжение)
0055 B4 0F MOV AH,0FH
0057 CD 21 INT 21H
0059 8D 16 0025 R LEA DX,OUTPUT ; Создание выводимого файла
005D B4 16 MOV AH,16H
005F CD 21 INT 21H
0061 8D 1E 0000 R LEA BX,INPUT
0065 C7 47 0E 0010 MOV [BX].RECORD_SIZE,16 ; Установка размера записи для ввода
006A C6 47 20 01 MOV [BX].SEQ_NUMBER,1 ; Пропуск первой записи
006E C7 06 0033 R 0010 MOV OUTPUT.RECORD_SIZE,16 ; Установка размера записи для
; вывода
0074 8D 16 0000 R LEA DX,INPUT ; Чтение второй записи из файла
0078 B4 14 MOV AH,14H
007A CD 21 INT 21H
007C 8D 16 0025 R LEA DX,OUTPUT ; Вывод введенной записи
0080 B4 15 MOV AH,15H
0082 CD 21 INT 21H
0084 B4 10 MOV AH,10H ; Закрытие выводимого файла
0086 CD 21 INT 21H
0088 CB RET
0089 STRUCTURES ENDP
0089 CODE ENDS
END
Microsoft (R) Macro Assembler Version 4.00 4/16/89 23:15:19
Фиг. 6.14 Структуры Symbols-1
Structures and Records:
N a m e Width # fields
Shift Width Mask Initial
FCB . . . . . . . . . . . . . . 0025 000A
DRIVE . . . . . . . . . . . . 0000
FILE_NAME . . . . . . . . . . 0001
FILE_EXT . . . . . . . . . . . 0009
CURRENT_BLOCK . . . . . . . . 000C
RECORD_SIZE . . . . . . . . . 000E
FILE_SIZE . . . . . . . . . . 0010
DATE . . . . . . . . . . . . . 0014
RESERVED . . . . . . . . . . . 0016
SEQ_NUMBER . . . . . . . . . . 0020
RANDOM_NUMBER . . . . . . . . 0021
Фиг. 6.14 Структуры (продолжение)
На Фиг. 6.14 приведена очень простая программа, которая
использует файлы системы DOS. Эта программа открывает файл DOS на
носителе в дисководе A:, считывает вторую запись этого файла и
записывает ее в файл на носителе в дисководе B:. Маловероятно,
чтобы вы когда-нибудь применили эту программу чтобы сделать
что-либо существенное, но сейчас она дает нам возможность
использовать структуру данных для блока FCB.
Первая часть программы на Фиг. 6.14 определяет структуру данных
FCB. Оператор языка ассемблера STRUC отмечает начало определения
структуры. Метка FCB является здесь именем данной конкретной
структуры. В примере определяется каждое поле структуры данных FCB.
Обратите внимание, что в столбцах слева ассемблер генерирует
объектный код данной структуры. Однако, при редактировании связей
ассемблированного объектного кода, область данных в программе
отсутствует. Ассемблер распечаьываеь данную структуру данных в
оттранслированном виде исключительно для вашего сведения.
Точно так же, как и вслучае макрокоманды, имя FCB становится
как бы новым оператором языка ассемблера. Первым оператором в
сегменте CODE является вызов структуры данных FCB. В примере этой
структуре данных присваивается имя INPUT. Данная структура FCB
идентифицирует входной набор данных. Заметьте, что в этом операторе
FCB имеются операнды. Они заменяют или перекрывают значения,
которые были включены в исходное определение структуры данных.
Если мы сравним объектный код определения структуры FCB с
объектным кодом структуры INPUT, то увидим, что они различаются по
значениям первых трех полей. В определении структуры данных поле
DRIVE равно 0, в структуре INPUT - 1. Первый операнд в угловых
скобках определения структуры INPUT равен 1. Это значение заменяет
исходно определенное значение 0. Аналогично в данном примере
изменяются значения второго и третьего полей, относящихся к имени
файла. Закрывающая угловая скобка в определении структуры INPUT
завершает процедуру замены значений полей структуры данных.
Оставшаяся часть структуры INPUT идентична определеной в структуре
данных FCB.
Программа может изменять любой из полей структуры FCB, если
определение этого поля содержит только один элемент. В
рассматриваемом примере программа может изменять любое поле
структруыр FCB, за исключением поля RESERVED. Мы определили это
поле поле как 10 отдельных элементов, и оно не может быть изменено.
Аналогично, если поле определено оператором
DB 10,20
то его нельзя перекрыть. При вызове структуры можно изменять только
поля, состоящие из единственного элемента. Символьная строка,
включающая несколько символов, рассматривается ассемблером как один
элемент. В данном примере поле FILE_NAME содержит несколько
символов, но является одним элементом, значение которого можно
изменить.
Операнды, перечисленные в угловых скобках, заменяют операнды,
входящие в определение, по позиционному принципу, как и в
макрокоманде. Если вы не хотите модифицировать заданное в
определении значение, но желаете изменить следующее за ним поле, то
в список значений нужно включить пустой параметр. Например, для
модификации полей FILE_NAME и CURRENT_BLOCK, оставляя в то же время
по умолчанию заданные значения полей DRIVE и FILE_EXT, структуру
FCB следует вызвать оператором:
EXAMPLE FCB <,NEWNAME,,12>
Первый параметр пуст, так что ассемблер использует для него
значение по умолчанию. В следующем поле NEWNAME заменяет строку
пробелов. Для значения поля FILE_EXT по умолчанию используется
строка пробелов, и наконец, нулевое значение поля CURRENT_BLOCK
заменяется на 12.
В следующей исходной программной строке на Фиг. 6.14 программа
определяет структуру FCB под именем OUTPUT для выходного файла.
Здесь снова модифицируются первые три поля определения данных путем
включения новых значений в поля операндов оператора FCB.
Выигрыш от использования структур данных проявляется в
действительных командах программы. Программа может ссылаться на
имена INPUT и OUTPUT так же, как и на любые другие метки в
программе. Вы можете видеть это, в том участке программы, где
открывается входной файл INPUT и оператор
LEA DX,INPUT
используется для установки адреса входной структуры данных FCB.
В программе можно использовать каждое из указанных полей
структуры данных. Значению каждого имени соответствует смещение
какого-либо поля в структуре данных. Например, программа помещает в
регистр BX адрес FCB INPUT. После этого программа обращается к
полям RECORD_SIZE и SEQ_NUMBER в режиме адресации по базе. Так как
регистр BX уже указывает на структуру данных FCB, то нужно задать
смещение относительно этой базы. Способ адресации
[BX].RECORD_SIZ
указывает ассемблеру, что в команде, которую он должен
сгенерировать, смещение поля RECORD_SIZE складывается со значением
базы, хранящимся в регистре BX. Если вы рассмотрите соответствующие
команды на машинном языке, то увидите, что в них присутствуют
смещения для полей RECORD_SIZE (0EH) и SEQ_NUMBER (20H). Символ "."
идентифицирует имена полей как смещения в структуре данных.
Помимо режимов адресации по базе и индексу можно использовать
структуру данных и для прямой адресации. Следующий участок
программы непосредственно изменяет поле RECORD_SIZE в FCB OUTPUT.
Программа именует это поле OUTPUT.RECORD_SIZE. Имя OUTPUT
определяет конкретную структуру данных, а RECORD_SIZE - имя поля в
этой структуре данных.
Прежде, чем покончить с этим примером, посмотрим, какой
информацией о структуре данных располагает ассемблер. Фиг. 6.14
включает в себя фрагмент таблицы символических имен. Ассемблер
выделяет один из разделов этой таблицы для структур и записей.
Ассемблер показывает вам всю информацию о структуре данных, которой
располагает. Этот раздел имеет заголовок "Structures and records"
("Структуры и записи"). В первой строке этого раздела для структуры
FCB из нашего примера указано, что эта структура имеет длину 25H
байт и содержит 0AH полей. Далее ассемблер перечисляет все эти
поля, печатая их с отступом по отношению к имени структуры. Для
каждого из полей приводится значение соответствующего смещения. Для
структур ассемблер использует два столбца, обозначеные "width"
(ширина) и "#dfields" (число полей). Вторая строка в меток колонок
используется для записей. Структуры данных, которые ассемблер
интерпретирует как записи, будут рассмотрены в следующем разделе.
Программа, приведенная на Фиг. 6.14, не делает ничего
полезного. Кроме того, в ней нет никакой обработки ошибок. Однако
она хорошо иллюстрирует применение оператора STRUC. Этот способ
определения данных особенно подходит для часто используемых
структур данных. Использование имен полей в качестве значений
смещений очень удобно при модификации структур данных на этапе
разработки программы. Если вы внесете изменения в структуру данных,
то ассемблер автоматически изменит значения смещений при повторном
ассемблировании программы. Кроме того, использование структур
данных делает программу на языке ассемблера более читабельной и
понятной.
Записи
Записи
Рассмотренные в предыдущем разделе структуры предназначены для
многобайтовых данных. Но в некоторых случаях требуется побитовое
определение объектов данных. Для таких случаев в Макроассемблере
имеется механизм описания данных, который называют записью
(RECORD). Действие оператора RECORD аналогичны действиям операторов
STRUC и MACRO. Оператор RECORD задает определенную конфигурацию
данных. Присвоенное записи имя становится для ассемблера еще одним
оператором. Вы можете использовать это имя записи для задания
специальных конфигураций данных. От оператора STRUC оператор RECORD
отличается тем, что он определяет объекты на уровне отдельных
битов. Каждому из полей оператор RECORD присваивает имя и указывает
его ширину в битах. Оператор RECORD можно использовать для
формирования битовых полей длиной до 16 бит.
Здесь мы опять воспользуемся примером. На Фиг. 6.15 приведена
еще одна ничего не делающая программа, связанная с установоением
даты изменения файла. В определении Блока управления файлом
содержится 16-битовое поле, в котором содержится дата формирования
или последнего изменения данного файла операционной системой. При
открыти файла DOS заполняет это поле в блоке FCB на основе
информации из каталога дискеты. В 16-ти батих поля даты
закодированы год, месяц и число. Из приведенного на Фиг. 6.15
оператора RECORD видно строение этого слова данных.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:43
Фиг. 6.15 Записи Page 1-1
PAGE ,132
TITLE Фиг. 6.15 Записи
DATE_WORD RECORD YEAR:7,MONTH:4,DAY:5
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 FCB LABEL BYTE
0000 01 DRIVE DB 1 ; Номер устройства
0001 46 49 47 36 2D 31 35 FILE_NAME DB 'FIG6-15 ' ; Имя файла
20
0009 41 53 4D FILE_EXT DB 'ASM' ; Тип файла
000C 0000 CURRENT_BLOCK DW 0 ; Номер текущего блока
000E 0080 RECORD_SIZE DW 80H ; Логический размер записи
0010 00000000 FILE_SIZE DD 0 ; Размер файла в байтах
0014 0000 DATE DATE_WORD <> ; Дата последнего изменения
0016 000A[ RESERVED DB 10 DUP (?) ; Зарезервировано ДОС
??
]
0020 00 SEQ_NUMBER DB 0 ; Номер текущей записи
0021 00000000 RANDOM_NUMBER DD 0 ; Номер записи при прямом
0025 RECORDS PROC FAR
0025 1E PUSH DS ; Адрес возврата
0026 B8 0000 MOV AX,0
0029 50 PUSH AX
002A 0E PUSH CS ; Установка DS на сегмент CODE
002B 1F POP DS
ASSUME DS:CODE
002C 8D 16 0000 R LEA DX,FCB ; Открытие файла
0030 B4 0F MOV AH,0FH
0032 CD 21 INT 21H
0034 A1 0014 R MOV AX,DATE
0037 25 FE00 AND AX,MASK YEAR ; Выделение года
003A B1 09 MOV CL,YEAR ; Сдвиг вправо
003C D3 E8 SHR AX,CL
003E 8A F8 MOV BH,AL ; Сохранение года в регистре BH
0040 A1 0014 R MOV AX,DATE
0043 25 01E0 AND AX,MASK MONTH
0046 B1 05 MOV CL,MONTH
0048 D3 E8 SHR AX,CL
004A 8A D8 MOV BL,AL ; Сохранение месяца в регистре BL
Фиг. 6.15 Записи (начало)
004C A1 0014 R MOV AX,DATE
004F 25 001F AND AX,MASK DAY ; Сохранение дня в регистре AL
0052 CB RET
0053 RECORDS ENDP
0053 CODE ENDS
END RECORDS
Фиг. 6.15 Записи (продолжение)
В примере на Фиг. 6.15 записи присваивается имя DATE_WORD. В
поле операндов оператора RECORD видно, что в записи DATE_WORD
имеется три поля. Первые 7 бит содержат год (YEAR), следующие 4
бита - месяц (MONTH) и последние 5 бит - число (DAY). Так же, как и
в случае оператора MACRO, данная исходная программа просто
определяет тип конкретной записи, названной DATE_WORD. Оператор
RECORD не формирует соответствующих данных в памяти до тех пор,
пока вы не используете имя записи в качестве ассемблерного
оператора.
В рассматриваемом примере запись DATE формируется в блоке FCB с
использованием определения записи DATE_WORD. Метка DATE обозначает,
а запись DATE_WORD формирует эту 16-битовую область данных. Как мы
увидим ниже, существуют способы определения для полей записей
значений по умолчанию и их изменения. В данном примере мы ничего не
делали для изменения стандартных нулевых значений каждого из
полей.
Приведенная на Фиг. 6.15 программа иллюстрирует не только
структуру оператора RECORD, но и некоторые операции, которые
упрощаются благодаря этому оператору. В первой части программы
открывается файл, имя которго указано в блоке FCB. Остальная часть
программы извлекает из FCB информацию о дате и перебрасывает
отдельные поля в регистры микропроцессора 8088.
Во-первых, программа выделяет в дате из блока FCB значение
года: переслав слово данных DATE в регистр AX, программа обнуляет в
нем командой AND значения, относящиеся к месяцу и числу. Обратите
здесь внимание на непосредственный операнд MASK YEAR. Так как YEAR
- это поле записи, то оператор MASK возвращает значение, которое
выделяет в данном слове поле YEAR. В данном случае это значение
равно 0FE00H. Первые 7 бит в нем - единицы, а остальные биты
обнулены. Это значение маски соответстувет битам, которые формируют
значения YEAR в слове данных. В результате логического умножения
данного значения и всей остальной записи остается только поле
YEAR.
Следующими командами программа перемещает поле YEAR в правый
конец слова. Полю YEAR соответствует значение, равное сдвигу,
который необходим для перемещения данного поля до правой границы
слова. В данном случае это значение равно девяти. Сдвиг вправо на 9
бит обращает значение года в число в регистре AL. (Следует помнить,
что DOS кодирует год в виде числа в перделах от 0 до 119. Эти
значения соответствуют годам от 1980 до 2099).
В последующей группе команд программа выделяет из записи поле
MONTH. Здесь также используются оператор MASK и значения сдвигов,
из записи DATE_WORD. Подобным образом программа выбирает из записи
значение поля DAY.
В данном примере не выполняется никакой полезной работы, так
как записанные в регистры значения оказываются потерянными при
возвращении управления DOS. Однако вы можете запустить программу
через отладчик и установить точку прерывания на команде возврата.
Отладчик выводит на дисплей содержимое регистров BH, BL и AL, так
что вы можете увидеть дату. Более практичная программа после
считывания значений, относящихся к дате, преобразовывала бы их в
код ASCII для вывода на экран. Либо вы могли бы оформить нашу
программу как процедуру, которая вырабатывает информацию о дате для
другой программу.
Имеется еще несколько особенностей операции RECORD, которые
следует рассмотреть. На Фиг. 6.15 приведен фрагмент таблицы
символических имен из ассемблерного листинга. В этой таблице
содержится информация, которая имеется у ассемблера о каждом из
полей записи. В этой таблице нас будет интересовать второй ряд
заголовков: "Shift Width Mask Initial" (Сдвиг Длина Маска Начальное
значение"). Как видно из таблицы символических имен, запись
DATE_WORD имеет длину 16 бит и состоит из трех полей. Каждое поле
имеет четые атрибута. Значение сдвига равно числу битов в записи,
остающихся справа от поля. Это значение указывает ассемблеру,
насколько нужно сдвинуть данное поле, чтобы выровнять его на правый
край. Значение маски служит для выделения поля в записи. Цифра 1 в
слове маски показывает, что соответствующая позиция относится к
данному полю.
Ассемблер может оперировать значением длины любого поля записи.
Вы можете задать длину поля при ассемблировании с помощью оператора
WIDTH. Например, команда
MOV AL,WIDTH YEAR
в нашем примере помещает в регистр AL значение, равное семи.
Столбец начальных значений в таблице символических имен
показывает, какие значения ассемблер вставляет при формировании
записи. Можно задавать записи с начальными значениями, отличными от
нуля. Имеется также возможность изменить эти значения при генерации
области данных. Для задания начальных значений вы после указания в
операторе RECORD каждого из полей ставите знак равенства и
соответствующее значение. Запись DATE_WORD с начальными значениями,
соответствующими 1 января 1983 года, будет иметь вид:
DATE_WORD RECORD YEAR:7=3, MONTH:4=1, DAY:5=1
Эти значения можно переназаначить точно таким же способом, как
и в случае структур. Когда вы формируете запись, в угловых скобках
содержатся конкретные значения для данной генерации. Если нужно,
чтобы дата была 5 января 1984 года, то вы можете сгенерировать
запись следующим образом:
DATE DATE_WORD <4,,5>
Как и в случае макрокоманд или структур, параметры здесь
являются позиционно-зависимыми. Так как параметр, соответствующий
месяцу, не указан, то ассемблер воспользуется для него начальным
значением, указанным в операторе RECORD.
Обратите внимание, что программа на Фиг. 6.15 определяет блок
FCB, не полбзуясь оператором STRUC, рассмотренным в предыдущем
разделе. Мы не могли воспользоваться оператором STRUC, потому что
данные каждого поля структуры должны определяться одним из
операторов типа DEFINE. Мы не можем использовать имя записи в
качестве одного из полей структуры. Тот способ, который мы
применили на Фиг. 6.15, - один из позволяющих обойти эту
трудность.
Можно решить эту же задачу другим способом. Хотя ассемблер и не
генерирует область данных, пока имя записи не использовано в
программе в качестве оператора, но он хранит описания полей,
указанных в операторе RECORD. Это позволяет определить в программе
запись DATE_WORD, не используя ее для задания поля DATE структуры
данных. Это все равно, что определить макрокоманду, но не вызывать
ее. Остальная часть программы остается без изменений. Имена
различных полей записи DATE_WORD имеют в программе смысл и могут
использоваться как параметры при сдвиге и маскировании.
То же самое верно и для оператора STRUC. Определение структуры
задает для ассемблера значения смещений, даже если вы не вызываете
затем эту структуру для формирования области данных. Вы можете
воспользоваться этим для размещения блока FCB по умолчанию по
адресу 05CH в префиксе программного сегмента. Так как блок FCB
существует всегда, то нет необходимости использовать эту структуру
для генерации соответствующей области данных. Программы приведенные
на Фиг. 6.16 и 6.15, почти идентичны, за исключением некотрых
деталей. В программе на Фиг. 6.16 блок FCB определяется не
последовательностью команд типа DEFINE, а оператором STRUC.
Обратите внимание, что при ассемблировании данной программы не
называется ни запись DATE_WORD, ни структура FCB: они служат лишь
для задания смещений в области данных.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:49
Фиг. 6.16 Структуры и записи Page 1-1
PAGE ,132
TITLE Фиг. 6.16 Структуры и записи
DATE_WORD RECORD YEAR:7,MONTH:4,DAY:5
FCB STRUC
0000 00 DRIVE DB 0 ; Номер устройства
0001 20 20 20 20 20 20 20 FILE_NAME DB ' ' ; Имя файла
Фиг. 6.16 Структуры и записи (начало)
20
0009 20 20 20 FILE_EXT DB ' ' ; Тип файла
000C 0000 CURRENT_BLOCK DW 0 ; Номер текущего блока
000E 0080 RECORD_SIZE DW 80H ; Логический размер записи
0010 00000000 FILE_SIZE DD 0 ; Размер файла в байтах
0014 0000 DATE DW 0 ; Дата последнего изменения файла
0016 000A[ RESERVED DB 10 DUP (?) ; Зарезервировано ДОС
??
]
0020 00 SEQ_NUMBER DB 0 ; Номер записи в блоке
0021 00000000 RANDOM_NUMBER DD 0 ; Номер записи в файле
0025 FCB ENDS
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 RECORDS PROC FAR
0000 1E PUSH DS ; Занесение в стек адреса возврата
0001 B8 0000 MOV AX,0
0004 50 PUSH AX
ASSUME DS:CODE ; DS файтически указывает на PSP
0005 BA 005C MOV DX,05CH ; Адрес FCB в PSP
0008 B4 0F MOV AH,0FH ; Открыть файл
000A CD 21 INT 21H
000C BB 005C MOV BX,05CH ; Адрес FCB
000F 8B 47 14 MOV AX,[BX].DATE
0012 25 FE00 AND AX,MASK YEAR ; Выделение года из полной даты
0015 B9 0009 MOV CX,YEAR ; Выравнивание вправо
0018 D3 E8 SHR AX,CL
001A 8A F0 MOV DH,AL ; Сохранение года в регистре DH
001C 8B 47 14 MOV AX,[BX].DATE
001F 25 01E0 AND AX,MASK MONTH
0022 B9 0005 MOV CX,MONTH
0025 D3 E8 SHR AX,CL
0027 8A D0 MOV DL,AL ; Сохранение месяца в регистре DL
0029 8B 47 14 MOV AX,[BX].DATE
002C 25 001F AND AX,MASK DAY ; Сохранение дня в регистре AL
002F CB RET
0030 RECORDS ENDP
0030 CODE ENDS
END RECORDS
Фиг. 6.16 Структуры и записи (продолжение)
И последнее замечание об использовании записей и структур. Мы
применяем эти средства, потому что они позволяют писать программы
не вникая в конкретные детали структуры данных. Используя для
описания данных оператор STRUC, мы можем рассматривать ссылки на
каждое из полей как смещение внутри структуры данных. При этом
программисту не нужно знать фактическое смещение этого поля. То же
самое верно и для записи из битов. Если программа использует
операторы MASK и сдвига, то распределение бит этого поля достаточно
задать только в соответствующем операторе RECORD.
Выигрыш от применения этих программных средств становится
очевидным при разработке больших систем, с которыми связано большое
число программистов или программ. В этом случае вы наверняка будуте
модифицировать структуры данных по мере разработки программ. Если
при написании программ пользоваться описанными структурными
моделями, то изменить их и связанные с ними программы будет
несложно: вы модифицируете определенную структуру данных, а затем
вновь транслируете все программы с использовнием этой структуры.
Сами же программы в изменениях не нуждаются. Можно поступить еще
проще, если хранить структуру данных, как отдельный файл, который
будет включаться в каждую ассемблируемую программу оператором
INCLUDE: таким образом вы будете иметь только одну версию структуры
данных. Описанные средства упрощают процесс создания большой
программы со всеми изменениями, возникающими при ее разработке.
Assembler для начинающих
Арифметические команды
Арифметические команды
Настоящим "сердцем" микросхемы 8087 является блок выполнения
арифметических команд. Сопроцессор 8087 быстро и точно выполняет
Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:07:21
Фиг. 7.16 Арифметические команды сопроцессора 8087 Page 1-1
PAGE ,132
TITLE Фиг. 7.16 Арифметические команды сопроцессора 8087
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 WORD_INTEGER LABEL WORD
0000 SHORT_INTEGER LABEL DWORD
0000 SHORT_REAL LABEL DWORD
0000 LONG_REAL LABEL QWORD
0000 9B D8 C1 FADD st(0),ST(1)
0003 9B D8 C2 FADD st(0),ST(2)
0006 9B D8 C2 FADD ST(0),ST(2)
0009 9B DC C2 FADD ST(2),ST(0)
000C 9B DE 06 0000 R FIADD WORD_INTEGER
0011 9B DA 06 0000 R FIADD SHORT_INTEGER
0016 9B D8 06 0000 R FADD SHORT_REAL
001B 9B DC 06 0000 R FADD LONG_REAL
0020 9B DE C2 FADDP ST(2),ST(0)
0023 9B D8 E2 FSUB st(0),ST(2)
0026 9B DE 26 0000 R FISUB WORD_INTEGER
002B 9B DE EA FSUBP ST(2),ST(0)
002E 9B DC E2 FSUBR ST(2),ST(0)
0031 9B DA 2E 0000 R FISUBR SHORT_INTEGER
0036 9B DE E2 FSUBRP ST(2),ST(0)
0039 9B D8 0E 0000 R FMUL SHORT_REAL
003E 9B DE 0E 0000 R FIMUL WORD_INTEGER
0043 9B DE CA FMULP ST(2),ST(0)
0046 9B D8 F2 FDIV ST(0),ST(2)
0049 9B DA 36 0000 R FIDIV SHORT_INTEGER
004E 9B DE FA FDIVP ST(2),ST(0)
0051 9B D8 FA FDIVR st(0),ST(2)
0054 9B DE 3E 0000 R FIDIVR WORD_INTEGER
0059 9B DE F2 FDIVRP ST(2),ST(0)
005C CODE ENDS
END
Фиг. 7.16 Арифметические команды сопроцессора 8087
вычислительные операции, и не только четыре основных действия -
сложение, вычитание, умножение и деление, но также трансцендентные
и тригонометрические функции.
На Фиг. 7.16 показан ассемблерный листинг программы, состоящей
из некоторых команд для выполнения основных четырех действий.
Данный пример иллюстрирует только работу команды FADD, во всех
возможных комбинациях. Прежде чем рассматривать команды, разберем
возможные варианты их работы. Как видно из Фиг. 7.17, существует
пять различных методов выполнения арифметических команд. На
Фиг. 7.17а показаны пять способов использования данных в
арифметических командах. В случае 1 указывается только код
операции команды. В операции принимают участие вершина стека и
элемент ST1, а результат замещает вершину стека. Заметим, что на
Фиг. 7.17а приведены примеры для каждого случая с использованием
команды сложения. На рисунке также показаны схемы выполнения
вычислительных операций.
Случай 2 иллюстрирует операцию, выполняемую с двумя регистрами
стека сопроцессора 8087. Одним из этих регистров должна быть
вершина стека. Если вершина стека - приемник результата, ее можно
не упоминать, указав только регистр источника. Если же приемником
результата служит какой=то другой регистр, нужно указывать и
источник, и приемник.
Форма команды Пример FADD Действие
-----------------------------------------------------------------
1. Fop FADD ST0<-ST0+ST1
2. Fop STi FADD ST2 ST0<-ST0+STi
Fop ST0,STi FADD ST0,ST2 ST0<-ST0+STi
Fop STi,ST0 FADD ST2,ST0 ST2<-ST0+ST2
3. FopP STi,ST0 FADDP ST2,ST0 ST2<-ST0+ST2,Извлечь из стека
4. Fop вещ.пам. FADD ВЕЩЕСТ ST0<-ST0+ВЕЩЕСТ
5. Flop целоеПам FIADD ЦЕЛОЕ ST0<-ST0+ЦЕЛОЕ
(a)
Операция Действие
----------------------------------------------------------
ADD назначение <- назначение + источник
SUB назначение <- назначение - источник
SUBR назначение <- источник - назначение
MUL назначение <- назначение * источник
DIV назначение <- назначение / источник
DIVR назначение <- источник / назначение
----------------------------------------------------------
(b)
Фиг. 7.17 Арифметические операции (a) Комбинации данных
(b) Арифметические функции для NDP 8087
Случай 3 представляет собой стековую операцию. Команда такого
формата выполняет операцию с двумя стековыми операндами, а затем
уничтожает вершину стека. Так как данные из вершины стека
извлекаются из него после операции, она не может быть приемником
результата этой операции. Если приемник результата - элемент ST1,
то операция становится классической стековой. Классическая
стековая операция удаляет из вершины стека два верхних элемента,
использует их в требуемой операции, а затем помещает результат
назад в стек. Конечно в качестве приемника результата этой
операции может быть указан любой из регистров.
Два оставшихся варианта используют операнды в памяти. В случае
4 операнд в памяти - это короткое или длинное действительное число.
В случае 5 операнд - это короткое целое или целое слово.
Вернувшись к Фиг. 7.16 можно заметить, что в команде FADD
определены только четыре операнда в памяти. Это два целых числа -
короткое и слово, и два действительных числа - короткое и длинное.
Арифметические команды не могут непосредственно работать с
десятичным, длинным целым и временным действительным форматами
чисел, и перед использованием их в счете программа должна загрузить
такие числа в регистр.
На Фиг. 7.17 показаны шесть существующих арифметических команд.
В процессоре 8087 стандартные четыре арифметические операции
дополнены обратными операциями деления и вычитания. Поскольку
сложение и умножение коммутативны, обратных операций для них
вводить не надо; порядок же операндов у вычитания и деления
критичен. И иногда бывает, что число, находящееся в регистре
источника - не то число, которое нужно вычитать из
регистра=приемника; в этом случае вычитание правильно выполнит
обратная операция.
Десять в степени X
Десять в степени X
Второй пример использования сопроцессора 8087 гораздо глубже
раскрывает перед нами его работу. Этот пример - подпрограмма,
которая будет использоваться в дальнейшем. Подпрограмма
предполагает, что исходное число находится в вершине стека; после
возврата из подпрограммы в вершине стека находится число, равное
десяти в степени X. Исходный текст этой подпрограммы приведен на
Фиг. 7.24.
Сопроцессор 8087 не имеет команды возведения 10 в произвольную
степень, но мы можем возводить в любую степень двойки. Поэтому
нужжно пользоваться формулой
10**X = 2**(X*Log2(10))
Первые две команды программы формируют показатель степени двух.
Программа загружает константу Log210, а затем умножает ее на
исходное число X, давая необходимую степень 2, называемую здесь E.
Поле комментариев в примере используется для иллюстрации элементов
стека сопроцессора 8087. Символ "?" означает, что значение
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:40
Фиг. 7.24 Вычисление 10**ST Page 1-1
PAGE ,132
TITLE Фиг. 7.24 Вычисление 10**ST
0000 CODE SEGMENT PUBLIC
ASSUME CS:CODE,DS:CODE
PUBLIC TEN_TO_X
0000 ???? OLD_CW DW ?
0002 ???? NEW_CW DW ?
;--------------------------------------------
; Эта программа извлекает число с вершины стека
; сопроцессора 8087 и возводит 10 в эту степень.
; Параметры: X в ST(0)
; Результат: 10**X в ST(0)
; Эта программа использует две ячейки в стеке 8087
; плюс параметр - всего три ячейки.
;--------------------------------------------
0004 TEN_TO_X PROC NEAR
;----ST(0)------;-----ST(1)-----;--ST(2)--
; X ; ? ; ?
0004 9B D9 E9 FLDL2T ; LOG2(10) ; X ; ?
0007 9B DE C9 FMULP ST(1),ST(0) ; X*LOG2(10)=E ; ? ; ?
000A D9 3E 0000 R FNSTCW OLD_CW ;---------------;---------------;--------
000E 9B FWAIT ; Выборка текущего слова состояния
000F A1 0000 R MOV AX,OLD_CW ; Сохранение слова сотояния
0012 25 F3FF AND AX,NOT 0C00H ; Установка способа округления к минус
0015 0D 0400 OR AX,0400H ; бесконечности
0018 A3 0002 R MOV NEW_CW,AX
001B 9B D9 2E 0002 R FLDCW NEW_CW ;---------------;---------------;--------
0020 9B D9 E8 FLD1 ; 1 ; E ; ?
0023 9B D9 E0 FCHS ; -1 ; E ; ?
0026 9B D9 C1 FLD ST(1) ; E ; -1 ; E
0029 9B D9 FC FRNDINT ; INT(E) = I ; -1 ; E
002C 9B D9 2E 0000 R FLDCW OLD_CW ; ; ;
0031 9B D9 CA FXCH ST(2) ; E ; -1 ; I
0034 9B D8 E2 FSUB ST(0),ST(2) ; E - I = F ; -1 ; I
0037 9B D9 FD FSCALE ; F*2**-1 = F/2 ; -1 ; I
003A 9B D9 F0 F2XM1 ; (2**F/2)-1 ; -1 ; I
003D 9B DE E1 FSUBRP ST(1),ST(0) ; 2**F/2 ; I ; ?
0040 9B D8 8E 0000 U FMUL ST(0) ; 2**F ; I ; ?
0045 9B D9 FD FSCALE ; (2**F)*(2**I) ; I ; ?
0048 9B D9 C9 FXCH ST(1) ; I ; 2**(I+F) ; ?
004B 9B D8 D9 FCOMP ; 2**(I+F) ; ? ; ?
004E C3 RET ; 10**X ; ? ; ?
004F TEN_TO_X ENDP
004F CODE ENDS
END
Фиг. 7.24 Вычисление 10**ST
соответствующего элемента стека неопределено. В подпрограмме
используется всего три элемента стека, так что в поле комментариев
есть три колонки.
Хотя теперь мы имеем нужную степень двух, у сопроцессора 8087
отсутствует команда, завершившая бы всю работу за один шаг.
Команда F2XM1 возводит 2 в степень X, но только если X меньше или
равен 1/2. Это означает, что степень E мы должны разделить на
целую и дробную части; затем команда FSCALE сможет возвести 2 в
целую степень, а команда F2XM1 обработает дробную часть.
Перед разделением E на две части программа выполняет некоторые
вспомогательные действия. Эти действия - команды сопроцессора
8087, которые читают управляющее слово и устанавливают режим
округления в направлении меньшего числа. Теперь, когда мы возьмем
целую часть показателя степени, его значение будет округляться
влево, в направлении минус бесконечности. Дробная часть показателя
будет положительным числом, что также требуется для команды F2XM1.
Обратите внимание на использование команды FWAIT после команды
FNSTCW. Ожидать окончания выполнения умножения перед записью
управляющего слова не надо, так как умножение не меняет код в
управляющем слове. Но перед чтением управляющего слова из главной
памяти и модификацией его нужна гарантия, что сопроцессор 8087 уже
завершил запись. Значит, нужно выполнить команду ожидания FWAIT
перед чтением.
После установки способа округления команда FRNDINT округляет
показатель степени E до целого значения. Так как мы также
запомнили и исходное значение E в стеке, можно вычесть целую часть
из E и получить дробную часть показателя степени. То есть теперь E
= I + F, и можно записать
2**E = 2**I*2**F
Но перед тем обратим внимание на одну маленькую деталь.
Дробная часть F может оказаться значением большим 1/2, и поэтому не
может быть аргументом команды F2XM1. Сейчас мы используем число
-1, ранее помещенное в стек, чтобы разделить F на 2, получив при
этом F/2. Чтобы это сделать, воспользуемся командой FSCALE, так
как эта команда умножает содержимое ST0 на 2 в степени,
содержащейся в ST1. Поскольку в элементе ST1 содержится -1,
результирующим эффектом будет умножение на 1/2. Теперь можно
утверждать, что содержимое регистра ST0 меньше 1/2.
Далее команда F2XM1 возводит 2 в степень F/2, а -1 в стеке
помогает ликвидировать -1, порождаемую в результате работы команды
F2XM1. Обратное вычитание с извлечением из стека избавляется и от
-1 в стеке. Затем 2F/2 умножается само на себя, в элементе ST0
получается число 2F. Так как целая часть показателя степени теперь
переместилась в элементе ST1, команда FSCALE умножает 2I на число
2F, которое уже находится в элементе ST0, давая искомый результат.
Команда FCOMP удаляет из стека число I перед возвратом из
подпрограммы.
Форматы действительных чисел
Форматы действительных чисел
Существует три формата чисел с плавающей точкой, поддерживаемых
сопроцессором 8087. Эти форматы показаны нп Фиг. 7.1 и 7.2. На
Фиг. 7.1 показана логическая структура чисел, а на Фиг. 7.2 показано
расположение отдельных частей числа, когда оно записывается в
память.
Короткие и длинные форматы данных соответствуют предложенному
ИИЭР стандарту для представления чисел с плавающей точкой.
Короткое действительное число занимает 32 бита, или четыре байта
памяти. Этот формат часто называют числом с плавающей тоской
обычной точности. Его мантисса содержит 23 бита, что по точности
приблизительно соответствует шести - семи десятичным цифрам. То
есть семизначное десятичное число по точности примерно
соответствует 23-битовому двоичному числу. Восьмибитовое поле
порыдка имеет значение смещения 127, или 07FH. Значение порядка
лежит в диапазоне от -127 до 127, что приблизительно соответствует
диапазону от 10**-38 до 10**38. Оставшийся бит определяет знак
всего числа. Заметим, что внутри чисоа с плавающей точкой есть два
знаковых бита. Один из них - знак порядка, содержащийся внутри
поля порядка (модифицирующийся смещением). Другой знак показывает,
что отрицательно или положительно само число.
Длинный действительный формат числа занимает 64 бита, или
восемь байтов памяти. Это число удвоенной точности имет 52-
битовую мантиссу, что по точности соответствует примеоно 15- 16
десятичным цифрам. Одиннадцатибитовый порядок имеет диапазон от
2**-1023 до 2**1023 и значение смещения 1023, или 03FFH. В десятичной
форме это соответствует диапазону от 10**-307 до 10**307.
Сопроцессор 8087 всегда хранит в памяти длинные и короткие
действительные числа в нормализованном виде. В это означает, что
первый бит мантиссы всегда единичен; и поэтому хранить его
бессмысленно, но всегда подразумевается, что он присутствует, как
показано на Фиг. 7.2. Под изображением расположения данных длинных
и коротких действительных чисел показана 1 перед двоичной точкой.
Этот разряд отсутствует в памяти, но известно, что он на самом деле
существует.
Заметим теперь, что все шесть форматов данных сопроцессора
8087, которые были рассмотрены - четыре целых и два действительных
- существуют только за пределами процессора. То есть сопроцессор
8087 переводит данные в эти форматы только тогда, когда записывает
их в память; кроме того, он может читать данные, записанные в
память в этих форматах. В сопроцессоре 8087 данные всех типов
хранятся в седьмом формате - промежуточном, или временном
действительном. Это означает, что программа, в которой вы
работаете с сопроцессором 8087, использует преимущества
расширенного диапазона и точности временного действительного числа
только тогда, когда данные обрабатываются внутри процессора 8086.
Другие форматы данных существуют для удобства их хранения и
представления вне процессора.
Временное действительное число является наиболее точным и имеет
большой диапазон. Оно представляется 80 битами или 10 байтами.
Его мантисса имеет длину 64 бита, это дает точность, эквивалентную
примерно 19 десятичным цифрам. Мантисса в общем случае
нормализована, но в некоторых случаях может оказаться
денормализованной. По этой причине временный действительный формат
уже не пожразумевает, что старший бит мантиссы равен 1. На Фиг. 7.2
старшая нормализованная единица показана явно частью мантиссы, а не
чем-то подразумеваемым. Пятнадцатибитовое поле порядка смещается
значением 16383, или 03FFFH. Такой порядок допускает диапазон
изменения чисел от 2**-16383 до 2**16383, или примерно от 10**-4932 до
10**4932. Поскольку сопроцессор 8087 может денормализовать мантиссу
временного действительного числа, нижняя граница его расширяется
еще дальше. Так как число денормализовано, в мантиссе есть
незначащие нули, и это позволяет изобразить даже меньшее число, чем
допускает диапазон значений порядка. Например, давайте снова
рассмотрим простую форму числа с плавающей точкой, содержащую
3-битовое поле порядка и 4-битовую матиссу. Наименьшее
положительное число, которое здесь может быть представлено, есть
1.000*10000B=1*2**-3=1/8
В этом примере подразумевается: что значение порядка смещено
на значение 3. Если теперь мя денормализуем мантиссу, то сможем
представить даже меньшее число, например
0.100*10000B=1/2*2**-3=1/16
а наименьшее положительное число, которое можно представить
денормализацией мантиссы есть
Денормализация мантиссы расширяет нижнюю границу диапазона
чисел.
Этот дополнительный диапазон дается не даром. Поскольку мы
ввели незначащие нули, точность мантиссы уменьшилась. Временный
действительный формат числа дает возможность пожертвовать точностью
ради расширения диапазона числа, когда это необходимо. Такая
возможность требуется в первую очередь на промежуточных шагах
длительных вычислений. Иногда прикладная программа вычитает два
примерно одинаковых числа перед выполнением другой операции, и
результат много меньше, чем любое из исходных чисел. Эта разность
может оказаться очень важной. В таких специальных случаях
вычисления можно продолжать, допуская расширение диапазона
представления числа. Единственной альтернативой здесь была бы
установка нулевого результата вычитания, а в этом случае всякая
значимость теряется.
Изображение чисел с плавающей точкой
Изображение чисел с плавающей точкой
Следующая подпрограмма берет число из вершины стека и
преобразует его в печатную строку символов. Фактически,
подпрограмма извлекает число с вершины стека и посылает его на
дисплей. Далее эта подпрограмма будет использована в двух примерах
для вывода их результатов. Исходный текст программы показан на
Фиг. 7.25.
Эта подпрограмма достаточно просто строит выводимую символьную
строку. Если исходное число NAN, либо бесконечность, или другое
специальное число сопроцессора 8087, результат будет показан
неверно. Первая часть программы - удобное место для использования
команды FXAM, которая определила бы тип числа в вершине стека. Но
в данном примере эта команда не используется, так как
предполагается, что исходное число имеет нужный тип.
Эта программа не приспособлена для оформления формата
выводимого числа. Результат всегда содержит знак (либо пробел,
либо знак "-") и целую часть, состоящую из одной цифры. После
десятичной точки расположены восемь десятичных позиций, а затем
буква E и три позиции цифр для степени 10. Результат работы этой
программы не так хорош, как можно было желать, но он позволяет
видеть результат работы программы в читабельной форме. Более
красивое преобразование числа потребовало бы значительно больше
команд, и лишь малая часть из них помогла бы пониманию работы
сопроцессора 8087.
Программа преобразования работает следующим образом. Сначала
она определяет порядок числа. Например, число 1234 имеет порядок
3; это означает, что оно находится между значениями 103 и 104.
Найдя правильный порядок числа, программа сохраняет его значение
(показатель степени результата) и делит исходное число на 10 в этой
степени. Теперь число находится в интервале от 1 до 10. Затем
программа умножает число на 108. Запись этого числа в десятичной
форме дает девять десятичных цифр; старшая цифра - целая часть,
младшие восемь цифр - дробные разряды.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:46
Фиг. 7.25 Преобразование плавающего формата в текстовый Page 1-1
PAGE ,132
TITLE Фиг. 7.25 Преобразование плавающего формата в текстовый
0000 CODE SEGMENT PUBLIC
ASSUME CS:CODE,DS:CODE,ES:CODE
EXTRN TEN_TO_X:NEAR
0000 ???? OLD_CW DW ?
0002 ???? NEW_CW DW ?
0004 ???? EXPONENT DW ?
0006 ??????????????????? BCD_RESULT DT ?
?
0010 ??????????????????? BCD_EXPONENT DT ?
?
001A 00E1F505 TEN8 DD 100000000
001E 20 20 20 20 20 20 20 PRINT_STRING DB ' E ',10,13,'$'
20 20 45 20 20 20 20
0A 0D 24
Фиг. 7.25 Преобразование плавающего формата в текстовый (начало)
PUBLIC FLOAT_ASCII
;--------------------------------------------
; Эта программа извлекает из вершины стека
; сопроцессора 8087 число и выводит его на
; экран в плавающем формате.
; Параметры: число в ST(0)
; Результат: изображение числа на экране;
; число извлечено из стека сопроцессора 8087
;--------------------------------------------
002F FLOAT_ASCII PROC NEAR
;-----ST(0)-----;-----ST(1)-----;--ST(2)--
; X ; ? ; ?
002F 9B D9 C0 FLD ST(0) ; X ; X ; ?
0032 9B D9 E1 FABS ; |X| ; X ; ?
0035 9B D9 E8 FLD1 ; 1 ; |X| ; X
0038 9B D9 C9 FXCH ST(1) ; |X| ; 1 ; X
003B 9B D9 F1 FYL2X ; LOG2(X) ; X ; ?
003E 9B D9 E9 FLDL2T ; LOG2(10) ; LOG2(X) ; X
0041 9B DE F1 FDIVRP ST(1),ST(0) ; E=LOGX/LOG10 ; X ; ?
0044 D9 3E 0000 R FNSTCW OLD_CW ; ; ;
0048 9B FWAIT ; ; ;
0049 A1 0000 R MOV AX,OLD_CW ; ; ;
004C 25 F3FF AND AX,NOT 0C00H ; ; ;
004F 0D 0400 OR AX,0400H ; ; ;
0052 A3 0002 R MOV NEW_CW,AX ; ; ;
0055 9B D9 2E 0002 R FLDCW NEW_CW ; ; ;
005A 9B D9 FC FRNDINT ; I = INT(E) ; X ; ?
005D 9B D9 2E 0000 R FLDCW OLD_CW ; ; ;
0062 9B DF 16 0004 R FIST EXPONENT ; I ; X ; ?
0067 9B D9 E0 FCHS ; -I ; X ; ?
006A E8 0000 E CALL TEN_TO_X ; 10 ** (-I) ; X ; ?
006D 9B DE C9 FMULP ST(1),ST(0) ; X/10**I ; ? ; ?
0070 9B DA 0E 001A R FIMUL TEN8 ; Мантисса ; ? ; ?
0075 9B DF 36 0006 R FBSTP BCD_RESULT ; ? ; ? ; ?
007A 9B DF 06 0004 R FILD EXPONENT ; I ; ? ; ?
007F 9B DF 36 0010 R FBSTP BCD_EXPONENT ; ? ; ? ; ?
;----- Вывод на экран значений,запомненных как упакованные
; целые двоично-десятичные числа
0084 FC CLD
0085 8D 3E 001E R LEA DI,PRINT_STRING ; Указатель на выводимую
; строку
0089 A0 000F R MOV AL,BYTE PTR BCD_RESULT+9
008C E8 00C3 R CALL PRINT_SIGN ; Печать знака
008F A0 000A R MOV AL,BYTE PTR BCD_RESULT+4
0092 E8 00DF R CALL PRINT_NYBBLE ; Печать первой цифры
0095 B0 2E MOV AL,'.' ; Десятичная точка
0097 AA STOSB
0098 8D 1E 0009 R LEA BX,BCD_RESULT+3
009C B9 0004 MOV CX,4 ; Печать 8 байт (16 цифр)
009F DO_BYTE: ; после десятичной точки
009F E8 00CD R CALL PRINT_BYTE
00A2 E2 FB LOOP DO_BYTE
00A4 B0 45 MOV AL,'E' ; Символ экспоненты
00A6 AA STOSB
Фиг. 7.25 Преобразование плавающего формата в текстовый (продолжение)
00A7 A0 0019 R MOV AL,BYTE PTR BCD_EXPONENT+9
00AA E8 00C3 R CALL PRINT_SIGN ; Печать знака экспоненты
00AD A0 0011 R MOV AL,BYTE PTR BCD_EXPONENT+1
00B0 E8 00DF R CALL PRINT_NYBBLE ; Первая цифра экспоненты
00B3 8D 1E 0010 R LEA BX,BCD_EXPONENT
00B7 E8 00CD R CALL PRINT_BYTE ; Оставшиеся цифры
00BA 8D 16 001E R LEA DX,PRINT_STRING
00BE B4 09 MOV AH,9H
00C0 CD 21 INT 21H ; Вывод всей строки на экран
00C2 C3 RET
00C3 FLOAT_ASCII ENDP
;----- Эта подпрограмма выводит ' ' или '+'
00C3 PRINT_SIGN PROC NEAR
00C3 3C 00 CMP AL,0 ; Проверка на знак
00C5 B0 20 MOV AL,' ' ; Занесение положительного знака
00C7 74 02 JZ POSITIVE
00C9 B0 2D MOV AL,'-' ; Занесение минуса
00CB POSITIVE:
00CB AA STOSB ; Сохранение в выводимой строке
00CC C3 RET
00CD PRINT_SIGN ENDP
;----- Эта программа печатает две десятичные цифры,
; находящиеся в памяти по адресу [BX]
00CD PRINT_BYTE PROC NEAR
00CD 8A 07 MOV AL,[BX] ; Выборка байта
00CF 51 PUSH CX
00D0 B1 04 MOV CL,4
00D2 D2 E8 SHR AL,CL ; Сдвиг старшей цифры
00D4 59 POP CX
00D5 E8 00DF R CALL PRINT_NYBBLE ; Печать старшей цифры
00D8 8A 07 MOV AL,[BX] ; Выборка младшей цифры
00DA E8 00DF R CALL PRINT_NYBBLE ; Печать младшей цифры
00DD 4B DEC BX ; Переход на следующую пару цифр
00DE C3 RET
00DF PRINT_BYTE ENDP
;----- Печать одной десятичной цифры из регистра AL
00DF PRINT_NYBBLE PROC NEAR
00DF 24 0F AND AL,0FH ; Выделение младшей цифры
00E1 04 30 ADD AL,'0' ; Преобразование в символ
00E3 AA STOSB ; Сохранение в выводимой строке
00E4 C3 RET
00E5 PRINT_NYBBLE ENDP
00E5 CODE ENDS
END
Фиг. 7.25 Преобразование плавающего формата в текстовый (продолжение)
Первая часть программы определяет правильный порядок исходного
числа. В программе логарифм числа по основанию 10 находится с
помощью формулы
Log10(X) = Log2(X)/Log2(10)
Затем округляется порядок в направлении минус бесконечности,
опять используя управление округлением. В предыдущем примере,
вычисляя 10X, мы пользовались умножением для переноса исходного
числа в нужный диапазон. Теперь мы используем константу TENB
(которая содержит целое число 108) для того, чтобы вернуть число в
нужный диапазон. Наконец, команда FBSTP дважды преобразует числа в
десятичное представление; сначала она дает нам девять цифр мантиссы
числа, а затем - три цифры порядка.
Остальная часть программы выполняет символьную обработку,
необходимую для преобразования десятичного представления в строки
символов. Программа определяет и показывает знаки числа и порядка.
Она распаковывает десятичные байты и преобразует их в символы;
подпрограмма PRINT_BYTE делает распаковку, а подпрограмма
PRINT_NYBBLE выполняте преобразование в символы. Заметим, что в
этом случае не нужна команда XLAT, так как все цифры имеют значения
между 0 и 9. (Но если исходное число - одно из неопределенных
чисел, символьная строка будет содержать некоторые непонятные
символы).
Эта программа верно печатает любое число, лежащее в диапазоне
длинных действительных чисел. Любое число, выходящее за пределы
возможностей этого представления (например 101234) имеет поле
порядка, сокращенное до трех цифр. Конечно, вы можете изменить
программу так, чтобы она обрабатывала четыре цифры поля порядка,
если вы этого желаете. Но существует все же число, которое
программой обрабатывается верно, но вы, возможно, пожелаетет
изменить его изображение. Если исходное число 0, результат
печатается в виде 0.00000000E-932. Так происходит потому, что поле
порядка имеет смещение; процессор 8087 представляет число 0 с
минимально возможным порядком (-4932) и с нулевой мантиссой. Когда
программа преобразует число в код ASCII, она верно печатает
мантиссу и порядок (за исключением того, что ей приходится усекать
порядок до трех цифр). Если вы захотите обрабатывать такой порядок
отдельно, то измените программу, вставив в нее проверку на нуль
(чаще всего, с помощью команды FTST) в самом начале, рассматривая
это, как специальный случай.
Команды пересылки данных
Команды пересылки данных
В группе команд пересылки данных сопроцессора 8087 имеется
всего три основных команды. Команда загрузки помещает данные в
регистровый стек 8087. Обычно эти данные читаются из памяти
системы, но команда загрузки может также извлечь число из самого
стека и заменить им вершину стека. Команда записи берет данные из
вершины стека и помещает их в память ЭВМ. Команда замены
обменивает два числа в регистровом стеке сопроцессора 8087.
На Фиг. 7.10 показан листинг ассемблера команд пересылки
данных. В текст в самом начале помещен набор макрокоманд
процессора 8087 с помощью фрагмента:
IF1
INCLUDE 87MAC.LIB
ENDIF
Эта последовательность команд помещает в текст программы
макрокоманды определения команд сопроцессора 8087 во время первого
прохода ассемблера, когда должны обрабатываться макрорасширения.
Ассемблер не читает файл макрокоманд во время второго прохода, так
как этот файл больше не нужен. В листинге ассемблера появляется
только команда ENDIF.
Первая команда пересылки данных, которую мы рассмотрим -
команда загрузки. Название всех команд сопроцессора 8087
начинается с буквы "F". Так что, чтобы загрузить число в
микросхему 8087, используется команда FLD (Floating LoaD, плавающая
загрузка). В отличие от команд микропроцессора 8088, где команда
MOV обслуживает все форматы данных, здесь существует разная
Microsoft (R) Macro Assembler Version 5.00 1/1/80 01:21:45
Фиг. 7.10 Команды пересылки сопроцессора 8087 Page 1-1
PAGE ,132
TITLE Фиг. 7.10 Команды пересылки сопроцессора 8087
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 WORD_INTEGER LABEL WORD
0000 SHORT_INTEGER LABEL DWORD
0000 LONG_INTEGER LABEL QWORD
0000 BCD_INTEGER LABEL TBYTE
0000 SHORT_REAL LABEL DWORD
0000 LONG_REAL LABEL QWORD
0000 TEMPORARY_REAL LABEL TBYTE
0000 9B DF 06 0000 R FILD WORD_INTEGER
0005 9B DB 06 0000 R FILD SHORT_INTEGER
000A 9B DF 2E 0000 R FILD LONG_INTEGER
000F 9B DF 26 0000 R FBLD BCD_INTEGER
0014 9B D9 06 0000 R FLD SHORT_REAL
0019 9B DD 06 0000 R FLD LONG_REAL
001E 9B DB 2E 0000 R FLD TEMPORARY_REAL
0023 9B D9 C2 FLD ST(2)
0026 9B DF 16 0000 R FIST WORD_INTEGER
002B 9B DB 16 0000 R FIST SHORT_INTEGER
0030 9B D9 16 0000 R FST SHORT_REAL
0035 9B DD 16 0000 R FST LONG_REAL
003A 9B DD D2 FST ST(2)
003D 9B DF 1E 0000 R FISTP WORD_INTEGER
0042 9B DB 1E 0000 R FISTP SHORT_INTEGER
0047 9B DF 3E 0000 R FISTP LONG_INTEGER
004C 9B DF 36 0000 R FBSTP BCD_INTEGER
0051 9B D9 1E 0000 R FSTP SHORT_REAL
0056 9B DD 1E 0000 R FSTP LONG_REAL
005B 9B DB 3E 0000 R FSTP TEMPORARY_REAL
0060 9B DD DA FSTP ST(2)
0063 9B D9 CA FXCH ST(2)
0066 9B D9 EE FLDZ
0069 9B D9 E8 FLD1
006C 9B D9 EB FLDPI
006F 9B D9 E9 FLDL2T
0072 9B D9 EA FLDL2E
0075 9B D9 EC FLDLG2
0078 9B D9 ED FLDLN2
007B CODE ENDS
END
Фиг. 7.10 Команды пересылки сопроцессора 8087
мнемоника для разных типов данных. Так получилось потому, что
ассемблер может различать четырехбайтовые и восьмибайтовые
операнды, но не может знать, является ли операнд действительным или
целым числом.
Всякий раз, когда операнд - целое, используется команда FILD.
Итак, FILD загружает слово (16 бит), короткое целое число (32 бита)
или длинное целое число (64 бита). Чтобы загрузить упакованное
десятичное число (80 бит), используется команда FBLD. Буква B
указывает десятичные числа. Наконец, команда FLD загружает
действительные числа. Ассемблер определяет, какой вид целого или
действительного числа вы желаете использовать.
В ассемблере для имен команд сопроцессора 8087, ссылающихся к
памяти, используется соглашение о том, что в случае целых чисел
вслед за буквой F следует буква I, в случае десятичных чисел -
буква B, и никакой буквы не следует в случае действительных чисел.
Мы увидим, что то же соглашение используется и в командах записи, и
в арифметических командах, которые указывают операнд в памяти.
Как видно из Фиг. 7.10, для каждого из семи обслуживаемых
сопроцессором 8087 типов данных существует команда загрузки.
Команда загрузки указывает поле данных в памяти, микросхема 8087
преобразует данные из их внешнего представления во временный
действительный формат. Преобразованное число помещается в стек,
увеличивая его объем на единицу. Если вы попытаетесь поместить
число в стек, который уже содержит восемь чисел, сопроцессор 8087
сообщит об особой ситуации - переполнении стека. Если программа не
обрабатывает особую ситуацию в своей собственной подпрограмме,
встроенный обработчик особой ситуации пометит загруженное значение,
как "неопределенное". Это означает, что дальнейшие действия с этим
числом дадут неопределенные результаты. Если вы сделаете ошибку,
сопроцессор 8087 проследит за тем, чтобы она не осталась
незамеченной.
Оставшаяся разновидность команды загрузки берет один из
элементов стека и помещает его в стек. Например, команда
FLD ST0
дублирует вершину стека. После нее два верхних элемента имеют
одинаковые значения. Команда
FLD ST3
помещает копию четвертого элемента стека в стек. Заметим, что
число, которое было раньше ST3, стало теперь ST4.
Давайте посмотрим на машинный язык, в действительности
формируемый этими командами. Поскольку этот текст порождает
команды процессора 8087 с помощью макрокоманд, сравнительно легко
увидеть, откуда появляются различные части команд. Во=первых,
каждая команда начинается с байта 09BH. Это - команда WAIT. Как
вы помните, сопроцессор 8087 должен быть синхронизирован с работой
микропроцессора 8088. Если микропроцессор 8088 попытается
выполнить следующую команду сопроцессора 8087 до того, как
сопроцессор 8087 завершит текущую команду, действия микросхемы 8087
дадут неверный результат. Фактически все макрокоманды 8087
содержат команду WAIT для обеспечения синхронизации. (Команды без
синхронизации сопроцессора 8087 - это все команды управления,
обычно не требеющие ожидания результата. Эти команды можно легко
отличить так как они все начинаются с FN, где буква N означает
отсутствие синхронизации).
По макрорасширениям также можно видеть, что команды процессора
8087 формируются командами ESC. Чтобы указать адрес памяти,
команда ESC имеет два операнда. Первый определяет, какая это
команда ESC, а второй ссылается на ячейку памяти. Команда ESC
может иметь длину два, три или четыре байта, в зависимости от
размера поля индексного смещения, сопровождающего байт mod=r/m. В
комбинации с командой WAIT максимальная длина команды сопроцессора
8087 достигает пяти байт.
Команда записи имеет два варианта. Первый вариант этой команды
извлекает число с вершины стека и записывает ее в поименованную
ячейку памяти. Выполняя эту команду, сопроцессор 8087 делает
преобразование данных из временного действительного формата в
желаемую внешнюю форму. Эта команда имеет коды операций FST и
FIST. (Заметим, что здесь продолжают свое действие соглашения об
именах команд). Этой же командой вы можете занести вершину стека в
любое место внутри стека.
Вероятно вы заметили, что команда FST не допускает запись всех
возможных внешних типов данных. Допустимы лишь типы из "большой
четверки" - целое слово, короткое целое, короткое и длинное
действительные. Эта команда не поддерживает все внешниие типы
данных, потому что создатели процессора 8087 понимали, что это не
обязательно из=за свойств следующей команды.
Второй вариант команды записи, кроме записи данных, также
изменяет положение указателя стека. Команды FSTP (а также команды
FISTP и FBSTP) выполняют ту же операцию записи данных из
сопроцессора 8087 в память, но они также извлекают число из стека.
Эта разновидность команд поддерживает все внешние типы данных.
Конструкторы микросхемы 8087 кое=где экономили на командах, и
поэтому только команды FLD и FSTP поддерживают все внешние типы
данных. Все остальные команды сохранения данных в памяти работают
только с "большой четверкой" типов данных. Конструкторы понимали,
что эти четыре типа будут преобладать над всеми, и использование
других форматов может быть реализовано только командами FLD и FSTP.
Команда замены FXCH - следующая команда в группе команд
пересылки данных. Команда FXCH меняет местами содержимое вершины
стека с содержимым другого регистра стека. Эта команда может
использовать в качестве операнда только другой элемент стека.
Нельзя одной командой поменять местами содержимое вершины стека и
ячейки памяти. Эта процедура потребует несколько команд и рабочее
поле где=то в памяти. В отличие от микропроцесоора 8088,
сопроцессор 8087 может в одной команде выполнить чтение из памяти
или запись в память, но не то или другое одновременно.
Команда Константа
--------------------------
FLDZ 0
FLD1 1
FLDPI PI
FLDL2T LOG2(10)
FLDL2E LOG2(e)
FLDG2 LOG10(2)
FLDLN2 LOGe(2)
-------------------------- Фиг. 7.11 Константы 8087
Остальные команды группы команд персылки данных обслуживают
константы. Они загружают в стек заранее известные значения. Эти
константы описывают набор величин, необходимых программам при
вычислениях, и были выбраны из соображений упрощения счета
трансцендентных и тригонометрических функций. Мы используем
некоторые из этих констант в демонстрационных программах. Таблица
на Фиг. 7.11 показывает, какое именно значение загружается в элемент
ST0 в случае каждой команды. В каждом случае мнемоника команды
выбрана так, чтобы отражать значение константы.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:07
Фиг. 7.12 Команды управления сопроцессора 8087 Page 1-1
PAGE ,132
TITLE Фиг. 7.12 Команды управления сопроцессора 8087
0000 CODE SEGMENT
ASSUME CS:CODE
0000 STATUS_WORD LABEL WORD
0000 CONTROL_WORD LABEL WORD
0000 ENVIRONMENT LABEL BYTE ; Область размером 14 байт
0000 STATE LABEL BYTE ; Область размером 94 байта
0000 9B DB E3 FINIT
0003 9B DB E0 FENI
0006 9B DB E1 FDISI
0009 9B 2E: D9 2E 0000 R FLDCW CONTROL_WORD
000F 9B 2E: D9 3E 0000 R FSTCW CONTROL_WORD
0015 9B DB E2 FCLEX
0018 9B 2E: D9 36 0000 R FSTENV ENVIRONMENT
001E 9B 2E: D9 26 0000 R FLDENV ENVIRONMENT
0024 9B 2E: DD 36 0000 R FSAVE STATE
002A 9B 2E: DD 26 0000 R FRSTOR STATE
0030 9B D9 F7 FINCSTP
0033 9B D9 F6 FDECSTP
0036 9B DD C2 FFREE ST(2)
0039 9B D9 D0 FNOP
003C 9B FWAIT
003D CODE ENDS
END
Фиг. 7.12 Команды управления сопроцессора 8087
Команды сравнения
Команды сравнения
Как и в наборе команд микропроцессора 8088, у сопроцессора 8087
есть команды, сравнивающие два числа. Сопроцессор 8087 отбрасывает
результат сравнения, но устанавливает в соответствии с ним флаги
состояния. Перед тем как опросить флаги состояния, программа
должна считать слово состояния в память. Далее проще всего
загрузить флаги состояния в регистр AH, а затем, для легкости
проверки условия, - в регистр флагов макропроцессора 8088.
На Фиг. 7.18 показан листинг ассемблера команд сравнения
сопроцессора 8087. Так как в операции всегда участвует вершина
стека, в программе надо указать только один регистр или операнд в
памяти. После сравнения в слове состояния содержится информация о
соотношении двух чисел. Таблица на Фиг. 7.19 показывает возможные
варианты соотношений. Для отражения результата сравнения
необходимы только два бита состояния - C3 и C0; расположение C3 и
C0 в слове состояния показано на Фиг. 7.8.
На Фиг. 7.20 приведен фрагмент программы, которая сравнивает
содержимое вершины стека и слово в памяти. Переходы в этой
программе выполняются на основе результата сравнения. В одном из
четырех случаев числа нельзя сравнить. Это происходит тогда, когда
одно из чисел есть NAN (не число) или одна из форм бесконечности.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:18
Фиг. 7.18 Команды сравнения сопроцессора 8087 Page 1-1
PAGE ,132
TITLE Фиг. 7.18 Команды сравнения сопроцессора 8087
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 WORD_INTEGER LABEL WORD
0000 SHORT_INTEGER LABEL DWORD
0000 SHORT_REAL LABEL DWORD
0000 LONG_REAL LABEL QWORD
0000 9B D8 D1 FCOM
0003 9B D8 D2 FCOM ST(2)
0006 9B DE 16 0000 R FICOM WORD_INTEGER
000B 9B D8 16 0000 R FCOM SHORT_REAL
0010 9B D8 D9 FCOMP
0013 9B DA 1E 0000 R FICOMP SHORT_INTEGER
0018 9B DC 1E 0000 R FCOMP LONG_REAL
001D 9B DE D9 FCOMPP
0020 9B D9 E4 FTST
0023 9B D9 E5 FXAM
0026 CODE ENDS
END
Фиг. 7.18 Команды сравнения сопроцессора 8087
Сопроцессор 8087 помещает флаги состояния C3 и C0 в точности на те
же места, которые в регистре флагов микропроцессора 8088 занимают
флаги нуля C3 и переноса C0. Как показано на рисунке, если
программа переслала старший байт слова состояния сопроцессора 8087
в регистр флагов микропроцессора 8088, она может выполнить условный
переход с помощью непосредственной проверки состояния флагов с
использованием команды условного перехода, и не нужно выделять и
проверять отдельные биты слова состояния.
С3 С0 Порядок
--------------------------------------------------------------
0 0 ST > источник
0 1 ST < источник
1 0 ST = источник
1 1 ST и источник несравнимы
--------------------------------------------------------------
Фиг. 7.19 Порядок сравнения
На Фиг. 7.18 демонстрируется, что существуют и другие варианты
команды FCOM, в том числе и то, что для команды FCOM существует
версия FICOM необходимая для ставнения целых чисел. Команды FCOMP
и FICOMP идентичны командам FCOM и FICOM, за исключением того, что
сопроцессор 8087 извлекает из стека содержимое вершины после
выполнения операции. Это позволяет сравнить два числа с помощью
сопроцессора 8087, не беспокоясь о том, что нужно удалить из стека
первый операнд после операции.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:23
Фиг. 7.20 Условный переход Page 1-1
PAGE ,132
TITLE Фиг. 7.20 Условный переход
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 WORD_INTEGER LABEL WORD
0000 ???? STATUS_WORD DW ?
;----- Сравнивается WORD_PTR со словом на вершине стека сопроцессора 8087
0002 9B DE 16 0000 R FICOM WORD_INTEGER ; Сравнение слова с ST
0007 9B DD 3E 0000 R FSTSW STATUS_WORD ; Сохранение слова состояния 8087
000C 9B FWAIT ; Синхронизация процессора с сопроцессором
000D 8A 26 0001 R MOV AH,BYTE PTR STATUS_WORD+1
0011 9E SAHF ; Занесение флагов (C0=CF,C3=ZF)
0012 72 02 JB CONTINUE ; Проверка флага C0,переход если 0
0014 75 00 JNE ST_GREATER ; Проверка флага C3
0016 ST_EQUAL: ; Попадаем сюда,если C3=1,C0=0 -
; ... ; значения равны
0016 ST_GREATER: ; Попадаем сюда,если C3=0,C0=0 - число
; ... ; на вершине стека 8087 больше WORD_PTR
0016 CONTINUE:
0016 75 00 JNE ST_LESS ; Проверка флага C3
0018 UNORDERED: ; Попадаем сюда,если C3=1,C0=1 - невоз-
; ... ; можно определить какое из чисел больше
0018 ST_LESS: ; Попадаем сюда,если C3=0,C0=1 - число
; ... ; на вершине стека 8087 меньше WORD_PTR
0018 CODE ENDS
END
Фиг. 7.20 Условный переход
У команды FCOMPP программист не указывает никаких операндов.
Эта команда всегда сравнивает верхние два элемента стека. После
сравнения они оба исчезают из стека.
Команды сравнения с извлечением из стека обеспечивают удобный
способ очистки стека. Поскольку у сопроцессора 8087 нет команды,
удобно извлекающей операнд из стека, вместо нее можно использовать
команды сравнения с извлечением из стека. Эти команды также
изменяют и регистр состояния, и их нельзя использовать, если биты
состояния имеют значение для дальнейшей работы, но в большинстве
случаев эти команды позволяют быстро извлечь из стека один или два
операнда. Так как сопроцессор 8087 регистрирует ошибку при
переполнении стека, необходимо удалить все операнды из стека при
окончании вычислений.
Существуют две специальные команды сравнения. Команда
сравнения содержимого вершины стека с нулем FTST, с помощью которой
можно быстро определить знак содержимого вершины стека. (Результат
сравнения иллюстрируется таблицей на Фиг. 7.19, нужно только всюду в
таблице слово "источник" заменить на слово "нуль").
Команда FXAM, строго говоря, не является командой сравнения.
Хотя она и работает с содержимым вершины стека, но не сравнивает
содержимое вершины ни с одним другим числом. Скорее команда FXAM
устанавливает все четыре флага регистра состояния (от C3 до C0
включительно), показывая, какого типа число находится в вершине
стека. Так как сопроцессор 8087 может обрабатывать любые формы
чисел, а не только нормализованные числа с плавающей точкой,
команда FXAM определит, что же находится в вершине стека. На
Фиг. 7.21 показаны значения битов состояния в каждом случае.
Если при арифметической обработке вы не делаете что=либо из
ряда вон выходящее и не работаете на пределе разрядной сетки
сопроцессора 8087, то не нужно рассматривать никакие из приведенных
выше результатов команды FXAM; вы должны ожидать увидеть лишь
положительные либо отрицательные нормализованные числа или нули.
Если выясняется, что вершина стека пустая, то обычно выдается
ошибка. Программа может сделать такую проверку для контроля
параметра, переданного в вершине стека.
Остальные значения - это реакция микросхемы 8087 на ошибку.
Когда сопроцессор 8087 обнаруживает ошибку, он пытается возбудить
прерывание по особой ситуации, устанавливая соответствующие биты в
слове состояния. Однако если ситуация замаскирована с помощью
управляющего слова, сопроцессор 8087 сам реагирует на нее. Он
решает, какая реакция соответствует данной ошибке и возбуждает
появление специфического числа в регистре. Например, результат NAN
возникает, если операция некорректна, как в случае извлечения
квадратного корня из отрицательного числа. Бесконечность
появляется, если результат операции слишком велик для представления
с плавающей точкой.
C3 C2 C1 C0 Значение С3 С2 С1 С0 Значение
---------------------------------------------------------------------------------------------------
0 0 0 0 + Ненормально 1 0 0 0 + 0
0 0 0 1 + NAN 1 0 0 1 Пусто
0 0 1 0 - Ненормально 1 0 1 0 - 0
0 0 1 1 - NAN 1 0 1 1 Пусто
0 1 0 0 + Нормально 1 1 0 0 + Ненормальное
0 1 0 1 + Бесконечность 1 1 0 1 Пусто
0 1 1 0 - Нормально 1 1 1 0 - Ненормальное
0 1 1 1 - Бесконечность 1 1 1 1 Пусто
---------------------------------------------------------------------------------------------------
Фиг. 7.21 Пример интерпретации кодов состояний
Денормализованные и ненормализованные числа возникают при
достижении другого предела представления чисел с плавающей точкой.
Когда число становится настолько маленьким, что у него уже нет
смещенного порядка, оно становится денормализованным. Вместо того,
чтобы заменить число нулем, процессор 8087 записывает наименьшее
значение порядка и ненормализованную мантиссу. Это означает, что
число теряет точность, так как у его мантиссы появляются незначащие
нули. Но денормализованный результат все же точнее, чем просто
нуль. И это тот случай, при котором сопроцессор 8087, продолжая
работу, наилучшим образом реагирует на плохие условия. Пометка
числа, как денормализованного, должна насторожить вас. При этом
также устанавливается бит денормализации в регистре особых
ситуаций, и остается установленным до тех пор, пока команда FCLEX
не сбросит биты особых ситуаций. С помощью анализа флагов
программа может зарегистрировать возникновение ошибки и отнестись к
результатам с подозрением.
Команды управления
Команды управления
Команды управления микросхемы 8087 ничего не вычисляют, однако
они необходимы для управления ее работой. На Фиг. 7.12 показан
литсинг ассемблера команд управления сопроцессора 8087.
Многие из команд управления могут выполняться без синхронизации
с микропроцессором 8088. На Фиг. 7.12 показаны эти команды в
форме, ассемблированной с использованием команды WAIT, а код
операции в поле комментариев изображает команды без команды WAIT.
Ассемблеру сообщается о том, что операция выполняется без ожидания,
с помощью символов FN в качестве первых двух символов мнемоники
команды.
На Фиг. 7.13 перечислены действия всех команд управления
сопроцессором 8087. Команды FENI, FDISI, FCLEX обслуживают особые
ситуации в сопроцессоре 8087. В управляющем регистре содержится
маска прерываний, которая выделяет те ситуации, которые могут
вызвать прерывание. Маской прерываний сопроцессора 8087 в целом
управляют команды FENI и FDISI; эти команды аналогичны
соответственно командам STI и CLI микропроцессора 8088, за
исключением того, что они управляют прерываниями только от
сопроцессора 8087. Команда FCLEX сбрасывает биты особых ситуаций
регистра состояния. Микросхема 8087 помнит все особые ситуации,
так что если последовательность команд возбудила более одного типа
ошибки, все эти ошибки будут отмечены в регистре состояния. И
команда FCLEX - единственный способ сброса этих флагов.
Команда Действие
--------------------------------------------------------
FINIT Инициализация 8087. Переустановка программ
FENI Освобождение прерываний по исключительным состояниям
FDISI Блокирование прерываний по исключительным состояниям
FLDCW Загрузка управляющего регистра 8087 из памяти
FSTCW Сохранение управляющего регистра 8087 в память
FSTSW Сохранение регистра состояния 8087 в память
FCLEX Очистка индикаторов исключительных состояний
FSTENV Сохранить оборудование 8087 в память
FLDENV Загрузить оборудование 8087 из памяти
FSAVE Сохранить состояние 8087 в память
FRSTOR Загрузить состояние 8087 из памяти
FINCSTP Увеличеть указатель вершины стека
FDECSTP Уменьшить указатель вершины стека
FFREE Освободить регистр стека
FNOP Ничего не делать
FWAIT Идентично WAIT
--------------------------------------------------------
Фиг. 7.13 Управляющие действия
Мы уже рассмотрели управляющее слово и слово состояния в
составе программной модели сопроцессора 8087. Команды управления
FLDCW, FSTCW и FSTSW загружают и сохраняют эти регистры.
Рабочая среда сопроцессора 8087 содержит все регистры
микросхемы, за исключением стека данных; рабочая среда состоит из
14 байт. Рисунок 7.14 показывает структуру рабочей среды после
того, как сопроцессор 8087 записал ее в память. Запись рабочей
среды - это обычное действие при обработке особой ситуации в
сопроцессоре 8087, так как рабочая среда содержит все данные об
особых ситуациях. Один 20=битовый адрес указывает на последнюю
команду, которую выполнил сопроцессор 8087. Другой адрес указывает
последнюю из вызывавшихся ячеек данных. Код последней
выполнявшейся сопроцессором 8087 команды тоже входит в рабочую
среду.
ВОЗРАСТАНИ АДРЕСОВ
ДДДДДДДДДДДДДДДДДДДДДї
15 0 і
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і
і УПРАВЛЯЮЩЕЕ СЛОВО і+0 і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і
і СЛОВО СОСТОЯНИЯ і+2 і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і
і СЛОВО ПРИЗНАКА і+4 і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і
і IP15-0 і+6
УКАЗАТЕЛЬ ГДДДДДДДДДДДВДВДДДДДДДДДДДДДДДДДґ
КОМАНДЫ і IP19-16 і0і КОД ОПЕРАЦИИ і+8
ГДДДДДДДДДДДБДБДДДДДДДДДДДДДДДДДґ
і OP15-0 і+10
УКАЗАТЕЛЬ ГДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДґ
ОПЕРАНДА і OP19-16 і 0 і+12
АДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ
Фиг. 7.14 Рабочая среда 8087
Состояние микросхемы 8087 - это рабочая среда вместе с
регистрами данных. Так как в сопроцессоре 8087 имеется восемь
10=байтовых регистров, состояние содержит 94 байта. Рисунок 7.15
иллюстрирует структуру состояния сопроцессора 8087, записанного в
память. Структура состояния идентична рабочей среде с добавленным
в конце регистровым стеком. Программа может записать состояние
сопроцессора 8087, когда происходит переключение задач, или если в
обработчике прерываний потребуется использование сопроцессора 8087.
Когда ранее выполнявшаяся задача снова получает управление,
состояние может быть программно восстановлено.
Две команды - FINCSTP и FDECSTP - взаимодействуют с указателем
стека; они перемещают его. Все данные в регистрах остаются на
местах, т.е. слово "этикеток" не изменяется. Увеличение указателя
стека не эквивалентно извлечению данных из стека, так как
"этикетка" для этих данных все же показывает, что в регистре есть
данные. Попытка загрузить какое=либо число в такой стек приведет к
появлению особой ситуации - переполнению стека. Команда FFREE
освобождает ячейку стека, устанавливая "этикетку" для этой ячейки
так, что она показывает отсутствие данных в ячейке. Но команда
FFREE не меняет указатель стека, и если вам просто нужно выбросить
верхний элемент из стека, то в большинстве случаев это легче
сделать с помощью арифметической команды.
A
ВОЗРАСТАНИЕ АДРЕСОВ
ДДДДДДДДДДДДДДДДДДДДДї
15 0 і
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї і
і УПРАВЛЯЮЩЕЕ СЛОВО і+0 і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і
і СЛОВО СОСТОЯНИЯ і+2 і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і
і СЛОВО ПРИЗНАКА і+4 і
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ і
і IP15-0 і+6
УКАЗАТЕЛЬ ГДДДДДДДДДДДВДВДДДДДДДДДДДДДДДДДґ
КОМАНДЫ і IP19-16 і0і КОД ОПЕРАЦИИ і+8
ГДДДДДДДДДДДБДБДДДДДДДДДДДДДДДДДґ
і OP15-0 і+10
УКАЗАТЕЛЬ ГДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДґ
ОПЕРАНДА і OP19-16 і 0 і+12
ГДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 15-0 і+14
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 31-16 і+16
ЭЛЕМЕНТ ВЕРШИ-ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
НЫ СТЕКА:ST і МАНТИССА 47-32 і+18
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 63-48 і+20
ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
іsі ПОРЯДОК 14-0 і+22
ГДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 15-0 і+24
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 31-16 і+26
СЛЕДУЮЩИЙ ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
ЭЛЕМЕНТ СТЕКА і МАНТИССА 47-32 і+28
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 63-48 і+30
ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
іsі ПОРЯДОК 14-0 і+32
ГДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
... . . . . . . . ...
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 15-0 і+84
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 31-16 і+86
ПОСЛЕДНИЙ ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
ЭЛЕМЕНТ СТЕКАі МАНТИССА 47-32 і+88
ГДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і МАНТИССА 63-48 і+90
ГДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
іsі ПОРЯДОК 14-0 і+92
АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
ПРИМЕЧАНИЯ:
S = Знак
Бит 0 каждого поля - самый правый младший бит соответствующего
поля регистра
бит 63 мантиссы - целый бит (подразумевая двоичная точка
находится рядом с ним, справа)
Фиг. 7.15 Состояние 8087
Квадратное уравнение
Квадратное уравнение
Теперь приведем два примера, использующих программу индикации
чисел с плавающей точкой. Первый пример - решение квадратного
уравнения. Найдем корни уравнения, задаваемого следующей формулой
0 = A*X**2 + B*X + C
Из школьного курса математики известно, что решение этого
уравнения
X = ( -B +- SQR( B**2 - 4*A*C))/(2*A)
Программа решения этого уравнения очевидна и показана на
Фиг. 7.26. В ней предполагается, что все три параметра A, B и C
записаны в виде целых чисел. Конечно, если вы будете использоваь
программу не только как пример, нужно организовать процедуру ввода
различных коэффициентов.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:54
Фиг. 7.26 Вычисление корней квадратного уравнения Page 1-1
PAGE ,132
TITLE Фиг. 7.26 Вычисление корней квадратного уравнения
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE,ES:CODE
EXTRN FLOAT_ASCII:NEAR
0000 0001 A DW 1
0002 FFFB B DW -5
0004 0006 C DW 6
0006 ???? STATUS DW ?
0008 0004 FOUR DW 4
000A 0002 TWO DW 2
000C 8C AD A8 AC EB A5 20 ERROR_MSG DB 'Мнимые корни',10,13,'$'
AA AE E0 AD A8 0A 0D
24
001B QUADRATIC PROC FAR
001B 1E PUSH DS ; Сохранение адреса возврата
001C 2B C0 SUB AX,AX
001E 50 PUSH AX
001F 8C C8 MOV AX,CS
0021 8E D8 MOV DS,AX
0023 8E C0 MOV ES,AX
0025 9B DB E3 FINIT ;-----ST(0)-----;-----ST(1)------
0028 9B DF 06 0002 R FILD B ; B ; ?
002D 9B D8 8E 0000 FMUL ST(0) ; B**2 ; ?
0032 9B DF 06 0000 R FILD A ; A ; B**2
0037 9B DE 0E 0008 R FIMUL FOUR ; 4*A ; B**2
003C 9B DE 0E 0004 R FIMUL C ; 4*A*C ; B**2
0041 9B DE E1 FSUBRP ST(1),ST(0) ; D=B**2-4*A*C ; ?
0044 9B D9 E4 FTST
0047 9B DD 3E 0006 R FSTSW STATUS
004C 9B FWAIT
004D 8A 26 0007 R MOV AH,BYTE PTR STATUS+1
0051 9E SAHF
0052 72 37 JB IMAGINARY
0054 9B D9 FA FSQRT ; SQR(D) ;
Фиг. 7.26 Вычисление корней квадратного уравнения (начало)
0057 9B D9 C0 FLD ST(0) ; SQR(D) ; SQR(D)
005A 9B D9 E0 FCHS ; -SQR(D) ; SQR(D)
005D 9B DE 06 0002 R FIADD B ; B-SQR(D) ; SQR(D)
0062 9B D9 E0 FCHS ; -B+SQR(D) ; SQR(D)
0065 9B D9 C9 FXCH ST(1) ; SQR(D) ; -B+SQR(D)
0068 9B DE 06 0002 R FIADD B ; B+SQR(D) ; -B+SQR(D)
006D 9B D9 E0 FCHS ; N1=-B-SQR(D) ; N2=-B+SQR(D)
0070 9B DE 36 0000 R FIDIV A ; N1/A ; N2
0075 9B DE 36 000A R FIDIV TWO ; ROOT1=N1/2*A ; N2
007A E8 0000 E CALL FLOAT_ASCII ; N2 ; ?
007D 9B DE 36 0000 R FIDIV A ; N2/A ; ?
0082 9B DE 36 000A R FIDIV TWO ; ROOT2=N2/2*A ; ?
0087 E8 0000 E CALL FLOAT_ASCII ; ? ; ?
008A CB RET
008B IMAGINARY:
008B 8D 16 000C R LEA DX,ERROR_MSG
008F B4 09 MOV AH,9H
0091 CD 21 INT 21H ; Вывод сообщения об ошибке
0093 CB RET
0094 QUADRATIC ENDP
0094 CODE ENDS
END QUADRATIC
Фиг. 7.26 Вычисление корней квадратного уравнения (продолжение)
В примере отсутствует обработка комплексных чисел, но имеется
проверка дискриминанта (B**2 - 4*A*C) на отрицательность, и если это
число отрицательно, программа завешается с сообщением об ошибке.
Можно было бы ввести в программу комплексную арифметику, тем не
менее, ее нет в данном примере. Необходимо помнить, что
сопроцессор 8087 не обрабатывает автоматически комплексные или
мнимые числа, и нужно писать программу раздельной обработки
действительной и мнимой частей комплексного числа.
Команда FTST проверяет дискриминант на отрицательность; она
подобна сравнению с встроенным нулевым операндом источника.
Программа записывает слово состояния в память, а затем загружает
его в регистр флагов микропроцессора 8088. После этого делается
проверка JB (переход, если меньше), определяющая, меньше ли нуля
дискриминант. Оставшаяся часть программы проделывает работу по
вычислению двух корней уравнения, и здесь используется то
преимущество, что коэффициенты находятся в памяти. Такой подход
минимизирует объем используемого в сопроцессоре 8087 стека. Но
если вы переделаете эту программу так, что она будет подпрограммой,
вызываемой из другой программы, вам, вероятно, захочется передавать
параметры ей с помощью стека сопроцессора 8087. В этом случае
потребуется другой способ для загрузки подпрограммой некоторых
величин.
Хотя физически микросхема 8087 представляет
Модель программирования 8087
Хотя физически микросхема 8087 представляет собой отдельный
процессор, его нужно рассматривать, как расширитель микропроцессора
8088, добавляющий к нему новые возможности. Эти возможности
включают в сеьы дополнительные типы данных, дополнительные командв
и регистры. Рисунок 7.6 иллюстрирует программную модель
сопроцессора 8087. Его регистры показаны вместе с регистрами всей
системы программирования.
Команды сопроцессора 8087 должны рассматриваться
Набор команд сопроцессора 8087
Команды сопроцессора 8087 должны рассматриваться как расширение
набора команд микропроцессора 8088, т.е. микросхема 8087 добавляет
команды к общему набору. Адресация памяти выполняется так же, как
и в микропроцессоре 8088. Как мы уже видели ранее, так происходит
потому, что в действительности микропроцессор 8088 сам порождает
адреса памяти, а сопроцессор 8087 только выполняет требуемые
опреции над данными.
Сороцессор 8087 имеет набор регистров для чисел с плавающей
точкой, организованный в виде стека. Вершину стека микросхема 8087
локализует с помощью 3=битового указателя, содержащегося в слове
состояния. Прочитав слово состояния, вы сможете узнать, какой из
восьми регистров является текущей вершиной стека; но, как будет
видно далее, эта информация в программе требуется редко.
Вся адресация регистрового стека микросхемы 8087 делается
относительно текущей вершины стека. Мнемонически на ассемблере
вершина стека называется ST(0) или просто ST. Элемент, лежащий
прямо под вершиной стека, называется ST(1), второй элемент - ST(2),
и так далее, вплоть до последнего элемента, ST(7). Для простоты
использования мы будем опускать скобки в обозначениях элементов
стека, используя ST0, ST1, ST2 и так далее.
А теперь давайте посмотрим, как программа может добраться к
элементам стека. Команда
FADD ST0, ST3
складывает вершину стека и четвертый его элемент, помещая
результат в вершину стека. На Фиг. 7.9а показаны действия этой
команды. Ссылка на регистры ST0 и ST3 жестко связана с указателем
стека. На рисунке складываются значения A и D, а результат A + D
замещает значение A, так как оно лежит в вершине стека. На
Фиг. 7.9б до выполнения той же самой команды в стек был помещен
элемент E. Команда по=прежнему складывает содержимое вершины стека
с его четвертым элементом; но, как показано на Фиг. 7.9б, она теперь
складывает значения E и C, замещая результатом E + C зеачение E. В
результате помещения элемента E в стек перераспределения операндов
внутри сопроцессора 8087 не произошло, но произошло их перемещение
по отношению к вершине стека. То есть элемент ST3 всегда является
четвертым элементом стека, независимо от того, где расположен
указатель стека.
Мы можем разбить команды сопроцессора 8087 на три широкие
группы. В первой группе находятся пересылки данных - загрузки и
записи данных в сопроцессор 8087 и из него. Вторая группа -
управление сопроцессором, команды, которые служат для нужд
внутреннего сервиса 8087. В третьей группе команд сосредоточены
все возможности сопроцессора 8087, это команды числовой обработки.
Далее мы рассмотрим каждую группу детальнее. Этот текст не
содержит исчерпывающего описания команд микросхемы 8087. Мы будем
использовать примеры, чтобы с их помощью сообщить достаточное
количество информации о значении той или иной команды, а
рассмотрение всех вычислительных возможностей сопроцессора 8087
выходит за рамки данной книги.
ЪДДДДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДДДДї
і і і і
і ЪДДДДДДДДДДДДї і і ЪДДДДДДДДДДДДї і
АДДґ A ГДДДД + АДДґ E ГДДДД +
ГДДДДДДДДДДДДґ і ГДДДДДДДДДДДДґ і
і B і і і A і і
ГДДДДДДДДДДДДґ і ГДДДДДДДДДДДДґ і
і C і і і B і і
ГДДДДДДДДДДДДґ і ГДДДДДДДДДДДДґ і
і D ГДДДДДЩ і C ГДДДДДЩ
ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ
і і і D і
ГДДДДДДДДДДДДґ
і і
(a) (b)
Фиг. 7.9 Действие FADD ST0,ST3
Определение действительных чисел
Определение действительных чисел
Ассемблер отдельно порождает поля данных дествительных чисел с
помощью четырех-, восьми- и десятибайтовых описаний. Короткому
действительному числу здесь соответствует оператор DD; длинному
действительному числу соответствует оператор DQ; а временному
действительному чисоу - оператор DT.
К сожалению, Макроассемблер фирмы IBM не обслуживает числа с
плавающей точкой процессора 8087. Если вы укажите действительное
число в качестве операнда оператора DD или DQ, то в качестве данных
получите запутанный набор нулей и единиц. Этот формат плавающих
чисел соответствует формату, используемому внутри интерпретатора
языка Бейсик, и не является форматом ИИЭР, используемым в
сопроцессоре 8087. Поэтому программа для сопроцессора 8087 должна
использовать несколько иной метод порождения действительных чисел.
Существует два метода, которые можно использовать для
построения действительных чисел. Первый метод - ручное
ассемблирование. Вы выписываете действительное число в праивльном
формате с помощью бумаги и ручки. Эта операция содержит:
тщательный анализ числа, преобразование десятичного числа в
двоичную форму, разделение порядка и мантиссы, смещение порядка, и
наконец, преобразование результата в двоичную или шестнадцатеричную
систему. Это реальный метод, но он требует изрядного опыта и
огромных затрат времени программиста. Второй метод порожления
действительных чисел - косвенный метод, требующий большого
количества машинного времени, но гораздо меньших затрат времени
программиста. Здесь вы записываете действительное число в виде
произведения ил частного двух или более целых. Ассемблер может
непосредственно порождать целые числа с помощью команд DW, DD или
DQ. Сразу же после инициализации микросхемы 8087 программа может
построить необходимые ей действительные числа, выполняя необходимые
умножения и деления. Например, в программе нужна константа с
плавающей точкой 1.234*105. Сформировать такую константу в целом
представлении программист сможет легко, используя следующий оператор
DD 123400
Заметим, что число слишком велико для представления оператором
DW, но удовлетворяет ограничениям 32-битовых целых чисел.
Но предположим, что нужно построить число 1.234*10**-5, которое
очень мало. В этом случае в программе потребуется два целых числа:
одно целое число соответствует значению мантиссы, а другое
соответствует десяти в степени порядка. Можно воспользоваться
следующими определениями
DW 1234
DD 100000000
чтобы построить необходимое число. Программист делит дробную
часть числа 1234 на значение 10**8 и получает нужное число.
При использовании такого подхода возникают сложности в работе с
очень большими или малыми числами. Эти числа требуют представления
степеней десяти, которые по значению много больше любых
представимых целых чисел. Далее приводится пример, который строит
действительный формат каждой третьей степени десяти и может помочь
в таком случае. Отложим рассмотрение этой программы до тех пор,
пока не изучим необходимые для нее команды. С помощью таких
действителтных представлений степеней 10 записать числа от 1036 до
10-24 будет достаточно легко. Программа может разбить число на
целую часть и степень десяти, а умножение и деление дадут верное
значение. Микросхема 8087 также допускает и другие способы
выполнения таких операций масштабирования, но методы, используюшие
целые числа и степени 10, наиболее просты, особенно для людей,
работающих с сопроцессором 8087 впервые.
Перед тем, как мы окончим
Отладка программ с использованием 8087
Перед тем, как мы окончим обсуждение процессора 8087, хотелось
бы немного поговорить об отладке написанных для него программ.
Проблема, с которой мы сталкиваемся здесь, заключается в том, что
утилита DEBUG (отладчик) в DOS не поддерживает процессор 8087.
Это означает, что при обнаружении отладчиком контрольной точки он
не отображает на экране содержимого регистров сопроцессора 8087.
Это сильно затрудняет отладку программы, изменяющей регистры
сопроцессора 8087.
В книге предлагается метод, который можно применять для отладки
программ сопроцессора 8087, используя отладчик DOS. Этот метод
может быть и не наилучший, но он использовался при отладке программ
приведенных в этой главе.
Основное препятствие заключается в неспособности программы
DEBUG показывать содержимое регистрового стека сопроцессора 8087.
Без переписывания заново программы DEBUG этот метод дает
существенную иинформацию, необходимую при отладке программы
сопроцессора 8087. Этот метод отладки требует, чтобы программа
была написана, как отдельно выполняемая, либо как файл типа .EXE,
либа типа .COM. Даже если вы пишете подпрограмму, отладьте ее
сначала, как головную программу. Одной из первых команд программы
должна быть команда FINIT, сбрасывающая процессор 8087 в состояние,
в которое он попадает при включении питания. Это надо сделать,
чтобы можно было прогонять программу снова и снова, всегда начиная
сначала. Рассматриваемый здесь метод отладки не позволит вам
остановиться, проанализироваь регистры сопроцессора 8087, а затем
продолжить программу с этого же места. Этот метод основывается на
возможности начинать все сначала после каждой контрольной точки.
Вы должны организовать все параметры подпрограммы в виде ячеек
памяти, и программа должна загружать все эти числа в
соответствующие регистры вслед за командой FINIT. Это необходимо,
даже если идет работа с программой, принимающей параметры,
переданные через стековые регистры. Сначала отлаживаемая программа
работает с параметрами, лежащими в памяти. После того, как
арифметика и логика программы будет отлажена, можно будет изменить
программу так, чтобы она принимала параметры из регистрового стека.
Цель всех этих действий - позволить программе выполняться без
внешнего вмешательства. Это означает, что можно запустить
программу сначала и выполнять ее до некоторой команды, и перезапуск
программы приведет к точно такому же ее выполнению. Такое свойство
необходимо, так как предлагаемый метод индикации регистров
разрушает содержимое стека процессора 8087, и когда это произошло,
продолжать выполнение программы с этого же места уже нельзя.
Программу надо перезапустить с самого начала и остановить ее уже в
другом месте, а это возможно благодаря принятым мерам. Последние
два примера, квадратное уравнение и функция синуса, устроены именно
таким образом: их параметры находятся в памяти, и программы
начинаются с команды FINIT.
Следующий этап процедуры отладки требует размещения
специального программного фрагмента в заранее фиксированном месте
вашей программы. Для отладки примеров был выбран адрес 200, так
как ни один из этих примеров не занимает более 500 байт. Этот
программный фрагмент предназначен только для отладки, и вы удалите
его перед получением окончательной версии программы. Такой
фрагмент показан на Фиг. 7.29. Как вы видите, он очень короток и
содержит только три команды и два поля данных. Первое поле данных
содержит константу, в данном случае 106, или 1000000. Выбор этого
значения остается за вами; другое значение может оказаться
подходящим, если ваша программа работает с числами, меньшими 10-6,
или большими 1012.
Смысл этого программного фрагмента заключается в том, что он
преобразует содержимое вершины стека в число, которое вы сможете
увидеть. Этот фрагмент умножает содержимое текущей вершины стека
на число с массой нулей, а это эквивалентно сдвигу десятичной точки
вправо. В данном случае, если вершина стека содержит 1/2,
умножение преобразует ее в 500000.
После того, как число преобразовано в большое целое (вместо
дробного), команда FBSTP записывает его в упакованной десятичной
форме в поле, также находящееся в этом специальном программном
фрагменте. Затем команда INT 3 возвращает управление программе
DEBUG. Теперь можно использовать команду Display программы DEBUG,
чтобы посмотреть на 10 байт, записанных командой FBSTP. Конечно,
читать показанное значение нужно наоборот, так как это - способ,
которым сопроцессор 8087 записывает десятичные числа. Также надо
учесть модификацию десятичнлй точки, которую выполнило умножение.
------------------------------
TEN6 DD 1000000
ORG 200H
BCD_TEMP DT ?
ORG 210H
FIMUL TEN6
FBSTR BCD_TEMP
INT 3
--------------------------- Фиг. 7.29 Отладка процедуры
для числового сопроцессора
Отладка программы для сопроцессора 8087 осуществляется
следующим образом. Как только вы решили, что программа работает
неверно, вы находите место контрольной точки по листингу программы.
Использование команды Unassemble многого не даст, так как все
команды сопроцессора 8087 дезассемблируются как команды ESC. Так
что использование литсинга программы существенно.
Теперь вы выполняете программу с начала до контрольной точки;
именно для этого вы сконструировали программу так, чтобы ее можно
было перезапустить с начала баз какой=либо подготовки. Всякий раз,
когда вы вновь устанавливаете контрольную точку, нужно выполнять
программу с начала.
Когда программа попадает на контрольную точку, управление
передается в отладчик. Теперь вы можете выполнить тот специальный
фрагмент кода, который помещен в программу. Команда INT 3 в конце
этого фрагмента возвращает управление в программу DEBUG, так что вы
можете увидеть, что за число находилось в вершине стека, когда
выполнялась исходная контрольная точка. Так как была использована
команда FBSTP, она извлекла число из вершины стека, записав аго в
память. Поэтому, чтобы увидеть второе число стека, ST1, вы можете
еще раз выполнить отладочный фрагмент; все это можно повторять
столько раз, сколько вы хотите. Когда по этому методу будет
получено десятичное число, содержащее значение 0FFH как в старшем,
так и в знаковом байтах, знайте, что из стека извлеклось пустое
значение. Далее в программе можно установить новую контрольную
точку, и снова выполнить программу сначала. Таким образом вы
можете пройти путь по всей программе, пока не найдется место
ошибки. Как только ошибка найдена, можно либо исправить ее на
месте ("залатать" ошибку), или выйти назад в DOS, чтобы
отредактировать и заново ассемблировать программу. Когда, наконец,
программа выполняется верно, и вам больше уже не нужна программа
DEBUG, можно удалить отладочный фрагмент из программы. В этот же
момент вы можете изменить программу так, чтобы принимать параметры
через регистры стека, а не через память.
Представление данных с плавающей точкой
Представление данных с плавающей точкой
Для изображения чисел с плавающей точкой в процессоре 8087
существует три формата данных. Два из них совпадают с предложенным
ИИЭР стандартом для таких чисел. Короткий формат имеет 32 бита, а
длинный - 64 бита. Третий формат определяет 80-битовые числа, и не
совпадает со стандартом ИИЭР. Сопроцессор 8087 использует такой
формат "промежуточного действительного числа", чтобы обеспечить
очень высокую точность для промежуточных результатов вычислений.
Оставшаяся часть этого раздела предназначена для тех, кто не
имел дела с вещественными числами в ЭВМ. Вы можете пропустить этот
раздел, если понимаете о общих чертах способы представления
вещественных чисел - как на бумаге, так и в ЭВМ; а в следующем
разделе мы рассмотрим конкретный способ, используемый в
сопроцессоре 8087.
Целые числа - лучший способ представления многих величин.
Целые чила просто понимать и использовать, а также легко
преобразовывать в двоичное представление. Однако с целыми числами
плохо работать в случае очень больших значений. Очень большое
целое число обычно оканчивается длинной строкой нулей. Например:
Солнце находится на расстоянии около 93000000 миль от Земли. Целые
числа, кроме того, не способны представить значение, содержащее
дробную часть, то есть ЭВМ не может запомнить число 1/2 в целом
представлении. Любые другие дроби, меньшие 1, также невозможно
представить, используя целые числа.
Ученые и математики давным-давно разработали способ
представления этих чисел в достаточно удобном виде. На первом
этапе вводится десяточная точка. Этот символ, "." показывает
границу между целой и дробной частью числа. В случае целого числа
позиция, представляющая единицы, всегда находится на рпавом краю
числа; в случае, когда используется десятичная точка, цифры справа
от нее представляют значения, меньшие единицы.
У целых чисел каждая позиция числа соответствует степени 10.
То есть число 1234 есть
1234=1000+200+30+4=1*10**3+2*10**2+3*10**1+4*10**0
Десятичная точка показывает границу между позицией,
соответствующей 100, и дробной частью. В дробной части позиции
снова являются сиепенями 10, но теперь эти степени 10 -
отрицательные. Поэтому число 1.234 есть
1.234=1+0.2+0.03+0.004=
1*10**0+2*10**-1+3*10**-2+4*10**-3
Десятичная точка позволяет записывать дроби. Число 1/2 теперь
выглядит как 1.5, а 1/4 выглядит как 0.25, и 1/5 выглядит как 0.2.
Поскольку каждая позиция десятичного числа отличается от
соседней на степень 10, умножение числа на 10 эквивалентно сдвигу
десятичной точки на одну позицию вправо. Аналогично деление на 10
сдвигает десятичную точку на позицию влево. Это свойсто можно
использовать для сдвига десятичной точки на соответствующее место:
мы сдвигаем десятичную точку и одновременно корректируем число на
же степень 10. Такое представление чисел называется представление
"с плавающей точкой", поскольку десятичная точка "плавает" в числе;
она больше не помечает абсолютное место между целой и дробной
частями. Положение десятичной точки можно выбрать из соображений
удобства, а затем умножить число на нужную степень 10, чтобы
получить правильное значение.
Например, Солнце находится на расстоянии примерно 93000000 миль
от Земли. С такой формой записи тяжело работать из-за обилия
нулей. Можно записать это число в формате с плавающей точкой:
9.3*107. То есть 93000000 эквивалентно 9.3, умноженному на 10 в
седьмой степени. Фактически,
93000000=9.3*10**7=93*10**6=930*10**5
и так далее. Мы можем двигать десятичную точку куда угодно,
меняя степень 10.
Десятичное число с плавающей точкой состоит из двух частей.
Значащая часть числа называется мантиссой. В предыдущем примере
число 9.3 есть мантисса. На практике мантисса обычно находится в
пределах 1<=мантисса<10; т.е. она может быть равна 1.3, 7.6 или
9.97. Другая часть числа с плавающей точкой - это порядок,
степень, в которую нужно возвести 10 перед умножением его на
мантиссу. То есть 9.3*107 имеет мантиссу 9.3 и порядок 7. Если
основание системы счисления определено раз и навсегда, в нашем
случае это 10, то для восстановления первоначального числа должны
быть заданы только два числа - мантисса и порядок.
Представление с плавающей точкой позволяет записявать в
компактном виде как очень большие (например, 1.234*1085) так и
очень малые (1.234*10-85) числа. Чтобы записать те же числа без
использования степени десяти, потребовались бы длинные строки,
состоящие из нулей.
Двоичные числа с плавающей точкой изображаются аналогично
десятичным; отличие заключается в том, что основание системы
счисления здесь 2, а не 10. Мантисса имеет значение 1<=мантисса<2,
а порядок показывает степень 2. То есть число 1.101*10100 в
двоичной форме означает, что мантисса 1.101 умножается на 24, или
16. Значение мантиссы определяется таким же позиционным способом,
как и для десятичного числа, за исключением того, что основание
теперь равно 2. Позиции, находящиеся справа от двоичной точки,
представляют отрицательные степени двойки. Таблица на Фиг. 7.4
показывает значения первых пяти позиций.
Теперь мы можем вычислить десятичное значение числа из примера:
1.101b=1+1/2+1/8=15/8=1.625
10**100b=2**4=16
1.101*10**100B = (1+5/8)*16 = 26
Кроме того, мы могли бы вычислить это значение точно так же,
как в случае десятичных чисел. Значение порядка показывает, на
сколько позиций надо сдвинуть двоичную точку. В этом случае,
поскольку значение порядка равно 4, двоичную точку надо сдвинуть на
четыре позиции вправо. Поэтому
1.101*10**100B=11010B=26
Оба метода правильные и дают идентичные результаты. В первом
случае вычисляется мантисса, затем она умножается на основание,
возведенное в степень порядка; во втором случае сначала выполняется
умножение, а затем вычисляется мантисса.
Двоичное значение Десятичное значение
-------------------------------------------
0.1 1/2
0.01 1/4
0.001 1/8
0.0001 1/16
0.00001 1/32
----------------------------------------- Фиг. 7.4 Отрицательные
степени двух
Как же эти числа представляются в ЭВМ? Место, отводимое для
числа с плавающей точкой, делится на два поля. Одно поле содержит
знак и значение мантиссы, а другое содержит знак и значение
порядка. Размер поля мантиссы определяет точность представления
числа. Чем большее число бит отводится в ЭВМ под поле мантиссы,
тем выше точность. Например, десятичная мантисса 1.234 более
точная, чем мантисса 1.2. Две дополнительные цифры мантиссы дают
возможность более точно передавать значение числа.
Для того, чтобы сохранить максимальную точность, вычислительные
машины почти всегда хранят мантиссу в нормализованном виде. Это
означает, что мантисса есть число, лежащее между 1 и 2
(1<=мантисса<2). Два соображения говорят в пользу нормализации.
Во-первых, ни один незначащий нуль не дает никакого вклада в
точность числа. (Это несправедливо для нулей, лежащих в конце
числа; мы считаем, что число 1.000 более точно, чем число 1.0).
Если в мантиссе с плавающей точкой появились незначащие нули,
точность числа падает. Во-вторых, способ хранения мантиссы с
плавающей точкой подразумевает, что двоичная точка находится на
фиксированном месте. Фактически подразумевается, что двоичная
точка следует после первой двоичной цифры. То есть нормализация
мантиссы делает единичным первый бит, помещая тем самым значение
мантиссы между 1 и 2. Для выполнения нормализации ЭВМ корректирует
порядок числа на соответствующее значение.
При нормализации встречаются исключения. Наиболее очквидное
исключение - нуль. В этом случае вся мантисса нулевая. Второе
исключение не так очевидно, и возникает в случае приближения числа
к нижней границе своего диапазона. Давайте разберемся, что это
означает.
Точно так же, как длина мантиссы числа определяет его точность,
длина поля порядка определяет диапазон числа. Поле порядка
содержит степень двойки, и чем больше бит в этом поле, тем большее
число может быть представлено.
Если под поле порядка (представленного в дополнительном коде)
отведено три двоичных цмфры, наибольшее представимое число есть
1.111 ... * 10011B. Мантисса - это число, чуть меньше двух, а
множитель мантиссы равен 23, или 8. Поэтому максимальное число
чуть меньше 16. Наименьшее ненулевое положительное число есть
1.000 ... * 10100B, или 1*2-4, или 1/16. Поскольку в числах с
плавающей точкой для представления мантиссы не используется
дополнительный код, диапазон отрицательных чисел такой же. В
случае 3-битового порядка диапазон положительных чисел от 1/16 до
16, а отрицательных - от -16 до -1/16.
Если для поля порядка используется 4 бита, наибольшее число
равно 1.111... *100111B<2*27=256. Наименьшее ненулевое
положительное число 1.000... *101000B=1*2-8=1/256. Таким образом
четыре бита порядка допускают диапазон изменения чисел от 1/256 до
256. Чем большее число бит в поле порядка, тем шире диапазон
представимых чисел.
Важно отметить, что хотя диапазон и расширяется с увеличением
числа бит порядка, точность не увеличивается. В только что
рассмотренных примерах предполагалось, что имеется 4 бита мантиссы.
Если выбрать конкретное число, попадающее в диапазон обоих
примеров, то оно будут иметь одну и ту же точность независимо от
диапазона порядка. Например, число 1.010*10001B=21/2. Увеличение
количества бит порядка не увеличивает точность числа. Размер
мантиссы есть единственное ограничение точности данного числа в
пределах системы.
Значение порядка хранится не как число, представленное в
дополнительном коде. Для упрощения вычислений значение порядка в
ЭВМ хранится в виде смещенного числа. Это означает, что к
действительному значению порядка прибавляется смещение перед
записью его в память. Смещенным значение порядка делается для
того, чтобы можно было сравнивать значения порядка с помощью
обычной операции сравнения чисел с фиксированной точкой без знака.
В частности, это полезно при сравнении двух чисел с плавающей
точкой. Значения порядка и мантиссы записываются в одном элементе
данных, причем порядок записывается перед мантиссой. В случае
смещенного значения порыдка программа может сравнивать числа
побитно, начиная со старших позиций. Первое же неравенство
показывает соотношение чисел, и больше не нужно учитывать никакие
части чисел. Значение смещения определяется размером поля порядка.
Давайте это рассмотрим на примере форматов данных сопроцессора
8087.
В сопроцессоре 8087 формат короткого действительного числа
использует 32 бита, восемь из которых содержат значение порядка;
таким образом, оно может находиться в пределах от - 128 до 127.
Однако значение -128 зарезервировано для обозначения некоторого
специального, а именно, неопределенного числа, так что значения
порядка находятся в диапазоне от -127 до 127. Значение порядка -
это не число, представленное в дополнительном коде. Сопроцессор
8087 прибавляет значение смещения 127 к значению порядка
действительного числа перед тем, как его запомнить.
Значение порядка Хранимое значение
---------------------------------------
-127 000H
-1 07EH
0 07FH
1 080H
127 0FEH
------------------------------------- Фиг. 7.5 Хранение порядка
В таблице на Фиг. 7.5 показаны некоторые возможные значения
порядка и его запоминаемое значение. Как видно из рисунка,
смещение меняет содержимое поля порядка так, чтобы наименьшему
значению порядка соответствовало наименьшее действительное число;
аналогично, наибольшему значению порядка соответствовало наибольшее
число. Если два числа с плавающей точкой различаются значением
порядка, простое сравнение полей порядка упорядочивает оюа числа.
Поскольку такое сравнение можно продолжать и дальше с полями
мантисс, программа может легко сравнить два числа с плавающей
точкой. И вся эта простота - только благодаря смещению порядка.
распространенных операций научит вас применять
Примеры
Этот раздел главы с помощью примеров исполльзования наиболее
распространенных операций научит вас применять сопроцессор 8087,
предполагая, что вы этого совсем не умеете. Все примеры просты,
т.е. в них не делается попыток обработки всех возможных ошибок или
реакции на какие=либо специальные числа, с которыми может работать
микросхема 8087. Сопроцессор 8087, конечно же, имеет такие
возможности, но эти действия относятся к более сложным. Когда вы
освоите основы техники, вы сможете добавить к этим примерам
некоторые фрагменты, которые превратят их в настоящие подпрограммы
общего назначения.
микросхема 8087 выполняет арифметическую команду,
Работа 8087
Сопроцессор 8087 обрабатывает команды с плавающей точкой,
контролируя команды, выполняемые процессором 8088. Арифметический
сопроцессор "смотрит" на команды, выполняемые процессором 8088.
Когда сопроцессор 8087 "видит" команду, котрую он должен выполнить,
он начинает ее обработку. Микросхема 8087 выполняет свои числовые
операции параллельно с микропроцессором 8088. То есть, пока
микросхема 8087 выполняет арифметическую команду, микропроцессор
8088 может продолжать выполнять свои команды. Тем самым возникает
истинная параллельность работы; микропроцесоор 8088 может выполнять
команду, пока сопроцессор 8087 выполняет другую команду. В
частности, это имеет значение в случае, когда команда сопроцессора
8087 занимает много времени, что характерно для некоторых команд с
плавающей точкой.
Поскольку два процессора могут работать параллельно, необходима
некоторая синхронизация межлу ними. Эту работу выполняет команда
WAIT микропроцессора 8088. Микросхема 8087 подключена к
микропроцессору 8088 таким образом, что когда процессор числовой
обработки занят (выполняет команду плавающей точки), вход TEST
микропроцессора 8088 неактивен. Команда WAIT останавливает
обработку команд в микропроцессоре 8088 до тех пор, пока вход TEST
не станет активен, сигнализуя о завершении команды сопроцессора
8087. Таким образом, при работе микропроцессора 8088 можно
гарантировать, что микросхема 8087 завершит свою работу до того,
как микропроцессор 8088 выберет на выполнение следующую команду.
Это также предотвращает выборку данных до завершения исполнения
команды.
Процессор и сопроцессор связаны только по внешним линиям
управления, таким как вход TEST. Микропроцессор 8088 не может
читать внутренние регистры микросхемы 8087, и наоборот. Все
данные, передаваемые между ними, должны быть помещены в память, к
которой оба процессора имеют доступ. Но из-за того, что регистры
адресации находятся в микропроцессоре 8088, микросхеме 8087 трудно
эффективно адресовать память, используя те же способы адресации,
которые используют микропроцессор 8088. Чтобы позволить микросхеме
8087 адресовать память с помощью способов адресации микропроцессора
8088, существует взаимодействие двух процессоров при выполнении
команд плавающей точки.
Набор команд микропроцессора 8088 содержит команду ESC, которая
сама по себе не выполняется в микропроцессоре 8088. В системах без
сопроцессора 8087 команда ESC идентична команде NOP - за
исключением того что для ее выполнения требуется большн времени.
Все команды ESC имеют встроенную адресную информацию, а именно, для
вычисления адреса, они используют байт mod=r/m. Несмотря на то,
что команда ESC действует как команда NOP, микропроцессор 8088
выполняет вычисление исполнительного адреса, а заетм выполняет
чтение памяти по результирующему адресу, хотя и не производит
никаких действий с данными. Если байт mod=r/m определяет регистр,
а не память микропроцессора 8088, никакого чтения памяти не
происходит.
Тем временем микросхема 8087 "наблюдает" за последовательностью
команд, выполняемых микропроцессором 8088. Когда микропроцессор
выполняет команду ESC, микросхема 8087 распознает эту команду, как
свою собственную. Затем микросхема 8087 ждет, пока микропроцесоор
8088 выполнить фиктивное чтение памяти. Когда адрес памяти
оказывается на системной шине, микросхема 8087 "захватывает" его, и
начиная с этого момента знает, где находятся данные в памяти, не
вычисляя при этом адреса. Микропроцессор 8088 вычисляет адрес, а
микросхема 8087 выполняет остальную часть команд. Теперь
микросхема 8087 может "похищать" некоторые циклы памяти для чтения
или записи данных, а микропроцесоор 8088 в это время продолжает
работу.
Микросхема 8087 добавляет арифметические возможности в систему,
но не замещает ни одну команду микропроцессора 8088. Команды ADD,
SUB, MUL и DIV, описанные в гл.4, выполняются микропроцессором
8088, а арифметический сопро- цессор 8087 выполняет дополнительные,
более эффективные команды арифметической обработки. С точки зрения
программиста, система с установленной в ней микросхемой 8087 должна
выглядеть, как единый процессор с большим набором команд, чем
простой микропроцессор 8088. Лишь в немногих местах надо помнить,
какой процессор какую команду выполняет. Только тогда, когда
микропроцессор 8088 должен непосредственно использовать результат
работы микросхемы 8087 и требуется синхронизирующая команда WAIT,
различать процессоры становится необходимо.
Когда вы используете Макроассемблер фирмы IBM, возникает
проблема при написании программ, использующих набор команд
микросхемы 8087. Чтобы использовать эту микросхему, нужно
сформировать ее команды, используя коды операций WAIT и ESC.
Наилучший способ сделать это - написать набор макрокоманд, которые
позволяют писать команды микросхемы 8087. В гл.6 были приведены
некоторые макрокоманды, необходимые для написания команд
сопроцессора 8087. Если вы собираетесь программировать микросхему
8087, вам нужно дописать примеры гл.6 для полного набора команд
сопроцессора 8087.
Регистровый стек
Регистровый стек
Сопроцессор 8087 имеет четыре регистра специального назначенмя
и восьмирегистровый стек для арифметических операндов. Этот
регистровый стек лежит в основе работы сопроцессора 8087, и поэтому
мы прежде всего рассмотрим именно его.
Регистровый стек процессора числовой обработки состоит из
восьми позиций, каждая из которых имеет ширину 80 бит. Сороцессор
8087 запоминает все объекты во временном действительном формате,
независимо от того, как они представлены в памяти системы;
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і ПОЛЕ ДАННЫХ ПОЛЕ і
і 79 78 64 63 0 1 0 і
іЪДДДДДДВДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДї ЪДДДДДДїі
ііЗНАК і ПОРЯДОК і МАНТИССА і і іі
іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі
іі і і і і іі
іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі
іі і і і і іі
іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі
іі і і і і іі
іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі
іі і і і і іі
іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі
іі і і і і іі
іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі
іі і і і і іі
іГДДДДДДЕДДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДґ ГДДДДДДґі
іі і і і і іі
іАДДДДДДБДДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ АДДДДДДЩі
і 15 0 і
і ЪДДДДДДДДДДДДДДДДДДДДДДї і
і іУПРАВЛЯЮЩИЙ РЕГИСТР і і
і ГДДДДДДДДДДДДДДДДДДДДДДґ і
і іРЕГИСТР СОСТОЯНИЯ і і
і ГДДДДДДДДДДДДДДДДДДДДДДґ і
і і і і
і ГД УКАЗАТЕЛЬ КОМАНД Дґ і
і і і і
і ГДДДДДДДДДДДДДДДДДДДДДДґ і
і і і і
і ГД УКАЗАТЕЛЬ ДАННЫХ ДДґ і
і і і і
і АДДДДДДДДДДДДДДДДДДДДДДЩ і
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Фиг. 7.6 Регистровая труктура микросхемы INTEL 8087
сопроцессор 8087 преобразует целые и действительные числа во
внутренний формат. Как мы уже видели, такое представление
допускает очень высокую точность и большой диапазон. В случае
целых чисел сопроцессор 8087 получает точные результаты вплоть до
значения 2**64. Внутренний формат с плавающей точкой малоинтересен
для тех программистов, которые обрабатывают целые числа с помощью
микросхемы 8087.
Регистры сопроцессора 8087 функционируют как обычный стек,
такой же, как и стек микропроцессора 8088. Но у этого стека
имеется ограниченное число позиций - только вомесь. Процессор 8087
имеет еще один регистр, труднодоступный для программиста. Этот
регистр представляет собой слово, содержащее "этикетки" каждой
позиции стека. Такой регистр позволяет сопроцессору 8087
отслеживать, какие из позиций стека используются, а какие свободны.
Любая попытка помещения объекта в стек на уже занятую позицию
приводит к врзникновению особой ситуации - недействительной
операции. Ниже будут обсуждены особые ситуации, и то, как
программа может бороться с проблемой переполнения стека.
Программа может заносить данные в стек сопроцессора 8087 с
помощью команды загрузки, и все команды загрузки помешают данные в
вершину стека. Если число в главной памяти записано не во
временном действительном формате, микросхема 8087 преобразует его в
80-битовое представление во время выполнения команды загрузки.
Аналогично команды записи извлекают значение из стека микросхемы
8087 и помещают их в главную память, а если необходимо
преобразование формата данных, сопроцессор 8087 выполняет его как
часть операции записи. Некоторые формы операции записи оставляют
вершину стека нетронутой для дальнейших действий.
После того, как программа поместила данные в стек сопроцессора
8087, они могут быть использованы в любой вычислительной команде.
Вычислительные команды процессора 8087 допускают как действия между
регистрами, так и действия между памятью и регистрами. Точно так
же, как и в случае микропроцессора 8088, из любых двух операндов
арифметической операции один должен находиться в регистре. У
сопроцессора 8087 один из операндов должен быть всегда верхним
элементом стека, а другой операнд может быть взят их памяти, либо
из стека регистров. Стек регистров сопроцессора 8087 всегда должен
быть приемником результата любой арифметической операции.
Непосредственно записать результат в память той же командой,
которая выполнила вычисления, процессор числовой обработки не
может. Чтобы переслать операнд обратно в память, необходима
отдельная команда записи (или команда извлечения из стека с записью
в память). Некоторые арифметические команды извлекают верхний
элемент стека и сбрасывают его, не записывая в память.
Синус угла
Синус угла
Последний пример использования сопроцессора 8087 - вычисление
синуса угла. У сопроцессора 8087 нет команды вычисления функции
SIN; самое большее, что он может - это выполнить команду FPTAN,
нахождение частичного тангенса. Чтобы выполнить операцию SIN,
воспользуемся этой командой, а также командой FPREM (частичный
остаток).
Программа, вычисляющая SIN, показана на Фиг. 7.27. Эта
программа вычисляет и печатает синусы углов от 1/2 до 6 с шагом 1/2
радиана. Выдача программы аналогична выдаче следующей программы на
языке Бейсик:
10 FOR X = .5 TO 6.0 STEP .5
20 PRINT SIN(X)
30 NEXT X
Для печати результатов используется подпрограмма на Фиг. 7.25.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:01
Фиг. 7.27 Вычисление синуса угла Page 1-1
PAGE ,132
TITLE Фиг. 7.27 Вычисление синуса угла
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE,ES:CODE
EXTRN FLOAT_ASCII:NEAR
0000 0001 NUM_ANGLE DW 1
0002 0002 DEN_ANGLE DW 2
0004 ???? STATUS DW ?
0006 0004 FOUR DW 4
= 0040 C3 EQU 40H
= 0004 C2 EQU 04H
= 0002 C1 EQU 02H
= 0001 C0 EQU 01H
0008 93 A3 AE AB 20 E1 AB ERROR_MSG DB 'Угол слишком большой', 10, 13, '$'
A8 E8 AA AE AC 20 A1
AE AB EC E8 AE A9 0A
0D 24
001F SIN PROC FAR
001F 1E PUSH DS
0020 2B C0 SUB AX, AX
0022 50 PUSH AX
0023 8C C8 MOV AX, CS
0025 8E D8 MOV DS, AX
0027 8E C0 MOV ES, AX
0029 DO_AGAIN:
0029 9B DB E3 FINIT ;-----ST(0)-----;-----ST(1)------
002C 9B DF 06 0000 R FILD NUM_ANGLE ; ;
0031 9B DE 36 0002 R FIDIV DEN_ANGLE ; X = Угол ;
Фиг. 7.27 (a) Процедура SIN (начало)
0036 9B D9 EB FLDPI ; PI ; X
0039 9B DE 36 0006 R FIDIV FOUR ; PI/4 ; X
003E 9B D9 C9 FXCH ; X ; PI/4
0041 9B D9 F8 FPREM ; R ; PI/4
0044 9B DD 3E 0004 R FSTSW STATUS
0049 9B FWAIT
004A 8A 26 0005 R MOV AH, BYTE PTR STATUS+1
004E F6 C4 04 TEST AH, C2
0051 75 55 JNZ BIG_ANGLE
0053 F6 C4 02 TEST AH, C1 ; Определяется, необходимо ли вычитание PI/4
0056 74 05 JZ DO_R ; Если 0, то не необходимо вычитание PI/4
0058 9B DE E1 FSUBRP ST(1), ST(0) ; A = PI/4-R ; ?
005B EB 06 JMP SHORT DO_FPTAN
005D DO_R:
005D 9B D9 C9 FXCH ; PI/4 ; R
0060 9B D8 D9 FCOMP ; R ; ?
0063 DO_FPTAN:
0063 9B D9 F2 FPTAN ; OPP ; ADJ Где OPP/ADJ=Tan(A)
;----- Опеределение того, что нужно - синус или косинус
0066 F6 C4 42 TEST AH, C3 or C1
0069 7A 03 JPE DO_SINE
006B 9B D9 C9 FXCH ; ADJ ; OPP
006E DO_SINE: ; D ; N
;----- Вычисление N/SQR(N**2 + D**2)
006E 9B D8 8E 0000 U FMUL ST(0) ; D**2 ; N
0073 9B D9 C9 FXCH ST(1) ; N ; D**2
0076 9B D9 C0 FLD ST(0) ; N ; N ; D**2
0079 9B D8 8E 0000 U FMUL ST(0) ; N**2 ; N ; D**2
007E 9B DC 06 0000 U FADD ST(2) ; N**2 + D**2 ; N ; D**2
0083 9B D9 FA FSQRT ; SQR(N2 + D2) ; N ; D**2
0086 9B DE F1 FDIVRP ST(1) ; SIN(X) ; D**2
0089 9B D9 C9 FXCH ST(1) ; D**2 ; SIN(X)
008C 9B D8 D9 FCOMP ; SIN(X) ; ?
008F F6 C4 01 TEST AH, C0
0092 74 03 JZ SIGN_OK
0094 9B D9 E0 FCHS
0097 SIGN_OK:
0097 E8 0000 E CALL FLOAT_ASCII
009A FF 06 0000 R INC NUM_ANGLE
009E 83 3E 0000 R 0D CMP NUM_ANGLE, 13
00A3 77 02 JA RETURN_INST
00A5 EB 82 JMP DO_AGAIN
00A7 RETURN_INST:
00A7 CB RET
00A8 BIG_ANGLE:
00A8 8D 16 0008 R LEA DX, ERROR_MSG
00AC B4 09 MOV AH, 9H
00AE CD 21 INT 21H
00B0 CB RET
00B1 SIN ENDP
00B1 CODE ENDS
END SIN
Фиг. 7.27 (a) Процедура SIN (продолжение)
A>SIN
4.79425539E-001
8.41470985E-001
9.97494987E-001
5.98472144E-001
1.41120008E-001
-3.50783228E-001
-7.56802495E-001
-9.77530118E-001
-9.58924275E-001
-7.05540326E-001
-2.79415498E-001
2.15119988E-001
Фиг. 7.27 (b) Вывод процедуры SIN
Фиг. 7.27 Вычисление синуса угла
В первой части программы происходит ее инициализация для работы
в качестве файла типа .EXE. Затем сопроцессор 8087 загружает два
целых числа и делит их, формируя исходный угол. Это - пример
использования двух целых чисел для порождения числа с плавающей
точкой (в данном случае 1/2), что нельзя сделать непосредственно с
помощью ассемблера.
Как вы помните из тригонометрии, синус - периодическая функция.
То есть функция дает один и тот же результат в случае исходных
чисел, различающихся ровно на 2*PI. Поэтому первой задачей
подпрограммы SIN является замена исходного угла соответствующим
значением, лежащим в диапазоне
0 <= X < 2*PI
В команде FPTAN требуется, чтобы угол находился в диапазоне
0 <= X < PI/4
Это означает, что даже если угол и меньше 2*PI, мы должны
уменьшить его еще, чтобы он удовлетворял ограничениям команды
FPTAN. К счастью, если исходный угол уменьшен до значения,
меньшего PI/4, все еще можно определить верное значение
тригонометрических функций. Чтобы это сделать, надо знать, в каком
месте исходного диапазона от 0 до 2*PI находился исходный угол.
Нужное уменьшение угла выполняет команда FPREM. Она не только
вычисляет остаток, но и три младших бита частного, определяемого в
течение процесса поиска остатка. Эти три бита команда записывает в
слово состояния. Следовательно, хотя мы и уменьшили угол до
значения одной восьмой исходного диапазона, все же можно определить
октант, в который попадет угол. Зная его, можно найти формулу
вычисления синуса с помощью тригонометрических преобразований.
Таблица на Фиг. 7.28 показывает связь между исходным октантом и
методом вычисления синуса угла. В таблице предполагается, что
число R - это остаток от уменьшения исходного угла до значения
меньше PI/4. Номер октанта появляется в разрядах C3 = C1 = C0
после выполнения команды FPREM.
С помощью этой таблицы мы можем определить формулу вычислений,
применяемую в каждом случае выполнения программы. После загрузки
значения угла в радианах программа загружает число и делит его на
4, чтобы использовать в команде FPREM. В этот момент
"захватывается" слово состояния. Если процесс поиска остатка не
завершился на этом единственном шаге, это означает, что исходный
угол был больше 2**64. Следовательно, его значение настолько больше
максимально возможного при вычислениях тригонометрических функций,
что мы отбрасываем это число, как слишком большое. Этого не
происходит со значениями, выбранными в примере, но здесь для
иллюстрации введена такая проверка.
Октанты
C0 C3 C1 Диапазон SIN(X) = :
-----------------------------------------------------
0 0 0 0 PI/4 SIN(R)
0 0 1 PI/4 PI/2 COS(PI/4-R)
0 1 0 PI/2 3*PI/4 COS(R)
0 1 1 3*PI/4 PI SIN(PI/4-R)
1 0 0 PI 5*PI/4 - SIN(R)
1 0 1 5*PI/4 3*PI/2 - COS(PI4-R)
1 1 0 3*PI/2 7*PI/4 - COS(R)
1 1 1 7*PI/4 2*PI - SIN(PI/4-R)
(R - остаток, 0
Фиг. 7.28 SIN(X) в восьми секторах
Программа проверяет разряд C1 в регистре состояния, чтобы
определить, должна ли она использовать остаток R, или его надо
вычесть из PI/4. Так как PI/4 еще находится в одном из регистров,
это сделать просто. Если вычитание не требуется, команда FCOMP
удаляет из стека ненужное значение PI/4.
Затем команда FPTAN вычисляет частичный тангенс. Результат
работы команды показан, как OPP/ADJ (сокращения от английских слов
Opposite (противоположный) и Adjacent (соседний)), что равно
тангенсу угла R или PI/4-R, в зависимости от того, что было
выбрано. С помощью этих двух чисел теперь можно опеределить синус
или косинус угла. Например, синус, заданный парой чисел OPP/ADJ,
можно вычислить по формуле
SIN(X) = OPP/SQR(OPP**2+ADJ**2), где TAN(X) = OPP/ADJ
Чтобы вычислить косинус, нужно числитель заменить на ADJ. Мы
решаем, нужен ли синус или косинус, анализируя запомненные
описатели октанта, т.е. проверяя значения разрядов C3 и C1.
Команда TEST выделяет эти значения, а команда JPE делает переход,
если они оба нулевые или оба единичные. В этом случае мы вычисляем
синус; если же они различны, мы вычисляем косинус, что достигается
заменой местами значений OPP и ADJ в стеке регистров.
Далее следующие команды сопроцессора 8087 вычисляют значение
синуса (или косинуса) по значению частичного тангенса.
Единственный шаг, который еще надо выполнить - это определение
окончательного знака результата. В случае синуса результат
отрицателен, если угол находится в октантах от четвертого до
седьмого. Проверка разряда C0 определяет верный знак результата.
Затем программа FLOAT_ASCII, показанная на Фиг. 7.25, печатает
число в плавающем формате. Управление возвращается назад, к началу
цикла, если еще не пройдены все октанты. Нижняя часть Фиг. 7.27
иллюстрирует результат выполнения этой программы.
Слово состояния
Слово состояния
Слово состояния микросхемы 8087 содержит текущее состояние
процессора. Расположение отдельных битов слова состояния показано
на Фиг. 7.8. В слове состояния имеются биты, показывающие особые
ситуации, так что обработчик прерывания сможет определить суть
возникшей ситуации. Также в слове состояния имеется бит,
показывающий, занят сопроцессор или нет. Это тот же самый бит,
который выведен наружу для синхронизации с микропроцессором 8088.
Слово состояния содержит и указатель на текущую вершину стека
внутри набора регистров процессора 8087.
15 7 0
ЪДДДВДДДВДДДДДДДДДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї
і B іC3 і ST іC2 і C1іC0 іIR і іPE іUE іOE іZE іDE іIE і
АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
і і ДДДДДВДДДДД ДДДДДВДДДДД і і і і і і і і ФЛАГИ ИСКЛЮЧИТЕЛЬНЫХ СОСТОЯНИЙ (1=ЕСТЬ)
і і і і і і і і і і і АД НЕВЕРНАЯ ОПЕРАЦИЯ
і і і і і і і і і і АДДДДДД НЕНОРМАЛИЗОВАННЫЙ ОПЕРАНД
і і і і і і і і і АДДДДДДДДДД ДЕЛЕНИЕ НА НОЛЬ
і і і і і і і і АДДДДДДДДДДДДДД ПЕРЕПОЛНЕНИЕ
і і і і і і і АДДДДДДДДДДДДДДДДДД ИСЧЕЗАНИЕ ПОРЯДКА
і і і і і і АДДДДДДДДДДДДДДДДДДДДДД ТОЧНОСТЬ
і і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)
і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЗАПРОС НА ПРЕРЫВАНИЕ
і АДДДДДДДЕДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДСОД УСЛОВИЯ (1)
і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУКАЗАТЕЛЬ ВЕРШИНЫ СТЕКА (2)
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЗАНЯТО
(1) См. описание команды сравнения, тестирования, опроса, и остатка в разделе
S.7 для прерывания по коду состояния
(2) Значения ST:
000 = Вершина стека - регистр 0
001 = Вершина стека - регистр 1
*
*
111 = Вершина стека - регистр 7
Фиг. 7.8 Формат слова состояния INTEL 8087
Вероятно, наиболее используемая часть слова состояния
микросхемы 8087 - это поле кода условия. В слове состояния есть
четыре бита, которые могут устанавливаться командами сопроцессора
8087. Два из этих битов кода условия непосредственно соответствуют
флагам переноса и нуля микропроцессора 8088; фактически, они
расположены на тех же позициях в старшем байте слова состояния
процессора 8087. Преимущества такого расположения этих бит можно
использовать, записав слово состояния в память; затем загрузить
старший байт слова состояния в регистр AH, и с помощью команды SAHF
устанавливаете флаги переноса и нуля в соответствии с результатом
операции сравнения в сопроцессоре 8087. Так как все числа в
сопроцессоре 8087 - действительные со знаком, этих двух флагов
достаточно для сравнения любых двух чисел. Далее мы будем иметь
дело с примерами, которые используют слово состояния для сравнения
чисел. Оставшиеся два бита регистра кода условия используются
специальной командой процессора 8087 для проверки появления любого
из чисел, соответствующих специальным условиям, которые известны
сопроцессору 8087. Поскольку многие из этих чисел требуют
индивидуальных способов обработки, регистр кода условия
предоставляет способ распознавания таких чисел.
Степени десяти
Степени десяти
Первый пример - исходный текст программы на Фиг. 7.23. Эта
программа распечатывает короткое действительное представление
степеней 10 от 103 до 1039. Как мы уже видели в разделе,
посвященном представлению данных, Макроассемблер фирмы IBM не имеет
возможностей непосредственно генерировать действительные числа.
Наличие такой таблицы чисел облегчит вам представление в виде
констант степеней 10. По этой таблице вы сможете определить
шестнадцатеричное представление числа, которое нужно использовать в
программе.
Программа вычисляет только каждую третью степень 10, и
использует короткий действительный формат. Если вы работаете с
много большими числами, или вам нужна большая точность, тогда нужно
выполнить этот пример, используя длинный действительный формат
чисел, а также построить каждую степень 10. Выполните это в
качестве упражнения.
Первоочередная цель этого примера - введение в программирование
и работу на сопроцессоре 8087. Это отдельная самостоятельная
программа, предназначенная для выполнения в виде файла типа .EXE.
Прежде чем разобраться в самой программе, заметим, что в нее входит
сегмент STACK, необходимый в файле типа .EXE. Сначала в сегменте
CODE записаны поля данных, а выполнение программы начинается с
метки CALCULATE_POWER. Заглянув вперед, на оператор END, вы
увидите, что первой выполняемой командой будет команда, помеченная
CALCULATE_POWER, так как она указана в операторе END.
Первая часть программы выполняет инициализацию. Перед
загрузкой указателя на сегмент CODE в регистр DS программа помещает
в стек адрес возврата из файла типа .EXE. Затем с помощью команды
FINIT инициализируется сопроцессор 8087, что аналогично аппаратному
сбросу. Тем самым сопроцессор 8087 оказывается настроенным на
обработку особых ситуаций по умолчанию, что наилучшим образом
подходит для примеров этой книги. Команда FINIT также сбрасывает
регистровый стек сопроцессора 8087, освобождая все его восемь
позиций. Программа должна использовать команду FINIT только в
момент запуска. Команда FINIT никогда не должна быть использована
внутри подпрограммы для сопроцессора 8087.
Следующие команды загружают число 1000 в регистр ST1 и число 1
в регистр ST0. Все следующие команды сопроцессора 8087 используют
эти два регистра стека. В регистре ST0 находится текущая степень
десяти, а в регистре ST1 находится значение 103. Мы будем
использовать число в регистре ST1 для увеличения числа в регистре
ST0 после каждой итерации программы. Целая переменная POWER
содержит текущую степень 10, находящуюся в регистре ST0.
После метки POWER_LOOP элемент ST0 умножается на элемент ST1,
(в котором содержится число 1000), чтобы увеличить содержимое
регистра ST0 в 103 раз. Команда FST записывает результат в память.
Оставшаяся часть программы после метки POWER_LOOP печатает
результаты вычислений. В подпрограмме TRANSLATE шестнадцатеричный
байт преобразуется в двухбайтовую строку в коде ASCII так, что
программа может его распечатать. Текущее значение POWER (степень
десяти), а также шестнадцатеричная строка, записанная процессором
8087, преобразуются в код ASCII. Затем функция DOS печатает строку
на дисплее. Цикл POWER_LOOP продолжается до тех пор, пока
последнее напечатанное значение не станет больше 1038. Это
значение выбрано потому, что 1038 - это максимальное число, которое
может быть представлено в коротком действительном формате. Если бы
использовался длинный действительный формат чисел, это значение
было бы равно 10308. Заключительная часть Фиг. 7.23 показывает,
как выглядит результат работы этой программы на дисплее.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:33
Фиг. 7.23 Степени 10 Page 1-1
PAGE ,132
TITLE Фиг. 7.23 Степени 10
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 ???????? POWER_OF_TEN DD ? ; Область данных для 10**x,
; короткое плавающее
0004 0002[ OUTPUT_POWER DB 2 DUP (' ') ; Текстовый буфер для значения
20
]
0006 48 20 20 20 20 DB 'H ' ; степени
000B 0008[ OUTPUT_STRING DB 8 DUP (' ') ; Текстовый буфер для результата -
20 20 20 20 20
20 20 20 20
]
0053 48 0D 0A 24 DB 'H',13,10,'$' ; Конец строки
0057 00 POWER DB 0 ; Текущая степень 10
0058 03E8 THOUSAND DW 1000 ; Константа
005A 03BF CONTROL_87 DW 03BFH
005C CALCULATE_POWER PROC FAR
005C 1E PUSH DS ; Занесение в стек адреса возврата
005D B8 0000 MOV AX, 0
0060 50 PUSH AX
0061 0E PUSH CS
0062 1F POP DS
ASSUME DS:CODE ; Адресация области данных
0063 9B DB E3 FINIT ; Инициализация сопроцессора 8087
Фиг. 7.23 (а) (начало)
0066 9B DF 06 0058 R FILD THOUSAND ; Загрузка 10**3 в стек сопроцессора 8087
006B 9B D9 E8 FLD1 ; Загрузка начального значения в стек 8087
006E POWER_LOOP:
006E 9B DC 8E 0000 FMUL ST(1) ; Умножение ST(0) на ST(1)
0073 9B D9 16 0000 R FST POWER_OF_TEN ; Сохранение в памяти результата
0078 80 06 0057 R 03 ADD POWER, 3 ; Увеличение показателя степени
007D A0 0057 R MOV AL, POWER ; Выборка показателя степени
0080 8D 1E 0004 R LEA BX, OUTPUT_POWER
0084 E8 00AC R CALL TRANSLATE
0087 B9 0004 MOV CX, 4
008A 8D 1E 000B R LEA BX, OUTPUT_STRING
008E 8D 36 0003 R LEA SI, POWER_OF_TEN+3
0092 FD STD ; Установка пересылки с уменьшением адреса
0093 VALUE_OUTPUT:
0093 AC LODSB ; Выборка байта результата
0094 E8 00AC R CALL TRANSLATE ; Занесение символа в выводимую строку
0097 E2 FA LOOP VALUE_OUTPUT ; Цикл по всем байтам результата
0099 8D 16 0004 R LEA DX, OUTPUT_POWER
009D B4 09 MOV AH, 9H
009F CD 21 INT 21H
00A1 80 3E 0057 R 26 CMP POWER, 38
00A6 72 C6 JB POWER_LOOP
00A8 9B DE D9 FCOMPP ; Удаление из стека двух чисел
00AB CB RET
00AC CALCULATE_POWER ENDP
00AC TRANSLATE PROC NEAR
00AC 50 PUSH AX ; Сохранение исходного значения
00AD 51 PUSH CX
00AE B1 04 MOV CL, 4 ; Сдвиг старшей цифры выводимого числа
00B0 D2 E8 SHR AL, CL ; на место младшей цифры
00B2 59 POP CX
00B3 E8 00CB R CALL XLAT_OUTPUT ; Вывод старшей цифры выводимого числа
00B6 58 POP AX ; Восстановление младшей цифры
00B7 E8 00CB R CALL XLAT_OUTPUT ; Вывод младшей цифры выводимого числа
00BA C3 RET
00BB TRANSLATE ENDP
00BB 30 31 32 33 34 35 36 ASCII_TABLE DB '0123456789ABCDEF'
37 38 39 41 42 43 44
45 46
00CB XLAT_OUTPUT PROC NEAR
00CB 24 0F AND AL, 0FH ; Выделение младшей цифры
00CD 53 PUSH BX
00CE 8D 1E 00BB R LEA BX, ASCII_TABLE ; Адрес таблицы трансляции
00D2 D7 XLAT ASCII_TABLE ; Преобразование в символьную форму
00D3 5B POP BX
00D4 88 07 MOV [BX], AL ; Сохранение очередного символа результата
00D6 43 INC BX ; Переключение на следующий символ
00D7 C3 RET
00D8 XLAT_OUTPUT ENDP
00D8 CODE ENDS
END CALCULATE_POWER
Фиг. 7.23 (а) (продолжение)
A>PRINT10
03H 447A0000H
06H 49742400H
09H 4E6E6B28H
0CH 5368D4A5H
0FH 58635FA9H
12H 5D5E0B6BH
15H 6258D727H
18H 6753C21CH
1BH 6C4ECB8FH
1EH 7149F2CAH
21H 76453719H
24H 7B4097CEH
27H 7F800000H
Фиг. 7.23 (b)
Фиг. 7.23 (a) Степени 10; (b) Вывод процедуры степеней 10
Вам нужно посмотреть часть программы TRANSLATE, несмотря на то,
что она не использует ни одной команды сопроцессора 8087. Эта
часть - пример подготовки чисел к печати. В частности, команда
XLAT преобразует для печати шестнадцатеричную тетраду (значение от
0 до 0FH) в правильный символ кода ASCII (от 0 до F). Просто
прибавлять значение тетрады к значению 0 нельзя, так как в коде
ASCII символы от A до F не следуют непосредственно за символами от
0 до 9; преобразование прекрасно выполняет команда перекодировки.
Мы используем аналогичный метод, когда преобразуем число с
плавающей точкой в пригодную для печати десятичную форму.
Степенные и тригонометрические функции
Степенные и тригонометрические функции
Оставшаяся группа команд сопроцессора 8087 выполняет вычисления
сложных математических функций. Эти команды позволяют микросхеме
8087 вычислять арифметические выражения, требующие логарифмов,
экспонент и тригонометрических функций. На Фиг. 7.22 изображен
список этих команд. В этом списке нет тех операций, которые есть у
карманного калькулятора. Конструкторы процессора 8087 не смогли
реализовать все нужные функции на одном кристале, так как это
оказалось слишком сложным. Вместо этого они создали набор функций
более низкого уровня, из которых можно программно построить функции
калькулятора. Например, таких тригонометрических функций как синус
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:04:28
Фиг. 7.22 Арифметические команды над вершиной стека 8087 Page 1-1
PAGE ,132
TITLE Фиг. 7.22 Арифметические команды над вершиной стека 8087
0000 CODE SEGMENT
ASSUME CS:CODE,DS:CODE
0000 9B D9 FA FSQRT
0003 9B D9 FD FSCALE
0006 9B D9 F8 FPREM
0009 9B D9 FC FRNDINT
000C 9B D9 F4 FXTRACT
000F 9B D9 E1 FABS
0012 9B D9 E0 FCHS
0015 9B D9 F2 FPTAN
0018 9B D9 F3 FPATAN
001B 9B D9 F0 F2XM1
001E 9B D9 F1 FYL2X
0021 9B D9 F9 FYL2XP1
0024 CODE ENDS
END
Фиг. 7.22 Арифметические команды над вершиной стека 8087
и косинус нет; вместо этих функций есть функция частичного
тангенса. Эта функция вычисляет отношение, равное тангенсу угла.
Из этого отношения программа может определить синус, косинус,
тангенс и любую другую тригонометрическую функцию. Из обратных
тригонометрических функций есть операция частичного арктангенса,
которая берет значения отношения чисел и вычисляет угол этого
тангенса. Эта частичная функция позволяет сконструировать
арксинус, арккосинус и другие функции, не имея их в явном виде.
Ниже приведен список команд этой группы с коротким комментарием
работы каждой команды. Ни у одной из этих команд не пишутся
операнды, так как все они работают с вершиной стека, и возможно,
также с элементом ST1.
FSQRT (квадратный корень)
ST квадратный корень из (ST)
ST должно быть неотрицательно.
FSCALE (масштабирование) ST <- ST * 2ST1
Эта команда необходима для возведения в степень. Другая, и
притом единственная, функция возведения в степень имеет ограничения
на значение показателя. Эта команда возводит 2 в степень, равную
целому числу. Далее будет приведен пример возведения 10 в
произвольную степень.
FPREM (частичный остаток)
ST <- ST mod ST1 (частичный)
Полностью операцию деления по заданному модулю команда FPREM не
выполняет. Эта команда за один раз уменьшает содержимое вершины
стека максимум на 264. Команда выполняет нахождение истинного
остатка и требует очень много времени для уменьшения большого числа
по очень маленькому основанию. Максимально уменьшая число в
течение каждого выполнения команды, программист может разрешить
прерывания в течение всей операции поиска остатка. Если функция не
завершена, команда FPREM устанавливает флаг C2 равным 1, а когда
завершает вычисление остатка, то устанавливает другие три флага C3,
C1 и C0 равными трем младшим битам частного. Когда команда FPREM
используется в тригонометрических примитивах для ограничения
величины угла, это оказывается необходимым для определения октанта
первоначального угла. Чтобы продемонстрировать работу этой
команды, мы далее приведем пример тригонометрических вычислений.
FRNDIN T (округление до целого)
ST <- Integer(ST)
Эта команда округляет текущее содержимое вершины стека до
целого числа. Текущее управляющее слово определяет направление
округления.
FXTRACT (извлечение)
ST <- дробная часть ST
ST1 показатель степени двойки ST
Эта команда разбивает текущую вершину стека на компоненты.
Аргументом этой функции является вершина стека. Значение
показателя степени замещает содержимое вершины стека, а затем
дробная часть аргумента помещается в стек и становится новой
вершиной стека. Действия этой команды FXTRACT обратны действиям
команды FSCALE. Если в вершине стека находится некоторое число, то
выполнение последовательно команд FXTRACT и FSCALE оставляет в
вершине стека то же число. Но команда FSCALE не удаляет из стека
показатель степени, так что теперь в стеке дополнительно окажется
еще одно число.
FABS (абсолютная величина)
ST <- абсолютное значение ST
Эта команда устанавливает у числа в вершине стека знаковый
разряю в нуль (что соответствует положительному значению).
FCHS (смена знака)
ST <- -ST
Эта команда изменяет знак у вершины стека.
Следующие команды выполняют также трансцендентные функции, как
тригонометрические, а также логарифмические и возведение в степень.
FPTAN (частичный тангенс)
ST <- X
ST1 <- Y, где Y/X = TAN(угол)
Эта команда позволяет вычислить все тригонометрические функции.
Исходное число - угол, выраженный в радианах, значение которого
должно быть в интервале 0 < угол < PI/4, - помещается в вершину
стека. Уменьшить угол до правильного значения можно с помощью
команды FPREM. Результатом является отношение Y/X, которое равно
тангенсу угла; Y замещает вершину стека, а затем в стек помещается
X. Другие тригонометрические функции можно вычислить с
использованием этих значений; например, косинус COS(угол) =
X/SQRT(X2 + Y2).
FPATAN (частичный арктангенс)
ST <- Arctan(Y/X) = Arctan(ST1/ST)
Эта функция дополняет предыдущую, FPTAN. Команда FPATAN
вычисляет угол в соответствии с отношением чисел ST1 и ST0. Она
извлекает из стека число X, а затем записывает результирующий угол
вместо числа Y в качестве новой вершины стека. Исходные значения
должны подчиняться неравенству
0 < Y < X < бесконечность
F2XM1 (два в степени X минус 1)
ST <- 2ST - 1
Эта функция выполняет возведение в степень; она возводит 2 в
степень, указанную в вершине стека. Исходное число должно
находиться в диапазоне 0 <= ST <=0.5, и чтобы вычислить два в
степени, большей 0.5, эту команду нужно использовать вместе с
командой FSCALF. С помощью команд FLD, загружающих специальные
константы, программа может возвести в заданную степень также числа,
отличные от 2, для этого используются следующие формулы:
10**X = 2**(X*Log2(10))
e**X = 2**(X*Log2(e))
Y**X = 2**(X*Log2(Y))
Далее приводится пример возведения 10 в произвольную степень.
FYL2X (Y умножить на логарифм по основанию 2 от X)
ST <- Y*Log2(X) = ST1*Log2(ST)
Эта функция выполняет операцию логарифмирования. Она берет
логарифм по основанию 2 от содержимого вершины стека и затем
умножает его на элемент ST1. Команда FYL2X извлекает из стека
число X и замещает результат числом Y. Исходные числа должны
удовлетворять следующим соотношениям:
0 < X < бесконечности и - бесконечность < Y < бесконечности.
Эта функция позволяет вычислять логарифмы и по основаниям, отличным
от 2. Следующая формула:
Logn(2) * Log2(X)
вычисляет логарифм числа X по основанию n; значение Logn2
вычисляется как 1/(Log2n).
FYL2XP1 (Y умножить на логарифм по основанию 2 от X+1)
ST <- Y*Log2(X+1) = ST1*Log2(ST+1)
Эта функция идентична функции FYL2X за исключением того, что к
X прибавляется 1. Функция FYL2XP1 накладывает более жесткие
ограничения на X, и предназначена для вычисления логарифмов чисел,
значения которых очень близки к 1. Эта функция дает наиболее
высокую точность, если
0 < ABS(X) < 1 - (корень из 2 )/2
Микросхема 8087 имеет расширенный набор
Типы данных 8087
Микросхема 8087 имеет расширенный набор типов данных,
необходимых для поддержки ее расширенных арифметических
возможностей. В то время как микропроцессор 8088 может
непосредственно работать только с байтами и словами, микросхема
8087 имеет семь типов данных. Шесть из них присущи лишь микросхеме
8087. На Фиг. 7.1 показаны все семь типов данных, с которыми
работает микросхема 8087. Четыре формата представляют целые числа,
а три формата - вещественные, или числа с плавающей точкой. Один
формат представляет упакованные десятичные числа.
Рисунок 7.2 иллюстрирует способы, которыми микросхема хранит
эти числа в памяти. Как и в случае данных микропроцессора 8088,
все данные хранятся с младшей частью операнда, записанной в младших
адресах. Бит знака всегда оказывается в байте по старшему адресу
памяти. Мы будем обсуждать назначение различных полей по мере
того, как будем рассматривать разные типы данных.
Сопроцессор 8087 работает с тремя типами целых чисел: словом,
которое имеет длину 16 бит и идентично слову микропроцессора 8088;
коротким целым числом, имеющим длину 32 бита; длинным целым числом,
64-значением. Все эти числа являются числами, представленными в
двоичном дополнительном коде.
В программе целое слово определяется с помощью оператора DW.
Такое целое слово может иметь значение в диапазоне от - 32768 до
32767. Этот целый формат уже использовался в наборе команд
микропроцессора 8088. Это - единственный формат данных, общий для
микропроцессора 8088 и арифметического сопроцессора 8087. Короткий
целый формат требуется в операторе описания данных длиной 32 бита.
Такое описание двойного слова выполняет оператор DD, определяющий
целые числа, лежащие в диапазоне от -232 до 232-1. Напоминаем, что
с помощью оператора DD можно также определить пару
СЕГМЕНТ:СМЕЩЕНИЕ. Ассемблер решает сам, какую именно форму
сгенерировать, основываясь на операнде. Если операнд - адрес,
порождается пара СЕГМЕНТ:СМЕЩЕНИЕ; если же операнд - просто число,
соответственно порождается длинное целое число. Для описания
длинных 64-битовых целых чисел используется оператор определения
счетверенного слова DQ. Эта директива вынуждает ассемблер
сформировать поле данных, состоящее их четырех слов (восьми байт).
Такой тип целого может иметь значения в диапазоне от -264 до 264-1.
Этот оператор ассемблера, так же как и операторы DB, DW и DD, может
определить константу, неопределенное поле (если задан операнд "?"),
<ДДДД ВОЗРАСТАНИЕ ЗНАЧЕНИЯ
ЪДВДДДДДДДДДДДДДДДї
СЛОВА ЦЕЛЫЕ іSі ВЕЛИЧИНА і(ДВОИЧНОЕ
АДБДДДДДДДДДДДДДДДЩ ДОПОЛНЕНИЕ)
15 0
ЪДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
КОРОТКОЕ ЦЕЛОЕ іSі ВЕЛИЧИНА і(ДВОИЧНОЕ
АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ ДОПОЛНЕНИЕ)
31 0
ЪДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
ДЛИННОЕ ЦЕЛОЕ іSі ВЕЛИЧИНА і
АДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
63
ЪДВДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
УПАКОВАННОЕ ДЕСЯТИЧНОЕ іSі X і ВЕЛИЧИНА і
АДБДДДДДДДБd17Бd16Бd15Бd14Бd13Бd12Бd11Бd10Бd9ДБd8ДБd7ДБd6ДБd5ДБd4ДБd3ДБd2ДБd1ДБd0ДЩ
79 72 0
ЪДВДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДї
КОРОТКОЕ ВЕЩЕСТВЕННОЕ іSіПОРЯДОК і МАНТИССА і
АДБДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДЩ
31 23 0
ЪДВДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
ДЛИННОЕ ВЕЩЕСТВЕННОЕ іSі ПОРЯДОК і МАНТИССА і
АДБДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
63 52 0
ЪДВДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
ВРЕМЕННОЕ ВЕЩЕСТВЕННОЕ іSі ПОРЯДОК ГДДї МАНТИССА і
АДБДДДДДДДДДДДДДДДДДДБДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
79 64 63 0
КОММЕНТАРИИ:
S - бит знака (0 - плюс, 1 - минус)
dn - Десятичная цифра (по две на бит)
X - Незначащие биты; 8087 их игнорирует при загрузке и обнуляет при сохранении
- Позиция подразумеваемой двомичной точки
I - Целый бит мантиссы; сохраняется для временных действительных, подразумевается - для коротких и длинных
ПОРЯДОК (нормализованные значения):
Короткие действительные: 127 (7FH)
Длинные действительные: 1023 (3FFH)
Временные действительные: 16383 (3FFFH)
Фиг. 7.1 Форматы данных 8087 (Copyright Intel 1980)
а также несколько восьмибайтовых полей с помощью команды DUP.
Оставшийся целый тип данных - упакованный десятичный формат.
Этот тип данных представляет целое число в упакованном десяточном
формате. Такие данные занимают десять байт. Один байт
резервируется для знака, а оставшиеся девять байт содержат 18
десяточных цифр. Такое упакованное представление десятичных чисел
идентично представлению десятичных операндов микропроцессора 8088,
но при этом представлении одновременно обрабатывается 18 цифр.
Команды десятичной коррекции упакованных десятичных чисел
микропроцессора 8088 допускают одновременно только две десятичные
цифры. Кроме того, упакованные десятичгые числа микропроцессора
8088 требуют, чтобы программист определил метод обработки знака
числа, если используются отрицательные числа. Упакованные
десятичные числа сопроцессора 8087 имеют бит знака в старешм байте.
Упакованное десятичное число хранится в десятичном коде, причем
старший бит 10-байтного поля содержит знак (0 - положительно, 1 -
отрицательно).
Для описания упакованного десяточного числа используется
оператор определения десятибайтового поля DT. Чтобы задать
уракованное десятичное число в этом поле, необходимо использовать
шестнадцатеричную запись. Если в поле операнда окажется целое
число, ассемблер преобразует его в дополнительный двоичный код, а
не в упакованное десятичное число. К счастью, преобразовать
десятичное число в необходимую шестнадцатеричную форму легко.
A
ЪДВДВДДДДДДДДДДДї
і |M| і
+3іS|S| і
і |B| і
ГДБДБДДДДДДДДДДДґ
і і
+2і і
і і
ЪДВДВДДДДДДДДДДДї ГДДДДДДДДДДДДДДДґ ЪДВДВДДДДДДДДДДДї
і |M| і і і і |M| і
+1іS|S| і +1і і +3іS|S| і
і |B| і і і і |E| і
ГДБДБДДДДДДДДДВДґ ГДДДДДДДДДДДДДВДґ ГДЕДЕДДДДДДДДДДДґ
і |Lі і |Lі іL|M| і
+0і |Sі +0і |Sі +2іS|S| і
і |Bі і |Bі іE|F| і
АДДДДДДДДДДДДДБДЩ АДДДДДДДДДДДДДБДЩ ГДБДБДДДДДДДДДДДґ
7 0 7 0 і і
ЦЕЛЫЕ СЛОВА КОРОТКИЕ ЦЕЛЫЕ +1і і
і і
і ЪДВДДДДДДДДДДДДДї ГДДДДДДДДДДДДДВДґ ЪДВДВДДДДДДДДДДДї
і і | і і |Lі і |M| і
і +9іS| (X) і +0і |Sі +9іS|S| і
і і | і і |Fі і |E| і
і ГДБДДДДДВДДДДДДДґ АДДДДДДДДДДДДДБДЩ ГДБДБДДДДДДДДДВДґ
і і | і 7 0 і |Lі
Р +8і | і КОРОТКИЕ ВЕЩЕСТВ. +8і |Sі
О і | і і |Eі
ЪДВДВДДДДДДДДДДДї С ГДДДДДДДЕДДДДДДДґ ЪДВДВДДДДДДДДДДДї ГДВДВДДДДДДДДДБДґ
і |M| і Т і | і і |M| і і і |M| і
+7іS|S| і +7і | і +7іS|S| і і +7іI|S| і
і |B| і А і | і і |E| і і і |F| і
ГДБДБДДДДДДДДДДДґ Д ГДДДДДДДЕДДДДДДДґ ГДБДБДДВДВДВДДДДґ і ГДБДБДДДДДДДДДДДґ
і і Р і | і і |L|M| і і і і
+6і і Е +6і | і +6і |S|S| і і +6і і
і і С і | і і |E|F| і Р і і
ГДДДДДДДДДДДДДДДґ О ГДДДДДДДЕДДДДДДДґ ГДДДДДДБДБДБДДДДґ О ГДДДДДДДДДДДДДДДґ
і і В і | і і і С і і
+5і і +5і | і +5і і Т +5і і
і і і | і і і і і
ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ А ГДДДДДДДДДДДДДДДґ
і і і | і і і Д і і
+4і і +4і | і +4і і Р +4і і
і і і | і і і Е і і
ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ С ГДДДДДДДДДДДДДДДґ
і і і | і і і О і і
+3і і +3і | і +3і і В +3і і
і і і | і і і і і
ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДВДґ
і і і | і і і і | і
+2і і +2і | і +2і і +2і | і
і і і | і і і і | і
ГДДДДДДДДДДДДДДДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДґ
і і і | і і і і і
+1і і +1і | і +1і і +1і і
і і і | і і і і і
ГДДДДДДДДДДДДДВДґ ГДДДДДДДЕДДДДДДДґ ГДДДДДДДДДДДДДВДґ ГДДДДДДДДДДДДДВДґ
і |Lі і | і і |Lі і |Lі
+0і |Sі +0і | і +0і |Sі +0і |Sі
і |Bі і | і і |Fі і |Fі
АДДДДДДДДДДДДДБДЩ АДДДДДДДБДДДДДДДЩ АДДДДДДДДДДДДДБДЩ АДДДДДДДДДДДДДБДЩ
7 0 7 0 7 0 7 0
ДЛИННЫЕ ЦЕЛЫЕ УПАКОВАННЫЕ ДЕС. ДЛИННЫЕ ВЕЩЕСТВ. ВРЕМЕННЫЕ ВЕЩЕСТВ.
S: Бит знака S: Бит знака
MSB/LSB: Старший/младший бит MSE/LSE: Старший/младший бит порядка
MSD/LSD: Старшая/младшая десятичная цифра MSF/LSF: Старший/младший бит дробной части
(X): Биты не имеют значения I: Целый бит мантиссы
A
Фиг. 7.2 Структура хранения данных в 8087
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:56
Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087 Page 1-1
PAGE ,132
TITLE Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087
0000 CODE SEGMENT
0000 04D2 WORD DW 1234
7_3.ASM(6): warning A4016: Reserved word used as symbol: WORD
0002 FB2E DW -1234
0004 40E20100 SHORT_INTEGER DD 123456
0008 C01DFEFF DD -123456
000C D202964900000000 LONG_INTEGER DQ 1234567890
0014 2EFD69B6FFFFFFFF DQ -1234567890
001C ???????????????? DQ ?
0024 78563412907856341200 PACKED_BCD DT 00123456789012345678H
002E 78563412907856341280 DT 80123456789012345678H ; Отрицательное от предыдущего
0038 0002[ DT 2 DUP (?)
???????????????
?????
]
004C CODE ENDS
END
Фиг. 7.3 Целочисленные форматы данных сопроцессора 8087
Просто запишите нужное число в десятичной форме, а затем добавьте
букву H, показывающую, что это - шестнадцатеричное число.
Изобразить отрицательное число труднее. Если вы напишите перед
десятичным числом знак "-", ассемблер преобразует его в двоичный
дополнительный код, даже если есть буква H. Поэтому в этом случае
нужно подсчитать десятичные цифры и удлинить число до 20 цифр.
первые две цифры должны быть 80, чтобы показать, что число
отрицательно. То есть, чтобы изобразить -1234 в упакованном
десятичном формате, надо записать:
DT 80000000000000001234H
На Фиг. 7.3 изображен листинг ассемблера, иллюстрирующий
сформированные ассемблером значения в случае четырех типов целых
чисел.
Управляющее слово
Управляющее слово
Сопроцессор 8087 содержит два управляющих регистра. Один
регистр нужен для записи управляющего слова, другой - для
считывания слова состояния. Управляющий регистр позволяет
программисту задать режим рабоиы микросхемы 8087, далее о
возможностях некоторых из них будет лишь упомянуто; рассомтрение
всех возможных вариаций управляющего слова выходит за пределя этой
книги. Структура управляющего регистра показана на Фиг. 7.7.
Управляющее слово позволяет выбрать один из двух различных
типов бесконечности. По умолчанию способом замыкания числовой
системы является проективное замыкание, при котором сопроцессор
8087 трактует положительную, и отрицательную бесконечности как
единую бесконечность без знака. Другой способ - аффинное
замыкание, в которое входят и положительная, и отрицательная
бесконечности. Хотя может показаться, что проективное замыкание
теряет информацию, оно никогда не приводит к бессмысленным
результатам. Аффинное замыкание в программе необходимо
использовать только тогда, когда неоюходима дополнительная
информация и когда программа написана для того, чтобы иметь дело с
(возможно) неверными результатами.
Сопроцессор 8087 позволяет выбрать один из четырех способов
округления чисел. Округление возникает тогда, когда результат
15 7 0
ЪДДДДДДДДДДДВДДДВДДДДДДДВДДДДДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДДї
і іIC і RC і PC іIEMі іPM іUM іOM іZM іDM іIM і
АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДДЩ
ДДДДДВДДДДД і ДДДВДДД ДДВДД і і і і і і і і МАСКИ ИСКЛЮЧЕНИЯ (1=МАСКИРУЕТСЯ)
і і і і і і і і і і і АД НЕВЕРНАЯ ОПРЕАЦИЯ
і і і і і і і і і і АДДДДДД НЕНОРМАЛИЗОВАННЫЙ ОПЕРАНД
і і і і і і і і і АДДДДДДДДДД ДЕЛЕНИЕ НА НОЛЬ
і і і і і і і і АДДДДДДДДДДДДДД ПЕРЕПОЛНЕНИЕ
і і і і і і і АДДДДДДДДДДДДДДДДДД ИСЧЕЗАНИЕ ПОРЯДКА
і і і і і і АДДДДДДДДДДДДДДДДДДДДДД ТОЧНОСТЬ
і і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)
і і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДМАСКА РАЗБЛОКИРОВКИ ПРЕРЫВАНИЙ (1)
і і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ ТОЧНОСТЬЮ (2)
і і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ ОКРУГЛЕНИЕМ (3)
і АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДУПРАВЛЕНИЕ НЕОПРЕДЕЛЕННОСТЬЮ (4)
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД(ЗАРЕЗЕРВИРОВАНО)
(1) Маска разблоктровки прерываний:
0 = Разблокировка
1 = Прерывания заблокированы (маскированы)
(2) Управление точностью:
00 = 24 бита
01 = (зарезервировано)
10 = 53 бита
11 = 64 бита
(3) Управление округлением:
00 = Округление до ближайшего или четного
01 = Округление в меньшую сторону
10 = Округление в болюшую сторону
11 = Обрезание к нулю
(4) Управление неопределенностью:
0 = Прожективная
1 = Афинитивная
Фиг. 7.7 Формат управляющего слова INTEL 8087
требует большей точности, чем позволяет представление чисел. Метод
округления определяет, какое число выбирается в качестве
результата. Методы округления включают: выбор следующего большего
числа в системе представления чисел, следующего меньшего числа,
округление в направлении нуля, и округление в направлении четного
числа. Сопроцессор 8087 также дает возможность программе не
использовать полностью 64-битовую точность временного
действительного формата ИИЭР. Эта возможность не уменьшает время
выполнения команд и не должна выполняться только для того, чтобы
сделать команды процессора 8087 совместимыми с некоторыми
существовавшими ранее процедурами обработки.
Процессор 8087 может регистрировать множество ситуаций
появления ошибки, известных как особые ситуации; он также может
возбуждать прерывания, чтобы сообщить об этих ситуациях. В
управляющем слове имеется группа бит, которая позволяет
программисту определить, какие особые ситуации будут приводить к
прерываниям, а какие будут обрабатываться другими способами. Эти
биты известны как маски прерываний, так как они могут маскировать,
или предотвращать, возникновение прерываний.
Необходимости разрешать прерывание в каждой из этих ситуаций
нет. Сопроцессор 8087 имеет широкие возможности по обработке
особых ситуаций, встроенных в него самого. Для любой возможной
ситуации в микросхеме 8087 имеется способ обработки ее по
умолчанию. Например, деление на нуль вызывает прерывание по особой
ситуации, если она разрешена (немаскирована). Однако если
программист не разрешил это прерывание, сопроцессор 8087 использует
значение бесконечности, как результат деления на нуль. Это
значение распространится через все оставшиеся вычисления, давая
результат, непосредственно показывающий, что во время работы
возникла ошибка некоторого вида.
Иногда в какой-то из ваших программ, использующих плавающую
точку, может оказаться необходимым обрабатывать особую ситуацию
сразу же, как она возникает; и тогда у вас возникает желание
заменить обработчик прерываний вашей собственной подпрограммой,
обрабатывающей особые ситуации. В IBM PC вывод прерывания по
особой ситуации от микросхемы 8087 подключен ко входу
немаскируемого прерывания NMI (Non Maskable Interrupt). Это то же
самое прерывание, которое сигнализирует об ошибке четности.
В качестве примера особой ситуации рассмотрим упомянутое выше
переполнение стека. Если программа помещает в стек девятый объект,
сопроцессор 8087 отвечает прерыванием по особой ситуации. Если это
прерывание не разрешено, сопроцессор 8087 отмечает операцию как
недействительную и дает результат со специальным значением,
известным как NAN (Not A Number, не число). Если вы собираетесь
выполнять сложные вычисления, требующие более восьми позиций стека,
то можете выгодно использовать прерывание по этой особой ситуации.
Когда возникает переполнение стека, обработчик этого прерывания
может удалить нижние элементы стека, используя команды записи.
Затем программа освобождает эти позиции стека для дальнейшего
использования. Существует также и соответствующее прерывание по
исчерпыванию стека, которое возникает при использовании пустой
позиции стека; подпрограмма обработки прерывания может обрабатывать
и эту ситуацию: она может восстановить прежнее состояние стека из
области сохранения.
Далее мы увидим, что микросхема 8087 содержит информацию о
своем состоянии, что делает возможным обработать особые ситуации в
обработчике прерываний. Микросхема 8087 имеет исчерпывающую
информацию о команде, которая вызвала особую ситуацию.
Assembler для начинающих
Адаптер цветного графического монитора
Адаптер цветного графического монитора
Другой видеоадаптер, который может быть установлен на IBM PC -
адаптер цветного графического монитора. Цветная плата разработана в
фирме IBM для подключения к устройствам подобным телевизору.
Черно-белый адаптер работает только с монохромным дисплеем фирмы
IBM, а цветную плату можно подключить к любому цветному или
черно-белому монитору, использующему стандартные телевизионные
сигналы. Или, вы можете отдельно приобрести радиочастотный
модулятор, который позволит вам подключить выход цветной платы к
вашему домашнему телевизору. Цветная плата может быть подключена к
разнообразным мониторам, так как она работает на телевизионных
частотах.
Цветная плата имеет много режимов работы, и программы фирмы IBM
обслуживают некоторые из этих режимов. Обслуживание других режимов
оставлено вам. Цветная плата дает намного больше гибкости, чем
монохромная.
Для цветного графического адаптера возможны два текстовых
режима. Один текстовый режим формирует экран, имеющий 25 строк по
80 символов, идентичный монохромному адаптеру. Другой текстовый
режим уменьшает количество символов в строке до 40. Этот режим с
меньшим разрешением необходим, если вы хотите использовать цветную
плату или с монитором, имеющим меньшее разрашение, или с домашним
телевизором, так как 80-символьное изображение может оказаться
нечитабельным при некоторых изображениях. Чтобы использовать все
возможности цветной платы, вы должны работать с высококачественным
монитором. Монитор с высокой разрешающей способностью позволит вам
без искажений выводить полные 80-символьные строки.
Цветная плата имеет также два графических режима. В этих
режимах вы можете управлять каждой отдельной точкой экрана. В
текстовых режимах независима каждая символьная позиция, а в
графических режимах независимо управление каждой точкой.
Графический режим среднего разрашения дает 200 строк растра, по 320
точек в строке. Каждая из этих точек может иметь один из четырех
возможных цветов. Это означает, что любая точка управляется двумя
битами. Графический режим высокого разрешения дает 200 строк
растра, каждая из которых состоит из 640 точек; в этом режиме
возможны только два цвета, черный и белый.
Адаптер дисковода
Адаптер дисковода
Адаптер дисковода является интерфейсом между микропроцессором и
накопителем на гибких магнитных дисках. Схемы, смонтированные на
плате этого адаптера, обеспечивают все функции, необходимые для
ввода и вывода данных на гибкий диск (дискету). Адаптер
обеспечивает также физическое кодирование и декодирование данных,
необходимое для использования дискет.
Центральной частью адаптера дисковода фирмы IBM является
микросхема контроллера гибкого диска FDC (Floppy Disk Controller)
мPD765 фирмы NEC. Эта компонента также производится фирмой Intel
как микросхема 8272. Микросхема FDC управляет потоком данных на
дискету и с нее. Микросхема FDC имеет два порта ввода-вывода, один
для данных, а другой для отражения состояния. Порт данных находится
по адресу 3F4H, а порт состояния по адресу 3F5H. Порт данных -
двунаправленный, т.е. в различные моменты времени можно и читать
данные из этого порта, и записывать их в порт. Регистр состояния
можно только читать в любой момент времени. Он сообщает, как в
данный момент нужно работать с регистром данных.
В регистре состояния есть 2 бита, которые используются во время
работы с дисководом. Бит 6 - это признак ввода-вывода данных (DIO -
Data Input/Output). Этот бит сообщает, каких действий в отношении
регистра данных ожидает от вас контроллер. Если признак DIO равен
1, микросхема FDC ждет от вас чтения регистра данных. Если DIO
равен 0, FDC ждет записи в регистр данных. Бит 7 порта состояния -
это бит запроса устройства (RQM - Request For Master). Он
аналогичен биту "занято" печатающего устройства. Когда бит RQM
содержит 1, микросхема FDC готова к тому, чтобы читался или
записывался регистр данных. Если вы не уделите внимание биту RQM,
то введете микросхему FDC в заблуждение, и после этого ничего не
будет работать.
Регистр данных в действительности не является единственным.
Подобно контроллеру 6845, порт данных фактически состоит из группы
регистров. Но в отличие от микросхемы 6845, регистра индекса
регистров данных здесь нет. Данные, которые вы посылаете в
контроллер, должны поступать в определенном порядке. Аналогично, в
определенном порядке данные поступают из порта, когда вы их
читаете.
В техническом описании содержится диаграмма, показывающая
входные и выходные комбинации для всех операций дисковода.
Рассмотрим простейшую команду микросхемы FDC, опрос состояния
механизма. Вы выполняете эту операцию тогда, когда хотите что-либо
узнать о текущем состоянии дисковода. На Фиг. 8.19 показаны данные
для команды опроса состояния, а на Фиг. 8.20 показана программа,
выполняющая операцию опроса состояния дисковода.
----------------------------------
Команда...................04H
Модификатор команды.......00H
статус возврата...........ST3 Фиг. 8.19 Команда опроса
---------------------------------- состояния
A
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:03
Фиг. 8.20 Проверка состояния дисковода Page 1-1
PAGE ,132
TITLE Фиг. 8.20 Проверка состояния дисковода
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
FDC_STATUS RECORD RQM:1, DIO:1, OTHER:6
0000 CODE SEGMENT
ASSUME CS:CODE
0000 SENSE PROC FAR
0000 1E PUSH DS ; Адрес возврата
0001 2B C0 SUB AX, AX
0003 50 PUSH AX
0004 BA 03F4 MOV DX, 3F4H ; Порт состояния контроллера дисков
0007 B4 04 MOV AH, 04H ; Команда состояния устройства
0009 E8 001E R CALL OUTPUT ; Передача в контроллер
000C B4 00 MOV AH, 0 ; Второй байт команды
000E E8 001E R CALL OUTPUT
;----- Чтение состояния из контроллера дисков
0011 IN_DIO:
0011 EC IN AL, DX ; Ждать, пока флаг DIO разрешит ввод
0012 A8 80 TEST AL, MASK RQM ; из контроллера
0014 74 FB JZ IN_DIO
0016 IN_RQM:
0016 EC IN AL, DX ; Ждать, пока флаг RQM покажет, что
0017 A8 80 TEST AL, MASK RQM ; контроллер готов
0019 74 FB JZ IN_RQM
001B 42 INC DX ; Установка на порт данных
001C EC IN AL, DX ; Чтение состояния контроллера
001D CB RET ; Конец примера
001E SENSE ENDP
;----- Подпрограмма посылки байта в контроллер
001E OUTPUT PROC NEAR
001E EC IN AL, DX ; Ожидание пока DIO разрешит чтение
001F A8 40 TEST AL, MASK DIO ; из контроллера из контроллера
0021 75 FB JNZ OUTPUT
0023 OUT_RQM:
0023 EC IN AL, DX ; Ожидание пока RQM покажет, что
0024 A8 80 TEST AL, MASK RQM ; контроллер готов
0026 74 FB JZ OUT_RQM
0028 42 INC DX ; Установка на порт данных
0029 8A C4 MOV AL, AH ; Посылаемые данные
002B EE OUT DX, AL ; Вывод в порт
002C 4A DEC DX ; Установка на порт состояния
002D C3 RET
002E OUTPUT ENDP
002E CODE ENDS
END SENSEA
Фиг. 8.20 Проверка состояния дисковода
Каждое действие, выполняемое контроллером дисковода, состоит из
трех фаз: команды, выполнения и результата. В фазе команды
микросхема FDC ожидает данные, и это отражает бит DIO. Когда
микросхема FDC устанавливает бит RQM, чтобы он указывал на
готовность принять данные, программа может посылать команду в
контроллер. В случае опроса состояния дисковода, она выводит в
микросхему FDC два байта команды. Первый байт, 04H, является кодом
операции этой команды. Второй байт сообщает, какой из механизмов
опросить. В течение командной фазы признак DIO всегда показывает,
что микросхема FDC ждет данные, и программа использует бит RQM для
определения момента, когда можно посылать очередной байт данных.
Теперь контроллер переходит в фазу выполнения. В течение этой
фазы контроллер выполняет команду. В данном случае он опрашивает
состояние дисковода. В течение этого времени бит RQM сообщает
программе, чтобы она не использовала порт данных. После завершения
операции признак DIO переключается на 1, сообщая программе, что она
может читать регистр данных. По разрешению RQM программа может
прочитать единственный байт состояния от этой операции. Как только
программа прочитает всю информацию состояния признак DIO снова
принимает значение 0, ожидая ввода следующей команды.
Как видно из таблицы в техническом описании, команда опроса
состояния дисковода - одна из самых простых. Команде чтения данных
требуется девять байт данных во время командной фазы. Когда
операция завершится, программа должна прочитать семь байт состояния
из контроллера. Выполнение не начинается до тех пор, пока не
присылается шестой командный байт, и вы не можете начать другую
операцию, пока не будут прочитаны все семь байт состояния.
По адресу ввода-вывода 3F2H находится цифровой выводной регистр
контроллера дисковода. Этот выводной порт выполняет некоторые
дополнительные операции управления дисководом. Основное назначение
этого порта - управление двигателями дисковода. У механизмов
дисководов 5 1/4 дюйма, используемых в IBM PC, двигатели работают
не непрерывно. Программа должна включить двигатель перед чтением
или записью на дискету - и выключить его после. Если вы оставите
двигатель включенным на все время, это приведет к быстрому износу
дискеты. Когда двигатель работает, на передней панели дисковода
горит красная лампочка.
Адаптер использует цифровой выводной регистр и для других
целей. Два бита выбирают необходимый дисковод. Этот регистр также
используется для сброса микросхемы FDC, так как существуют
ошибочные ситуации, которые вводят контроллер в неопределенное
состояние. В таких случаях единственный выход - сбросить контроллер
и попробовать снова.
Адаптер монохромного дисплея и принтера
Адаптер монохромного дисплея и принтера
Сначала поговорим о монохромном дисплее, он - простейший из
двух адаптеров. Мы отложим описание той части платы, которая
работает с печатающим устройством, пока не дойдем до адаптера
печатающего устройства. Схема адаптера печатающего устройства на
монохромной плате идентична схеме отдельного адаптера печатающего
устройства, поэтому способы программирования их также одинаковы.
Черно-белая плата работает в одном режиме. Этот видео адаптер
предназначен для вывода 25 строк символов по 80 символов в строке.
Такое построение изображения называют режимом 80*25. Вы помещаете
символы на экран, записывая коды ASCII в буфер дисплея. Буфер
дисплея - это специальная область памяти, расположенная в адресном
пространстве по адресу 0B0000H. Эта память - часть платы адаптера,
а не системной памяти. Всякий раз, когда вы записываете символ в
коде ASCII в буфер дисплея, он появляется в соответствующем месте
экрана. Преобразование символа из кода ASCII в точки на экране
выполняется аппаратно.
Каждый символ на дисплее имеет атрибут. Атрибуты символов
определяют вид, в которым адаптер выводит символы на экран. На Фиг.
8.7 показаны атрибуты символов и их значения. Вам нужно знать эти
значения, так как их тоже надо помещать в буфер дисплея. Позиция
каждого символа в буфере дисплея занимает два байта. Четный байт
пары содержит код символа, а нечетный - значение атрибута. По Фиг.
8.7 можно определить значение атрибута, с которым выводить на экран
символ. Обычно выводится белый (на самом деле зеленый) символ на
черном фоне. Для этого случая значение атрибута равно 07H. Чтобы
сделать изображение негативным, нужно изменить значение атрибута на
70H. Атрибут 00H делает символ невидимым. Хотя код ASCII символа и
записан в байт символа, значение атрибута не дает символу появиться
на экране.
Дисплейный буфер черно-белой платы содержит 4K байта памяти.
Этого достаточно, чтобы иметь байты символов и атрибутов для каждой
из 2000 позиций экрана. Первый символ буфера дисплея появляется в
верхнем левом углу экрана, следующие два байта изображают следующий
символ справа, и так далее. Первый символ второй строки находится в
байтах 160 и 161. Теперь можно определить адрес любого символа на
экране. Сначала определим позицию в верхнем левом углу экрана, как
находящуюся в строке 0 и колонке 0; нижний правый угол находится в
строке 24 и колонке 79. Тогда формулой вычисления адреса для
произвольной строки и позиции будет
адрес = 2*(строка*80 + колонка) + 0B0000H
Умножение на 2 выравнивает адрес к двум байтам на позицию.
Прибавление значения 0B0000H отражает начальный адрес буфера
дисплея. Обычно в программе либо регистр DS, либо регистр ES
загружается значением 0B0000H, и в остальном программа работает со
смещениями в буфере дисплея.
Значение Атрибут
----------------------------------------------------------
00H ничего не выводится
01H подчеркнутые символы
07H белый символ на черном фоне
0FH ярко-белый символ, черный фон
70H черный символ, белый фон
80H при добавлении к любому другому символ
мигает
----------------------------------------------------------
Фиг. 8.7 Символьные атрибуты для монохромного адаптера
На Фиг. 8.8 в качестве примера для монохромного дисплея
приведена программа, которая перемещает содержимое экрана на одну
позицию вправо. Она выбрасывает крайнюю справа колонку, а слева
помещает колонку из пробелов. Если вы хотите испытать эту
программу, но имеете в своей системе только цветную плату, то
программа работает при установке сегмента DISPLAY на адрес 0B800H.
Оба видео адаптера очень похожи при работе в текстовом режиме, и
различаются только адресами памяти и ввода-вывода.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:26
Фиг. 8.8 Горизонтальный сдвиг вправо Page 1-1
PAGE ,132
TITLE Фиг. 8.8 Горизонтальный сдвиг вправо
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 DISPLAY SEGMENT AT 0B800H
0000 DISPLAY_START LABEL WORD
0FA0 ORG 4000
0FA0 DISPLAY_END LABEL WORD
0FA0 DISPLAY ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 SIDE_SCROLL PROC FAR
0000 1E PUSH DS
0001 B8 0000 MOV AX, 0
0004 50 PUSH AX
0005 B8 ---- R MOV AX, DISPLAY
0008 8E D8 MOV DS, AX
000A 8E C0 MOV ES, AX
ASSUME DS:DISPLAY, ES:DISPLAY
;----- Заполнение столбца 79 пробелами
Фиг. 8.8 Горизонтальный сдвиг вправо
000C B9 0019 MOV CX, 25
000F 8D 3E 004E R LEA DI, DISPLAY_START+78
0013 B8 0720 MOV AX, 720H
0016 BLANK:
0016 89 05 MOV [DI], AX
0018 81 C7 00A0 ADD DI, 160
001C E2 F8 LOOP BLANK
;----- Сдвиг вправо
001E B9 07D0 MOV CX, 2000
0021 8D 36 0F9E R LEA SI, DISPLAY_END-2
0025 8D 3E 0FA0 R LEA DI, DISPLAY_END
0029 FD STD
002A F3/ A5 REP MOVSW
002C A3 0000 R MOV DISPLAY_START, AX
002F CB RET
0030 SIDE_SCROLL ENDP
0030 CODE ENDS
END
Фиг. 8.8 Горизонтальный сдвиг направо
Программа на Фиг. 8.8 делает этот горизонтальный сдвиг очень
простым способом. Правая колонка заполняется пробелами в результате
записи 25 пробелов через каждые 160 байт памяти. Затем программа
сдвигает весь дисплейный буфер по памяти на один байт вверх.
Поскольку буфер дисплея непрерывно продолжается строка за строкой,
символ, который был в колонке 79 строки 0, после сдвига оказывается
в колонке 0 строки 1. Наконец, программа заменяет первый символ
буфера пробелом.
Монохромный адаптер имеет также несколько портов ввода-вывода,
которые можно использовать при программаровании дисплея, но они
будут рассмотрены очень бегло. Конструкция монохромного адаптера
использует эти порты ввода-вывода в первую очередь для удобства
аппаратной организации. Кроме того, программирование черно-белой
платы в общих чертах аналогично цветной плате. Поскольку цветная
плата имеет намного больше специальных возможностей в использовании
портов ввода-вывода, мы поговорим о них в основном в следующем
разделе.
Монохромный адаптер порождает сигналы вертикальной и
горизонтальной синхронизации, необходимые для нормального
изображения, с помощью контроллера электронно-лучевой трубки 6845
фирмы Motorola. Эта микрсхема содержит два порта ввода-вывода,
имеющих адреса 3B4H и 3B5H. Адаптер также имеет управляющий порт
3B8H и порт состояния 3BAH. После инициализации адаптера
модифицировать коды в этих портах вряд ли стоит. Существуют и
другие режимы работы, возможные для монохромной платы, но мы
сконцентрируем наше внимание на цветной плате. Полное, детальное
описание портов ввода-вывода монохромного адаптера можно найти в
техническом описании.
Адаптер параллельного принтера
Адаптер параллельного принтера
Чтобы подключить печатающее устройство фирмы IBM или любое другое,
которое подключается через параллельный интерфейса, вам нужен
адаптер параллельного печатающего устройства (принтера). Этот
адаптер встроен в адаптер монохромного дисплея и принтера. Если вы
используете адаптер цветного графического монитора, то нужен
отдельный адаптер принтера. С точки зрения интерфейса с печатающим
устройством, эти два адаптера идентичны, за исключением адресов
ввода-вывода. Порты принтера на монохромной плате имеют адреса от
3BCH до 3BEH, а отдельная плата принтера имеет адреса от 378H до
37AH.
Адаптер принтера имеет два выводных порта и один порт ввода.
Этот адаптер очень похож на микросхему 8255, используемую для
интерфейса клавиатуры. Фактически, сначала в конструкции платы
печатающего устройства использовалась микросхема 8255. Но фирма IBM
решила лучше делать адаптеры с раздельными компонентами.
Выводной 8-битовый порт данных по адресу 3BCH или 378H передает
данные принтеру. Адаптер посылает символьный код ASCII, помещаемый
в этот порт, прямо в принтер. Второй порт вывода, расположенный
по адресам 3BEH или 37AH, имеет 5 выводных бит. В нем содержатся
управляющие сигналы для принтера; эти линии управляют его работой и
инициализацией. В частности, бит 0 инициирует передачу данных в
принтер. Простая запись данных в порт вывода данных не означает
пересылку символа на принтер. Для того, чтобы в него поступил
символ, нужно установить бит строба (бит 0 порта 3BEH или 37AH)
равным 1, а затем снова сбросить на 0. На Фиг. 8.15 показана
короткая программа, передающая печатающему устройству строку
символов. Подпрограмма с именем PRINT обеспечивает сам процесс
передачи данных в принтер.
Обратите внимание, что процедура PRINT читает код из вводного
порта (3BCH или 379H). Этот порт возвращает информацию состояния
печатающего устройства программе. В данном примере программа
проверяет состояние, чтобы выводить следующий символ именно тогда,
когда принтер готов его принять. Бит 7 состояния порта ввода
показывает занятость принтера. Если этот бит содержит 1, печатающее
устройство готово принимать следующий символ для печати. В
противном случае программа должна подождать. Остальные 4 вводных
бита этого порта отражают возможные ошибки на печатающем
устройстве, например, отсутствие бумаги. Наш пример не контролирует
эти ситуации. Техническое описание содержит структуры вводных и
выводных портов платы адаптера печатающего устройства.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:39
Фиг. 8.15 Вывод на принтер Page 1-1
PAGE ,132
TITLE Фиг. 8.15 Вывод на принтер
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
= 0378 BASE EQU 378H
0000 CODE SEGMENT
ASSUME CS:CODE
0000 94 A8 A3 2E 20 38 2E MSG DB 'Фиг. 8.15', 13, 10, '$'
31 35 0D 0A 24
000C MAIN PROC FAR
000C 1E PUSH DS ; Адрес возврата
000D 2B C0 SUB AX, AX
000F 50 PUSH AX
0010 8D 1E 0000 R LEA BX, MSG
0014 PRINT_LOOP:
0014 2E: 8A 07 MOV AL, CS:[BX] ; Выбор символа из строки для вывода
0017 3C 24 CMP AL, '$' ; Конец строки?
0019 74 06 JE MAIN_RETURN
001B E8 0022 R CALL PRINT ; Печать символа
001E 43 INC BX
001F EB F3 JMP PRINT_LOOP ; Переход к следующему символу
0021 MAIN_RETURN:
0021 CB RET
0022 MAIN ENDP
;----- Эта подпрограмма печатает символ в регистр AL
0022 PRINT PROC NEAR
0022 BA 0378 MOV DX, BASE ; Порт вывода данных на принтер
0025 EE OUT DX, AL ; Занесение символа в порт вывода на принтер
0026 42 INC DX ; Адрес порта состояния принтера
0027 WAIT_BUSY:
0027 EC IN AL, DX ; Опрос состояния принтера
0028 A8 80 TEST AL, 80H ; Проверка разряда занятости принтера
002A 74 FB JZ WAIT_BUSY ; Цикл до освобождения принтера
002C 42 INC DX
002D B0 0D MOV AL, 0DH ; Установка разряда готовности данных
002F EE OUT DX, AL
0030 B0 0C MOV AL, 0CH ; Сброс разряда готовности данных
0032 EE OUT DX, AL
0033 C3 RET
0034 PRINT ENDP
0034 CODE ENDS
END MAIN
Фиг. 8.15 Вывод на принтер
Один из управляющих битов порта 3BEH (или 37AH) управляет
линией прерывания от печатающего устройства. Для того, чтобы
печатающее устройство могло посылать свой сигнал прерывания в
контроллер 8259, этот бит нужно установить равным 1. Однако адаптер
печатающего устройства выдает неверный сигнал прерывания, т.е.
выбранный для этой цели сигнал не вызывает правильного прерывания.
Поэтому не стоит и пытаться писать программу, которая бы
использовала возможности прерывания от адаптера печатающего
устройства (если вы не захотите физически изменить плату
печатающего устройства). Далее мы приведем пример, который обходит
эту проблему с помощью системного таймера.
Адаптер синхронных коммуникаций
Адаптер синхронных коммуникаций
Адаптер асинхронных коммуникаций дает возможность связываться с
IBM PC по последовательному интерфейсу. Этот адаптер дает
возможность связываться с другими ЭВМ, службами баз данных, а также
с другими источниками информации. Мы не будем обсуждать принципы
работы этого канала, а поговорим о методах программирования этого
конкретного адаптера IBM PC.
Интегральная микросхема коммуникаций проделывает всю работу по
приему и передаче символов по асинхронной линии. Элемент
асинхронной связи ACE (Asynchronous Communication Element) 8250
можно запрограммировать для управления самыми различными аспектами
связи. При инициализации элемента ACE под пограммным контролем
оказываются размер символа, частота передачи, символы останова и
биты четности. Адаптер также позволяет проверять и задавать
стандартные сигналы управления модемом (модулятора -
демодулятора).
С помощью элемента ACE символ передается просто посредством его
записи в регистр передачи. Микросхема далее выполняет все, что
соответствует кодам, которые вы передали ей при инициализации.
Чтобы принять символ, вы просто читаете его из буфера приема.
Существует регистр состояния, называемый регистром состояния линии,
который показывает, когда буфер передачи пуст и может принять
другой символ. Другой бит регистра состояния сообщает, когда
элемент ACE уже принял символ из другой системы.
В техническом описании приводятся и другие регистры, входящие в
элемент ACE 8250. Эти регистры дают возможность управления модемом
и определения его состояния. Вы также можете разрешить выработку
прерывания при возникновении в элементе ACE различных условий. Это
позволяет вашей программе быстро реагировать на любую смену внешних
условий. east-font-family:"MS Mincho"'>
Программа на Фиг. 8.16 демонстрирует основные механизмы,
необходимые для инициирования элемента ACE, посылки и приема
символа. Базовый адрес ввода-вывода платы адаптера равен 3F8H, так
что регистры элемента ACE расположены по адресам от 3F8H до 3FEH.
Можно также модифицировать адаптер асинхронной связи фирмы IBM так,
чтобы его регистры соответствовали адресам ввода-вывода от 2F8H до
2FEH. С помощью такой модификации можно установить в персональную
ЭВМ второй адаптер и связаться с двумя различными внешними
устройствами. Фактически, можно подключить печатающее устройство к
системе с помощью последовательного, а не параллельного сопряжения.
В этом случае нужны два адаптера: один из них работает с печатающим
устройством, а другой обслуживает внешние связи.
Один из портов ввода-вывода элемента ACE выполняет несколько
функций. Оба буфера, передачи и приема, находятся по адресу 3F8H,
так что когда что-либо записывается по этому адресу, информация
попадает в буфер передачи, но при чтении по этому адресу, вы
получаете последний символ, принятый микросхемой ACE. Этот же порт
ввода-вывода выполняет и третью функцию. Значение делителя,
определяющее скорость работы адаптера, записывается в этот порт
ввода-вывода. Микросхема ACE делит входную частоту на число,
помещенное в регистр делителя, позволяя тем самым выбрать скорость
от 50 до 9600 бод. Режим использования порта 3F8H задает один из
битов управляющего регистра.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:45
Фиг. 8.16 Управление последовательным каналом Page 1-1
PAGE ,132
TITLE Фиг. 8.16 Управление последовательным каналом
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
= 03F8 SERIAL EQU 03F8H
0000 CODE SEGMENT
ASSUME CS:CODE
0000 ASYNC PROC FAR
0000 1E PUSH DS ; Адрес возврата в ДОС
0001 2B C0 SUB AX,AX
0003 50 PUSH AX
0004 BA 03FB MOV DX,SERIAL+3 ; Управляющий регистр
0007 B0 80 MOV AL,80H
0009 EE OUT DX,AL ; Настройка на установку скорости
000A B8 0180 MOV AX,384 ; Делитель частоты для скорости 300 бод
000D BA 03F8 MOV DX,SERIAL
0010 EE OUT DX,AL ; Младшая часть делителя
0011 8A C4 MOV AL,AH
Фиг. 8.16 Управление последовательным каналом (начало)
0013 42 INC DX
0014 EE OUT DX,AL ; Старшая часть делителя
0015 BA 03FB MOV DX,SERIAL+3 ; Управляющий регистр
0018 B0 03 MOV AL,00000011b ; Режим без проверки на четность,8 бит
001A EE OUT DX,AL
;----- Вывод символа в канал
001B BA 03FD MOV DX,SERIAL+5 ; Регистр состояния канала
001E SEND:
001E EC IN AL,DX
001F A8 20 TEST AL,20H
0021 74 FB JZ SEND
0023 B0 41 MOV AL,'A'
0025 BA 03F8 MOV DX,SERIAL
0028 EE OUT DX,AL
;----- Прием символа
0029 BA 03FD MOV DX,SERIAL+5 ; Регистр состояния канала
002C RECV:
002C EC IN AL,DX
002D A8 02 TEST AL,2
002F 74 FB JZ RECV
0031 BA 03F8 MOV DX,SERIAL
0034 EC IN AL,DX
0035 CB RET
0036 ASYNC ENDP
0036 CODE ENDS
END ASYNC
Фиг.8.16 Установка, пересылка и получение данных по
асинхронному каналу (продолжение)
Первая часть программы примера инициализирует микросхему ACE
8250. Первым делом программа настраивает скорость работы адаптера.
Значение делителя, равное 384, устанавливает скорость 300 бод.
Обратите внимание, что перед записью значения делителя программа
заносит 1 в бит 7 управляющего регистра по адресу 3FBH.
Окончательный вывод в порт 3FBH задает характеристики линии. В этом
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:50
Фиг. 8.17 Обработка прерываний от последовательного канала Page 1-1
PAGE ,132
TITLE Фиг. 8.17 Обработка прерываний от последовательного канала
0000 ABS0 SEGMENT AT 0
002C ORG 0BH*4
002C ASYNC_INTERRUPT LABEL WORD
002C ABS0 ENDS
0000 STACK SEGMENT STACK
Фиг. 8.17 Обработка прерываний от последовательного канала (начало)
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 0049 R BUFFER_POINTER DW BUFFER
0002 SET_INTERRUPT PROC FAR
0002 2B C0 SUB AX,AX
0004 8E D8 MOV DS,AX
ASSUME DS:ABS0 ; Адресация по сегментному регистру DS
; в область векторов прерываний
;----- Установка прерывания
0006 C7 06 002C R 0024 R MOV ASYNC_INTERRUPT,offset INT_HANDLER
000C 8C 0E 002C R MOV ASYNC_INTERRUPT,CS ; Занесение вектора прерывания
0010 BA 03F9 MOV DX,03F9H ; Регистр разрешения прерываний
0013 B0 04 MOV AL,04H ; Прерывание по приему из канала
0015 EE OUT DX,AL
0016 E4 21 IN AL,21H ; Регистр маски прерываний 8259
0018 24 F7 AND AL,0F7H ; Занесение 0 в разряд 3
001A E6 21 OUT 21H,AL ; Прерывание не маскируется
001C BA 03FC MOV DX,3FCH ; Регистр управления модемом
001F B0 08 MOV AL,08H ; разряд OUT2
0021 EE OUT DX,AL
0022 EB FE HERE: JMP HERE ; Конец задания режима работы последователь-
0024 SET_INTERRUPT ENDP ; ного канала,ожидание прерывания
;----- Программа обработки прерываний от последовательного канала по приему
0024 INT_HANDLER PROC FAR
0024 50 PUSH AX ; Сохрание используемых регистров
0025 53 PUSH BX
0026 52 PUSH DX
0027 BA 03FD MOV DX,3FDH ; Регистр состояния канала
002A EC IN AL,DX
002B A8 01 TEST AL,01H ; Был ли получен символ?
002D 74 12 JZ INT_RETURN ; Нет,возврат из прерывания
002F BA 03F8 MOV DX,3F8H ; Регистр приема данных
0032 EC IN AL,DX ; Выбор символа из канала
0033 2E: 8B 1E 0000 R MOV BX,BUFFER_POINTER
0038 2E: 88 07 MOV CS:[BX],AL ; Сохранение в буфере
003B 43 INC BX
003C 2E: 89 1E 0000 R MOV BUFFER_POINTER,BX
0041 INT_RETURN:
0041 5A POP DX ; Восстановление регистров
0042 5B POP BX
0043 B0 20 MOV AL,20H ; Сброс контроллера прерываний
Фиг. 8.17 Обработка прерываний от последовательного канала (продолжение)
0045 E6 20 OUT 20H,AL
0047 58 POP AX
0048 CF IRET ; Возврат из прерывания
0049 INT_HANDLER ENDP
0049 0080[ BUFFER DB 128 DUP (?)
??
]
00C9 CODE ENDS
END SET_INTERRUPT
Фиг. 8.17 Асинхронные прерывания (окончание)
Оставшиеся две части примера посылают и принимают символ. В
регистре состояния линии по адресу ввода-вывода 3FDH есть биты
состояния буферов передачи и приема. Посылать символ до тех пор,
пока буфер передачи не опустеет, нельзя; и естественно, нельзя
читать символ до того, как он принят.
Адаптер асинхронной связи также работает с прерываниями. Сигнал
OUT2 в регистре управления модемом передает сигнал прерывания от
микросхемы ACE системе. Регистр разрешения прерываний в микросхеме
ACE выбирает те возможные изменения состояний, которые приведут к
возбуждению внешнего прерывания. Адаптер асинхронной связи
возбуждает прерывание уровня 3 контроллера прерываний 8259.
Давайте посмотрим, как можно использовать прерывание от
асинхронной платы для того, чтобы принимать символы. На Фиг. 8.17
показана последовательность событий, необходимых для включения
системы прерываний. В случае аппаратного прерывания, программа
устанавливает вектор прерывания, соответствующий уровню 3
контроллера 8259 (прерывание 0BH по адресу 58H), на адрес процедуры
обслуживания прерывания. Затем она сбрасывает бит регистра маски,
соответствующий прерыванию от платы связи. В микросхеме ACE 8250
программа загружает регистр разрешения прерывания так, чтобы
разрешить прерывания по состоянию приемной линии. И наконец,
программа включает линию OUT2, чтобы в систему поступали
прерывания. Когда все это работает, не возникает никаких проблем о
бработке символов по мере их получения системой. Программа на Фиг.
8.17 помещает эти символы в буфер, где их может не торопясь
просматривать другая программа.
Адаптер управления играми
Адаптер управления играми
Адаптер управления играми подключает к системе джойстики и другие
органы управления игрой. Это все аналоговые устройства - т.е., они
не могут работать с нулями и единицами. Их входные значения - это
сопротивление, которое ЭВМ не может прочитать непосредственно.
Адаптер управления играми преобразует значение сопротивления в
нечто такое, с чем ЭВМ может иметь дело.
Адаптер управления играми не преобразует значение сопротивления
непосредственно в двоичное число. Вместо этого адаптер преобразует
значение сопротивления во временную задержку. Чем больше
сопротивление, тем больше временная задержка. Эту задержку
компьютер может измерить. Преобразовать задержку в число,
соответствующее положению джойстика, можно программным способом.
Нашей задачей является написание программы, которая преобразует
временную задержку в число.
Адаптер управления играми обслуживает до четырех входов
сопротивления. Механизм временной задержки по каждому из этих
входов подключен к отдельному биту порта ввода-вывода 201H. Когда
вы выводите любое число в порт 201H, четыре его младших бита
сбрасываются в 0. Биты возвращаются в состояние 1 после некоторго
интервала времени. Этот интервал времени определяется значением
сопротивления, подключенного к адаптеру. Программа на Фиг. 8.18 -
это пример определения значения сопротивления двух из четырех
входных портов. Эта программа использует простой метод. Вместо
определения временной задержки всех четырех портов одновременно,
она обслуживает их последовательно. Количество времени, нужного для
определения одной задержки, невелико. В результате, поочередная
обработка этих задержек вместо одновременной выполняется без
проблем.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:57
Фиг. 8.18 Адаптер управления играми Page 1-1
PAGE ,132
TITLE Фиг. 8.18 Адаптер управления играми
= 0201 GAME_PORT EQU 201H
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 GAME_CONTROL PROC FAR
0000 1E PUSH DS ; Адрес возврата
0001 2B C0 SUB AX, AX
0003 50 PUSH AX
0004 BA 0201 MOV DX, GAME_PORT
0007 B8 B000 MOV AX, 0B000H ; Сегмент дисплейной памяти
000A 8E D8 MOV DS, AX
000C B5 21 MOV CH, 21H ; Символ, который будет записываться в буфер
000E B1 00 MOV CL, 0
0010 WRITE_LOOP:
0010 B4 01 MOV AH, 1 ; Выбор координаты X
0012 E8 0042 R CALL POSITION
0015 8B D8 MOV BX, AX ; Сохранение координаты X в буфере
0017 D1 EB SHR BX, 1
0019 D1 EB SHR BX, 1 ; Деление на 4
Фиг. 8.18 Адаптер управления играми (начало)
001B B4 02 MOV AH, 2 ; Выбор координаты Y
001D E8 0042 R CALL POSITION
0020 D0 E8 SHR AL, 1
0022 D0 E8 SHR AL, 1
0024 D0 E8 SHR AL, 1
0026 D0 E8 SHR AL, 1 ; Деление на 16
0028 B4 A0 MOV AH, 160
002A F6 E4 MUL AH ; Преобразование в смещение в буфере
002C 03 D8 ADD BX, AX
002E 88 2F MOV [BX], CH ; Сохранение символа
0030 EC IN AL, DX
0031 24 10 AND AL, 10H
0033 3A C1 CMP AL, CL
0035 74 D9 JE WRITE_LOOP
0037 8A C8 MOV CL, AL
0039 80 F9 10 CMP CL, 10H
003C 75 D2 JNE WRITE_LOOP
003E FE C5 INC CH ; Следующий символ
0040 EB CE JMP WRITE_LOOP
0042 GAME_CONTROL ENDP
;----- В AH бит маски
0042 POSITION PROC NEAR
0042 51 PUSH CX
0043 2B C9 SUB CX, CX ; Начальное значение для цикла ввода
0045 EE OUT DX, AL ; Запуск таймера
0046 POS_LOOP:
0046 EC IN AL, DX
0047 84 C4 TEST AL, AH
0049 E0 FB LOOPNE POS_LOOP ; Цикл пока 1 - таймер не закончил отсчет
004B B8 0000 MOV AX, 0
004E 2B C1 SUB AX, CX ; Определение значения счетчика
0050 59 POP CX ; в диапазоне 0-255
0051 C3 RET
0052 POSITION ENDP
0052 CODE ENDS
END
Фиг. 8.18 Адаптер управления играми (продолжение)
Последняя часть программы на Фиг. 8.18 берет позицию X-Y,
определенную по входу от джойстика, и записывает символ в
соответствующую позицию дисплея. Адаптер управления играми также
допускает четыре переключающих ввода, значения которых можно
прочитать в старших четырех битах порта 201H. Программа в примере
опрашивает один из этих битов переключения, чтобы перейти от одного
символа к другому и вывести его на экран.
А теперь рассмотрим цвета, которые
Цвета в режиме APA 320*200
А теперь рассмотрим цвета, которые можно получить в графическом
режиме среднего разрешения. Так как точке отведено два бита, для
нее можно указать один из четырех цветов. Цвет 0 (00B) - это цвет
фона. В качестве этого цвета можно выбрать любой из 16 цветов,
показанных на Фиг. 8.10, записав соответствующее 4-битовое значение
в регистр выбора цвета (3D9H). Остальные три цвета определены
фирмой IBM: вы не можете выбирать произвольно цвета 1, 2 и 3. Фирма
IBM определила две различные палитры цветов, они показаны на Фиг.
8.14. Палитру можно выбрать, устанавливая бит 5 регистра выбора
цвета.
Как показано на Фиг. 8.14, если сбросить бит 5 в 0, получатся
цвета зеленый, красный и желтый вместе с выбранным цветом фона.
Установка бита 5 равным 1 дает голубой, пурпурный и белый. Вы также
можете модифицировать палитру и другим битом регистра выбора цвета.
Установка бита 4 равным 1 делает цвета палитры более яркими.
Программы инициализации BIOS обычно загружают в регистр выбора
цвета значение 30H. Это соостветствует черному цвету фона (0) и и
яркой палитре 1.
Значение цвета Палитра 0 цвета Палитра 1 цвета
--------------------------------------------------
1 (01B) Зеленый Голубой
2 (10B) Красный Фиолетовый
3 (11B) Желтый Белый
бит 5 = 0 бит 5 = 1
--------------------------------------------------
Фиг. 8.14 Цветные палитры
для графики 320*200
Динамик
Динамик
Внутри корпуса IBM PC есть маленький динамик. Программа может
управлять звуками, генерируемыми этими динамиком. Для этого нужно
управлять некоторыми выходными битами микросхемы 8255 и генератора
тона в микросхеме 8253.
На Фиг. 8.1 показана программа, которая управляет динамиком
двумя разными способами. Первый способ, помеченный в листинге
меткой DIRECT, непосредственно управляет динамиком. Бит 1 выводного
порта 61H подключен к динамику. Всякий раз, когда программа меняет
значение этого бита, диффузор динамика двигается либо наружу, либо
внутрь. Быстро меняя значение этого бита, программа генерирует
звук. Это иллюстрирует первая часть программы на Фиг. 8.1, она
меняет значение бита 1, порождая высокочастотный тон. Скорость, с
которой программа меняет бит 1, определяет частоту тона.
Взяв на себя непосредственное управление динамиком, вы должны
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:08
Фиг. 8.1 Управление динамиком Page 1-1
PAGE ,132
TITLE Фиг. 8.1 Управление динамиком
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 SPEAKER PROC FAR
0000 1E PUSH DS ; Адрес возврата
0001 B8 0000 MOV AX, 0
0004 50 PUSH AX
;----- Задание режима работы динамика
0005 2B C9 SUB CX, CX ; Счетчик цикла
0007 E4 61 IN AL, 61H
0009 24 FE AND AL, 0FEH
000B E6 61 OUT 61H, AL ; Установка разряда 0 порта 61H в 0 для
; задания прямого управления динамиком
000D DIRECT:
000D 0C 02 OR AL, 2
000F E6 61 OUT 61H, AL ; Включить динамик
0011 24 FD AND AL, 0FDH
0013 E6 61 OUT 61H, AL ; Выключить динамик
0015 E2 F6 LOOP DIRECT
;----- Управление высотой звука динамика
0017 B0 B6 MOV AL, 10110110b
0019 E6 43 OUT 43H, AL ; Установка режима для 2-го канала
001B B8 03E8 MOV AX, 1000 ; Выбор высоты звука
001E E6 42 OUT 42H, AL
0020 8A C4 MOV AL, AH
0022 E6 42 OUT 42H, AL ; Занесение высоты звука в порт динамика
0024 E4 61 IN AL, 61H
0026 8A E0 MOV AH, AL
0028 0C 03 OR AL, 3
002A E6 61 OUT 61H, AL ; Выбор режима управления динамикаом
002C 2B C9 SUB CX, CX
002E KILL_TIME:
002E E2 FE LOOP KILL_TIME ; Цикл ожидания, во время которого
; работает динамик
0030 8A C4 MOV AL, AH
0032 E6 61 OUT 61H, AL ; Выключение динамика
0034 CB RET
0035 SPEAKER ENDP
0035 CODE ENDS
END
Фиг. 8.1 Управление динамиком
прежде всего работать с выводным портом микросхемы 8255 системной
платы. Программируемый периферийный
интерфейс микросхемы 8255 (PPI) имеет всего три входных или
выходных, порта. IBM PC инициализирует микросхему 8255 так, чтобы получилось два входных
порта - 60H и 62H - и один порт вывода, 61H. Порт 60H в первую
очередь вводит значения с клавиатуры. Его можно
также использовать для чтения положения переключателей на системной
плате. Обычно состояния этих переключателей читаются только один
раз, во время инициализации при включении питания системы.
Результат программа BIOS записывает в память для дальнейшего
использования. Поэтому с точки зрения наших целей можно считать,
что порт 60H непосредственно обслуживает ввод с клавиатуры. Вообще
входной порт выполняет важную функцию. Он служит буфером между
микропроцессором и устройством ввода-вывода; он передает данные
микропроцессору только тогда, когда последний запрашивает их
командой IN. Все остальное время вводной порт задерживает данные и
не допускает, чтобы они повлияли на работу микропроцессора.
Другой порт ввода микросхемы 8255, порт 62H, обслуживает другие
входы. Четыре его бита непосредственно соответствуют
переключателям, показывающим объем памяти, подсоединенной к
системному каналу ввода-вывода. Другие четыре бита имеют
индивидуальное назначение. Два из них показывают тип системной
ошибки. Программа обслуживания немаскируемого прерывания NMI
использует эти биты для определения причины системной аварии. Бит 5
порта 62H используется для обратной связи с одним из каналов
таймера-счетчика. Этот бит служит индикатором текущего выхода
второго канала микросхемы 8253. Бит 4 порта 62H отражает текущее
состояние ввода с кассетного магнитофона. Сзади у IBM PC, рядом с
разъемом для подключения клавиатуры, имеется разъем для подключения
кассетного магнитофона. При чтении данных с кассеты, этот бит
используется для определения текущего значения, вводимого с
кассеты.
Порт 61H - это порт вывода микросхемы 8255 в машине фирмы IBM.
Всякий выходной порт захватывает (временно запоминает) данные,
выводимые программой. Если бы аппаратура не запоминала данные, они
бы пропали в течение микросекунды или около этого. Такое
запоминание данных позволяет сохранять их значение в порте до тех
пор, пока они снова не будут изменены программой. То есть, когда мы
выводим значение, меняющее положение диффузора динамика, оно
остается неизменными до тех пор, пока его не изменит программа.
На Фиг.8.2 показано значение битов порта 61H. Эти данные взяты
из из технического описания.
При изучении управления динамиком имеют значение только биты 0
и 1. Из всех других - только бит 3 - управление двигателем
кассетного магнитофона - и бит 7 - сброс ввода с клавиатуры - имеют
какое-то значение для наших программ. Остальные биты предназначены
только для инициализации и диагностики. Чтобы полностью разобраться
в них, нужно детально изучить принципиальные схемы системы.
Возвращаясь к обсуждению управления динамиком, мы можем
заметить, что биты 0 и 1 используются для непосредственного
Бит Значение
-----------------------------------------------------
0 Порт 2 таймера (упраление динамиком)
1 Прямое управление динамиком
2 Мультиплексный порт 62H
3 Управление мотором кассетного магнитофона
4 Включение контроля доступа на системной
плате памяти
5 Включение контроля доступа в памяти
каналов ввода-вывода
6 Временной контроль клавиатуры
7 Мультиплексный/сброса ввода с клавиатуры
порт 60H
-----------------------------------------------------
Фиг. 8.2 Значение битов порта 61H
управления. Как показано на Фиг. 8.1, установка бита 0 в положение
0 включает прямое управление динамиком, блокируя механизм генерации
звука микросхемой 8253. Этот метод используется во второй части
програмы.
Обратите внимание на то, как программа сбрасывает бит 0.
Команда OUT включает все 8 бит порта 61H. Способа изменить только
бит 0, оставив остальные биты нетронутыми, не существует. Если в
программе нужно изменить только бит 0, она должна считать из порта
текущее значение других разрядов. К счастью, микросхема 8255
допускает прямое программное чтение выводных портов.
Последовательность команд
IN AL , 61H
AND AL , 0FFH
OUT 61H, AL
читает текущий код из выводного порта, затем команда AND сбрасывает
младший бит, а команда OUT посылает результат в выводной порт. Если
бы программа вывела в порт просто число 0, динамик работал бы
верно, но клавиатура была бы выключена. Работая с любым портом
вывода побитовой настройки, стройте программу так, чтобы она не
влияла ни на один из других бит, если только вы не собираететсь
изменять и их.
Оставшаяся часть первой программы на Фиг.8.1 изменяет значение
бита 1 выходного порта. Исходное значение порта 61H находится уже в
регистре AL, так что программе не нужно читать его при каждом
выполнении цикла. Регистр CX используется таким образом, чтобы
выполнить цикл 64K раз. При выполнении программы вам, возможно, не
удастся услышать звук, генерируемый программой. В этом случае
попытайтесь вставить несколько добавочных команд NOP в цикл DIRECT.
Это снизит частоту тона.
Вторая часть программы на Фиг. 8.1 для генерации тональности
использует таймер-счетчик 8253. Прежде чем двигаться дальше,
обсудим функционирование микросхемы 8253, чтобы понять, как она
используется в системе. Микросхема 8253 фирмы Intel содержит три
16-битовых счетчика, которые могут быть использованы в системе для
счета или задания временных интервалов. В один из счетчиков
программа загружает 16-битовое значение. Содержимое счетчика
уменьшается на единицу по каждому импульсу от таймера; частота
импульсов, подводимых с таймера ко всем трем каналам, равна
1.19МГц. Это означает, что содержимое счетчика уменьшается на
единицу каждые 840 наносекунд. Каждый из трех каналов имеет выход.
Строка контроля выхода изменяется всякий раз, когда содержимое
счетчика достигает нуля. Командами управления определяют способ,
которым микросхема 8253 ведет счет.
Выходы этих трех каналов счетчика-таймера подключаются к
различным узлам системной платы. Канал 0 подключается к контроллеру
прерываний 8259. Система использует этот канал для порождения
прерывания времени суток. Канал 1 соединен с контроллером прямого
доступа к памяти (ПДП или DMA) 8237, и использовать этот канал
схемы 8253 нельзя, так как смена кода в этом счетчике с большой
вероятностью уничтожит вашу программу и все другие данные в памяти
системы. Канал 2 подключен к динамику для генерации звука.
Позже мы вернемся к каналу 0 микросхемы 8253. Канал 2 Дает
выход на динамик. Для установки канала таймера программа посылает
код 0B6H в порт 43H, управляющий порт микросхемы 8253. Тем самым
канал 2 таймера-счетчика настраивается на работу в качестве
делителя частоты. Таймер делит исходную частоту - в данном случае
1.19МГц - на 16-битовое число, которое программа загружает в
регистр канала 2. Регистр канала 2 расположен по адресу порта 42H
(канал 0 - это порт 40H и, поскольку вы никогда не должны изменять
содержимое канала 1, задачу определения адреса его порта мы
оставляем вам). Программа в примере загружает в регистр канала
число 1000. Это означает, что на выходе вы услышите частоту 1190Гц.
На самом деле, вы услышите основную частоту 1190 Гц плюс обертоны,
вызванные прямоугольной формой сигнала таймера.
Заметим, что число 1000 - 16-битовое, в то время как порт 42H -
8-битовый. Команда установки режима работы, которую мы послали в
порт 43H, сообщила микросхеме 8253, что в нее будет выводиться
16-битовое число в виде двух 8-битовых. Сначала посылается младший
значащий байт, а за ним следует старший. Такая двухшаговая
процедура загружает в канальный регистр требуемое значение.
Далее программа должна дать управляющему порту 61H такую
установку, чтобы он пропускал сигнал на динамик. Для этого
программа устанавливает равными 1 биты 0 и 1 управляющего порта.
Заметим, что программа в начале сохраняет первоначальное значение
кода из управляющего порта и восстанавливает его в конце. Это
отключает динамик по окончании звука. Если этот способ
недостаточен, - например, если программа генерирует звук тогда,
когда не совсем ясно, был ли выключен динамик - можно выключить
его, сбросив в нуль бит 1 порта 61H.
Эти два метода управления динамиком наиболее прямолинейны. Эти
методы можно пытаться комбинировать в поисках интересных эффектов.
После установки на вывод звука при помощи микросхемы 8253 можно
модулировать выходной сигнал посредством битов 1, 0 или обоих,
порта 61H, а также менять число в канальном счетчике при включенном
динамике. Программу на Фиг. 8.1 можно изменить так, чтобы она
выводила значение регистра CX при каждой итерации цикла. Это
приведет к тому, что частота сигнала из динамика будет расти от
очень низкого к очень высокому тону. Работая с этими тремя
управляющими значениями, вы сможете создать множество интересных
эффектов.
Графический режим
Графический режим
Цветной графический адаптер имеет два режима отображения, в которых
можно управлять отдельными точками экрана. Эти режимы называются
режимами Полной Адресации точек (APA: All Points Addressable),
поскольку с помощью этих режимов можно адресовать и изменять все
точки. В действительности цветная плата допускает более двух
режимов APA, но мы рассмотрим только те два, которые поддерживаются
системой программирования. Используя информацию из технического
описания, вы можете попробовать и другие режимы.
Графический режим среднего разрешения обеспечивает вывод
изображения из 320 точек и 200 строк растра. Каждая из этих точек
может иметь один из четырех цветов. Это означает, что для каждой
точки требуется для представления цвета 2 бита. В каждом байте
графической памяти помещается четыре точки или пиксела (от англ.
picture element - графический элемент, которые называют также
"пелами"). Если вы умножите горизонтальный размер на вертикальный,
а затем разделите на 4 - число точек в байте, то увидите, что в
режиме среднего разрешения требуется 16000 байт. Именно поэтому
цветная плата имеет память объемом 16K.
На Фиг. 8.13 показано расположение элементов в каждом байте.
Пара бит с номерами 6 и 7 отображается первой точкой (0), а биты 0
и 1 представляют последнюю точку (3), отображаемую из этого байта.
В первых 80 байтах графической памяти содержится 320 точек
строки растра 0, т.е. первой строки экрана.
7 6 5 4 3 2 1 0
ЪДДДДДДДДДВДДДДДДДДДВДДДДДДДДДВДДДДДДДДДї
і і і і і
і точка 0 і точка 1 і точка 2 і точка 3 і
і і і і і і і і і
АДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДЩ
Фиг. 8.13 Битовая структура для графики 320*200
Но байт 80 уже не содержит первые четыре точки строки 1. По
причинам, которые объясняются конструкцией аппаратуры, четные и
нечетные строки хранятся в разных концах графической памяти. Все
нечетные строки имеют смещение 2000H байт от соответствующих четных
строк. Поэтому строка 1 лежит в байтах от 2000H до 204FH. Строка 2
- четная и находится по адресам от байта 50H до 9FH; строка 3
находится по адресам от 2050H до 209FH, и так далее.
На Фиг. 8.12в изображена программа, которая иллюстрирует
структуру графической памяти. Эта программа рисует диагональную
линию на дисплее 320*200. Линия начинается в точке (0, 0), верхнем
левом углу, и идет до точки (199, 199), в самый низ и слегка правее
центра. Первая часть программы вводит дисплей в режим APA среднего
разрешения с помощью вызова программы BIOS. В следующей главе
рассматривается, как это делается.
В программе регистр DI используется в качестве указателя
нужного байта в буфере дисплея. В целом программа выводит 200
точек. В регистр BL загружается число 50, так как внутренний цикл
пишет по четыре точки за одну итерацию. В регистр CL заносится 2 -
значение счетчика переключений. Цикл заносит в регистр AL код 0C0H,
что устанавливает старшие два бита на цвет 3; остальные три точки
байта имеют цвет 0, т.е. цвет фона. После записи этого байта
регистр AL сдвигается вправо на 2 бита, и теперь активизируется
вторая точка байта. Прибавление значения 2000H к содержимому
регистра DI смещает указатель на нечетную строку. Третья точка
снова получается сдвигом регистра AL и вычитанием числа (2000H -
80) из содержимого регистра DI. Это возвращает регистр DI назад в
четную строку, но уже на следующие 80 байт. Наконец, после записи
четвертого байта, регистр DI снова возвращается в четное поле, но
еще и увеличивается, смещаясь тем самым на следующий байт строки
растра. В примере цикл работает с четырьмя байтами, так как на
каждый байт приходится четыре элемента.
Графика высокого разрешения
Графика высокого разрешения
Другой режим APA дает 640 точек в строке растра и 200 строк. В этом
режиме каждая точка соответствует одному биту. Если этот бит
содержит 0, точка черная. Если этот бит содержит 1, точка цветная;
цвет выбирается с помощью регистра выбора цвета. Обычно этот
регистр содержит значение 1111B (белый), давая черно-белую
картинку. Вы можете выбрать любой другой цвет.
Отображение элементов почти идентично графическому режиму
нормального разрешения. Отличие заключается в том, что в байте
содержится восемь точек. Старший значащий бит (бит 7) выводится
первым, а бит 0 - последним. Здесь также строка содержит 80 байт;
так же, как и при нормальном разрешении, четные строки находятся в
начеле буфера дисплея, а нечетные - по адресу 2000H.
Клавиатура
Клавиатура
Следующее устройство ввода-вывода, которое мы рассмотрим, -
клавиатура. Клавиатура отделена от ЭВМ и подключено к системе
четырехжильным проводом. Хотя внутри клавиатуры и находится
отдельный микропроцессор, для его программирования нет простого
способа, так что мы оставим эту задачу инженерам фирмы IBM. Мы же
можем заняться информацией, которую клавиатура посылает в систему.
Цепь обслуживания клавиатуры на системной плате подключена к
системе прерываний. Кадый раз, как эта цепь регистрирует нажатие
клавиши, она возбуждает прерывание в системе. Это прерывание
передает управление обработчику прерываний от клавиатуры. Эта
процедура получает от клавиатуры данные и сохраняет их для
дальнейшего использования. Обработчик прерываний клавиатуры
обслуживает также специальные случаи, например, перезагрузка
системы (CTL-ALT-DEL) и снятие програмы (CTL-BREAK). Но мы не будем
рассказывать, как это делается, до следующей главы, поскольку все
это обслуживает встроенная система программ BIOS. Пока же осмотрим,
как можно управлять аппаратурой обслуживания клавиатуры.
Когда клавиатура посылает сигнал прерывания, он, прежде, чем
попадет в микропроцессор 8088, проходит через контроллер прерываний
8259. Этот контроллер обслуживает систему прерываний IBM PC почти
во всех ее аспектах.
Микросхема 8259 может обслужить до восьми прерывающих
устройств. К линиям прерываний в IBM PC подключен системный таймер,
клавиатура, адаптер асинхронной связи, фиксированный диск
(винчестер), накопитель на гибких магнитных дисках и печатающее
устройство. Остальные уровни прерываний доступны другим устройствам
ввода-вывода, подключенным к системному каналу ввода-вывода.
Конструктивно каждое из прерывающих устройств назначено к своему
входу прерывания микросхемы 8259. Вход прерывания, к которому
подключено прерывающее устройство, называется уровнем прерывания
этого устройства. Как мы сейчас увидим, микросхема 8259
упорядочивает приоритеты прерываний в соответствии с уровнем. На
Фиг. 8.3 показаны уровни прерываний каждого устройства ЭВМ.
Прерывание от каждого устройства можно по-отдельности
заблокировать или разблокировать. Микросхема 8259 работает точно
так же, как микропроцессор 8088, который имеет флаг прерываний,
разрешающий или запрещающий прерывания по командам STI и CLI. Но
8259 имеет восемь флагов прерываний - по одному на каждое возможное
прерывающее устройство. Эти флаги содержит регистр маски прерываний
IMR (Interrupt Mask Register), расположенный в порту по адресу 21H.
Бит 7 соответствует прерыванию 7, бит 6 - прерыванию 6, и так
далее. Если вы установите бит в 1, устройство не может вызвать
прерывание; если же бит сброшен в 0, контроллер прерываний передает
прерывание дальше, микропроцессору 8088. Разумеется, даже если
какое-либо прерывание разблокировано в регистре IMR, прежде чем это
прерывание сможет произойти, необходимо также сбросить флаг
прерываний в микропроцессоре 8088.
В зависимости от уровня прерывания контроллер назначает
приоритеты каждому прерывающему устройству. Уровень прерывания
определяется аппаратным подключением и не может быть изменен
программным путем. Прерывание номер 0 имеет высший приоритет,
прерывание номер 7 - низший. Если любые два устройства пытаются
одновременно возбудить прерывание, обслуживается устройство более
высокого приоритета. Обслуживание устройства более низкого
приоритета откладывается до тех пор, пока не будет обслужено
устройство с более высоким приоритетом. Микросхема 8259
автоматически реализует такое разбиение по приоритетам, но в
Уровень Устройство
--------------------------------------------------
0 Канал таймера 0
1 Клавиатура
2 -
3 Асинхронные коммуникации
4 Упорядоченные асинхронные коммуникации
5 Фиксированный диск
6 Дискета
7 Принтер
--------------------------------------------------
Фиг. 8.3 Уровни прерываний для микросхемы 8259
управлении прерываниями должна принимать участие и ваша программа;
именно, она должна сообщить контроллеру 8259 о том, что она
закончила работу с текущим прервавшим устройством. Такая команда
называется EOI (конец прерывания), она сообщает микросхеме 8259,
что обработка прерывания от устройства с наивысшим приоритетом
закончилась, и теперь вызвать прерывание в системе может устройство
с более низким приоритетом.
Контроллер 8259 также предотвращает прерывание микропроцессора
устройством с более низким приоритетом, если система в этот момент
обслуживает прерывание с более высоким приоритетом. Микросхема 8259
запоминает текущее активное прерывание, и считает, что
микропроцессор обслуживает это прерывание до тех пор, пока не
получит команду EOI. Микросхема 8259 делает это с помощью двух
внутренних регистров. Первый из них идентифицирует устройства,
которые к настоящему моменту выдали запрос на прерывание; этот
регистр называется регистром запросов прерываний IRR. Другой
регистр следит за текущими обслуживаемыми прерываниями, и
называется регистром обслуживаемых прерываний ISR. Если вы забыли
выполнить команду EOI, микропроцессор сможет прерывать только
программы обработки прерываний от устройств с боле высоким
приоритетом. Если вы не выполнили команду EOI после прерывания от
таймера, система попадает в останов. Так как таймер имеет наивысший
приоритет, отсутствие завершения его прерывания блокирует
возбуждение всех других прерываний.
Контроллер 8259, кроме того, упорядочивает все прерывания. Это
означает, что контроллер заставляет микропроцессор правильно
выбирать обработчик прерываний. Когда контроллер распознает
прерывание, он вынуждвет микропроцессор 8088 начинать работу по
адресу, указанному в одном из 256 векторов прерываний в первом
килобайте памяти. IBM PC отображает восемь уровней прерывания
контроллера 8259 на вектора прерываний от 8 до 0FH включительно.
Так, если поступает прерывание от клавиатуры (уровень 1),
начинается выполнение программы по адресу CS:IP, определенному
двойным словом в векторе прерываний 9, лежащему по адресам от 24H
до 27H.
Давайте посмотрим, как все это вместе происходит в IBM PC.
Сначала процедура самопроверки при включении питания (POST)
инициализирует контроллер 8259 с соответствующей управляющей и
упорядочивающей информацией. В это же время процедура POST снимает
маску прерываний от таймера, клавиатуры, а также прерываний от
дискет в регистре маски прерываний микросхемы 8259. После
завершения процедуры POST включается система прерываний по команде
STI. Теперь система готова к приему прерываний.
Вы нажимаете клавишу на клавиатуре. Клавиатура посылает символ
в системную плату, где он записывается в регистр и возбуждает
прерывание уровня 1. Далее начинает работу микросхема 8259. Она
устанавливает бит 1 в регистре IRR, отмечая запрос на прерывание.
Если не установлены ни нулевой, ни первый биты регистра ISR,
показывающие обслуживание прерывания с более высоким приоритетом,
контроллер возбуждает вход прерывания микропроцессора 8088. Когда
микропроцессор окажется готов к приему прерывания, он выполнит цикл
подтверждения прерывания. Микропроцессор 8088 поместит в стек
текущее содержимое регистра флагов, CS и IP. Контроллер 8259
отвечает на цикл подтверждения прерывания номером прерывания, в
данном случае это 9; затем контроллер установит равным единице бит
1 в регистре ISR, показывая, что прерывание 1 обслуживается. Тем
временем микропроцессор 8088 загружает пару регистров CS:IP из
ячеек 24H- 27H и начинает работу с указанного в них адреса.
Затем микропроцессор выполняет программу обработки прерываний
от клавиатуры. На Фиг. 8.4 показана схема обработчика прерывания от
клавиатуры. Обратите внимание, что первая команда, которую должна
выполнить эта программа, сохраняет содержимое регистра AX в стеке.
Это делается потому, что программа изменяет содержимое регистра AX.
Если обработчик прерываний не восстановит первоначальное значение
этого регистра перед возвратом в прерванную программу, возникнет
ошибка выполнения. Это ведь очень трудно - писать верно работающие
программы, если во время их выполнения содержимое регистров будет
произвольно изменяться другими программами. Если обработчику
прерывания потребуется больше регистров, то он должен все их
сохранить, а затем восстановить.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:14
Фиг. 8.4 Управление клавиатурой Page 1-1
PAGE ,132
TITLE Фиг. 8.4 Управление клавиатурой
0000 CODE SEGMENT
ASSUME CS:CODE
0000 KEYBOARD_INTERRUPT PROC FAR
0000 50 PUSH AX ; Сохранение регистра AX
0001 E4 60 IN AL, 60H ; Выборка номера клавиши
0003 50 PUSH AX ; Сохранение номера клавиши
0004 E4 61 IN AL, 61H
0006 8A E0 MOV AH, AL ; Сохранение текущего значения регистра AL
0008 0C 80 OR AL, 80H
000A E6 61 OUT 61H, AL ; Сигнал об успешном получении символа
000C 8A C4 MOV AL, AH
000E E6 61 OUT 61H, AL ; Возврат Клавиатуры в нормальный режим
0010 58 POP AX
; ...
0011 B0 20 MOV AL, 20H
0013 E6 20 OUT 20H, AL ; Сигнал об окончании прерывания
0015 58 POP AX ; Восстановление регистра AX
0016 CF IRET ; Возвращение в прерванную программу
0017 KEYBOARD_INTERRUPT ENDP
0017 CODE ENDS
END
Фиг. 8.4 Контроль клавиатуры
Обработчик прерывания читает код клавиши, так называемый код
сканирования, из порта 60H. Программа доложна "убрать" прерывание,
сообщив устройству ввода-вывода, что оно должно снять запрос на
прерывание. Программа сбрасывает прерывание от клавиатуры,
устанавливая бит 7 порта 61H. Это не только отменяет запрос на
прерывание, но и сообщает клавиатуре, что она может присылать
следующий символ в микропроцессор 8088. Теперь программа прерывания
может обработать код от клавиши.
Когда обслуживание прерывания завершено, обработчик прерывания
посылает в микросхему 8259 команду EOI. Она сбрасывает в 0 бит 1 в
регистре ISR, и теперь могут произойти любые ожидающие обслуживания
прерывания с меньшими приоритетами. Выполнение команды EOI состоит
в посылке в порт 20H кода 20H. Программа обработки прерывания
восстанавливает содержимое регистра AX, команда IRET
восстанавливает регистры флагов IP, CS, а также приводит флаги к их
первоначальному виду до прерывания.
Точно такая же последовательность событий происходит в случае
любого прерывания, исходящего от микросхемы 8259 и обслуживаемого
микропроцессором 8088. Единственная разница заключается в том,
какое именно прерывание возникает. От этого изменяется установка
битов в регистрах IRR и ISR, и номер вектора прерывания,
выдаваемого микропроцессору 8088. Обработчик прерывания должен все
также сохранять состояние микропроцессора в момент прерывания, а
программа должна выполнить команду EOI в конце обработки
прерывания, или когда определит, что выполняемая программа может
принять прерывание меньшего приоритета. И еще одно предостережение.
Ваша программа обработки прерывания не должена посылать две команды
EOI. Если выполняющаяся программа обработки прерывания прервала
работу обработчика с меньшим приоритетом, вторая команда EOI
поступит вместо этой команды от обработчика с меньшим приоритетом.
Это может привести к неприемлимым последствиям, почему вам и не
следует допускать такого наложения.
Итак, мы обработали прерывание от клавиатуры, правильно
установили все биты и выполнили все команды. Что же мы получили?
Клавиатура посылает в микропроцессор "коды сканирования". Их
значения отражают расположение клавиш на клавиатуре и не имеют
никакого отношения к символу, изображенному на клавише. Например,
клавише ESC возвращает код сканирования 1, клавиша "1" - код 2, и
так далее. Клавише DEL соответствует код сканирования 83. Каждая
клавиша имеет свой собственный уникальный код сканирования. Так что
данные из порта 60H не могут рассматриваться как символ кода ASCII.
Код сканирования нужно еще перекодировать в правильный код
символа.
Клавиатура передает несколько больше, чем только эти 83 кода
сканирования. Первые коды, от 1 до 83, известные как "коды нажатия"
клавиатура посылает, при нажатии клавиши. Когда клавиша
отпускается, клавиатура посылает другой код сканирования, "код
отпускания". Код отпускания формируется прибавлением числа 128 к
коду нажатия. То есть коды отпускания попадают в диапазон от 129 до
211; их легко распознать по 7-му биту, равному 1.
Поскольку клавиатура посылает различные коды для каждого
нажатия и отпускания клавиши, программа может отследить состояние
каждой клавиши. Вы узнаете о нажатии клавиши, получив от клавиатуры
код нажатия. Клавиша нажата, пока вы не получите код отпускания,
сообщающий, что клавиша отпущена. Для большинства клавиш эта
информация не важна, но для регистровых клавиш (например, клавиши
верхнего регистра) она решающая. Например, большие буквы
формируются только тогда, когда клавиша верхнего регистра нажата.
Так как такая информация доступна от любой клавиши клавиатуры IBM,
работа с регистровыми клавишами не составляет труда. Если вы
пожелаете изменить кодировку клавиатуры, устроив свое собственное
расположение клавиш, любая клавиша может трактоваться вами как
регистровая.
И наконец, каждая клавиша клавиатуры IBM имеет встроенный
механизм автоповторения. Если вы держите любую клавишу нажатой
более 1/2 секунды, клавиатура начинает посылать коды нажатия со
скоростью 10 раз в секунду. Это удобно для обычных клавиш, особенно
для клавиш управления курсором. Вы просто держите их нажатыми, и
курсор движется в нужное место. Но если у вас есть клавиши,
выполняющие некоторую работу только в момент первого нажатия (и не
может использовать автоповторение), вы опять-таки должны
отслеживать нажатия и отпускания этой клавиши. Только первый код
нажатия должен вызывать ее срабатывание, а остальные коды нажатия
вы должны игнорировать.
Прямой доступ у памяти
Прямой доступ у памяти
Плата адаптера дисковода устроена фирмой IBM так, что она
использует возможность прямого доступа в память системы (ПДП).
Прямой доступ в память позволяет устройству ввода-вывода передавать
данные непосредственно в память или из нее. При этом микропроцессор
не "касается" данных. Принтер, например, требует передачи каждого
печатаемого символа самим микропроцессором. В случае же обмена с
дискетой микропроцессор был бы тяжелой обузой для достаточно
быстрой передачи данных. Программа микропроцессора для передачи
данных дисковода была бы очень похожа на программу Фиг. 8.15, где
символы посылались в принтер. То есть программа должна была бы
читать бит RQM, в цикле, чтобы проверить наличие очередного байта
данных. Тем не менее, если микропроцессор не ответит дисководу
достаточно быстро, то данные будут потеряны.
В случае передачи данных с помощью ПДП микропроцессор должен
только инициировать операцию. Все остальное выполняет контроллер
ПДП 8237 фирмы Intel, расположенный на системной плате. В случае
чтения с дискеты программа инициализирует ПДП для обслуживания
передачи данных. Затем программа посылает команду в контроллер
дисковода, чтобы он выполнил чтение. Во время выполнения программа
не должна передавать данные, так как эту работу выполняет
контроллер ПДП. Когда операция завершается, программа выполняет
фазу обработки результата, как и раньше.
Давайте посмотрим, как нужно настраивать ПДП на операцию чтения
с дискеты. На Фиг. 8.21 показана программа, предназначенная для
этой цели. ПДП имеет четыре канала. Дисковод подключен к каналу 2
ПДП. Каналы 1 и 3 доступны через системный канал ввода-вывода для
других устройств ввода-вывода, а канал 0 используется для очень
важной аппаратной функции - поддерживания регенерации памяти. Если
вы вмешаетесь в работу канала 0 ПДП, содержимое всей памяти
системы, вероятнее всего, изменится.
Каждый канал ПДП имеет два регистра: адресный регистр и регистр
счетчика. Регистр адреса задает область памяти, куда передаются
данные. В нашем случае операции чтения, значение в регистре адреса
указывает начало буфера данных. Когда контроллер дисковода читает
байт с дискеты, контроллер ПДП помещает этот байт в память по
адресу, определяемому регистром адреса. После каждого байта
контроллер ПДП увеличивает адресный регистр так, что он указывает
на следующий байт буфера.
На Фиг. 8.21 BUFFER - имя области данных. Программа определяет
абсолютный адрес буфера BUFFER в системе. Для этого она прибавляет
смещение BUFFER к сдвинутому (умноженному на 16) значению регистра
CS, который содержит значение сегмента буфера. Затем программа
помещает младшие 16 бит адреса в регистр адреса ПДП канала 2.
Старшие 4 бита адреса помещаются в специальный регистр "страницы".
В действительности контроллер ПДП 8237 работает только с
16-битовым адресом. В IBM PC этот регистр страницы добавлен для
того, чтобы программа могла читать данные в любое место памяти.
Имеется три регистра страницы, по одному для каналов 1, 2 и 3.
Регистр страницы имеет размер всего 4 бита, и поэтому старшие биты
регистра AL не играют роли при формировании физического адреса
буфера данных.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:09
Фиг. 8.21 Настройка прямого доступа в память Page 1-1
PAGE ,132
TITLE Фиг. 8.21 Настройка прямого доступа в память
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
= 0000 DMA EQU 0 ; Адрес порта DMA
0000 CODE SEGMENT
ASSUME CS:CODE
0000 DMA_SET PROC FAR
0000 1E PUSH DS ; Адрес возврата
0001 2B C0 SUB AX, AX
0003 50 PUSH AX
0004 B0 46 MOV AL, 46H ; Установка DMA в режим чтения с дискеты
0006 E6 0B OUT DMA+11, AL ; в память
0008 E6 0C OUT DMA+12, AL
000A 8C C8 MOV AX, CS ; Текущий адрес сегмента
000C B1 04 MOV CL, 4
000E D3 C0 ROL AX, CL ; Умножение на 16
0010 8A E8 MOV CH, AL ; Старшие 4 разряда в регистре CH
0012 24 F0 AND AL, 0F0H ; Очистка младших разрядов
0014 05 0032 R ADD AX, offset BUFFER ; Прибавление адреса буфера
0017 80 D5 00 ADC CH, 0
001A E6 04 OUT DMA+4, AL ; Вывод младшего байта адреса
001C 8A C4 MOV AL, AH
001E E6 04 OUT DMA+4, AL ; Вывод старшего байта адреса
0020 8A C5 MOV AL, CH
0022 E6 81 OUT 081H, AL ; Установка регистра страницы
0024 B8 01FF MOV AX, 511 ; Счетчик на один сектор
0027 E6 05 OUT DMA+5, AL ; Младший байт счетчика
0029 8A C4 MOV AL, AH
002B E6 05 OUT DMA+5, AL ; Старший байт счетчика
002D B0 02 MOV AL, 2 ; Открыть для прямого доступа канал 2
002F E6 0A OUT DMA+10, AL
0031 CB RET
0032 DMA_SET ENDP
0032 0200[ BUFFER DB 512 DUP (?) ; Буфер для чтения с диска
??
]
0232 CODE ENDS
END
Фиг. 8.21 Настройка прямого доступа в память
Программа также посылает в контроллер ПДП число передаваемых
байт данных. Контроллер дисковода использует это значение,
записанное в регистр счетчика канала 2, для завершения операции
чтения данных. ПДП посылает устройству специальный управляющий
сигнал, называемый завершением счета, когда оно записывает в память
последний байт. Последняя команда, выдаваемая ПДП - разрешение
работы канала 2. Теперь программа может войти в командную фазу
контроллера дисковода.
Адаптер дисковода соединяет множество компонентов программного
и аппаратного обеспечение компьютера. Адаптер дисковода использует
и ПДП, и прерывания для обслуживания работы дисковода. Сам по себе
контроллер дисковода - сложное, "интеллектуальное" устройство
управления, требующее получения "программы" перед началом работы. В
следующей главе при обсуждении управления механизмом дисковода с
помощью программы BIOS все это будет рассмотрено в комплексе.
Системное оборудование
Системное оборудование
В каждом пункте этой главы мы будем обсуждать свои аспекты
системы ввода-вывода IBM PC. В этом пункте речь пойдет о
стандартных компонентах аппаратуры - о тех, которые находятся на
процессорной плате системы. Другие части будут посвящены отдельным
платам адаптеров ввода-вывода, которые по необходимости можно
установить в систему.
Центральным процессором IBM PC является Intel 8088, тот самый,
естественно, который рассматривался на протяжении первых глав
книги. У нас, как будто еще есть что о нем рассказать. Рядом с
микросхемой 8088 на системной плате имеется пустое гнездо, в
которую вставляется арифметический сопроцессор Intel 8087; он был
рассмотрен в гл.7 и теперь уже должен быть знаком вам.
Остальные компоненты системной платы выполняют функции, которые
и превращают микропроцессор в ЭВМ. На системной плате находится до
64K байт оперативной памяти, а также 40K байт памяти только для
чтения (ПЗУ, постоянное запоминающее устройство). Это ПЗУ содержит
интерпретатор языка Бейсик, а также систему BIOS, которую мы
обсудим в следующей главе.
На системной плате есть много компонент, необходимых для работы
IBM PC. Мы рассмотрим только программируемые или полезные для
программирования компоненты. На системной плате этими компонентами
являются микросхемы программируемого периферийного интерфейса 8255,
таймера-счетчика 8253, контроллера прерываний 8259, и контроллера
прямого доступа в память 8237. Остальные микросхемы выполняют
аппаратные функции, которые нельзя изменять с помощью
программирования. Подробно, на элементном уровне, микросхемы
рассматриваться не будут; если вас это интересует, обратитесь к
каталлогу фирмы Intel, либо к другим справочным материалам. Вместо
этого мы рассмотрим функции ввода-вывода, реализованные на
системной плате IBM PC. Управляя этим средством доступа, мы сможем
пльзоваться всеми перечисленными выше компонентами.
Текстовый режим
Текстовый режим
Два текстовых режима цветного графического адаптера дают
простой способ вывода символов на экран. В текстовых режимах
цветная плата аналогична монохромному адаптеру, но она позволяет
указать цвет каждого символа. Так же, как и в монохромной плате,
каждая символьная позиция цветной платы имеет байт атрибутов. Этот
Номер бита 7 6 5 4 3 2 1 0
ЪДДВДДДДДДДДВДДВДДДДДДДДї
і Мі Ф О Ні Яі СИМВОЛ і Фиг. 8.9 Байт атрибута
АДДБДДБДДБДДБДДБДДБДДБДДЩ для ЦГА
- 289 -
байт атрибутов определяет цвета символов и фона каждой позиции.
На Фиг.8.9 показана структура байта атрибутов в цветном
текстовом режиме. Три бита, помеченных Ф О Н, определяют один из
восьми возможных цветов фона. Три бита СИМВОЛ определяют цвет
символа. Бит Я также влияет на цвет символа: его установка делает
цвет символа более ярким. Тем самым обеспечивается выбор одного из
16 цветов символа. Байт атрибута управляет единственным символом,
позволяя выбрать любую комбинацию цветов фона и символа каждой
позиции. Старший бит, помеченный буквой М, обычно вызывает мигание
символа. Установка бита мигания равным 1 заставляет контроллер
дисплея переключать цвет символа между его цветом и цветом фона со
скоростью примерно четыре раза в секунду. Так как символ,
изображенный в цвете фона, невидим, то получается эффект мигания.
Можно заменить бит мигания четвертым битом цвета фона, разрешив при
этом 16 цветов фона и 16 цветов символа. Это делает один из битов
регистра выбора цветов. Еще надо обратить внимание на то, что
одинаковая установка цветов символа и фона означает, что вы не
видите символ. Символ есть, но это все равно что пытаться увидеть
северного медведя в пургу - все одного цвета. На Фиг. 8.10 показаны
все 16 цветов, возможных в текстовом режиме.
I R G B Цвет
-------------------------------------
0 0 0 0 черный
0 0 0 1 синий
0 0 1 0 зеленый
0 0 1 1 морской волны
0 1 0 0 красный
0 1 0 1 магента
0 1 1 0 коричневый
0 1 1 1 светло-серый
1 0 0 0 темно-серый
1 0 0 1 голубой
1 0 1 0 светло-зеленый
1 0 1 1 светлый морской
1 1 0 0 розовый
1 1 0 1 светлый магента
1 1 1 0 желтый
1 1 1 1 белый
------------------------------------------- Фиг. 8.10 Цвета
(Фирма IBM, приоритет 1981 г.)
Если вы сравните байт атрибута для ЦГА с атрибутами для
монохромного адаптера на Фиг. 8.7, то увидите, что они аналогичны.
Конечно, вы не можете указать цвета монхромному адаптеру, но все
остальное совпадает. Поскольку цветная плата не поддерживает
атрибут подчеркивания, то установка монохромного атрибута
подчеркивания соответствует голубому символу на черном фоне.
Такая организация байта атрибутов является попыткой сделать два
видео адаптера по-возможности совпадающими. Каждый символ в буфере
дисплея находится по четному адресу, а байт атрибутов - по
нечетному. Память цветного дисплея находится на видео адаптере, но
по другому адресу: у монохромного дисплея она имеет адрес 0B0000H,
а у цветного - 0B8000H. О сходстве дисплеев говорит то, что если
при модификации программы для монохромного дисплея на Фиг. 8.8,
изменить содержимое указателя AT сегмента на значение 0B800H,
программа будет верно работать и на цветной плате. То есть одна и
та же программа работает на любом видео адаптере при минимуме
изменений.
Для управления адаптером на цветной плате также используется
контроллер ЭЛТ 6845 фирмы Motorola. Два порта ввода-вывода этого
контроллера имеют адреса ввода-вывода 3D4H и 3D5H. На самом деле
контроллер 6845 имеет 18 внутренних регистров. Доступ ко всем
регистрам осуществляется с помощью двух портов ввода-вывода и
косвенной адресации. Для обращения к регистру микросхемы 6845,
нужно сначала загрузить индекс регистра в порт 3D4H, а затем читать
этот регистр или записать в него данные через порт 3D5H.
Приведем пример, чтобы пояснить, как работает контроллер. На
Фиг. 8.11 перечислены все 18 регистров микросхемы 6845. В примере
мы используем только регистры R10 и R11. Эти регистры определяют
начальную и конечную строки растра одного знакоместа для курсора.
Каждый символ, порождаемый цветной платой, состоит из восьми строк
растра, имеющих номера от 0 до 7. Вы можете поместить курсор в
любых из этих восьми строк. Регистр R10 сообщает микросхеме 6845,
на какой строке начинается курсор, а регистр R11 определяет
последнюю строку курсора. ROM BIOS инициализирует курсор,
находящийся на строках 6 и 7; это делается загрузкой числа 6 в R10
и числа 7 в R11.
Прогамма на Фиг.8.12(а) модифицирует курсор цветной графической
платы. Она смещает курсор так, что он оказывается на верхних пяти
строках растра вместо нижних двух. Сначала программа загружает в
индексный регистр контроллера 6845 (3D4H) число 10, а затем
записывает номер начальной строки, равный 0, в регистр данных
(3D5H). Затем, поместив 11 в индексный регистр, она устанавливает
номер конечной строки равным 4. Теперь курсор имеет вид мигающего
блока в верхней части позиции символа, а не митающего
подчеркивания. Подобный этому способ модификации курсора
используется несколькими редактирующими программами (редакторами)
персональной ЭВМ, включая встроенный редактор интерпретатор языка
Бейсик. При установке режима вставки во время редактирования,
курсор становится жирнее; интерпретатор Бейсика делает это, меняя
параметры в микросхеме 6845.
Вернувшись к Фиг. 8.11, можно увидеть, что в микросхеме 6845
есть и другие регистры. Большинство из них управляет сигналами
горизонтальной и вертикальной синхронизации телевизионного растра.
Вы можете модифицировать коды в этих регистрах для каких-либо
действий дисплеем. Например, команда MODE системы DOS, которая
может сдвигать катринку на экране влево и вправо, модифицирует
регистр R2, задающий положение строчного синхроимпульса.
Если вы захотите поэкспериментировать с этими регистрами, вам
придется писать короткие программы, делающие требуемые изменения.
ЪДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДВДДДДДДДДДДДВДДДДДДДДВДДДДДДДДї
іРегистр і і Объект в і і і
і # і Регистр хранит і программе і чтение і запись і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R0 і Гориз. сумма і Символ і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R1 і Выводимая горизонталь і Символ і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R2 і Гориз. поз. синхронизации і Символ і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R3 і Гориз. ширина синхронизации і Символ і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R4 і Вертикальный итог і Симв. ряд і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R5 і Общая вертикальн.коррекция іСкан.строкиі Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R6 і Выводимая вертикаль і Симв. ряд і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R7 і Поз. вертикальной синхрон. і Симв. ряд і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R8 і Режим совмещения і - і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R9 і Макс.адрес скан. строки іСкан.строкиі Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R10 і Начало курсора іСкан.строкиі Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R11 і Конец курсора іСкан.строкиі Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R12 і Начальный адрес (гориз.) і - і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R13 і Начальный адрес (строка) і - і Нет і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R14 і Курсор (Гориз.) і - і Да і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R15 і Курсор (Строка) і - і Да і Да і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R16 і Световое перо (гориз.) і - і Да і Нет і
ГДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЕДДДДДДДДДДДЕДДДДДДДДЕДДДДДДДДґ
і R17 і Световое перо(строка) і - і Да і Нет і
АДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДБДДДДДДДДДДДБДДДДДДДДБДДДДДДДДЩ
Фиг. 8.11 Регистры микросхемы 6845
(с разрешения фирмы Motorola)
Если попытаться изменять регистры с помощью утилиты DEBUG, то это
может не сработать. Регистры R14 и R15 управляют позицией курсора,
и если утилита DEBUG переместит курсор между вашими обращениями к
индексному регистру и к регистру данных, изменения не произойдет.
Так получилось потому, что утилита DEBUG изменила содержимое
индексного регистра микросхемы 6845, и он уже больше не указывает
на тот внутренний регистр, который вы хотели изменить.
Другая интересная регистровая пара микросхемы 6845 - пара
регистров начального адреса R12 и R13. Адаптер цветного
графического дисплея имеет 16K байт памяти, а монохромная плата 4K
байта. Дополнительная память в цветной плате используется для
графических режимов, но она целиком в вашем распоряжении и в
текстовых режимах. Для текстового режима 80*25 требуется 4K байта
памяти, так что в буфере есть место для четырех разных страниц
памяти. Сдвигать данные на экране можно переносом данных с одного
места на другое, как это делалось на Фиг. 8.8. В случае же
вертикального сдвига нужно лишь изменить начальный адрес в
микросхеме 6845. Обычно начальный адрес равен 0. Если вы измените
его на 80 (число символов в строке), отображение будет начинаться
со второй строки. Это даст немедленный эффект сдвига всех данных
экрана вверх на одну строку.
На самом деле вы не изменяли данные, а сдвинули начало
сканирования памяти дисплея. Можно рассматривать дисплей как окно
80*25, через которое мы смотрим в 8192-символьный буфер дисплея.
При использовании начального адреса для сдвига данных возникают
сложности, когда вы приближаетесь к границе 16K байт. В этой точке
отображение "заворачивается". Верхние строки извлекаются из конца
буфера, а нижние строки - из его начала. Вы, конечно, сможете
решить эту проблему, но для этого потребуются некоторые размышления
и эксперименты.
[Прим. перев.: стр. 252 текста оригинала в моей копии отсутствует.
Очевидно, на ней находится только начало Фиг. 8.12]
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:31
Фиг. 8.12 Программа управления цветным дисплеем Page 1-1
PAGE ,132
TITLE Фиг. 8.12 Программа управления цветным дисплеем
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 DISPLAY_BUFFER SEGMENT AT 0B800H
0000 DISPLAY_START LABEL WORD
0000 DISPLAY_BUFFER ENDS
= 03D4 CRT_INDEX EQU 03D4H
= 03D5 CRT_DATA EQU 03D5H
= 03DA CRT_STATUS EQU 03DAH
= 000A CURSOR_START EQU 10 ; Регистры управления курсором
= 000B CURSOR_END EQU 11 ; в контроллере дисплея 6845
0000 CODE SEGMENT
ASSUME CS:CODE
0000 COLOR_GRAPHICS PROC FAR
0000 1E PUSH DS ; Адрес возврата в ДОС
0001 2B C0 SUB AX, AX
0003 50 PUSH AX
Фиг. 8.12 Программа управления цветным дисплеем (начало)
;----- Фиг. 8.12 (а) Изменение курсора
0004 BA 03D4 MOV DX, CRT_INDEX
0007 B0 0A MOV AL, CURSOR_START ; Установка индексного регистра
0009 EE OUT DX, AL ; 6845 на регистр начала курсора
000A 42 INC DX
000B B0 00 MOV AL, 0
000D EE OUT DX, AL ; Установка начала курсора
000E 4A DEC DX
000F B0 0B MOV AL, CURSOR_END
0011 EE OUT DX, AL ; Выбор регистра конца курсора
0012 42 INC DX
0013 B0 04 MOV AL, 4
0015 EE OUT DX, AL ; Установка конца курсора
;----- Фиг. 8.12 (б) Использование регистра состояния 6845
0016 B8 0002 MOV AX, 2
0019 CD 10 INT 10H ; Выбор символьного режима 80*25
001B B8 ---- R MOV AX, DISPLAY_BUFFER
001E 8E C0 MOV ES, AX ; Адрес буфера дисплейной памяти
0020 B8 0720 MOV AX, 0720H ; Вывод начинается с пробела
0023 NEXT_CHAR:
0023 BF 0000 MOV DI, 0
0026 B9 0050 MOV CX, 80
0029 F3/ AB REP STOSW ; Вывод строки из 80 символов
002B FE C0 INC AL ; Следующий символ
002D 75 F4 JNZ NEXT_CHAR
002F BB 0720 MOV BX, 0720H
0032 NEXT_CHAR_1:
0032 B9 0050 MOV CX, 80
0035 BF 0000 MOV DI, 0
0038 BA 03DA MOV DX, CRT_STATUS ; Порт состояния для адаптера
; цветного дисплея
003B WAIT_NO_RETRACE:
003B EC IN AL, DX ; Проверка на обратный ход луча
003C A8 01 TEST AL, 1
003E 75 FB JNZ WAIT_NO_RETRACE ; Запрещение прерываний
0040 FA CLI
0041 WAIT_RETRACE:
0041 EC IN AL, DX
0042 A8 01 TEST AL, 1
0044 74 FB JZ WAIT_RETRACE ; Ожидание обратного хода луча
0046 8B C3 MOV AX, BX ; Выборка символа
0048 AB STOSW ; Занесение в дисплейный буфер
0049 FB STI ; Разрешение прерываний
004A E2 EF LOOP WAIT_NO_RETRACE
004C FE C3 INC BL
004E 75 E2 JNZ NEXT_CHAR_1
;----- Фиг. 8.12 (в) Вывод линии по диагонали
Фиг. 8.12 Программа управления цветным дисплеем (продолжение)
0050 B8 0004 MOV AX, 4
0053 CD 10 INT 10H ; Установка графического
; режима 320*200
0055 06 PUSH ES
0056 1F POP DS
0057 B3 32 MOV BL, 50 ; Число групп строк
0059 B1 02 MOV CL, 2 ; Счетчик сдвига
005B BF 0000 MOV DI, 0
005E DOT_LOOP:
005E B0 C0 MOV AL, 0C0H ; В AL маска первой точки в байте
0060 88 05 MOV [DI], AL
0062 81 C7 2000 ADD DI, 2000H ; Переключение на нечетную строку
0066 D2 E8 SHR AL, CL ; Сдвиг до следующей точки в байте
0068 88 05 MOV [DI], AL
006A 81 EF 1FB0 SUB DI, 2000H-80 ; Переключение на четную строку
006E D2 E8 SHR AL, CL
0070 88 05 MOV [DI], AL
0072 81 C7 2000 ADD DI, 2000H ; Переключение на нечетную строку
0076 D2 E8 SHR AL, CL
0078 88 05 MOV [DI], AL
007A 81 EF 1FAF SUB DI, 2000H-81 ; Переключение на четную строку,
007E FE CB DEC BL ; следующий байт
0080 75 DC JNZ DOT_LOOP
;----- Возврат к символьному режиму 80*25
0082 B8 0002 MOV AX, 2
0085 CD 10 INT 10H
0087 CB RET
0088 COLOR_GRAPHICS ENDP
0088 CODE ENDS
END
Фиг. 8.12 Программа управления цветным дисплеем (продолжение)
Цветной графический адаптер имеет еще три регистра ввода-
вывода. Регистр режима находится по адресу ввода-вывода 03D8H. Этот
регистр устанавливает аппаратные переключатели различных режимов
отображения. Например, вы можете установить бит 5 этого регистра в
"0" и получить 16 цветов фона в текстовых режимах, а установка бита
5 в "1" переключает 7-й бит атрибута на функцию мигания. Полное
описание структуры этого регистра, и всех других, дано в
техническом описании.
Регистр выбора цвета находится по адресу ввода-вывода 03D8H.
Этот регистр устанавливает цвет окаймления в текстовых режимах.
Поскольку сам по себе текст не занимает весь экран, текст можно
окружить каким-либо выбираемым цветом. Кроме того, регистр выбора
цвета может управлять выбором палитр в графических режимов, как мы
увидим в следующем разделе. Младшие 4 бита этого регистра
устанавливают для окаймления один из 16 цветов, перечисленных на
Фиг. 8.10.
И наконец, регистр состояния по адресу ввода-вывода 3DAH
организует информационную обратную связь между цветной платой и
программой. Например, программа может использовать этот регистр для
определения текущего состояния светового пера, если оно подключено
к системе. Более важным является то, что регистр состояния
сообщает, когда безопасно читать или записывать данные в дисплейный
буфер.
Не вдаваясь в технические детали, заметим, что чтение или
запись данных в буфер дисплея цветной платы может вызвать "снег" на
экране, если вы будете это делать в неподходящие моменты времени.
Это случается только в текстовом режиме с высоким разрешением
80*25. Вспомните, что ранее приводился пример на Фиг. 8.12, в
котором постоянно записывались данные в буфер дисплея. Если для
этого примера использовать цветную плату, на экране появится масса
помех. Чтобы избежать этого, можно использовать регистр
состояния.
Существуют моменты, когда можно безопасно работать с данными в
буфере дисплея. В такие моменты бит 0 регистра состояния равен 1.
Нужно подождать, когда бит 0 станет равным 1, а затем читать или
записывать данные. На Фиг. 8.12б изображена программа, которая
записывает символ на экран дисплея. Первые строки этой программы
записывают данные в буфер, не используя регистр состояния. Эта
часть программы 224 раза записывает 80 симвлов в первую строку
буфера, что эквивалентно заполнению экрана около девяти раз. Когда
вы запустите программу, то увидите короткую вспышку помех в тот
момент, когда будут записываться данные.
Вторая часть программы на Фиг. 8.12б повторяет те же действия,
но на этот раз проверяя бит состояния. Обратите внимание, что эта
часть программы проверяет бит состояния двумя различными способами.
Сначала она ждет, пока бит состояния не станет равным 0. Затем, как
только он станет равен 1, программа записывает данные. Так сделано
потому, что интервал, в течение которого можно записывать данные,
очень мал, и если бы программа делала проверку только на равенство
1, она могла бы захватить бит состояния непосредственно перед его
обращением в 0. В этом случае, чтобы избежать помех, микропроцессор
уже не может записывать данные достаточно быстро, но такая
организация цикла гарантирует, что бит состояния захватывается в
первый же момент, когда он становится равным 1.
Используя бит состояния, вы уже не увидите на экране никаких
помех, но и заметите, что выполнение программы стало гораздо
медленнее. Выполняться медленнее программу заставляет
дополнительное время, затрачиваемое на ожидание бита состояния.
Если вам нужно записать на экране много данных - например,
организовать горизонтальный сдвиг, как на Фиг. 8.8, то потребуется
другой способ. Простейший способ - сбросить в нуль разряд
отображения в регистре режима. Бит 3 регистра режима (3D8H)
управляет отображением на дисплее. Если сбросить этот бит в 0,
экран погаснет. Чтобы переслать много данных в буфер цветного
дисплея, вы можете выключить отображение и переслать данные без
проверки бита состояния. Поскольку отображение выключено, на экране
не возникнут помехи. Когда пересылка блока закончится, включите
отображение. Вы заметите короткое мигание изображения в момент его
выключения и включения. Фирма IBM использует этот метод для
обслуживания вертикального сдвига цветного дисплея.
Видеоадаптеры
Видеоадаптеры
Система IBM PC, которая попадет к вам в руки, может быть
снабжена одним из двух различных дасплейных адаптеров. Один из них
- адаптер монохромного дисплея и принтера (мы будем называть его
монохромным адаптером, или иногда - черно-белой платой). Такой
видео адаптер лучше всего подходит для деловых приложений.
Черно-белая плата управляет монохромным дисплеем фирмы IBM.
Монохромный адаптер работает в одном режиме, и может показывать на
экране текст только одного цвета. Этот адаптер также содержит
аппаратные узлы, необходимые для управления печатающим устройством.
Такая комбинированная плата удобнее всего для деловых приложений.
Другой видео адаптер - адаптер цветного графического
монитора (мы будем называть его цветной платой). Цветная плата
работает с дисплеями, использующими нормальные телевизионные
частоты. Этот адаптер дает цветные изображения, а не только
одноцветные, как монохромный. У него есть несколько режимов работы,
включая такие, в которых можно управлять отдельными точками
экрана.
Возможности системы
Возможности системы
Эта часть главы описывает программные аспекты некоторых
системных возможностей. Не каждая система обладает теми
возможностями, которые мы опишем. Мы обсудим те из них, которые вы
сможете обнаружить в большинстве систем. Обсуждение коснется двух
видео адаптеров, адаптера дисковода, адаптера печатающего
устройства, адаптера асинхронных коммуникаций и игрового
адаптера.
Время суток
Время суток
Канал 0 таймера 8253 имеет специальное назначение в IBM PC. Выход
этого канала таймера подключен к уровню прерывания 0 микросхемы
8259. Это означает, что всякий раз, когда выход канала 0 имеет
активный уровень сигнала, возникает прерывание (при условии, что
все остальное установлено корректно). Процедура самопроверки при
включении питания инициализирует канал 0 таймера, загружая в него
число 0. Это дает наибольшее (не наименьшее) значение счетчика,
которое может записать в него программа. Имея на входе частоту 1.19
МГц, счетчик считает обратно к нулю чуть быстрее, чем за 55
миллисекунд. Программа инициализации устанавливает таймер таким
образом, что он считает непрерывно. Это означает, что прерывание 0
возникает 18.2 раза в секунду.
Как мы увидим в следующей главе, встроенная система программ
BIOS использует это постоянное прерывание от таймера, чтобы следить
за текущим временем. BIOS продвигает часы текущего времени вперед
всякий раз, когда возникает прерывание. Затем, с помощью
соответствующих вычислений, Вы можете преобразовать число циклов
таймера в часы, минуты и секунды.
Почему же выбрано значение 18.2? Почему счетчик не
программируется так, чтобы давать прерывание 20 раз в секунду, или
другое "хорошее" число раз? Это объясняет следующий пример.
Системный таймер может выполнять функцию измерения времени
отличного от времени дня. Время дня прекрасно подходит для
определения интервалов времени, измеряемых в секундах или минутах.
Но в некоторых ситуациях, возникающих при управлении
вводом-выводом, нужно определять интервалы времени порядка одной -
двух миллисекунд. Обычно программы отсчитывают такие интервалы с
помощью временного цикла. Программа для такого цикла выглядит
примерно так:
MOV CX, LOOP_VALUE
HERE:LOOP HERE
Вы выбираете константу LOOP_VALUE так, что цикл выполняется в
точности нужное число раз. Это очень хороший метод, если вам нужна
задержка на определенное время. В выше приведенном примере
начальное значение константы LOOP_VALUE, равное 0FFFFH, дает время
выполнения около 250-миллисекунд.
Но предположим, что вы хотите понаблюдать за внешним событием,
и определить, сколко времени займет его наступление. Можно использовать
вариант временного цикла такого, например, вида:
MOV CX, 0
HERE:
; --- проверка возникновения события
IN AL, DX
TEST AL, MASK_BIT
LOOPNE HERE
DONE:
; --- CX содержит число итераций цикла
Таким способом вы считаете число итераций цикла, чтобы
вычислить затраченное на него время. Этот метод предполагает, что
событие возникнет до того, как содержимое регистра CX второй раз
достигнет 0. Но если вам нужно измерить что-то с точностью до
микросекунд, этот метод не удобен, так как каждая итерация цикла
требует от 10 до 20 микросекунд. Системный таймер дает лучшее
решение. Поскольку он изменяет свое значение каждые 840 наносекунд,
вы сможете определить длительность события с точностью до
микросекунды.
На Фиг. 8.5 показан пример программы, вычисляющей время события
с помощью системного таймера. В этом примере в качестве
регистрируемого события используется канал 2 таймера. В первой
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:19
Фиг. 8.5 Управление системным таймером Page 1-1
PAGE ,132
TITLE Фиг. 8.5 Управление системным таймером
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 TIMER PROC FAR
0000 1E PUSH DS ; Занесение адреса возврата
0001 B8 0000 MOV AX, 0
0004 50 PUSH AX
0005 B0 B6 MOV AL, 10110110B ; Выборка таймера 2
0007 E6 43 OUT 43H, AL
0009 B8 0500 MOV AX, 500H
000C E6 42 OUT 42H, AL ; Таймер 2 установлен на 500 отсчетов
000E 8A C4 MOV AL, AH
0010 E6 42 OUT 42H, AL
0012 E8 001D R CALL LOW_TO_HIGH ; Выборка времени первого перехода с 0 на 1
0015 8B D8 MOV BX, AX ; Сохранение значения в регистре BX
0017 E8 001D R CALL LOW_TO_HIGH ; Выборка времени второго перехода с 0 на 1
001A 2B D8 SUB BX, AX ; Вычитая получаем длину цикла
001C CB RET
001D TIMER ENDP
Фиг. 8.5 Управление системным таймером (начало)
;--------------------------------------------
; Эта подпрограмма ждет перехода с нижнего уровня
; сигнала на верхний (с 0 на 1) в таймере 2
; и возвращает в регистре AX значение счетчика таймера 0
;--------------------------------------------
001D LOW_TO_HIGH PROC NEAR
001D E4 62 IN AL, 62H ; Проверка разряда таймера 2
001F A8 20 TEST AL, 20H
0021 75 FA JNZ LOW_TO_HIGH ; Цикл: сигнал таймера 2 на
; нижнем уровне
0023 WAIT_HIGH:
0023 E4 62 IN AL, 62H ; Проверка разряда таймера 2
0025 A8 20 TEST AL, 20H
0027 74 FA JZ WAIT_HIGH ; Цикл: сигнал таймера 2 на
; верхнем уровне
0029 B0 00 MOV AL, 0 ; Послать команду в регистр управления тай-
002B E6 43 OUT 43H, AL ; мером 2, которая "замораживает" таймер 2
002D 90 NOP
002E 90 NOP ; Задержка, необходимая для 8253
002F E4 40 IN AL, 40H ; Чтение младшего байта счетчика
0031 8A E0 MOV AH, AL
0033 90 NOP
0034 E4 40 IN AL, 40H ; Чтение старшего байта счетчика
0036 86 E0 XCHG AH, AL
0038 C3 RET ; Возвращение значения в AX
0039 LOW_TO_HIGH ENDP
0039 CODE ENDS
END
Фиг. 8.5 Системный таймер (продолжение)
части программы этот канал таймера загружается известным значением.
Здесь мы произвольно выбрали число 500H. Обратите внимание, что эта
часть программы идентична способу генерации звуков с помощью втого
канала таймера.
Наша программа вызывает подпрограмму LOW_TO_HIGH, которая
возвращает значение таймера в тот момент, когда на выходе канала 2
таймера отмечается переход от низкого к высокому уровню. Программа
рследит именно за переходом; если бы она регистрировала только
высокий уровень, было бы неизвестно, стал ли сигнал высоким только
что или уже готов стать низким. Подпрограмма посылает нуль в
управляющий регистр таймера (порт 43H), чтобы "заморозить" текущее
значение канала 0. Это позволяет ей прочитать текущее значение
таймера, продолжающего счет. Если бы программа временно не
зафиксировала таймер, она не смогла бы прочитать без ошибки его
16-битовое значение.
Обратим внимание на то, что подпрограмма на Фиг. 8.5 содержит
несколько команд NOP. Эти команды записаны в программе для выдержки
временных соотношений. Если очень внимательно прочитать инструкции
по микросхеме 8253, мы заметим, что между командами IN и OUT,
выполняемыми этой микросхемой, проходит не меньше 1 микросекунды.
Команда NOP занимает как раз достаточно времени, чтобы исключить
нарушение требований микросхемы по времени.
После возврата из подпрограммы программа сохраняет в регистре
BX значение счетчика таймера во время первого перехода с низкого
уровня на высокий. Затем программа снова вызывает подпрограмму,
чтобы зарегистрировать следующий переход с низкого уровня на
высокий на выходе канала 2 таймера. Потом она вычитает одно число
из другого, чтобы определить время цикла канала 2.
Мы уже говорили о том, что загрузка регистра 0 таймера
значением счета 0 - очень полезна. Данная программа подтверждает
это, так как она вычитает два значения таймера, не обращая внимания
на то, какое из них больше, а какое меньше. Так как канал 0 таймера
работает асинхронно по отношению к этой программе, нет никакой
гарантии, что первое читаемое из него число больше второго.
Например, предположим, что первый переход с низкого на высокий
уровень происходит, когда таймер 0 имеет значение 100H. После 500H
циклов значение числа в таймере будет 0FC00H. Счетчик таймера 0
автоматически "проскочил" от значения 0 к значению 0FFFFH, и
значение прочитанное вторым оказалось численно больше первого. Но
из-за того, что регистр таймера снова начинает счет со значения
0FFFFH, мы всегда можем вычитать эти два числа. При этом иногда
будет появляться перенос, иногда нет, но разность этих двух чисел
всегда будет равна числу отсчетов.
Чтобы убедить вас в правильности этого положения, рассмотрим
случай, когда счетчик загружается числом 8000H. Если первый переход
возникает при значении 6000H, второй появится при значении 5B00H, и
разница между ними составляет 500H. Но если первый переход
возникает при значении 100H, второй возникает при значении 7C00H, и
разница станет равна 8500H. Чтобы правильно отреагировать на эту
ситуацию, программа должна была бы проверить, не произошло ли
переполнение счетчика за время отсчета.
При выполнении этой программы вы обнаружите, что значение в
регистре BX составляет около 0A00H, и не равно ожидаемому значению
500H. Так происходит потому, что таймер работает в режиме
уменьшения содержимого счетчика на два по каждому временному
импульсу. Чтобы разобраться в работе микросхемы 8253, нужно
ознакомиться с инструкцией по ее программированию.
Фиг. 8.6 дает сводку для управляющего слова микросхемы 8253.
Для настройки одного из каналов на конкретный режим работы вы
выводите это управляющее слово в порт 43H. Мы уже встречались с
выводом некотрых значений в порт 43H. Чтобы "заморозить" счетчик,
мы послали в этот порт нуль, а для настройки генератора тональности
- код 0B6H. Посмотрим, откуда берутся эти значения.
Два старших бита управляющего слова определяют канал таймера.
Следующие два бита - выполняемую операцию. Когда мы выводим
значение 0, выбирается таймер 0 и запирание данных в счетчике.
Следующие 3 бита задают режим работы выбранного таймера. Эти биты
не играют роли, когда счетчик заперт, но нужны при инициализации
таймера. Оставшийся бит определяет, будет ли счетчик работать как
16-битовое двоичное число или как четырехзначное десятичное в
двоичном представлении.
A
Формат управляющего слова
D7 D6 D5 D4 D3 D2 D1 D0
ЪДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДї
і SC1і SC0і RL1і RL0і M2 і M1 і M0 і BCDі
АДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДЩ
Определение управления
SC - выбор счетчика:
SC1 SC0
ЪДДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДї
і 0 і 0 і задать счетчик 0 і
ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 0 і 1 і задать счетчик 1 і
ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 0 і задать счетчик 2 і
ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 1 і неопределено і
АДДДДДДДДДБДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДЩ
RL - чтение/загрузка:
RL1 RL0
ЪДДДДВДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і 0 і 0 і Операция запирания счетчика (см. і
і і і раздел процедуры READ/WRITE) і
ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 0 і Считать/загрузить старший байт і
ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 0 і 1 і Считать/загрузить младший байт і
ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 1 і Считать/загрузить младший байт, і
і і і затем - старший і
АДДДДБДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
M - режим:
M2 M1 M0
ЪДДДВДДДВДДДВДДДДДДДДДДДї
і 0 і 0 і 0 і Режим 0 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і 0 і 0 і 0 і Режим 1 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і X і 1 і 0 і Режим 2 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і X і 1 і 1 і Режим 3 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і 1 і 0 і 0 і Режим 4 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і 1 і 0 і 1 і Режим 5 і
АДДДБДДДБДДДБДДДДДДДДДДДЩ
BCD:
ЪДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і 0 і 16-битовый двоичный счетчик і
ГДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і десятичный в двоичном представлении і
і і (BCD) счетчик (4-х разрядный) і
АДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Фиг. 8.6 Программирование таймера/счетчика
(с разрешения фирмы Intel; приоритет Intel 1981г.)A
Управляющий код 0B6H, который использовался для генерации
тональности, можно расписать таким образом:
0B6H = 10110110B = 01 11 011 0
Это управляющее слово выбирает канал 2. Счетчик этого канала
работает с 16-битовым значением, младший байт которого загружается
в счетчик первым. Последный бит показывает, что счет будет
двоичным. Можно видеть также, что выбирается третий режим работы.
Микросхема 8253 может работать в шести разных режимах. Но для
наших целей, в конфигурации системы IBM, удобны только два из них.
Режим 3 устанавливается по умолчанию для всех трех каналов таймера.
Это режим работы генератора прямоугольных импульсов: половину
периода выход канала дает нижний уровень, а другую половину периода
- верхний. Счетчик работает в этом режиме, вычитая из своего
значения по два, а не по одному. Во время первого обратного счета
выход низкий, а затем, во время второго счета - высокий. Так как
счетчик считает по два, выход имеет высокий уровень точно половину
заданного времени, и низкий тоже точно половину. Из-за того, что
канал 0 таймера обычно работает в режиме 3, пример на Фиг. 8.5
заканчивает счет со значением счетчика 0A00H, а не 500H, как
ожидалось. Каждый отсчет таймера уменьшает содержимое счетчика на
два.
Так как счетчик считает двойками, он переполняется каждые 27
микросекунд. Если вы хотите измерять события более длительные, то
вам придется воспользоваться другим способом измерения времени.
Другой режим таймера, который используется в IBM PC - это режим
0. Этот режим называют прерыванием по завершению счета. В этом
режиме таймер не работает непрерывно. После его установки таймер до
тех пор не начинает считать (единицами), пока в него полностью не
будет загружено число. Затем счетчик считает в сторону уменьшения с
частотой синхроимпульсов, пока не достигнет нуля. В этот момент его
выход становится высоким. Поскольку выход канала 0 таймера
подключен к прерыванию 0 контроллера 8259, в системе возникает
прерывание.
Режим прерывания по завершению счета полезен, если вы хотите в
определенный момент подать программе сигнал с помощью прерывания.
Так как счетчик ограничен шестнадцатью битами, максимальный
отсчитываемый интервал времени составляет 55 миллисекунд. Если этот
интервал слишком мал, нужен другой метод измерения времени.
Если вы хотите измерять интервал времени в секундах, нужно
оставить таймер в его обычном режиме работы. Система BIOS позволяет
захватывать управление системой каждые 55 миллисекунд, и в каждый
такой момент вы можете решить, не исчерпался ли нужный промежуток
времени.
Если время нужной вам задержки находится между 55
миллисекундами и 5 секундами, можно использовать метод без
использования программ BIOS. Например, вам хочется сделать задержку
на 150 миллисекунд. Используя режим прерывания по завершению счета,
вы настраиваете таймер на прерывание через 50 миллисекунд (этому
соответствует значенние счетчика около 59500). Обработчик
прерывания программируется так, чтобы, получая управление первые
два раза, он заново устанавливал таймер на 50 миллисекунд. По
третьему прерыванию от таймера, когда 150 миллисекунд исчерпаны,
можно предпринять нужные действия.
При организации задержек через таймер всегда нужна некоторая
осторожность. Как упоминалось выше, канал 1 таймера выполняет одну
важную аппаратную функцию. Если вы модифицируете число в канале 1,
ваша программа может немедленно разрушиться. Использование канала 2
таймера безопасно. Этот канал подключен только к динамику и выходу
кассетного магнитофона. Отсюда, очевидно, следует, что нельзя
использовать канал 2 таймера для отсчета промежутков времени в одно
время с попытками воспроизводить мелодии через динамик. И наконец,
BIOS пользуется услугами таймера 0 для различных системных функций.
При обсуждении BIOS будет видно, что прерывание по времени суток
управляет не только текущим временем, но также обслуживает и
двигатель накопителя на дискетах. Перед тем, как изменять настройку
канала 0 таймера для любых целей, нужно понять, какие существующие
функции вы можете при этом изменить.
Assembler для начинающих
Блоки параметров
Блоки параметров
Блоки параметров придают гибкость аппаратным программам ПЗУ.
Векторы прерываний блоков параметров указывают на таблицы,
используемые BIOS. Блок параметров дисковода содержит данные,
которые BIOS использует при управлении дисководом. Поскольку
различные типы дисководов имеют разные характеристики, BIOS имеет
таблицу для всех типов дисководов поставляемых фирмой IBM. Если вы
захотели использовать другой тип дисковода вы можете модифицировать
таблицу параметров и использовать это устройство.
Существует также таблица параметров для задания режима работы
дисплея. Если ваш дисплей нуждается в некоторых других временных
соотношениях, можно модифицировать эту таблицу. Например, многие
телевизоры не могут показывать полную ширину 40-символьного
изображения. Один из параметров видеотаблицы управляет сдвигом
изображения на экране дисплея по-горизонтальным. Команда MODE в DOS
может сдвигать изображение на дисплее, модифицируя таблицу
параметров.
Последний блок параметров, использующий вектор прерываний BIOS,
на самом деле является таблицей образов символов. BIOS обеспечивает
вывод символов на дисплей во всех режимах работы дисплея. В
графических режимах BIOS изображает символы, составляя их из
соответствующих конфигураций точек. Таблица для первых 128 символов
находится в ПЗУ, она расположена, начиная со смещения 0FA6EH в
сегменте CODE. Вектор прерывания 01FH указывет на таблицу,
используемую для оставщихся 128 символов. В ПЗУ для этой таблицы не
нашлось места, так что ее должен организовать пользователь. Это
позволяет вам заменить набор символов, выбранный фирмой IBM для
старших 128 символов, своим собственным набором. Для этого нужно
только сконструировать точечные образы символов, загрузить
указатель на эту таблицу в вектор прерывания 01FH, и использовать
для вывода символов графический режим работы дисплея. Эта
возможность может оказаться очень полезной, так как позволяет
конструировать и использовать свой собственный набор символов.
Для того, чтобы изменить параметры, вам нужно изменить только
вектор прерывания указывающий на блок параметров. Где-нибудь в
своей программе вы составляете таблицу параметров, а затем
модифицируете вектор прерывания так, чтобы он указывал на эту
таблицу. Когда вы используете BIOS и ей требуется параметр, она
обращается в вашу, а не в стандартную таблицу параметров,
организованную в ПЗУ. Такие таблицы параметров делают BIOS очень
гибкой. Хотя команды и находятся в ПЗУ, вы можете изменить действие
BIOS, не заменяя ПЗУ на новое и не подменивая ни одной из программ,
вхолдящих в BIOS.
Чтение и запись символов
Чтение и запись символов
Подпрограммы видеопрограммы BIOS, соответствующие номерам от 8 до
10, обрабатывают символы на дисплее. Все эти три программы
работают с текущим положением курсора. Чтобы записать конкретный
символ на экран, программа должна прежде всего задать положение
курсора с помощью видеопрограммы BIOS при AH-2. После вывода
символа курсор не продвигается автоматически после того, как символ
записан на экране. Если программе нужно записать более одного
символа, она дожна переместить курсор на следующую позицию перед
записью символа. То же самое справедливо и для чтения символов с
дисплея. Так как все символьные операции происходят там, где
расположен курсор, программа, использующая видеопрограмму BIOS, не
сможет определить, по какому адресу расположен символ в буфере.
Программы чтения и записи символа определяют, где он расположен, а
вызывающая программа должна знать только строку и колонку символа.
Имеются две формы программы записи символа. Одна из этих
программ требует указания как символа, так и атрибутов (мигание,
яркость, цвета и так далее), чтобы поместить символ по текущему
адресу курсора. Другая программа записи символа записывает только
символ и не меняет атрибуты этой позиции. Эти две различные формы
программ записи символа позволяют вызывающей программе принимать
текущие атрибуты позиции символа, не выясняя, какие они. Функция
чтения символа возвращает и символ, и его атрибуты по адресу
курсора. Иметь здесь две разные функции не надо - информация
доступна, и если нужна вам, используйте ее.
В связи с проблемой помех цветной графической платы в программы
чтения и записи встроена проверка горизонтального обратного хода.
Эта проверка нужна для того, чтобы на экране не появился "снег" во
время работы микропроцессора. Эта проверка выполняется всегда,
независимо от того, в каком режиме находится дисплей; она
производится даже тогда, когда используется монохромная плата.
Максимальное время ожидания сигнала горизонтального обратного хода
составляет примерно 63 микросекунды, или 63 миллионных долей
секунды. Это ожидание - минимальное в процессе выдачи символа на
экран. У фирмы IBM не оказалось достаточно места в ПЗУ, чтобы
написать подпрограммы, проверяющие специальные случаи. Так что BIOS
всегда ждет сигнала отсутствия помех перед чтением и записью.
Данные клавиатуры
Данные клавиатуры
Поле данных программ клавиатуры начинается у смещения 17H в
сегменте DATA. Две флаговых переменных, KB_FLAG и KB_FLAG_1, имеют
битовое значение и отслеживают текущее положение регистровых
клавиш. За определением этих переменных следуют выражения,
показывающие назначение их битов. Например, бит 3 переменной
KB_FLAG следит за состоянием клавиши ALT. Если она нажата, этот
бит равен 1. Если клавиша ALT не нажата, бит равен 0. Биты
переменной KB_FLAG определяют текущее состояние всех регистровых
клавиш, обычных и триггерных. Триггерные клавиши управления
регистрами используют биты переменной KB_FLAG_1. Эти клавиши
изменяют состояние клавиатуры всякий раз, когда нажимаются.
Например, клавиша CAPS LOCK переключает клавиатуру с больших букв
на маленькие и наоборот каждый раз, когда нажимается.
BIOS использует биты переменной KB_FLAG_1 для того, чтобы
отслеживать, нажата ли в текущий момент клавиша CAPS LOCK (а также
другие триггерные клавиши). BIOS должна отслеживать их из-за того,
что все клавиши клавиатуры имеют встроенную функцию автоповторения
по прошествии некоторого времени. Если бы BIOS переключала бит
CAPS_STATE каждый раз, когда получала код "нажатия" от клавиши CAPS
LOCK, автоповторение клавиши сделало бы невозможным для оператора
определение текущего состояния клавиатуры. BIOS переключает бит
клавиши CAPS_STATE, когда поступает первый код нажатия. Затем
программа BIOS игнорирует все коды нажатия до тех пор, пока он не
получит код отпускания от клавиши CAPS LOCK, означающий, что
клавиша отпущена.
BIOS использует переменную ALT_INPUT для обеспечения
специального режима ввода в альтернативном регистре. Когда нажата
клавиша альтернативного регистра, можно ввести десятичное число с
помощью цифровой клавиатуры. Когда клавиша альтернативного регистра
отпускается, программа клавиатуры возвращает символ кода ASCII,
соответствующий этому десятичному числу. Такая техника позволяет
оператору вводить в IBM PC любые символы, даже если их нет на
клавиатуре. Например, нажмите клавишу ALT, затем напечатайте с
помощью цифровой клавиатуры 1, 1, 1 и отпустите клавишу ALT.
Появится символ "o". Этот символ имеет код со значением 111 в коде
ASCII.
Переменная ALT_INPUT хранит текущее значение кода, который
вводится в альтернативном регистре. Когда на цифровой клавиатуре
печатается цифра, и состояние клавиатуры показывает, что включен
альтернативный регистр, программа BIOS умножает текущее значение
переменной ALT_INPUT на 10, и прибавляет к нему новое число. Когда
клавиша альтернативного регистра отпускается, в переменной
ALT_INPUT содержится введенный символ. Обычно переменная ALT_INPUT
устанавливается равной нулю, и программа BIOS не считает нулевое
значение правильным результатом ввода в альтернативном регистре.
Это позволяет оператору использовать клавишу альтернативного
регистра вместе с другими клавишами, нажимая и отпуская ее (как это
делается в интерпретаторе языка Бейсик, где ALT-A порождает строку
символов AUTO и не вводя при этом кода 0 в момент отпускания этой
клавиши.
Остальные переменные поддерживают буфер клавиатуры. По мере
того, как на клавиатуре печатаются символы, возникают прерывания.
Программа KB_INT, входящая в BIOS принимает прерывание от
клавиатуры, читает код сканирования из порта 60H, и определяет код
ASCII этой клавиши. Затем программа BIOS записывает это значение в
буфер клавиатуры KB_BUFFER. В этом буфере есть 16 слов - каждая
клавиша записывается в виде слова. Первый байт - это код ASCII
клавиши, второй байт - код сканирования или расширенный код
сканирования клавиши. Использование расширенного кода сканирования
позволяет передавать в прикладную программу символы, которые не
имеют кода ASCII.
У буфера есть два указателя. Переменная BUFFER_HEAD содержит
смещение первого символа в буфере в сегменте DATA, соответствующего
клавише, нажатой раньше других. Переменная BUFFER_TAIL указывает на
символ, соответствующий самой последней нажатой клавише. Если
указатели имеют одинаковые значения, буфер пуст.
Когда обработчик прерывания BIOS реагирует на очередное нажатие
клавиши, при котором должен быть сгенерирован символ, он помещает
этот символ в буфер. Если буфер не заполнен, обработчик прерывания
помещает символ по адресу, на который указывает переменная
BUFFER_TAIL. Затем он увеличивает переменную BUFFER_TAIL на два,
чтобы указывать на следующую ячейку буфера. Если увеличение
указателя перемещает его за пределы буфера, указатель перемещается
на начало буфера. Это означает, что буфер клавиатуры "закольцован".
После шестнадцатого нажатия очередной символ попадает в начало
буфера. Такая организация буфера также называется циклической, так
как позиции буфера образуют замкнутый цикл, а не расположены в ряд.
Программа клавиатуры BIOS, вызываемая по команде INT 16H, имеет
три функции. Одна из них удаляет символ из буфера. Указатель
BUFFER_HEAD указывает на первый символ буфера. Если буфер не пуст,
(что получается, если указатель начала равен указателю конца), BIOS
удаляет слово по указателю BUFFER_HEAD и увеличивает указатель
начала на два. Если указатель превышает значение указателя
BUFFER_END, он перемещается назад в начало буфера. Подпрограмма K4
в программе клавиатуры BIOS обеспечивает сдвиг указателя и, если
необходимо, его перенос в начало.
Другая функция клавиатуры возвращает текущее состояние буфера
клавиатуры. Она сообщает вызвавшей программе, есть ли в буфере
символ или нет. Программа может использовать эту информацию для
того, чтобы избежать ожидания нажатия клавиши, если у нее есть чем
заняться в это время. Такой вызов функции чтения состояния можно
использовать для определения момента выхода из цикла. Во время
каждого прохода по циклу можно проверить, введен ли уже символ с
клавиатуры. Если нет, цикл продолжается. Если символ введен, цикл
завершается. Тело цикла не может выполняться в случае использования
циклящей программой функции чтения символа.
На Фиг. 9.3 показан листинг программы на языке ассемблера,
которая прибавляет единицу к четырехбайтовому целому числу всякий
раз, когда она проходит через цикл. Когда оператор нажимает клавишу
пробела, происходит выход из программы. Эта программа использует
две функции BIOS. Когда регистр AH установлен в 1, BIOS возвращает
состояние буфера клавиатуры. Если установлен флаг нуля, символа
нет. Если символ есть, программа должна также прочитать этот
символ, иначе он останется в буфере до тех пор, пока следующая
программа (или эта же, но позднее) не запросит символ. В этом
примере символ из буфера извлекается вызовом функции программы
обслуживания клавиатуры BIOS с нулевым значением в регистре AH.
BIOS возвращает символ в регистре AL, и программа сравнивает
значение символа с пробелом. Этот пример показывает, как можно
организовать проверку определенного символа в каждом проходе цикла.
Дискета
Дискета
Программа обслуживания дисковода BIOS выполняет блочные операции с
адаптером дисковода. BIOS позволяет вызывающей программе указывать
адреса дорожки и сектора для команды чтения или записи. BIOS
управляет адаптером и самим дисководом, настраивая их на операцию,
а затем пересылает данные из буфера или в буфер пользователя.
После операции BIOS собирает результаты и передает их как часть
выходных параметров программы обслуживания дисков BIOS. Вызывающей
программе не нужно обслуживать различные функции контроллера дисков
и беспокоиться о временных соотношениях.
Дисплей
Дисплей
Видеопрограмма BIOS управляет работой двух дисплейных адаптеров,
которые могут быть установлены в IBM PC. Мы оставили ее для
рассмотрения в последнюю очередь, так как она самая большая и
сложная из всех программ, входящих в базовую систему ввода-вывода.
Драйверы доступа
Драйверы доступа
Драйверы устройств - это сердце BIOS. Эти программы дают
возможность программисту, работающему на языке ассемблера, работать
с устройствами IBM PC. Любая программа может управлять устройствами
с помощью соответствующих последовательностей команд на аппаратном
уровне. Тем не менее во многих случаях вы можете работать с
устройствами стандартным образом, не вникая в их специфические
особенности. Например, только небольшое число программ делают с
дискетой что-либо еще кроме чтения и записи. В гл.8 была написана
программа для чтения состояния дисковода. Если нужно прочитать
некоторый сектор дискеты, можно использовать для этой цели BIOS и
не переписывать эти программы заново. Программист, работающий на
языке ассемблера, должен рассматривать BIOS как инструмент,
сокращающий его работу.
Функции BIOS вызываются с помощью программного прерывания.
Параметры для функции передаются через регистры. Например,
следующие команды опрашивают текущий режим дисплея
MOV AH, 15
INT 10H
Команда INT 10H вызывет драйыер обслуживания дисплея, входящий
в BIOS. Драйвер дисплея имеет большие возможности. Установка в
регистре AH числа 15 сообщает ему, что программист хочет знать
текущее состояние дисплея. BIOS возвращает информацию о состоянии
через регистр AL.
Каждый драйвер устройства входящий в BIOS имеет свои
собственные входные и выходные параметры. В общем случае регистр AH
определяет функцию для данного драйвера. Остальные регистры BIOS
использует для любых других принимаемых или возвращаемых
параметров. Каждая функция кратко описана в техническом описании
IBM PC, а также снабжена другими важными замечаниями. При разборе
драйверов мы вернемся к этим листингам, а перед этим рассмотрим
другие векторы прерываний.
Функции вводавывода дисплея
Функции ввода-вывода дисплея
Программа дисплея BIOS имеет много функций, все они перечислены на
Фиг.9.6. В связи с тем, что видеопрограмма имеет так много
функций, она использует таблицу переходов к этим функциям. Эта
таблица названа M1 и содержит смещения каждой точки входа программы
дисплея BIOS. Первая часть программы VIDEO_IO извлекает код из
регистра AH и преобразует его в адрес перехода. Первая часть
программы выполняет еще и некоторые другие действия, включая
проверку поля EQUIP_FLAG.
Фирма IBM написала видеопрограмму BIOS так, чтобы она могла
работать с двумя дисплейными адаптерами, как с цветным графическим,
так и с монохромным. Но BIOS также подразумевает, что из них
активен только один. Это означает, что вы не сможете использовать
BIOS для того, чтобы записать символ в цветной дисплей, а затем
сразу использовать BIOS для записи символа в монохромный дисплей.
Видеопрограмма BIOS может иметь дело только с одним дисплейным
адаптером.
AH Функция
-------------------------------------------------
0 Инициализация адаптера дисплея
1 Установка размера и формы курсора
2 Установка позиции курсора
3 Чтение позиции курсора
4 Чтение позиции светового пера
5 Назначение текущей страницы
6 Сдвиг вверх
7 Сдвиг вниз
8 Чтение символа
9 Запись символа и атрибута
10 Запись одного символа
11 Выбор палитры
12 Запись точки
13 Чтение точки
14 Запись на телетайп
-------------------------------------------------
Фиг. 9.4 Функции BIOS для видеомонитора
Всякий раз, когда программа вызывает видеопрограмму BIOS, она
определяет, какой дисплейный адаптер имеется в системе с помощью
проверки битов поля EQUIP_FLAG, которые соответствуют текущему
дисплею. Если биты 5 и 4 оба равны 1, то в системе присутствует
монохромный адаптер. Любая другая установка бит говорит о том, что
в системе работает цветной адаптер. Фирма IBM написала эту
программу таким способом, исходя из того, что система может иметь
только один дисплейный адаптер. Перед первым включением машины вы
должны установить переключатели на системной плате в положение,
показывающее, какой адаптер дисплея используется.
Информация флагов оборудования в поле EQUIP_FLAG определяет,
какой из адресов буфера будет использовать видеопрограмма BIOS. Для
монохромной платы BIOS загружает в регистр ES значение 0B000H, а
для цветной платы - значение 0B800H. Это позволяет остальным
программам дисплея BIOS работать без использования информации о
том, какой адаптер работает в системе. Все ссылки к буферу делаются
относительно регистра ES.
Вы можете решить, что, поскольку поле EQUIP_FLAG показывает,
какой адаптер используется, можно переключаться от одного адаптера
к другому просто изменяя биты в слове флагов. К сожалению, это не
так, Адрес ввода-вывода контроллера 6845 отличается для двух
адаптеров, и BIOS записывает этот базовый адрес в свою область
данных. Видеопрограмма BIOS заносит в переменную ADDR_6845 этот
адрес только при инициализации адаптера (команда AH = 0). Поэтому
переключение от одного дисплея к другому также требует
корректировки этой переменной.
Даже если переменная CURSOR_POSN содержит восемь позиций, она
не может обслуживать переключение на другой дисплей. Вы должны
сбрасывать положение курсора в области данных BIOS всякий раз при
переключении с одного адаптера на другой. Если вы не сделаете
этого, изображение курсора не будет соответствовать его положению,
записанному в области данных, и символ на экране будет записываться
в неверную позицию.
Фирма IBM опубликовала методы смены одного дисплея другим, как
с помощью программы на языке ассемблера, так и с помощью программы
на Бейсике. В этих методах требуется для указания адаптера, который
вы хотите использовать, изменить переменную EQUIP_FLAG, а затем
использовать видеопрерывание INT 10H при AH = 0. Эта функция
инициализирует адаптер и обеспечивает правильную установку всех
полей данных программы BIOS. После этого BIOS может работать с тем
дисплейным адаптером, который указан. При этом картинка на другом
дисплее остается видимой. Кроме того, дисплейный буфер того
адаптера продолжает отображать любые изменения текста и графики,
занесенной в него. Так что вы можете измекнять содержимое
дисплейного буфера с помощью вашей программы (а не с помощью BIOS),
чтобы скорректировать информацию, находящуюся на экране, с которого
вы только что переключились.
Давайте рассмотрим простой пример. Вы имеете IBM PC с двумя
адаптерами - цветным графическим и монохромным, и к каждому
адаптеру подключен дисплей. Когда вы сначала включаете машину,
система использует монохромный дисплей. Именно с учетом этого вы и
должны установить переключатели на системной плате, так как
монохромный дисплей может быть поврежден, если не будет
инициализирован сразу же после включения питания. Инструкция по
работе рекомендует, чтобы вы установили переключатели в положение,
показывающее, что в системе присутствует монохромный адаптер.
После этого вы можете использовать видеопрограмму BIOS с
монохромным дисплеем. Чтобы перейти на цветной адаптер, можно
выполнить программу на Фиг.П9.7. Эта программа включает цветной
адаптер в 80-символьном текстовом режиме. Символы, которые были
высвечены на монохромном дисплее, на нем и остаются, а вы теперь
можете использовать видеопрограмму BIOS для работы с цветным
графическим дисплеем. Но если вам потребуется изменить содержимое
монохромного дисплея, вы сможете сделать это, записав новые символы
или атрибуты в буфер дисплея по адресу 0B000H. Это не изменит
положения курсора, но изменит картинку. В этом случае если вы
хотите изменять текст одновременно на цветном и монохромном
дисплеях, вы должны написать собственную программу обслуживания
монохромного дисплея. Или вы можете выяснить, какие значения надо
изменить в поле данных BIOS, чтобы курсор перешел назад, и
выполнить нужные действия, не инициализируя адаптер каждый раз.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:26
Фиг. 9.7 Переключение на цветной дисплей Page 1-1
PAGE ,132
TITLE Фиг. 9.7 Переключение на цветной дисплей
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 ABS0 SEGMENT AT 0
0410 ORG 410H
0410 EQUIP_FLAG LABEL BYTE ; Будет изменяться только младший
0410 ABS0 ENDS ; байт поля флагов
0000 CODE SEGMENT
ASSUME CS:CODE,DS:ABS0
COLOR PROC FAR
0000 1E PUSH DS ; Адрес возврата в ДОС
0001 2B C0 SUB AX, AX
0003 50 PUSH AX
0004 8E D8 MOV DS, AX ; Загрузка адреса сегмента ABS0 в регистр DS
0006 80 26 0410 R CF AND EQUIP_FLAG, 11001111b ; Указание на цветной дисплей как
000B 80 0E 0410 R 20 OR EQUIP_FLAG, 00100000b ; на основной (режим 80*25)
0010 B8 0003 MOV AX, 3
0013 CD 10 INT 10H ; Сброс дисплея
0015 C3 RET ; Возврат в ДОС
COLOR ENDP
0016 CODE ENDS
END
Фиг. 9.7 Переключение на цветной дисплей
Графика
Графика
Видеопрограмма BIOS имеет несколько функций, которые обслуживают
графику на IBM PC. Устанавливая регистр AH равным 11, программа
может сделать выбор цвета в графическом режиме. Эта подпрограмма
устроена так, что она работает с истинной палитрой, а не с
предопределенными палитрами цветной платы. Если графический режим
320*200 имеет истинную палитру, то это означает, что должно
существовать отображение в четыре цвета, возможных для каждой
точки, из четырех цветов, которые хотела бы иметь программа. В
настоящее время это делается для цвета фона в режиме нормального
разрешения. Вы можете выбрать любой цвет в качестве цвета 0, цвета
фона. Интерфейс этой программы был разработан для того, чтобы
позволять определение истинной палитры цветов, если фирма IBM
когда-либо модифицирует аппаратуру так, чтобы позволять это.
Чтобы достичь этой цели, значение точки определяется в регистре
BH. В регистре BL определяется цвет, который присвоит адаптер этой
точке. Например, если содержимое регистра BH равно 0; регистр BL
содержит значение цвета фона. BIOS обслуживает значение регистра
BH, равное только 0 или 1 , так как можно выбрать только цвет фона
и одну из двух предопределенных палитр. Пролог BIOS определяет
палитру. С помощью этой функции можно также определить цвет
окаймления в текстовых режимах.
Две другие графические подпрограммы позволяют читать либо
записывать конкретные точки графического экрана. В простейших
случаях эта функция BIOS позволяет вам определить строку и колонку
без определения их отображения в буфер дисплея. При построении
больших картин, а также при выполнении любой графической работы эти
функции требуют очень много времени. Программа должна вызывать BIOS
при выдаче каждой точки на экран. В случае режима высокого
разрешения программа должна вызвать BIOS 128000 раз, чтобы
изобразить каждую точку правильно. Хотя программы, входящие в BIOS
выполняется очень быстро, она должна вычислять адрес буфера всякий
раз, когда получает информацию о строке и колонке. Для этого
действия требуется умножение и несколько сложений, и следовательно,
требуется некоторое время. В общем случае программа записывает
график с помощью стартового положения точки и смещений относительно
этого положения. Это означает, что программа вычисляет положение
первой точки с помощью алгоритма отображения в буфер, а положения
остальных точек отсчитывает от текущего адреса буфера.
Кассета
Кассета
Программа управления кассетным магнитофоном в BIOS - это пример
работы с последовательным устройством с помощью временных циклов.
Но из-за отличий временных параметров команд, программа
обслуживания кассетного магнитофона BIOS во всех критических
случаях использует таймер-счетчик 8253. Здесь будут рассмотрены
только две программы использующие таймер - READ_HALF_BIT и
WRITE_BIT.
В техническом описании содержится вся информация о методе
кодировки данных, записываемых на кассету. Программа WRITE_BIT
записывает на ленту один бит данных. Выход канала 2
таймера/счетчика непосредственно подключается к выводному порту
кассетного адаптера. Поэтому запись бита данных заключается в
установке правильной частоты канала 2 таймера и ожидании одного
полного цикла. Программа WRITE_BIT делает именно это, но в обратном
порядке. Когда программа WRITE_BIT получает управление, предыдущий
бит еще находится в процессе записи. Два цикла ожидания в программе
WRITE_BIT обеспечивают задержку на пол-цикла, необходимую для
завершения записи предыдущего бита. Когда запись бита завершена,
BIOS заносит новое значение частоты в канал 2 таймера. Программа
WRITE_BIT возвращает управление вызвавшей программе тогда, когда
новая частота начала выдаваться на ленту. Программа управления
кассетным магнитофоном достаточно быстрая (или скорость выдачи бит
в кассету достаточно медленная - это зависит от вашей точки
зрения), чтобы программа WRITE_BIT вызвалась снова до того, как
таймер завершит первые полцикла записи бита.
Программа READ_HALF_BIT выполняет противоположную работу. Эта
программа ждет до тех пор, пока бит ввода с кассетного механизма
(бит 4 порта 62H) не изменит состояние. Каждая смена состояния
этого бита соответствует чтению половины бита. Программа кассетного
механизма вычитает текущее значение таймера из его значения при
предыдущей смене бита. Это число соответствует времени, которое
потребовалось сигналу кассеты, чтобы перейти из одного состояния в
другое. Сложение двух полубитовых переходов дает общую длительность
цикла этого бита. Так как времена циклов у нулей и единиц разные,
программа READ_BYTE может определить значение текущего бита. Из
восьми прочтенных битов она формирует байт.
Программа READ_HALF_BIT иллюстрирует использование канала 0
таймера для целей измерения времени. BIOS замораживает значение
счетчика таймера, а затем читает его в регистр AX. Использование
значения 0, загружаемого в счетчик 0 таймера позволяет вычитать
любые два значения таймера, не анализируя, какое из них больше; в
любом случае получится верная разность.
Программа управления кассетным магнитофоном BIOS содержит в
себе подпрограммы, выполняющие четыре функции. Две из них - блочные
операции ввода-вывода, чтение блока и запись блока. Для
эффективного использования ленты данные записываются на нее блоками
по 256 байт. BIOS проверяет правильность ввода этих блоков с
помощью циклического избыточного кода CRC (Cyclic Redundacy Check).
Проверка ошибок с помощью CRC выявляет почти все ошибки, которые
могут возникнуть на ленте. Это позволяет IBM PC использовать
кассеты в качестве средства памяти с уверенностью, что вновь
читаемые с них данные правильны. Кроме того, BIOS помещает данные в
блоки в связи с несовершенством механизма кассетного магнитофона,
проявляющемся при записи блоков любого размера. Программа обязана
ждать до тех пор, пока двигатель кассетного магнитофона не
включится и разгонится до нужной скорости. Программа также должна
записывать на ленту синхронизирующие импульсы для того, чтобы
микропроцессор вошел в синхронизацию с данными тогда, когда они
будут читаться. Наконец, BIOS записывает слово CRC и конечный байт
в конце каждого блока. Вся эта дополнительная работа происходит с
любым блоком данных, независимо от того, один это байт или 10000
байт. Фирма IBM выбрала размер блока, равный 256, как компромисс
между слижком большим размером блока и нерациональным
использованием ленты.
Другие две функции программы управления кассетным магнитофоном
BIOS - просто включают двигатель ммагнитофона и выключают его. Если
вы думаете о разработке простого способа подсоединения вашей
аппаратуры к IBM PC, имейте в виду, что кассетный порт очень удобен
для этой цели. С помощью разъема кассетного магнитофона,
расположенного сзади корпуса машины, вы можете подключиться к
последовательной линии ввода-вывода. Существует также реле, которое
позволяет управлять низковольтным слаботочным двигателем. Но есть
одна вещь, о которой надо помнить. Выходной бит подключается прямо
к входному биту, когда реле двигателя включено. Такое соединение
позволяет диагностическим программам фирмы IBM проверять входные и
выходные цепи кассетного механизма без записи и чтения данных. Если
вы отдельно используете последовательные вход и выход, нужно
включить реле двигателя - даже если двигателя нет.
Клавиатура
Клавиатура
Программы BIOS, обслуживающие клавиатуру, дают примеры различной
программной техники. Прежде всего программы обслуживания
клавиатуры BIOS имеют большой обработчик прерываний, который
преобразует коды сканирования клавиатуры в коды ASCII. Обработчик
прерываний обслуживает обычные и регистровые клавиши. Программа
обслуживания клавиатуры также поддерживает кольцевой буфер длиной
15 символов, позволяя вам печатать быстрее, чем программа может
обрабатывать нажатия клавиш.
Команда форматирования
Команда форматирования
Команда форматирования инициализирует новую дискету. Когда вы
инициализируете дискету, происходит запись на нее маркеров
идентификации секторов. Эти поля контроллер использует при
операциях чтения и записи для опознавания секторов. Например, во
время операции чтения BIOS посылает четыре байта идентификации
сектора в контроллер дисковода. Эти четыре байта обычно
соответствуют номеру дорожки, номеру головки, номеру сектора и
размеру сектора, и называются номером цилиндра-головки-записи CHRN.
Контроллер использует значение номера CHRN сравнивая его со
значениями, записанными в поля идентификации секторов во время
форматирования.
Это означает, что контроллер не обращает внимания на то, что
записано в поле номера CHRN на дискете, т.е. сектора могут
пронумерованы в произвольном порядке, не от первого до восьмого на
каждой дорожке. Как только контроллер находит сектор, у которого
поле номера CHRN совпадает с заданным, он читает сектор. Значения
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:20
Фиг. 9.5 Форматирование дискеты Page 1-1
PAGE ,132
TITLE Фиг. 9.5 Форматирование дискеты
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE,ES:CODE
0000 00 00 01 02 00 00 02 ID_BUFFER DB 0, 0, 1, 2, 0, 0, 2, 2
02
0008 00 00 03 02 00 00 04 DB 0, 0, 3, 2, 0, 0, 4, 2
02
0010 00 00 05 02 00 00 06 DB 0, 0, 5, 2, 0, 0, 6, 2
02
0018 00 00 07 02 00 00 08 DB 0, 0, 7, 2, 0, 0, 8, 2
02
0020 FORMAT PROC FAR
0020 1E PUSH DS ; Адрес возврата в ДОС
0021 2B C0 SUB AX, AX
0023 50 PUSH AX
0024 8D 1E 0000 R LEA BX, ID_BUFFER ; Занесение адреса буфера в ES:BX
0028 0E PUSH CS
0029 07 POP ES
002A B9 0001 MOV CX, 1 ; Трек 0, сектор 1
002D BA 0000 MOV DX, 0 ; Дисковод 0, сторона 0
0030 TRACK_LOOP:
0030 8D 3E 0000 R LEA DI, ID_BUFFER ; Необходимо для занесения номера
0034 B0 08 MOV AL, 8 ; трека в буфер форматирования
0036 ID_SETUP:
0036 26: 88 2D MOV ES:[DI], CH ; Занесение номера трека (цилиндра)
0039 83 C7 04 ADD DI, 4 ; Переход на следующее поле
003C FE C8 DEC AL
003E 75 F6 JNZ ID_SETUP ; Цикл по полям в буфере
0040 B8 0501 MOV AX, 501H ; Форматирование
0043 CD 13 INT 13H
0045 FE C5 INC CH ; Переход на следующий трек
0047 80 FD 40 CMP CH, 40H ; Все сформатировано?
004A 75 E4 JNE TRACK_LOOP ; Цикл по трекам
004C CB RET ; Возврат в ДОС
004D FORMAT ENDP
004D CODE ENDS
END FORMAT
Фиг. 9.5 Форматирование дискеты
номера CHRN контроллер помещает на дискету во время операции
форматирования. Вы имеете возможность записать в качестве значений
номера CHRN любые значения, которые выберете. Буфер данных для
команды форматирования содержит байты номера CHRN для каждого
сектора дискеты. Это означает, что буфер данных может содержать
например такие значения:
DB 10,0,1,2,10,0,2,2
DB 10,0,3,2,10,0,4,2
для дорожки 10 стороны 0 дискеты. Это пример поля данных,
которое использует команда FORMAT операционной системы PC DOS или
MS DOS. На Фиг. 9.5 показана программа, которая форматирует
одностороннюю дискету с обычными значениями номера CHRN. Заменять
этой программой команду FORMAT операционной системы PC DOS нельзя,
так как система PC DOS также проверяет дискету и записывает на
дискету справочник и таблицу расположения файлов. Еще вы можете
заметить, что эта программа сразу же после запуска начинает
форматировать дискету в дисководе A:. Вы должны быть готовы к
этому, если собираетесь выполнить эту программу.
Вы можете использовать команду форматирования в том случае,
если хотите защитить дискету от копирования. Защита от копирования
означает, что дискета шифруется таким образом, что ее становится
трудно скопировать. Так как утилита DISKCOPY предполагает, что
идентификаторы секторов записаны обычным образом, она не может
копировать дискету с не стандартными номерами секторов. Записав на
дискету идентификатор сектора, отличный от нормального, вы защитите
ее от копирования.
В качестве примера давайте защитим дискету от копирования,
записав не стандартный номер сектора на дорожку 10. Пример,
приведенный выше, показывает обычные номера секторов. Если вместо
них буфер данных будет содержать значения DB 10, 0, 10, 2, 10, 0,
2, 2 DB 10, 0, 3, 2, 10, 0, 4, 2
дорожка 10 не будет иметь сектора 1. Вместо него на ней
появится сектор 10, которого не бывает на нормальной дискете
системы PC DOS. Программа DISKCOPY не может скопировать дорожку 10
правильно. Если теперь данная программа проверит (с помощью команды
проверки) наличие сектора 10 на дорожке 10 дискеты, отсутствие
ошибки будет означать, что дискета оригинальная, а не копия.
Этот способ защиты от копирования не совсем надежен. Каждый
опытный пользователь (и даже некоторые программы копирования) могут
обнаружить защиту такого типа и обойти ее. Но модификация
идентификаторов секторов не может производиться произвольно. Для
определения адреса установки головок BIOS использует номер дорожки
из поле CHRN, так что номер цилиндра должен соответствовать номеру
цилиндра, на котором находится сектор. Код в байте номера головки
определяет установку электронного переключателя, выбирающего
головку, поэтому это значение должно быть задано корректно. Длина
поля берется из таблицы параметров, а не из регистров при вызове,
так что ее изменить трудно. К тому же, это число использует и BIOS,
и контроллер, определяя длину сектора, так что изменить его вы
сможете только после тщательной подготовки. Свободно изменяемым
остается только номер сектора. Перед тем, как вы начнете изменять
номера секторов, запомните, что если при этом вы собираетесь еще
использовать эту дискету в рамках DOS, система будет пытаться
использовать сектор, который вы заменили сектором со своим
нестандартным номером, если вы не модифицируете таблицу
расположения файлов дискеты так, чтобы зарезервировать этот сектор.
Если вам нужно считывать по нескольку секторов (что позволяет
драйвер дисковода BIOS), номера у секторов должны быть
последовательными, но не обязательно начинаться с первого.
В общем, команда форматирования дает некоторое средство защиты
от копирования. Однако абсолютно надежный метод защиты еще не
найден. Только хороший выбор техники шифрования поможет оставить
честных людей честными.
Команда проверки
Команда проверки
Команда проверки аналогична команде чтения, за исключением того,
что данные не записываются в память, а просто отбрасываются. У
контроллера ПДП есть специальная команда, называемая командой
проверки, выполняя которую ПДП отвечает на запросы контроллера
дисковода и переключает шину ЭВМ в необходимый режим. Но в этом
случае ПДП не выполняет другую часть своего цикла, в которой данные
записываются в память. Команда проверки используется для проверки
того, правильно ли записались данные на дискету. Операционная
система использует операцию проверки во время выполнения команды
FORMAT. Проверка в этом случае ищет дефектные места на дискете, и
когда выявляет эти дефектные области, операционная система помещает
их за пределами справочника дискеты. Это позволяет использовать
дефектную дискету, а не выбрасывать ее.
Команда проверки, хотя и может определить, что в данных на
дискете есть ошибка, не гарантирует, что данные записались
правильно. Предположим, что в цепи записи на дискету есть
неисправность. Такая ошибка не вызовет появления ошибочной ситуации
во время записи, но на дискету данные не запишутся. Если вы
проверите область данных, команда проверки прочитает ранее
записанные данные (которые вы хотели модифицировать) без ошибки, и
вы будете считать, что все прошло нормально. Если вы хотите
удостовериться в том, что данные записаны верно, вы должны
прочитать данные назад в другой буфер после записи и затем сравнить
оба буфера. Это гарантирует, что данные записались верно.
Команды чтения и записи
Команды чтения и записи
Команды чтения и записи испоьзуют регистры микропроцессора 8088 как
входные параметры. Эти параметры указывают дорожку, сектор,
головку и дисковод, на котором должна выполняться операция. Парой
регистров ES:BX вызывающая программа указывает буфер, и драйвер
дисковода запускает операцию ПДП в этом буфере. Подпрограмма
DMA_SETUP вычисляет физический адрес буфера. Эта программа также
вычисляет общее число пересылаемых байт, используя входной
параметр, задающий количество секторов, а также табличный параметр
- размер сектора. Затем эта программа посылает значение счетчика и
адрес в контроллер ПДП. Заметим, что эта программа определяет
перекрытие буфером границы области размером 64K. Поскольку
четырехбитовый регистр страницы не изменяется при передаче данных,
то если адрес ПДП переходит через значение 0FFFFH, передача данных
будет неверной. Эта программа сигнализирует об ошибке, не допуская
выполнения ошибочной операции ввода-вывода.
AH Функция
-------------------------------------------------
0 Инициализация адаптера дисковода
1 Читать состояние от последней операции
2 Чтение с диска в память
3 Запись из памяти на диск
4 Проверка дискеты
5 Форматирование дорожки дискеты
-------------------------------------------------
Фиг. 9.4 Функции BIOS для дисковода
Область данных ROM BIOS
Область данных ROM BIOS
Сегмент DATA, расположенный по адресу 40H, содержит переменные,
используемые в BIOS. Мы не будем здесь перечислять все переменные
и их функции. Все они указаны в описании соответствующего драйвера
устройства.
Фирма IBM не меняет ни одну из этих ячеек без особой
необходимости. Некоторые из драйверов, входящих в BIOS не только
читают эти ячейки. Но для программы может оказаться довольно важным
просматривать содержимое этих ячеек. В следующей главе приводится
пример, в котором требуется изменить одну из переменных,
поддерживаемых BIOS. Замена этого числа дает дополнительные
возможности при использовании системы.
Так как, скорее всего, фирма IBM не изменит местоположение ни
одной из этих ячеек, разумнее использовать эти данные
непосредственно, и это вполне возможно. Некоторые из переменных
могут потерять смысл по мере того, как люди будут разрабатывать
новые версии аппаратуры. Например, если фирма IBM разработает
систему, у которой не будет никакой памяти (что совершенно
невероятно), исчезнет необходимость в ячейках, которые содержат
текущий объем памяти. В этом случае фирма IBM могла бы найти другой
вид использования этой ячейки. Но если найдется функция, имеющая
форму, сходную с текущей, то вероятнее всего переменная будет
использоваться тем же способом.
Области данных дисплея
Области данных дисплея
Секция поля данных BIOS, озаглавленная VIDEO DISPLAY DATA AREA и
начинающаяся со смещения 49H, содержит переменные, используемые
видеопрограммой. Все эти ячейки данных содержат значения,
используемые для работы с дисплейным адаптером в текущий момент
времени. Многие из этих значений копируют данные, находящихся в
регистрах дисплейных адаптеров, из которых запрещено чтение.
Видеопрограмма BIOS должна знать текущее значение таких переменных,
как CRT_MODE_SET и CRT_PALETTE при модификации регистров. В
отличие от порта вывода на системной плате (порт 61H), BIOS не
может прочитать эти регистры перед их изменением. Это означает,
что BIOS должна поддерживать в памяти образ регистра.
Все поля данных имеют содержательные комментарии, которые
поясняют назначение данных во время вашей работы с текстом BIOS.
Отдельного комментария заслуживает поле CURSOR_POSN. Так как
цветной графический адаптер может поддерживать более одной страницы
изображения в текстовом режиме, на каждой странице имеется свое
место для курсора. Контроллер CRT 6845 обслуживает только курсор
текущей страницы. Когда BIOS переключается со страницы на страницу,
контроллер запоминает положение курсора на соответствующей
странице. Так как цветная плата может содержать максимум восемь
страниц в 40-символьном режиме, имеется восемь ячеек для хранения
текущего положения курсора на каждой странице.
Области данных драйвера BIOS дискеты
Области данных драйвера BIOS дискеты
Области данных дискового драйвера BIOS начинаются у смещения 3EH в
сегменте DATA. Первые четыре байта поля данных хранят информацию
состояния дисководов между операциями. Семибайтовый буфер с именем
NEC_STATUS хранит информацию о состоянии контроллера, возвращаемую
контроллером дисководов фирмы NEC после операций чтения и записи.
Как видно из управляющих программ, этот буфер позволяет BIOS
расшифровывать любую ошибку и предоставлять все ошибки в виде
простого набора кодов ошибок. Эти коды ошибок программа BIOS
помещает в байт с именем DISKETTE_STATUS, одновременно возвращая
его в вызывающую программу в регистре AH после выполнения операций
ввода-вывода. Операторы ассемблера после имени DISKETTE_STATUS
перечисляют все коды ошибок, которые может получить вызывающая
программа.
Контроллер дисковода фирмы NEC знает положение головки
чтения-записи в каждом из четырех дисководов, которые он может
обслуживать. Но для этого контроллер должен войти в синхронизацию с
этими механизмами до того, как он начнет точно отслеживать текущее
положение головок; синхронизация нужна потому, что после включения
питания или после сброса контроллер фирмы NEC не знает, где
находятся головки. Байт SEEK_STATUS использует младшие 4 бита, по
одному биту на механизм, чтобы указать, известно ли контроллеру
текущее положение головок или нет. Когда BIOS посылает сигнал
сброса в контроллер, он заносит нуль в этот байт. Перед каждой
операцией обмена данными с дисководами BIOS проверяет этот байт
установки. Если содержимое бита, соответствующего механизму, с
которым идет работа, равно 0, BIOS посылает команду рекалибровки
перед командой установки. На этапе рекалибровки головка
чтения-записи устанавливается на дорожку 0, и теперь контроллер и
механизм согласованы в смысле положения головки. Все последующие
операции установки делаются без предварительной рекалибровки.
Операция рекалибровки играет важную роль и при обычной работе
для устранения условий появления ошибки. После любой ошибки
рекомендуется сбросить контроллер. Этим обеспечивается сброс
условия появления ошибки в контроллере. Байт SEEK_STATUS при этом
устанавливается равным нулю. Поэтому перед тем, как прикладная
программа повторит операцию (неудачную операцию нужно повторять по
крайней мере три раза, так как большинство таких ошибок устранимы и
не повторяются), выполнится рекалибровка механизма. Это
автоматически устраняет ошибку, вызванную неправильной установкой,
когда головка не попала на нужную дорожку. В обычных случаях
рекалибровка и повторная установка на эту же дорожку устраняет
ошибку.
Байты MOTOR_STATUS и MOTOR_COUNT управляют двигателем
дисковода. Адаптер дисковода имеет управляющий регистр, который
позволяет выбрать двигатель дисковода, который включается в двнное
время. В этот управляющий регистр можно только записывать данные,
читать из него нельзя, и байт MOTOR_STATUS является его образом в
памяти. Перед тем, как выполнить операцию записи-чтения на дискете,
вы должны дать возможность двигателю разогнаться до необходимой
скорости, подождав некоторое время - около половины секунды. Если
двигатель уже работает, ждать не нужно. Чтение конкретного бита в
этом байте состояния позволяет определить, нужно ли ожидание.
Далее после выполнения операции важно оставить двигатель
включенным. Есть вероятность, что доступ к дискете вскоре
повторится; однако здесь нужно выбирать между износом дискеты и
увеличением времени доступа. BIOS не допускает непрерывного
вращения двигателей дисководов, для этого в ячейке MOTOR_COUNT
находится уменьшающийся счетчик. При каждом прерывании от таймера
этот счетчик уменьшается, и когда счетчик достигает нуля, все
двигатели выключаются. Обычно временные параметры установлены так,
что двигатель работет примерно две секунды после завершения
операции.
В тексте программы обслуживания дисководов BIOS можно увидеть,
что величина MOTOR_COUNT на одном из первых шагов устанавливается
равной 255. Тем самым гарантируется, что прерывание от таймера не
выключит двигатель дисковода в течение операции. Текущее значение,
представляющее две секунды, записывается в байт счетчика перед
возвратом из BIOS в вызывающую программу.
На Фиг.9.4 приведены команды управления дисководом. Команда
сброса пересылает в контроллер параметры механизма, такие как режим
работы ПДП и скорость предачи. Команда сброса также выполняет
аппаратный сброс контроллера. Фирма IBM рекомендует выполнять это
действие после любой ошибки. Это необходимо, так как некоторые
ошибки (в частности, ошибка по исчерпыванию времени операции,
получающаяся, если в дисководе нет дискеты) ставят контроллер в
затруднительное положение. После таких ошибок вернуть контроллер к
нормальной работе можно только с помощью сброса.
Прерывания ROM BIOS
Прерывания ROM BIOS
Как показано в таблице на Фиг.9.1, BIOS использует векторы
прерываний микропроцессора 8088. Эти векторы служат для нескольких
различных целей. Первый блок векторов имеет дело непосредственно с
аппаратными прерываниями. Программы обслуживающие эти прерывания
получают управление всегда, когда возникает аппаратное прерывание.
Например, прерывание от клавиатуры использует вектор прерывания 9,
расположенный по адресу 9*4 или 24H. BIOS обслуживает не все
прерывания инициируемые контроллером 8259. Некоторые прерывания
зарезервированы за устройствами фирмы IBM, а другие вы можете
использовать для своих целей. И даже в том случае, если фирма IBM
зарезервировала какой-то вектор прерывания, вы все равно можете
использовать его по-своему. Но если вы хотите тиражировать
программу, нужно помнить, что у других пользователей компьютеры
имеют не совсем такие же конфигурации, как у вас.
Принтер и асинхронные коммуникации
Принтер и асинхронные коммуникации
Программы входящие в BIOS обслуживающие печатающее устройство и
последовательный канал очень похожи. Основная разница -
возможность чтения символов из асинхронного адаптера. Обе
программы имеют функции инициализации адаптера, вывода символа, и
чтения состояния адаптера. На Фиг.9.2 приведен список функций,
реализуемых этими программами BIOS.
Как видно из рисунка, эти две программы BIOS не совпадают.
Значение регистра AH, необходимое для задания конкретного действия,
разное для обеих программ и нам приходится с этим мириться.
Эти программы BIOS могут обслуживать более одного адаптера. В
поле данных BIOS по адресу 40:0H имеется областть из восьми слов.
BIOS использует эту область для хранения адресов адаптеров
печатающих устройств и последовательных каналов. Четыре слова со
смещением 0, которые помечены RS_232_BASE, являются местом для
хранения адресов портов четырех адаптеров последовательных каналов.
По смещению 8, помеченного PRINT_BASE, находится соответствующая
область для адаптеров печатающих устройств. Процедура POST
инициализирует эту область данных в зависимости от того, какие
устройства она обнаружит в системе. При поиске печатающих устройств
процедура POST сначала ищет черно-белую плату, затем адаптер
печатающего устройства по адресу ввода-вывода 378H и, наконец
адаптер печатающего устройства по адресу 278H. Если процедура POST
находит адаптер печатающего устройства по любому из этих адресов,
она помещает значение его базового адреса в область данных.
Аналогичную работу процедура POST делает с адаптерами
последовательного канала, сначала она ищет плату по адресу
ввода-вывода 3F8H, а затем по адресу 2F8H.
Программы BIOS написаны независимо от адресов ввода-вывода
конкретных плат адаптеров. В регистр DX помещается входной
параметр, указывающий, какую из имеющихся плат должна использовать
программа входящая в BIOS. Например, если у вас есть монохромная
плата, имеющая порт печатающего устройства по адресу ввода-вывода
3BCH, то этот адрес появится первым в таблице PRINT_BASE. Если вы
вызовете программу печати BIOS и загрузите в регистр DX 0,
программа BIOS направит ввод-вывод в этот адаптер. Если вы также
имеете отдельный адаптер печатающего устройства, расположенный по
адресу ввода-вывода 378H, установка регистра DX в 1 позволит
программисту работать с этим адаптером. Посмотрев в текст программы
обслуживания печатающего устройства и последовательного канала,
можно увидеть, что она использует регистр DX для выбора
соответствующего значения из таблицы базовых адресов в сегменте
DATA используемого BIOS. После того, как BIOS определит это
значение, весь ввод-вывод она будет делать, используя модификации
этого базового адреса. В программах есть некоторое количество
команд увеличения или уменьшения, работающих с регистром DX. Это
позволяет BIOS работать с разными регистрами адаптера ввода-вывода
Иден.(Значение AH) Функция печати Функция коммуникаций
-------------------------------------------------------------
0 Печать символа Инициализация адаптера
1 Инициализация Посылка символа
2 Чтение состояния Получение символа
3 - Чтение состояния
-------------------------------------------------------------
Фиг. 9.2 Печать и асинхронные коммуникации
без использования абсолютных значений. Все ссылки ввода-вывода
делаются относительно первоначального адреса, взятого из таблицы
базовых адресов.
Функции инициализации печати не требуется от пользователя
никаких входных параметров. Программа инициализации сбрасывает
печатающее устройство и подготавливает порт управления адаптера
печатающего устройства для дальнейшей работы. Но с другой стороны,
инициализация интерфейса RS232 требует от пользователя информации о
параметрах линии связи. Детали кода инициализации, передаваемого
программе с помощью регистра AL, показаны в прологе к программе
обслуживания последовательного канала.
Другие функции BIOS поддержки печати и последовательного канала
дают возможность записывать (для последовательной связи также
читать) данные в устройство. Особенно важно то, что ввод-вывод
делается синхронно. Это означает, что когда программа передает
управление BIOS, чтобы она выполнила нужную функцию, управление не
возвращается до тех пор, пока работа не будет завершена. Когда
символ печатается, управление остается в программе печати до тех
пор, пока она не передаст символ в устройство печати. Если
печатающее устройство занято, BIOS образует цикл, ожидая конца
работы печатающего устройства. Когда символ передается по каналу
асинхронной связи, программа BIOS ждет, пока аппаратура разрешит
передачу следующего символа. Аналогично программа приема
последовательного канала ждет до тех пор, пока адаптер не принял
символ. Если внешнее устройство никогда не пришлет символ,
программа, вызвавшая функцию BIOS, никогда не получит управление
назад.
По этой причине обе программы содержат функцию состояния. Она
позволяет программе решить, может ли BIOS выполнить операцию в
текущий момент времени. Функция состояния печати сообщает, занято
ли печатающее устройство в данный момент. Программа состояния
последовательного канала показывает, может ли символ быть передан
или принят в данный момент. Программа может использовать эти
программы состояния, чтобы определить, можно ли непосредственно
выполнить операцию. Вы можете решить сделать в вашей программе
что-либо еще в то время, пока операция ввода-вывода не может
выполняться. Если вы проверяете появление некоторого внешнего
события, например приема символа адаптером, программа состояния
позволит не останавливать программу до тех пор, пока символ не
принят. Проверка появления символа позволит вам продолжить работу с
ним при условии, что до этого программа выполняла другие действия.
Важно отметить также способ обработки ошибок программ BIOS.
Используя программы BIOS, очень трудно "подвесить" систему. За
исключеннием того случая, когда вы ожидаете символ из
последовательного канала, BIOS всегда возвращают управление
вызвавшей программе, даже если возникла ошибка внешнего устройства.
В каждом цикле, который ожидает выполнения некоторого действия
внешним устройством, BIOS использует счетчик. Например, когда
программа печати ожидает завершения работы печатающего устройства,
в регистрах BL и CX находится счетчик. Если значение счетчика в
этих регистрах становится нулевым до того, как печатающее
устройство освободится, BIOS возвращает управление с ошибкой
исчерпания времени. Это означает, что выключение печатающего
устройства до того, как оно закончило печать, не вызовет
"зависания" системы. BIOS в конце концов вернет управление
программе, указывая, что произошла ошибка устройства печати.
В BIOS возникают небольшие трудности в связи с исчерпанием
времени. Когда в печатающее устройство попадает символ перевода
страницы 0CH, бумага пропускается до начала очередной страницы.
Если на текущей странице более 51 строки, печатающее устройство
будет двигать бумагу долго, и возникнет ошибка по исчерпанию
времени. То есть можно получить индикацию ошибки даже тогда, когда
печать работает правильно. Величина, задающая интервал времени, в
течение которого контролируется печатающее устройство,
скорректировано во второй версии программы BIOS и устраняет эту
проблему. Если вы имеете первую версию, вы можете заново повторить
операцию печати, вызвавшую ошибку исчерпания времени. Получение
ошибки вновь гарантирует, что это не ошибка программы BIOS.
Процедура BIOS клавиатуры изнутри
Процедура BIOS клавиатуры изнутри
Мы не собираемся построчно анализировать программу клавиатуры BIOS.
Но в ней, однако, есть интересные места. Некоторые из них мы
упомянули раньше, например подпрограмму K4, которая сдвигает
указатель буфера.
Программа KB_INT использует несколько таблиц значений клавиш.
Если вы посмотрите программу, то увидите, что эти таблицы
используются различными способами. Таблицы, содержащие значения
кодов сканирования, используются для поиска шаблонов. BIOS
сравнивает код сканирования клавиатуры со значениями в таблице.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:15
Фиг. 9.3 Состояние клавиатуры Page 1-1
PAGE ,132
TITLE Фиг. 9.3 Состояние клавиатуры
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 0000 LITTLE DW 0
0002 0000 BIG DW 0
0004 COUNT PROC FAR
0004 1E PUSH DS ; Адрес возврата в ДОС
0005 2B C0 SUB AX, AX
0007 50 PUSH AX
0008 ADD_ONE:
0008 2E: FF 06 0000 R INC LITTLE
000D 75 05 JNZ STILL_LOW
000F 2E: FF 06 0002 R INC BIG
0014 STILL_LOW:
0014 B4 01 MOV AH, 1 ; Программа опроса статуса клавиатуры
0016 CD 16 INT 16H
0018 74 EE JZ ADD_ONE ; Переход, если нет символа в буфере ввода
001A B4 00 MOV AH, 0
001C CD 16 INT 16H ; Чтение символа
001E 3C 20 CMP AL, ' ' ; Сравнение с пробелом
0020 75 E6 JNZ ADD_ONE ; Переход, если не пробел
0022 CB RET ; Возврат в ДОС
0023 COUNT ENDP
0023 CODE ENDS
END COUNT
Фиг. 9.3 Состояние клавиатуры
Команда REPNE SCASB, используемая после метки K16, позволяет BIOS
просмотреть таблицу в поисках соответствия с одной из регистровых
клавиш. Когда BIOS находит соответствие в таблице кодов
сканирования, она использует смещение в таблице для получения
значения маски, используемого вместе с переменной KB_FLAG. Так как
все регистровые клавиши представлены битами в переменных флагов,
единая программа, пользуясь этими таблицами, может управлять
регистровыми клавишами.
BIOS использует также другие таблицы для перекодировки кодов
сканирования в коды ASCII. Определив текущее состояние регистров,
BIOS загружает в регистр BX указатель на нужную таблицу кодов
ASCII. Затем программа преобразует код сканирования в правильное
начальное значение выбранной таблицы (вычитая начальный адрес
таблицы). Команда XLAT переводит код сканирования в правильный код
ASCII. Этот прием используется там, где BIOS порождает коды
псевдосканирования цифровой клавиатуры в режиме использования
регистра клавиатуры CONTROL (метка K63).
Подпрограмма ERROR_BEEP - пример управления динамиком, которое
мы разбирали в предыдущей главе; она порождает сигнал, который BIOS
посылает всегда, когда оператор вводит символ, а буфер полон. Так
как этот сигнал может возникнуть всякий раз, когда система
обслуживает прерывание от клавиатуры, было бы неразумно менять
значение счетчика в канале таймера, управляя динамиком. Для этой
цели BIOS использует непосредственное управление динамиком. Если
уже генерируется какой-либо звук, он обрывается и появляется сигнал
о переполнении клавиатуры. Если вы внимательно послушаете сигнал
переполнения, то заметите, что он слегка дрожит. Возникающее 18 раз
в секунду прерывание таймера меняет тон, прерывая цикл прямого
управления динамиком. Как было предложено в предыдущей главе, вы
можете исследовать последствия использования различных временных
циклов таймера на выходную тональность динамика.
Процедуры пользователя
Процедуры пользователя
Существуют некоторые системные функции, которые требуют
непосредственного вмешательства пользовательской программы. Для
этого предназначены два прерывания. Первое, с вектором 1BH -
прекращение выполнения программы с по команде с клавиатуры. Чтобы
прервать выполняемую программу, пользователь системы нажимает
клавиши CTL-BREAK. В обычных случаях это возвращает управление в
текущую программную систему - DOS или Бейсик. Если пишется
программа, которая самостоятельно должна обрабатывать подобное
вмешательство пользователя, надо постоянно проверять, не нажал ли
пользователь соответствующие клавиши на клавиатуре. Или можно
использовать прерывание по отпусканию. Программа обслуживания
клавиатуры BIOS всякий раз, когда возникает нажатие клавиши
CTL-BREAK, дает програмное прерывани 1BH. Обычно это прерывание
указывает на возврат - команду IRET - так что ничего не происходит.
Если мы хотим сразу знать о нажатии клавиши CTL-BREAK, тогда нужно
сделать так, чтобы прерывание 1BH указывало на специальную
подпрограмму нашей программы. Эта подпрограмма сразу узнает о том,
что пользователь захотел выйти из программы и сможет предпринять
некоторые действия.
Аналогично, можно написать программу, которой нужно
периодическое прерывание. Например, игровая программа должна
постоянно знать положение клавиши управления игрой. BIOS выдает
прерывание 1CH всякий раз, когда срабатывает таймер. Как мы уже
видели, это случается примерно 18.2 раз в секунду, или раз в 55
миллисекунд. Можно написать программу, которая проверяет положение
клавиш управления игрой каждый восемнадцатый раз возникновения
прерывания, позволяя тем самым корректировать информацию об их
положении примерно раз в секунду. Этот метод дает нам возможность
периодического входа в заданную подпрограмму.
Прогарммы драйверов устройств
Прогарммы драйверов устройств
Теперь мы обсудим один за другим драйверы устройств. Вместо того,
чтобы рассматривать их в порядке номеров, давайте рассмотрим их в
порядке роста сложности. Простейшие из них - программы системного
сервиса, с них мы и начнем.
Самотестирование при включении питания
Самотестирование при включении питания
IBM PC выполняет процедуру самопроверки после каждого сброса
системы, включая момент, когда появляется питание. Такая проверка
преследует две цели: она выполняет быструю проверку основных
элементов системы и инициализирует основные аппаратные компоненты.
Как системный тест, процедура POST образует первую часть
трехуровнего диагностического пакета фирмы IBM для ее персональной
ЭВМ. Процедура POST выполняется всякий раз при включении системы.
Эта быстрая проверка тестирует работу системы и обнаруживает ошибку
до того, как она повлияет на выполнение программы. Программы,
обеспечивающие второй уровень диагностики приходит вместе с каждой
машиной в составе руководства по работе на IBM PC; эта дискета (или
кассета) содержит программу диагностики, она проверяет оьдельно
каждый компонент машины. Эта диагностическая программа определяет,
какую часть машины покуптель должен вернуть для обслуживания, если
найдена ошибка. Наконец, фирма IBM поставляет усовершенствованное
диагностическое средство. Это средство, доступное за дополнительную
плату, определяет, какой из сменных узлов не работает. Это
диагностическое средство создано для обслуживающего персонала,
работающего на таких машинах. Правда вы можете купить для себя и
усовершенствованные диагностические тесты.
Начало процедуры POST, возможно, будет трудно найти из-за того,
что оно находится в самом конце листинга. Когда микропроцессор 8088
сбрасывается (или когда включается питание), он начинает работу с
адреса 0FFFF:0000H. Эта ячейка находится всего в 16 байтах от
самого конца адресного пространства микропроцессора 8088. Этого
места хватает для команды перехода на настоящую программу POST. Как
вы видите, команда FAR JMP передает управление на метку START,
которая и есть начало процедуры POST. Фирма IBM использует
оставшиеся байты в конце памяти под дату; эта дата - момент выпуска
ПЗУ в серию.
Способ запуска процедуры POST также объясняет, почему фирма IBM
расположила ПЗУ в верхних адресах памяти. Именно здесь
микропроцессор начинает выполнение программы после сброса.
Системное ПЗУ, содержащее программу инициализации машины, должно
иметь некоторую информацию по адресу 0FFFFH:0000H. Так что есть
смысл помещать все ПЗУ в конце памяти. Также, есть смысл поместить
ОЗУ в нижних адресах, оставив в нем векторы прерываний. Возможность
модификации кодов в этих векторах в большой степени увеличивает
универсальность программы BIOS.
Вообще процедура POST состоит из неинтересных кодов. Многие
последовательности команд не имеют никакого смысла. Если вы
посмотрите на начальную последовательность команд, то увидите, что
они не делают ничего - если с микропроцессором ничего не случилось.
Если вас не интересует написание диагностических программ нет
смысла изучать технику процедуры POST. Отметим некоторые действия
процедуры POST, чтобы показать область проверки ошибок. Процедура
POST проверяет все ПЗУ системной платы, считая контрольную сумму.
Этот тест складывает все байты модулей ПЗУ. При сложении перенос из
8-битового результата игнорируется. Если окончательный результат
нулевой, ПЗУ прошло проверку. Конечно, перед тем, как запустить ПЗУ
в серию, фирма IBM обеспечила, чтобы сумма каждого ПЗУ была равна
нулю. Если ПЗУ плохое, этот тест находит ошибку.
Процедура POST также проверяет всю оперативную память в
системе. Переключатели не системной плате сообщают процедуре POST,
сколько у системы есть памяти в наличии. Каждый бит памяти
проверяется, может ли он быть установлен в единицу и сброшен в
нуль. По окончании теста процедура POST записывает нули по всем
адресам памяти. Это означает, что если вы написали программу,
которая будет работать сразу же после процедуры POST, содержимое
всей памяти окажется нулевым. Но надеяться, что некоторая другая
программа инициализирует поля данных вашей программы - плохой стиль
программирования. чтобы быть в этом уверенным, лучше делать это
самому.
Последнее, что можно сказать про процедуру POST - то, что она
Аппаратные прерывания
---------------------------------------------------------
Номер
прерывания Использование в ROM BIOS
---------------------------------------------------------
2 02H Ошибка четности в памяти
5 05H Печать экрана
8 08H Текущее время
9 09H Клавиатура
14 OEH Дискета
Драйверы BIOS
---------------------------------------------------------
Номер
прерывания Использование в ROM BIOS
---------------------------------------------------------
16 10H Видео
17 11H Проверка оборудования
18 12H Размер памяти
19 13H Дискета
20 14H Асинхронные
21 15H Кассета стриммера
22 16H Клавиатура
23 17H Принтер
24 18H Точка входа Бэйсика для кассеты
25 19H Точка входа в процедуру первичной
загрузки
26 1AH Текущее время
---------------------------------------------------------
Процедуры управляемые пользователем
---------------------------------------------------------
Номер
прерывания Использование в ROM BIOS
---------------------------------------------------------
27 1BH Прерывание клавиатуры
28 1CH Квантование времени
Блоки параметров BIOS
---------------------------------------------------------
Номер
прерывания Использование в ROM BIOS
---------------------------------------------------------
29 1DH Параметры видеомонитора
30 1EH Параметры дисковода
31 1FH Графические символы видеомонитора
---------------------------------------------------------
Фиг. 9.1 Векторы прерываний, используемые
ROM BIOS
инициализирует векторы прерываний для BIOS. Программы получают
доступ к BIOS с помощью векторов прерывания. Сами по себе эти
подпрограммы находятся в модуле ПЗУ, в том же, что и процедура
POST. Перед тем, как процедура POST передаст управление
операционной системе, она делает так, чтобы каждая входная точка
BIOS была записана в соответствующий вектор прерывания. BIOS
использует векторы прерываний для прерываний от 2 до 01FH.
Техническое описание содержит листинг векторов прерывания, в
котором показаны номера прерываний и первоначальное содержимое
векторов. На Фиг.9.1 изображена часть этой таблицы, которая будет
использоваться в описании BIOS.
Сдвиг изображения
Сдвиг изображения
Программы сдвига перемещают текстовую информацию либо вверх, либо
вниз, в зависимости от вызванной функции. Программы сдвига также
обеспечивают некоторые средства организации окон (фрагментов
экрана) на экране дисплея - т.е. BIOS может сдвигать только часть
содержимого экрана. Входные параметры программы сдвига определяют
прямоугольник, расположенный на экране. Он задает верхний левый и
правый нижний углы области сдвига. Видеопрограмма BIOS сдвигает
данные только в этой области. Остальная часть экрана не меняется.
Мы уже видели пользу сдвига окон при изучении операционной
системы и языка Бейсик, которые использовали для сдвига BIOS. Если
работает 80-символьный дисплей, DOS устанавливает верхний левый
угол окна сдвига равным (0, 0), а нижний правый угол (24, 79). Так
сдвигается весь экран. Но Бейсик использует двадцать пятую строку
для индикации состояния дисплея, и только 24 строки отводит под
изображение программы. Когда Бейсик сдвигает экран с помощью
базовой системы ввода-вывода, он устанавливает верхний левый угол в
точке (0, 0), а правый нижний угол - в точке (23, 79). Из-за того,
что последняя строка остается за пределами сдвига, она не
перемещается во время сдвига. В следующей главе приведен пример
сдвига окна, вызываемого из программы на Бейсике.
Видеопрограмма BIOS выполняет сдвиги с помощью переноса
символов и атрибутов в дисплейном буфере. Программа сдвига не
меняет стартового адреса дисплейного буфера; такой метод сдвига был
бы быстрее, но не позволяет прикладной программе определять, куда
должны попасть отдельные символы. Способ сдвига, реализуемый в
BIOS, подходит для обычной работы экрана. Заметим также, что
программа сдвигает изображение при необходимости более чем на одну
строку. Обычно программа сдвигает изображение на одну строку.
Функция сдвига видеопрограммы BIOS позволяет сдвинуть содержимое
экрана на несколько строк. Если же количество строк сдвига равно
нулю, программа BIOS очищает экран. Это - быстрый способ очистки
всего экрана или его части.
Когда программа работает в 80-символьном режиме на цветном
графическом адаптере, она не может записывать или читать его текст
в произвольные моменты времени. Если программа будет модифицировать
буфер не в строго определенные моменты времени, на экране появятся
помехи. Так как программа сдвига читает и записывает большие
количества данных, она должна быть написана с учетом проблемы
помех. Если посмотреть текст программы обслуживания операции
сдвига, то можно увидеть, что BIOS обслуживает режим 80*25 цветной
платы (переменная CRT_MODE равна 2 или 3), как специальный случай.
В случае операций сдвига эта подпрограмма BIOS ждет до тех пор,
пока не возникнет вертикальный обратный ход луча дисплея. Это
означает, что аппаратура адаптера выдала на экран все содержимое
буфера и готова начать новый кадр. (Аппаратура адаптера повторяет
этот процесс регенерации экрана 60 раз в секунду). При появлении
вертикального обратного хода программа BIOS выключает дисплей и
выполняет сдвиг. Когда программа сдвига переместит все символы, она
снова включает дисплей. Это вызывает короткое мигание дисплея. Если
вы внимательно посмотрите на экран во время сдвига, то заметите,
что верхние его шесть строк несколько темнее остальных. Это
происходит из-за того, что операция сдвига занимает несколько
большее время, чем один период регенерации экрана. Поэтому верхние
шесть строк выключаются на два интервала регенерации, а весь
остальной экран - только на один интервал. Метод, допускающий
появление помех на экране, настолько непригляден, что описанный
метод предпочтительнее. Вы можете написать несколько программ,
чтобы попробовать другие методы.
Когда экран находится в графическом режиме, начинает работать
другая часть программы сдвига. Хотя это и существенная часть
программы BIOS, ее обсуждение отложим до тех пор, пока не
рассмотрим чтение и запись символов на экран.
Системный сервис
Системный сервис
Два драйвера в BIOS дают самый простой системный сервис. Они
предназначены для определения объема памяти ЭВМ и конфигурации
внешних устройств.
Программа определения объема памяти не имеет параметров. BIOS
возвращает в регистре AX объем памяти системы, измеренный в
килобайтах (1024 байт). Если система имеет память 64K байт, в
регистре AX возвратится число 64. Любая программа, использующая всю
память системы, должна запрашивать у BIOS объем памяти, чтобы
определить, где находится ее конец. Программа могла бы определить
объем памяти, записав и прочитав подряд ячейки памяти, сравнивая
записанное и прочитанное значение. Но, как покажет пример в
следующей главе, важно писать все прикладные программы так, чтобы
они использовали для определения объема памяти подпрограмму,
возвращающую этот объем. Изменяя значение верхнего предела памяти,
можно зарезервировать участок в верхних адресах памяти. После того,
как программа изменит значение общего объема памяти, корректно
написанная прикладная программа не нарушит границу памяти.
Программа проверки конфигурации внешних устройств не имеет
входных параметров. Эта программа возвращает в регистре AX
16-битовый код, показывающий, какие устройства подключены к
конкретной системе. В прологе распечатки этой программы в
техническом руководстве по IBM PC указывается, что означает каждый
бит. Эта функция BIOS - простейший способ определения, существует
ли конкретное устройство в системе, или нет.
Последняя системная сервисная программа проверяет время суток.
У этой программы есть две функции: чтение времени и установка
времени. Время измеряется в квантах таймера, начиная с того
момента, когда машина включается, и отсчитывается от полуночи. BIOS
не преобразует это значение в часы, минуты и секунды. Но в листинге
BIOS показаны нужные для преобразования константы. Чтобы определить
время в часах, разделите 24-битовое значение таймера на 65543,
число квантов таймера в часе. Чтобы определить минуты, разделите
остаток от предыдущего деления на 1092, количество квантов в
минуте, и так далее.
Если точность преобразования значения времени не очень критична
для вас, можно воспользоваться более простым методом. Так как
количество квантов, соответствующее 24 часам не помещается в одно
слово, значение таймера представляется трехбайтовым целым числом.
Значение старшего байта отличается не более, чем на 1% от времени в
часах. Младшее слово можно разделить на 1092, чтобы определить
число минут, а деление остатка на 18 дает число секунд.
Функция времени дня использует аппаратное прерывание,
прерывание по кванту таймера. Это прерывание имеет уровень 0 в
контроллере прерываний 8259, и имеет вектор прерывания 8 в
микропроцессоре 8088. Эта программа получает управление каждые 55
миллисекунд. Основное назначение этой программы - увеличение
счетчика квантов таймера программы времени дня. Если программа
выключит прерывания на значительный промежуток времени, то весьма
вероятно, что время суток перестанет быть правильным.
Прерывание от таймера используется также программой
обслуживания дисковода. Двигатели дисковода включены не постоянно;
BIOS включает двигатели только на время доступа к дискете. Но BIOS
не выключает двигатель сразу же после выполнения операции. Есть
некоторый интервал времени между включением двигателя и тем
моментом, когда он разгонится и будет вращаться достаточно быстро,
для того, чтобы можно было читать данные. Если программа обращается
к дисководу почти сразу после предыдущего обращения, лучше оставить
двигатель включенным, а не выключать и включать его. Программа
обработки аппаратного прерывания от таймера учитывает это.
Обработчик дискового прерывания загружает число в переменную,
которая называется MOTOR_COUNT, когда завершается операция обмена с
дискеттой. Прерывание от таймера уменьшает значение этого счетчика.
Когда значение переменной MOTOR_COUNT достигает 0, выключается
двигатель дисковода. Программа обслуживания дисковода проверяет
этот счетчик, перед обращением к дискете. Если двигатель еще не
включен, нужна задержка, пока двигатель не разгонится. Обычно
двигатель дисковода продолжает работать две секунды после
завершения предыдущей операции. Это время - один из параметров
дисковода, и вы можете изменить его значение. Выбор этого значения
поддерживает балланс между повышением производительности и
снижением износа поверхности дискеты.
Все эти три сервисные программы BIOS передают числа из ячеек
памяти в вызывающую программу. Можно избежать использования BIOS
путем непосредственного чтения этих ячеек. Но зачастую проще
вызвать BIOS, чем организовывать адресацию к сегменту DATA
используемому в BIOS. С "наивной" точки зрения, проще использовать
программу BIOS.
Текст в графических режимах
Текст в графических режимах
Одной из важных возможностей программы BIOS фирмы IBM является
способность показывать на экране текст даже тогда, когда цветной
графический адаптер работает в графическом режиме. Это
осуществляется с помощью таблицы образов символов по адресу
0FFFFH:0FA6EH. Эта таблица содержит образы символов для первых 128
символов. Если нужно, пользователь может загрузить в вектор
прерывания 01FH указатель на таблицу образов оставшихся 128
символов.
Как видно в листинге BIOS, когда цветная графическая плата
находится в графическом режиме, программа записи символа переходит
к специальной части под названием GRAPHICS_WRITE. Эта часть
программы извлекает образ символа из таблицы в ПЗУ или из таблицы
пользователя и помещает точки в соответствующие места памяти
дисплея. В этой программе есть несколько интересных мест. В режиме
среднего разрешения программа BIOS расширяет 8-битовый по ширине
образ символа в 16-битовый. Подпрограмма S21 (EXPAND_BYTE) помещает
в регистр AL строку образа символа и расширяет ее до полного
слова, возвращаемого в регистре AX.
Программа записи символов должна также учитывать адресацию
четных и нечетных полей графической платы. В подпрограмме
GRAPHICS_WRITE происходит запись различных строк образов в байты,
расположенные на расстоянии 2000H друг от друга. Это лучше всего
видно в подпрограмме записи для режима с высоким разрешением. В
этом режиме BIOS может записывать символы со строками образа прямо
в буфер дисплея. Но вместо использования команды REP MOVSB для
пересылки восьми байт используется цикл для обслуживания четных и
нечетных полей. Сначала BIOS записывает четное поле с помощью
команды STOSB. Затем записывается нечетное поле с помощью команды
MOV по адресу [DI+2000H-1].
Другая возможность программ записи символов - запись символов
на экран с помощью функции "исключающее или". Это обычный способ
выдачи на экран символов, которые затем надо удалить. Когда
программа BIOS записывает символ на дисплей при включенном бите
XOR, он берет функцию "исключающее или" от содержимого буфера и
образа символа. Обычно это дает символ в читабельной форме, но его
фактический вид зависит от фона, на котором он записывается. Но
когда программа BIOS снова записывает символ на то же место, опять
используя функцию XOR, символ исчезает, и экран возвращается к
своему первоначальному виду. Этот метод предпочтительнее того, при
котором записывается символ, а затем поверх него выдается пробел.
Запись пробела не восстановит экран к виду, который он имел до
записи символа. Вы можете очень эффективно использовать эту
возможность записи со стиранием в случаях, когда вам надо выдать
временное сообщение на экран.
Программа чтения символа работает аналогично, когда дисплей
находится в графическом режиме; BIOS извлекает образ символа из
дисплейного буфера, а затем сравнивает этот образ с образами
таблицы символов. Когда она находит символ, это отвечает, что по
данному адресу находится найденный символ. Эта программа работает
только в случае точного соответствия, так что если часть другой
графической картинки вторгнется в позицию символа, BIOS не сможет
распознать символ. Более того, эта программа позволяет программисту
считать графический режим идентичным текстовому режиму. Пока
программа использует для взаимодействия с дисплеем BIOS, она может
работать с текстом независимо от режима работы дисплея.
Вспомним теперь, что и программы сдвига изображения тоже имеют
специальные части для обслуживания графических режимов. Если вы
вернетесь к соответствующей части BIOS, то обнаружите, что она
определяет окна сдвига в графическом дисплее и выполняет сдвиг
способом, который идентичен сдвигу в текстовом режиме. Сдвиг в
графических режимах происходит несколько медленне, чем в текстовом
режиме, в первую очередь из-за того, что программа должна переслать
все 16000 байт, а не 2000 или 4000 байт, нужных в текстовом режиме.
Это дает 4- - 8-кратное увеличение времени сдвига, и оно становится
заметно больше.
Способность BIOS обрабатывать символы в графических режимах
дает большие возможности. Становится достаточно простым нарисовать
график или картинку, а затем использовать символы для пометки
отдельных частей рисунка. Кроме того, вы можете отвести часть
экрана под графическое изображение, а другое окно использовать для
текста. В это окно вы можете заносить обычный текст с помощью
программ символьного вывода, а также сдвигать его. В других случаях
вы можете записывать символы на дисплей, не обращая внимания на его
текущий режим. BIOS сама определит, в каком режиме находится
дисплей, и правильно запишет символы.
Установка режима
Установка режима
Когда программа выполняет прерывание INT 10H при AH = 0, она
вызывает функцию установки режима видеопрограммы BIOS. Если в
переменной EQUIP_FLAG отмечено, что в системе есть монохромная
плата, не важно, какое значение оказалось в регистре AL. В этом
случае программа BIOS настраивает монохромный адаптер на режим 7,
что означает текстовый режим 80*50, поддерживаемый черно-белой
платой.
В случае цветной графической платы значение в регистре AL
определяет, в какой из двух графических или двух текстовых режимов
введет программа BIOS дисплейный адаптер. Вы видите, что имеются
черно-белые режимы наряду с цветными для текста, а также графика
320*200. Эти черно-белые режимы на самом деле не выключают цвета,
они только выключают сигнал цветности, который используется в
телевизоре для определения цвета каждой точки. Если вы используете
RGB-монитор, цвета останутся на местах. Если же вы используете
цветной (или черно-белый) видеомонитор или телевизор, установка
черно-белого режима цветной платы выключит цвета и даст более
четкое изображение на экране. Если вы занимаетесь задачей, в
которой цвета не нужны, то получите несколько лучшее изображение,
выбрав один из черно-белых режимов вместо цветного.
Когда выполняется программа установки режима, она настраивает
адаптер и поля данных видеопрограммы BIOS на обслуживание нужного
режима работы. Программа установки режима заполняет пробелами буфер
дисплея и помещает курсор в верхнем левом углу экрана. До тех пор,
пока вы не будете достаточно знакомы с устройством дисплея, вы
должны использовать программу установки режима BIOS. Хотя и нет
ничего плохого в смене кодов дисплея на ваши собственные, очень
трудно отлаживать программу, которая модифицирует дисплей. Если вы
сделаете что-либо неверно, дисплей станет неработоспособным, и
исчезнет возможность определить, что же неверно.
Функции видеопрограммы, соответствующие номерам от 1 до 5
служат для работы с регистрами микросхемы 6845. Как вы помните из
описания системной аппаратуры, микросхема 6845 содержит регистры,
управляющие формой и положением курсора, а также временными
характеристиками отображения. Эти подпрограммы видеопрограммы BIOS
позволяют модифицировать отображение, не зная базового адреса
микросхемы 6845. Эти подпрограммы входят в число сервисных программ
BIOS.
Выдача на телетайп
Выдача на телетайп
Функция выдачи на телетайп видеопрограммы BIOS предназначена для
тех программ, которые хотят использовать дисплей в простейшей
форме. Эта функция представляет дисплей в виде телетайпа. Данная
программа выполняет обслуживание установки курсора и выдачи
символа. После того, как BIOS заносит символ в текущую позицию, он
перемещает курсор в следующую позицию. Если курсор попал за
пределы строки, программа BIOS сдвигает дисплей вверх на одну
строку, и помещает курсор в первую позицию следующей строки.
Кроме того, что программа выдачи на телетайп дает удобный метод
выдачи символов на экран, она служит хорошим примером использования
видеопрограммы BIOS для обслуживания символов. Эта программа
записывает символы, перемещает курсор, а также сдвигает экран,
когда это небходимо, а также реагирует на некоторые служебные
символы. Символ возврата на шаг назад перемещает курсор на одну
позицию назад, возврат каретки перемещает курсор в начало строки, а
перевод строки переносит курсор на следующую строку, если надо
сдвигая экран. И наконец, символ звукового сигнала (код 7) вызывает
генерацию звука с помощью динамика. ОС использует эту функцию BIOS
для поддержки большинства своих дисплейных функций.
Замечания по листингу ROM BIOS
Замечания по листингу ROM BIOS
Листинг BIOS дан в приложении A технического описания IBM PC. Этот
листинг - описание модуля ПЗУ объемом 8K, расположенного по
адресам, начиная с 0FE000H в адресном пространстве микропроцессора
8088. Этот модуль ПЗУ - один из пяти модулей, размещенных на
системной плате фирмой IBM. Другие четыре модуля ПЗУ содержат в
себе интерпретатор с языка Бейсик. Исходные тексты программ
Бейсика, так же, как и DOS, являются частной собственностью фирмы и
не печатаются в техническом описании. Но тексты программ входящих
в BIOS фирма IBM опубликовала, так что каждый может изучить
интерфейсы с BIOS.
Листинг ассемблера в приложении A - это полный листинг
содержимого ПЗУ. Это не листинг Макроассемблера фирмы IBM потому,
что в то время, когда фирма IBM разрабатывала BIOS, Макроассемблер
не существовал. Для разработки BIOS использовался Макроассемблер
фирмы Intel, с помощью которого получился данный листинг. Ассемблер
фирмы Intel идентичен ассемблеру фирмы IBM в использовании и
синтаксисе. Как вы видите, ассемблер фирмы Intel не печатает в том
же виде адресное поле, а также отличается некоторыми
псевдооперациями ассемблера. Но с этими небольшими отличиями
работать будет не сложно.
В листинге программы BIOS описаны шесть различных сегментов. Из
них по-настоящему интересны для нас лишь три. Сегмент ABS0,
расположенный по адресу 0, содержит векторы прерываний, с которыми
имеет дело BIOS и процедура POST. В этом сегменте нет никаких
определений данных; он просто отводит место векторам. Эта область
попадает в ОЗУ, и поэтому программа BIOS должна инициализировать ее
после включения питания. Сегмент DATA, расположенный в параграфе
40H или по абсолютному адресу 400H, определяет все поля данных,
используемых в BIOS. Аналогично этот сегмент определяет места
переменных, но не их начальные значения. Наконец, сегмент CODE
начинается в параграфе 0F000H. В первых 56K байтах этого сегмента
ничего нет, первый байт сегмента CODE имеет абсолютный адрес
0FE000H или смещение 0E000H в сегменте. Эти данные, от 0FE000H до
0FFFFFH, представляют содержимое модуля ПЗУ с BIOS объемом 8K. Эти
программы вместе с ПЗУ с Бейсиком - единственные, которые
содержатся в машине в момент начала работы.
Еще одно замечание. Фирма IBM не разрабатывала BIOS в виде
одного большого исходного текста. Каждая функция была разработана в
виде отдельного модуля, а затем модули были связаны вместе,
сформировав BIOS. Возможно вы увидите некоторые связки в программе.
Для публикации фирма IBM скомбинировала все исходные тексты в один
большой исходный текст и ассемблировала его. Такое единое
ассемблирование позволило показать абсолютные адреса каждой функции
BIOS.
Assembler для начинающих
Компилируемые языки высокого уровня
Компилируемые языки высокого уровня
В предыдущих примерах рассматривалась программа на языке
ассемблера, используемая совместно с интерпретатором Бейсика.
Версия языка Бейсик, входящая в поставку IBM PC, является
интерпретируемым языком. Это означает, что программа хранится в
ЭВМ в виде, очень похожем на исходный текст. Интерпретатор не
преобразует операторы языка Бейсик в команды машинного языка.
Интерпретатор Бейсика во время выполнения просматривает каждый
оператор программы и делает все, что необходимо для выполнения
этого оператора.
По-другому работает компилятор. Он преобразует операторы языка
высокого уровня в команды машинного языка. Фирма IBM предлагает
компиляторы для персональной ЭВМ с языков Бейсик, Паскаль, Фортран
и Кобол. Выходом компилятора является программа на машинном языке
(файл *.OBJ), т.е. он во многом аналогичен выходу ассемблера.
Запуск программы, написанной на компилируемом языке высокого уровня
состоит из двух этапов. Сначала программа должна быть
скомпилирована, и должны быть отредактированы связи. Затем она
может быть выполнена. Интерпретируемая программа может выполняться
непосредственно, минуя этап компиляции.
Компилируемые языки на персональной ЭВМ аналогичны языку Бейсик
в том смысле, что не дают возможности делать с техническим
обеспечением все, что вздумается. На самом деле интерпретатор
Бейсика еще позволяет программисту при помощи операторов программы
считывать и записывать информацию с портов ввода-вывода и ячеек
памяти. Другие языки не всегда предоставляют даже эту возможность.
Поэтому применение подпрограмм на языке ассемблера в программе на
Паскале или Фортране может оказаться даже более необходимым.
Возможно, вам придется заняться этим, если вы захотите
воспользоваться всеми возможностями технического обеспечения.
К счастью, включить процедуру на языке ассемблера в программу
на компилируемом языке высокого уровня довольно просто, так как
выходом компилятора является объектный файл, готовый к
редактированию связей. Выход ассемблера - тоже объектный файл.
Следовательно, достаточно лишь связать программу на языке высокого
уровня и программу на языке ассемблера при помощи редактора связей
DOS. Нет необходимости соединять программы в процессе выполнения,
как это делалось для интерпретатора Бейсика.
Построим пример на языке Фортран (Фиг. 10.11). Для языка
Паскаль все очень похоже. Подобный пример приведен в приложении D
справочника к компилятору Фортрана. В примере головная программа,
написанная на Фортране, объединена с программой на языке
ассемблера, которая считывает текущее время, используя программное
прерывание базовой системы ввода-вывода. Подпрограмма на языке
ассемблера обращается к BIOS для определения текущего времени и
возвращает соответствующее значение в программу на Фортране.
Головная программа преобразует кванты таймера, в текущее время,
выраженное в часах, минутах и секундах.
На Фиг. 10.11 представлена головная программа на Фортране.
Эта программа вызывает внешнюю процедуру TIMER, имеющую один
параметр A - четырехбайтовое целое значение. Возрващаемое
процедурой TIMER значение представляет собой текущее время,
выраженное в квантах таймера и отсчитываемое от полуночи.
Программа на Фортране по полученному из процедуры TIMER значению
вычисляет время в часах(HOURS), минутах(MINS), секундах(SECS) и
сотых долях секунды(HSECS). Отметим, насколько проще реализовать
умножение и деление на языке Фортран, чем на языке ассемблера.
Можно убедиться, что выполнение всех подобных операций на Фортране
существенно упрощает программирование. Чрезвычайно удобен и способ
преобразования целых переменных в выдаваемые на печать символы при
помощи операторов Фортрана WRITE и FORMAT. На языке ассемблера для
выполнения тех же самых действий потребовалось бы несколько сот
строк. Вспомним пример для сопроцессора 8087, где программа
преобразовывала число с плавающей точкой в код ASCII. В этой
программе содержалось значительное число команд, и, кроме того,
использовался сопроцессор 8087.
$STORAGE=4
INTEGER A,HOURS,MINS,SECS,HSECS
CALL TIMER(A)
HOURS=A/65543
A=A-HOURS*65543
MINS=A/1092
A=A-MINS*1092
SECS=A/18
HSECS=(100*(A-SECS*18))/18
WRITE(*,10)HOURS,MINS,SECS,HSECS
10 FORMAT(1X,'THE TIME IS: ',I2,':',I2,':',I2,'.',I2)
END
Фиг. 10.11 Программа определения времени дня на Фортране
Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:07:35
Фиг. 10.12 Подпрограмма для программы на ФОРТРАНе Page 1-1
PAGE ,132
TITLE Фиг. 10.12 Подпрограмма для программы на ФОРТРАНе
FRAME STRUC
0000 ???? SAVEBP DW ?
0002 ???????? SAVERET DD ?
0006 ???????? A DD ? ; Указатель на параметр
000A FRAME ENDS
0000 CODE SEGMENT 'CODE'
DGROUP GROUP DATA
ASSUME CS:CODE,DS:DGROUP,ES:DGROUP,SS:DGROUP
0000 TIMER PROC FAR
PUBLIC TIMER ; Указание программе LINK на расположение
; программы TIMER
0000 55 PUSH BP
0001 8B EC MOV BP,SP ; Загрузка адреса стека
0003 B4 00 MOV AH,0
0005 CD 1A INT 1Ah ; Вызов BIOS для получения даты и времени
0007 C4 5E 06 LES BX,[BP].A ; Загрузка адреса поля параметров
000A 26: 89 17 MOV ES:[BX],DX ; Сохранение младшей части времени
000D 26: 89 4F 02 MOV ES:[BX+2],CX ; Сохранение старшей части времени
0011 5D POP BP
0012 CA 0004 RET 4 ; Возврат с удалением параметров из стека
0015 TIMER ENDP
0015 CODE ENDS
END
Фиг. 10.12 Ассемблерная процедура для программы на Фортране
На Фиг. 10.12 представлена подпрограмма на языке ассемблера -
процедура TIMER. В этой несложной программе для считывания
текущего времени и сохранения полученного значения в двойном слове
используется обращение к BIOS. Здесь нам необходимо рассмотреть
способ передачи параметров из программы на Фортране в подпрограмму
на языке ассемблера.
На Фиг.10.13 показано содержимое стека в начальный момент
выполнения подпрограммы на языке ассемблера. Точно так же, как
интерпретатор Бейсика, программа на Фортране помещает адрес
параметра в стек. Однако компиляторы Фортрана и Паскаля передают
указатель длиной в два слова, а не одно только смещение параметра.
Это означает, что программа на языке ассемблера, прежде чем
получить доступ к параметру, должна установить как сегментный
регистр, так и адрес смещения. Если бы параметров было более
одного, то программа на Фортране перед вызовом поместила бы в стек
значения адресов и остальных параметров.
ГДДДДДДДДДДДДґ
SPДДДД>і Смещение і
і возврата і
ГДДДДДДДДДДДДґ
і Сегмент і
і возврата і
ГДДДДДДДДДДДДґ
і Смещение і
і аргумента і
ГДДДДДДДДДДДДґ
і Сегмент і
і аргумента і
ГДДДДДДДДДДДДґ
Фиг. 10.13 Стек для вызова процедуры в Фортране
Подпрограмма TIMER на Фиг. 10.12 адресует стек, помещая в него
регистр BP и устанавливая его на вершину стека.Структура FRAME
помогает идентифицировать разные значения в стеке после того как
программа сохранит в нем значение BP. Команда LES BX,[BP]+A
помещает адрес параметра в пару регистров ES:BX. Используя этот
адрес, программа помещает четырехбайтовое значение текущего времени
в четырехбайтовую целую переменную.
Заметим, что процедура TIMER извлекает адрес параметра из стека
при выполнении команды возврата точно так же, как это делалось в
программах на языке Бейсик. Заметим также, что в этой ассемблерной
программе для идентификации имени TIMER используется оператор
PUBLIC. Делается это для того, чтобы редактор связей мог найти
подпрограмму и правильно связать ее с программой на Фортране. Для
интерпретатора Бейсика такой необходимости не было, поскольку
программа на Бейсике не редактировалась совместно с программой на
языке ассемблера.
Процедура Бэйсика BLOAD
Процедура Бэйсика BLOAD
При первом способе подпрограмма на машинном языке добавляется к
программе для интерпретатора Бэйсика. Пожалуй, использование
интерпретатора Бейсика является наиболее распространенным методом
написания программ для персональной ЭВМ. Подпрограмма на языке
ассемблера, которую предполагается включить в разрабатываемую
программу, довольно велика - более 100 байт. Ассемблерную процедуру
такой длины трудно вставить в текст программы на языке Бейсик,
способ, позволяющий сделать это будет рассмотрен в следующем
примере.
Функция, которую мы добавляем к программе на языке Бейсик
позволяет выводить на принтер графические изображения. IBM PC
снабжена графическими средствами. Графические команды позволяют
программно управлять отдельными точками, выводимыми на принтер, во
многом подобно тому, как в графическом режиме адаптер цветного
дисплея дает программисту возможность управлять отдельными точками
растра. На Фиг. 10.4 представлены графические команды, которые
потребуются в рассматриваемом примере. Практически, графические
функции реализуются на принтере через управляющие последователь-
ности символов. Вместо символа в коде ASCII программа выдает на
принтер служебный символ (27 в коде ASCII). Следующие за ним
символы задают уже не символы для вывода на печать, а определенные
действия принтера. Как видно из Фиг. 10.4, существуют команды для
вывода на принтер изображения точки, в результате выполнения
которых печатается определенная точка изображения.
Команда Действие
---------------------------------------------------------
ESC + "3" + n Установка промежутка между
строками n/216
Esc + "K" +n1+n2+v1...vk Печатать образы точек v1...vk
(k = n1 + 256*n2) как 480 точек поперек страницы
---------------------------------------------------------
Фиг.10.4 Графические команды для принтера
Приведенная на Фиг. 10.5 программа использует указанные команды
для вывода на принтер образа экрана графического дисплея размером
320*200 точек. Каждая точка растра передается на принтер. Если
точка на экране имеет цвет фона, то соответствующая точка на печать
не выдается. Если точка окрашена в один из трех основных цветов, то
программа выводит на печать черную точку. Эта программа не
масштабирует изображение, поэтому окружность на экране может
отобразиться в эллипс на принтере. Между тремя основными цветами не
проводится различий. Цветное изображение превращается в
черно-белое.
Подпрограмма PRINT_SCREEN является процедурой типа FAR. Вызов
ее из языка Бейсик является вызовом типа FAR, поэтому и возврат в
программу должен быть соответствующего типа. Последовательность ESC
+ "3" + 24 устанавливает такой интервал между строками печати, что
один ряд точек вплотную примыкает к другому. В печатающей головке
имеется восемь иголок, расстояние между которыми равно 1/72 дюйма.
Если сделать интервал между строками равным 8/72 дюйма (или 24/216
дюйма), то ряды точек соединятся. Приведенный фрагмент программы
показывает способ пересылки на устройство печати последовательности
служебных символов. Управляющая последовательность символов и чисел
пересылается на принтер как обычные символы. Остальное обеспечивает
устройство печати.
При каждом проходе печатающей головки на бумаге остается по
восемь рядов точек (по одному на каждую иголку печатающей головки)
в каждой из 320 колонок. От метки NEXT_ROW в программе
последовательность ESC + "K" + 64 + 1 пересылается на принтер. Это
означает, что последующие 320 байт (64 + 1*256) являются образами
точек для получения графического изображения на принтере. В
графическом режиме "K" на принтере можно получить изображение
шириной до 480 точек.
Для считывания точек с дисплея программа использует видео
функцию BIOS . Эта функция считывает восемь рядов точек текущнго
столбца и собирает их в один байт: "1" означает, что точка имеет
основной цвет и должна появиться на бумаге. Цикл продолжается через
метку NEXT_COLUMN - до тех пор, пока все 320 столбцов (что
соответствует 320 байтам) не будут переданы на принтер . После
перехода принтера на новую строку при помощи служебных символов
"возврат каретки" и "перевод строки" (13 и 10 в коде ASCII),
программа пересылает следующую группу из восьми рядов. За 25
проходов печатающей головки выводятся все 200 рядов. Возврат в
интерпретатор Бейсика производится при помощи возврата типа FAR.
В рассматриваемой программе было бы удобно использовать
процедуру PRINT. Эта процедура выдает один байт на принтер при
помощи функции печати BIOS. Функция помещает необходиые для базовой
системы ввода-вывода установки регистров в определенное место. Если
не использовать указанную функцию, то программа сама должна была бы
устанавливать регистры AH и DX равными нулю перед каждым вызовом
процедуры PRINT.
A
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:57
Фиг. 10.5 Печать графической копии дисплея Page 1-1
PAGE ,132
TITLE Фиг. 10.5 Печать графической копии дисплея
= 001B ESC EQU 27 ; Символ Escape
0000 CODE SEGMENT
ASSUME CS:CODE
0000 PRINT_SCREEN PROC FAR
0000 B0 1B MOV AL, ESC ; Установка перевода строки на 1/8 дюйма
0002 E8 0060 R CALL PRINT
0005 B0 33 MOV AL, '3'
0007 E8 0060 R CALL PRINT
000A B0 18 MOV AL, 24 ; 1/8 = 24/216 дюйма
000C E8 0060 R CALL PRINT
000F BA 0000 MOV DX, 0 ; Номер строки
0012 NEXT_ROW:
0012 B0 1B MOV AL, ESC
0014 E8 0060 R CALL PRINT
0017 B0 4B MOV AL, 'K'
0019 E8 0060 R CALL PRINT
001C B0 40 MOV AL, 320-256
001E E8 0060 R CALL PRINT
0021 B0 01 MOV AL, 1
0023 E8 0060 R CALL PRINT
0026 B9 0000 MOV CX, 0 ; Номер столбца
0029 NEXT_COLUMN:
0029 52 PUSH DX ; Сохранение номера строки
002A BB 0008 MOV BX, 8 ; Число одновлеменно обрабатываемых точек
002D NEXT_DOT:
002D D0 E7 SHL BH, 1 ; Освобождение младшего разряда
002F B4 0D MOV AH, 13 ; Чтение цвета точки из памяти дисплея
0031 CD 10 INT 10h
0033 0A C0 OR AL, AL
0035 74 03 JZ BACKGROUND ; Проверка на цвет фона
0037 80 CF 01 OR BH, 1 ; Не фон, необходимо вывести точку на печать
003A BACKGROUND:
003A 42 INC DX ; Переключение на следующую строку
003B FE CB DEC BL ; Уменьшение счетчика строк в данном проходе
003D 75 EE JNZ NEXT_DOT
003F 8A C7 MOV AL, BH ; Печать 8-ми точек
0041 E8 0060 R CALL PRINT ; Вывод на печать
0044 5A POP DX ; Восстановление номера строки начала прохода
0045 41 INC CX ; Переключение на следуюий столбец
0046 81 F9 0140 CMP CX, 320 ; Все столбцы выведены?
004A 75 DD JNZ NEXT_COLUMN
004C B0 0D MOV AL, 13 ; Переход на следующую строку на принтере
004E E8 0060 R CALL PRINT
0051 B0 0A MOV AL, 10
0053 E8 0060 R CALL PRINT
0056 83 C2 08 ADD DX, 8 ; Переключение на слдующую группу из 8 строк
0059 81 FA 00C8 CMP DX, 200 ; Все строки выведены?
005D 72 B3 JB NEXT_ROW
005F CB RET ; Возврат в BASIC
Фиг. 10.5 Печать графического экрана (начало)
0060 PRINT_SCREEN ENDP
0060 PRINT PROC NEAR
0060 52 PUSH DX
0061 B4 00 MOV AH, 0 ; Печать символа, находящегося в регистре AL
0063 BA 0000 MOV DX, 0
0066 CD 17 INT 17h
0068 5A POP DX
0069 C3 RET
006A PRINT ENDP
006A CODE ENDS
ENDA
Фиг. 10.5 Печать графического экрана (продолжение)
Как же обратиться к этой процедуре из программы, написанной на
языке Бейсик? В языке Бейсик существуют два способа подключения
подпрограмм. Во время работы интерпретатор Бейсика использует
оставшуюся память системы (до 64 кбайт) в качестве рабочей области.
Если в системе более 96 кбайт памяти, часть памяти будет не
доступна для интерпретатора Бейсика. Лучше всего поместить нашу
процедуру в эту область. Если свободной области памяти нет, то
можно специально выделить некоторый объем памяти из рабочей области
интерпретатора Бейсика для хранения подпрограммы. В данном примере
подпрограмма будет храниться вне рабочей области интерпретатора
Бейсика. В следующем примере будет показано, как включить процедуру
в контролируемую интерпретатором Бейсика область памяти.
На Фиг. 10.6 показана последовательность действий для
подготовки подпрограммы к дальнейшему использованию.
Соответствующая информация приведена в приложении C справочника по
языку Бейсик. Программа (на Фиг. 10.6) предназначена для машины с
оперативной памятью 96 кбайт и более. Программа ассемблируется
обычным образом. При редактировании связей задается опция /H.
Редактор связей создает файл типа .EXE таким образом, что программа
загружается в верхние адреса оперативной памяти, а не с самого
низкого из доступных адресов.
Чтобы подключить процедуру к программе, написанной на языке
Бейсик, нам потребуется программа DEBUG. После загрузки программы
на языке Бейсик во время работы программы DEBUG и уточнения
значений регистров, загружаем процедуру на языке ассемблера.
Приведенный пример реализован на машине с памятью 128 кбайт.
Значение регистра CS, равное 1FF9H, указывает на то, что программа
помещена в 70H байт от конца оперативной памяти. Заметим, что
рассматриваемая программа имеет объем около 6AH байт, так что
редактор связей разместил программу с самого старшего адреса
памяти, допускающего выравнивание по границе параграфа. Следует
также заметить, что эта программа является сегментно -
перемещаемой. Это означает, что ее можно перемещать в памяти,
поскольку первая ее команда имеет смещение 0 относительно текущего
сегмента кода. При переносе этой программы на машину с большим или
меньшим объемом памяти эта особенность оказывается решающей.
A
B>A:MASM FIG10-5,,,;
The IBM Persona Computer MACRO Assembler
Version 1.00 (C)Copyright IBM Corp 1981
Warning Severe
Errors Errors
0 0
B>A:LINK FIG10-5,,,/H;
IBM Personal Computer Linker
Version 1.10 (C)Copyright IBM Corp 1982
Warning: No STACK segment
There was 1 error detected
B>A:DEBUG A:BASIC.COM
-R
AX=0000 BX=0000 CX=2B80 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000
DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI PL NZ NA PO NC
04B5:0100 E91329 JMP 2A16
-NFIG10-5.EXE
-L
-R
AX=0000 BX=0000 CX=006A DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=04B5 ES=04B5 SS=1FF9 CS=1FF9 IP=0000 NV UP DI PL NZ NA PO NC
1FF9:0000 B01B MOV AL,1B
-RSS
SS 1FF9
:4B5
-RCS
CS 1FF9
:4B5
-RIP
IP 0000
:100
-G
---- В интерпретаторе Бэйсика введите команды
DEF SEG = &H1FF9
BSAVE "FIG10-5",0,&H70
Фиг. 10.6 (а) Создание подпрограммы для Бэйсика
B>A:MASM FIG10-5,,,;
The IBM Persona Computer MACRO Assembler
Version 1.00 (C)Copyright IBM Corp 1981
Warning Severe
Errors Errors
0 0
B>A:LINK FIG10-5,,,/H;
IBM Personal Computer Linker
Version 1.10 (C)Copyright IBM Corp 1982
Warning: No STACK segment
There was 1 error detected
B>A:DEBUG A:BASIC.COM /M:&H8000
-R
AX=0000 BX=0000 CX=2B80 DX=0000 SP=FFF0 BP=0000 SI=0000 DI=0000
DS=04B5 ES=04B5 SS=04B5 CS=04B5 IP=0100 NV UP DI PL NZ NA PO NC
04B5:0100 E91329 JMP 2A16
-NFIG10-5.EXE
-L
-R
AX=0000 BX=0000 CX=006A DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=04B5 ES=04B5 SS=0FF9 CS=0FF9 IP=0000 NV UP DI PL NZ NA PO NC
0FF9:0000 B01B MOV AL,1B
-RSS
SS 0FF9
:4B5
-RCS
CS 1FF9
:4B5
-RIP
IP 0000
:100
-G
---- В интерпретаторе Бэйсика; введите команды
DEF SEG = &H0FF9
BSAVE "FIG10-5",0,&H70
(b)
A
Фиг. 10.6 (а) Создание подпрограммы для Бэйсика; (b) Создание
подпрограммы для Бэйсика на машине с 64K
Теперь мы имеем дело с интерпретатором Бейсика. Нам необходимо
восстановить содержимое регистров таким, каким оно было после
загрузки Бейсика. После того, как интерпретатор Бейсика запущен,
для локализации подпрограммы используется команда DEF SEG. Команда
BSAVE помещает обратно на дискету объектный код, готовый к новой
загрузке из Бейсика при помощи команды BLOAD.
В части (b) Фиг. 10.6, повторяются действия из части (а), но
для машины с объемом памяти 64 кбайт. Различие здесь состоит в том,
что интерпретатор Бейсика не может использовать всю память под
рабочую область. Опция /M в командной строке Бейсика ограничивает
рабочую область Бейсика и оставляет место для подпрограммы.
Аналогичная команда потребуется и при запуске программы.
Программой DEBUG можно воспользоваться для вашей ассемблерной
процедуры и при работе интерпретатора Бейсика . Включите в команду
G при запуске программы Бейсика установку контрольной точки в
подпрограмме. По достижении контрольной точки работа
интерпретатора Бейсика приостанавливается, и производится обычная
для программы DEBUG выдача содержимого регистров.
Теперь все готово для выполнения подпрограммы на языке
ассемблера как части программы на языке Бейсик. Снова предположим,
что система располагает 128 кбайтами памяти, и выполним следующую
последовательность действий:
введите "BASIC" на уровне команд DOS;
введите "SCREEN1" во время работы интерпретатора Бейсика.
Фиг. 10.7 Печатная копия экрана
Эти действия вводят нас в интерпретатор и переводят режим
изображения на размер 320*200 точек. На Фиг. 10.7. показаны
оставшиеся события этой истории. Команда BLOAD загружает процедуру
в ту же область памяти, из которой она была сохранена. При желании
в команде BLOAD можно задать параметры, обеспечивающие загрузку
программы в другую область памяти. Оператор LINE дает процедуре
графической распечатки экрана информацию для печати. Для вызова
этой процедуры мы используем команду DEF SEG, чтобы установить
значение регистра CS на процедуру. Значение регистра IP для
процедуры помещается в простую переменную. Оператор CALL
осуществляет дальний вызов по заданному адресу. Фиг. 10.7
представляет собой копию реальной распечатки, полученной при
выполнении приведенной программы.
Если в системе 64Кбайт памяти, то программа будет отлична в
двух аспектах. Для вызова интерпретатора Бейсика используется
команда BASIC/M:&H8000, резервирующая верхнюю часть памяти для
нашей ассемблерной процедуры, а команда DEF SEG задает адрес
подпрограммы, как это было сделано в части (b) Фиг. 10.6.
Процедуры на языке Ассемблера
Процедуры на языке Ассемблера
Помимо постоянно находящихся в памяти драйверов и автономных
программ, язык ассемблера используется и для подпрограмм в больших
программах, написанных, как правило, на языке высокого уровня.
Такие языки высокого уровня как Бейсик или Паскаль позволяют быстро
и ясно писать большие программы. Однако эти языки позволяют делать
не все, что может понадобиться. Сказанное особенно справедливо для
персональных ЭВМ, поскольку хорошая прикладная программа здесь
требует использования всех возможностей машины. Достигнуть этого на
языке высокого уровня удается не всегда. Либо язык высокого уровня
не позволяет реализовать требуемую функцию (например, вызов
процедуры BIOS), либо накладные расходы языка делают прикладную
программу слишком медленной (например, за счет операторов Бейсика
PEEK и POKE для считывания конкретных ячеек памяти).
К счастью, в языках высокого уровня имеется механизм,
позволяющий вызывать подпрограммы, написанные на языке ассембоера.
Требуемую функцию можно выполнить быстро и эффективно на машинном
языке, а затем вернуться к языку высокого уровня для выполнения
остальной работы. В этом разделе будут приведены примеры,
иллюстрирующие два способа включения процедуры ассемблера в
программу, написанную на языке высокого уровня.
Расширение системы BIOS
Расширение системы BIOS
Некоторех программы - драйверы устройств мы хотим загружать в
память так, чтобы они становились постоянным расширением системы.
Хорошим примером программы такого типа является BIOS. Поместив
драйверы устройств в ПЗУ, фирама IBM сделала их постоянной частью
системы. Эти процедуры доступны для всех программ, запускаемых на
IBM PC, поскольку BIOS всегда находится в памяти.
Однако большинство из нас не может позволить себе роскошь
помещать свои программы в ПЗУ. Если программа не предназначена для
широкого тиражирования, то нет никакого смысла тратить тысячи
долларов на производство единичного модуля ПЗУ. Однако, существует
более дешевая альтернатива. Разработаны ПЗУ специальных типов,
которые пользователь может программировать самостоятельно.
Некоторые компании поставляют программаторы ППЗУ (программируемые
ПЗУ - PROM в англоязычной аббревиатуре), которые позволяют вам
помещать свои программы в постоянную память. Специальное
техническое обеспечение для программирования ППЗУ стоит несколько
сотен долларов, а отдельные модули ППЗУ - от 10 до 50 долларов.
Для некоторых программ вам может понадобиться этот способ. В
IBM PC имеется свободное гнездо для подключения модуля ПЗУ. К этому
гнезду можно подключить стандартное ПЗУ на 8 кбайт или ППЗУ. Это
сделает программу постоянной частью ЭВМ. Мы не будем более подробно
обсуждать установку ПЗУ и ППЗУ. Для этого требуется специальное
техническое обеспечение, и эта процедура различна для разных
пользователей.
Вместо этого будут рассмотрены способы загрузки программы в
оперативную память таким образом, чтобы она стала постоянной частью
системы. Программа будет находиться в памяти вплоть до выключения
машины. Преимуществом в данном случае является то, что такая
функция встроена в ЭВМ не навсегда. Ее можно изменять не разбирая
машину. Если вы обнаружите в программе какие-либо недостатки, то ее
можно модифицировать без повторения всех манипуляций с ПЗУ.
Расширение системы BIOS
Расширение системы BIOS
Некоторех программы - драйверы устройств мы хотим загружать в
память так, чтобы они становились постоянным расширением системы.
Хорошим примером программы такого типа является BIOS. Поместив
драйверы устройств в ПЗУ, фирама IBM сделала их постоянной частью
системы. Эти процедуры доступны для всех программ, запускаемых на
IBM PC, поскольку BIOS всегда находится в памяти.
Однако большинство из нас не может позволить себе роскошь
помещать свои программы в ПЗУ. Если программа не предназначена для
широкого тиражирования, то нет никакого смысла тратить тысячи
долларов на производство единичного модуля ПЗУ. Однако, существует
более дешевая альтернатива. Разработаны ПЗУ специальных типов,
которые пользователь может программировать самостоятельно.
Некоторые компании поставляют программаторы ППЗУ (программируемые
ПЗУ - PROM в англоязычной аббревиатуре), которые позволяют вам
помещать свои программы в постоянную память. Специальное
техническое обеспечение для программирования ППЗУ стоит несколько
сотен долларов, а отдельные модули ППЗУ - от 10 до 50 долларов.
Для некоторых программ вам может понадобиться этот способ. В
IBM PC имеется свободное гнездо для подключения модуля ПЗУ. К этому
гнезду можно подключить стандартное ПЗУ на 8 кбайт или ППЗУ. Это
сделает программу постоянной частью ЭВМ. Мы не будем более подробно
обсуждать установку ПЗУ и ППЗУ. Для этого требуется специальное
техническое обеспечение, и эта процедура различна для разных
пользователей.
Вместо этого будут рассмотрены способы загрузки программы в
оперативную память таким образом, чтобы она стала постоянной частью
системы. Программа будет находиться в памяти вплоть до выключения
машины. Преимуществом в данном случае является то, что такая
функция встроена в ЭВМ не навсегда. Ее можно изменять не разбирая
машину. Если вы обнаружите в программе какие-либо недостатки, то ее
можно модифицировать без повторения всех манипуляций с ПЗУ.
Возврат программы в DOS с сохранением ее резидентности
Возврат программы в DOS с сохранением ее резидентности
Первый способ написания и загрузки постоянной функции в DOS состоит
в том, чтобы, возвращая управление DOS, программа оставалась в
памяти резидентной. Такую функцию существляет прерывание INT 27H.
Обычно для выхода в DOS используется прерывание INT 20H, либо
программа производит переход по адресу 0 программного префикса, как
мы делали в программах типа .EXE. В результате управление
возвращается DOS. Операционная система освобождает память,
предоставленную этой программе. Следующую программу, которая
загружается после прерывания INT 20H, DOS помещает в ту же область
памяти, которая использовалась для предыдущей.
Выход в DOS через прерывание INT 27H отличается от
рассмотренного. Управление возвращается в DOS точно так же, как и в
случае прерывания INT 20H, но часть памяти, занимаемая программой,
не возвращается для дальнейшего использования. В регистре DX
указывает на адрес первой свободной ячейки после той области
памяти, котрую вы хотите зарезервировать. DOS резервирует эту
область памяти, как часть системы. Это означает, что ваша программа
становится частью DOS. Такую программу можно удалить из памяти
только перезагрузив DOS и начав все сначала.
Если выход в PC DOS осуществляется при помощи прерывания INT
27H, то в регистре CS должен находиться адрес программного
префикса. Легче всего это сделать, если писать использующую INT 21H
программу как .COM программу. Написать программу типа .EXE,
оставляющую при выходе содержимое регистров CS и DX корректным,
довольно трудно. Поскольку создание программ типа .COM было
рассмотрено в гл.5, будем считать, что все наши остающиеся
резидентными программы имеют тип .COM.
Рассматриваемый для прерывания DOS INT 27H пример довольно
сложен. Он иллюстрирует не только использование INT 27H, но и
способы замены существующей BIOS другой версией. В этом примере мы
даже применим несколько трюков с таймером для увеличения скорости
обработки.
Пример представлен на Фиг. 10.1. Приведенная здесь программа
предназначена для обслуживания буфера печати. Обычно при выдаче на
печать символа программа обращается к прерыванию INT 17H - драйверу
печати BIOS. Эта функция выдает символ на принтер после
проверки ошибок и ожидания готовности принтера. Как правило, при
этом обеспечивается достаточная производительность. Но допустим,
что вы пишете несколько программ и хотите вывести их на принтер.
Если вы попытаетесь сделать это, то не сможете обратиться к системе
до тех пор, пока принтер не закончит работу. Чтобы
продолжить редактирование или ассемблирование другой части
программы, вам придется ждать завершения печати.
A
Microsoft (R) Macro Assembler Version 5.00 4/2/89 16:06:27
Фиг. 10.1 Буфер для печати Page 1-1
PAGE ,132
TITLE Фиг. 10.1 Буфер для печати
0000 ABS0 SEGMENT AT 0
0020 ORG 4*8H
0020 ???????? TIMER_INT DD ? ; Аппратное прерывание от таймера
005C ORG 4*17H
005C ???????? PRINTER_INT DD ? ; Прерывание к BIOS для печати
0408 ORG 408H
0408 ???? PRINTER_BASE DW ? ; Базовый адрес адаптера принтера
040A ABS0 ENDS
0000 CODE SEGMENT
0100 ORG 100H
ASSUME CS:CODE,DS:CODE,ES:CODE
0100 EB 09 90 JMP START
0103 ???????? PRINT_VECTOR DD ? ; Место для хранения исходного вектора 17h
0107 ???????? TIMER_VECTOR DD ? ; Место для хранения исходного вектора 9h
010B START:
010B 2B C0 SUB AX,AX ; Установка регистра ES на сегмент ABS0
010D 8E C0 MOV ES,AX
ASSUME ES:ABS0
010F 26: A1 005C R MOV AX,WORD PTR PRINTER_INT
0113 26: 8B 1E 005E R MOV BX,WORD PTR PRINTER_INT+2
0118 26: 8B 0E 0020 R MOV CX,WORD PTR TIMER_INT
011D 26: 8B 16 0022 R MOV DX,WORD PTR TIMER_INT+2
0122 A3 0103 R MOV WORD PTR PRINT_VECTOR,AX
0125 89 1E 0105 R MOV WORD PTR PRINT_VECTOR+2,BX
0129 89 0E 0107 R MOV WORD PTR TIMER_VECTOR,CX
012D 89 16 0109 R MOV WORD PTR TIMER_VECTOR+2,DX
;----- Во время занесения векторов прерываний прерывания запрещены
0131 FA CLI
Фиг. 10.1 Буфер печати (начало)
0132 26: C7 06 005C R 0162 MOV WORD PTR PRINTER_INT,offset PRINT_HANDLER
R
0139 26: 8C 0E 005E R MOV WORD PTR PRINTER_INT+2,CS
013E 26: C7 06 0020 R 0196 MOV WORD PTR TIMER_INT,offset TIMER_HANDLER
R
0145 26: 8C 0E 0022 R MOV WORD PTR TIMER_INT+2,CS
014A B0 36 MOV AL,00110110b
014C E6 43 OUT 43H,AL
014E B0 00 MOV AL,0 ; Увеличение скорости работы таймера в 256 раз
0150 E6 40 OUT 40H,AL
0152 B0 01 MOV AL,1
0154 E6 40 OUT 40H,AL
0156 FB STI
0157 8D 16 28FE R LEA DX,BUFFER_END ; Занесение адреса конца программы
015B CD 27 INT 27H ; Выход с сохранением программы в памяти
015D 00 TIMER_COUNT DB 0
015E 01EE R BUFFER_HEAD DW BUFFER_START
0160 01EE R BUFFER_TAIL DW BUFFER_START
;----- Эта подпрограмма управляет вызовом прерывания 17h
0162 PRINT_HANDLER PROC FAR
ASSUME CS:CODE,DS:nothing,ES:nothing
0162 0A E4 OR AH,AH
0164 74 05 JZ BUFFER_CHARACTER ; Проверка на функцию вывода символа
0166 2E: FF 2E 0103 R JMP PRINT_VECTOR ; Переход на стандартный обработчик
; прерывания 17h
016B BUFFER_CHARACTER:
016B FB STI
016C 53 PUSH BX
016D 51 PUSH CX
016E 56 PUSH SI
016F 2B C9 SUB CX,CX ; Счетчик отсчетов таймера
0171 PRINT_LOOP:
0171 2E: 8B 1E 0160 R MOV BX,BUFFER_TAIL ; Выборка адреса конца буфера
0176 8B F3 MOV SI,BX
0178 E8 01E2 R CALL ADVANCE_POINTER ; Перемещение указателя на следующий байт
017B 2E: 3B 1E 015E R CMP BX,BUFFER_HEAD ; Проверка на наличие места в буфере
0180 74 0E JE BUFFER_FULL ; Нет места,ожидается пока оно появится
0182 2E: 88 04 MOV CS:[SI],AL ; Вывод символа в буфер
0185 2E: 89 1E 0160 R MOV BUFFER_TAIL,BX ; Занесение нового адреса конца буфера
018A B4 00 MOV AH,0 ; Код возврата из прерывания 17h
018C PRINT_RETURN:
018C 5E POP SI
018D 59 POP CX
018E 5B POP BX
018F CF IRET
0190 BUFFER_FULL:
0190 E2 DF LOOP PRINT_LOOP ; Повторить цикл проверки занятости буфера
0192 B4 01 MOV AH,1 ; Буфер занят слишком долго,ошибка
0194 EB F6 JMP PRINT_RETURN
0196 PRINT_HANDLER ENDP
Фиг. 10.1 Буфер печати (продолжение)
;----- Эта программа вызывает 4660 раз в секунду
0196 TIMER_HANDLER PROC FAR
ASSUME CS:CODE,DS:nothing,ES:nothing
0196 50 PUSH AX
0197 53 PUSH BX
0198 2E: 8B 1E 015E R MOV BX,BUFFER_HEAD
019D 2E: 3B 1E 0160 R CMP BX,BUFFER_TAIL ; Есть ли что-нибудь в буфере?
01A2 75 14 JNZ TEST_READY ; Переход,если буфер не пуст
;----- Эта подпрограмма управляет таймером в скоростном режиме
01A4 TIMER_RETURN:
01A4 5B POP BX
01A5 2E: FE 06 015D R INC TIMER_COUNT ; Увеличение счетчика делителя таймера
01AA 75 06 JNZ SKIP_NORMAL
01AC 58 POP AX ; Это выполняется один раз на 256 прерываний
01AD 2E: FF 2E 0107 R JMP TIMER_VECTOR ; Переход на стандартную программу обработки
; прерывания от таймера
01B2 SKIP_NORMAL:
01B2 B0 20 MOV AL,20H
01B4 E6 20 OUT 20H,AL ; Конец прерывания
01B6 58 POP AX
01B7 CF IRET
;----- Символ в буфере,производится попытка напечатать его
01B8 TEST_READY:
01B8 52 PUSH DX
01B9 1E PUSH DS
01BA 2B D2 SUB DX,DX
01BC 8E DA MOV DS,DX ; Установка регистра DS на сегмент ABS0
ASSUME DS:ABS0
01BE 8B 16 0408 R MOV DX,PRINTER_BASE
01C2 42 INC DX ; Установка на порт состояния
01C3 EC IN AL,DX
01C4 A8 80 TEST AL,80H ; Проверка готовности принтера
01C6 74 16 JZ NO_PRINT
01C8 4A DEC DX ; Установка на порт данных
01C9 2E: 8A 07 MOV AL,CS:[BX] ; Выбрка выводимого символа
01CC E8 01E2 R CALL ADVANCE_POINTER
01CF 2E: 89 1E 015E R MOV BUFFER_HEAD,BX
01D4 EE OUT DX,AL ; Вывод символа в порт принтера
01D5 83 C2 02 ADD DX,2 ; Установка на порт управления
01D8 B0 0D MOV AL,0DH
01DA EE OUT DX,AL ; Передача символа из порта в принтер
01DB B0 0C MOV AL,0CH
01DD EE OUT DX,AL
01DE NO_PRINT:
01DE 1F POP DS
01DF 5A POP DX
01E0 EB C2 JMP TIMER_RETURN ; Возврат через подпрограмму управления
01E2 TIMER_HANDLER ENDP ; таймером
01E2 ADVANCE_POINTER PROC NEAR
01E2 43 INC BX ; Сдвиг указателя
Фиг. 10.1 Буфер печати (продолжение)
01E3 81 FB 28FE R CMP BX,offset BUFFER_END
01E7 75 04 JNE ADVANCE_RETURN ; Проверка на конец циклического буфера
01E9 8D 1E 01EE R LEA BX,BUFFER_START ; Установка указателя на начало буфера
01ED ADVANCE_RETURN:
01ED C3 RET
01EE ADVANCE_POINTER ENDP
01EE BUFFER_START LABEL BYTE
01EE 2710[ DB 10000 DUP (?)
??
]
28FE BUFFER_END LABEL BYTE
28FE CODE ENDS
END
Фиг. 10.1 Буфер печати (продолжение)
Приведенная в примере программа может облегчить решение задачи.
Конечно, это не обойдется вам даром. Программа отводит под буфер
печати некоторую область памяти, котрая будет постоянно за ним
закреплена. DOS изымает эту область из общего объема памяти,
предоставляемой пользователю. Например, если в системе 96K байт
памяти, а 10 кбайт отводится под буфер печати, то пользоваться
Макроассемблером уже не удастся. Для макроассемблера требуется 96
кбайт, а после создания буфера печати останется лишь 86 кбайт.
Поэтому, прежде чем организовать буферизацию печати, убедитесь, что
в системе останется еще достаточный объем памяти.
Буферизация печати осуществляется примерно так. Стандартная
команда PRINT (INT 17H) заменяется процедурой, которая помещает
символы в буфер вместо того, чтобы посылать их на принтер. Эта
часть программы и называется буферизацией печати. Отдельная часть
программы, называемая выводом на печать, извлекает символы из
буфера печати и пересылает их на принтер.
Основным моментом в данном примере является замена прерывания
INT 17H базовой системы ввода-вывода. Почти все прикладные
программы для вывода на печать используют именно это прерывание, а
это означает, что теперь все обычные операции печати будут
приводить к пересылке символов в подпрограмму буферизации печати, а
не на принтер. В частности, в нашем примере, мы можем
листинг ассемблирования вывести на принтер, нажав клавиши
Ctrl-PrtSc, служащие для пересылки символов с экрана на печать.
Когда мы выводим листинг ассемблирования с программой
буферизации печати в памяти, символы поступают в буфер в памяти, а
не на принтер. Буферизация очень незначительно
увеличивает время просмотра. Когда файл выведен на экран (и в буфер
печати), управление возвращается DOS. Вы можете прекратить
пересылку символов на принтер, снова нажав клавиши Ctrl-PrtSc.
Листинговый файл находится в буфере, и DOS готова продолжить
выполнение других заданий, например, редактирование или
ассемблирование.
Затем начинает выполняться вторая часть программы. Эта
процедура извлекает символы из буфера и пересылает их на принтер.
Она управляется прерыванием от таймера. При каждом прерывании от
таймера процедура вывода на печать также получает управление. Если
в буфере имеется символ, и если устройство печати находится в
состоянии "готово", то подпрограмма пересылает этот символ на
принтер. Таким образом, символы извлекаются из буфера и
пересылаются на принтер со скоростью работы этого устройства.
Поскольку программа вывода на печать работает в фоновом режиме,
одновременно могут выполняться другие задания, например,
редактирование или ассемблирование.
Обратимся к программе, представленной на Фиг. 10.1, и
рассмотрим, как взаимодействуют ее компоненты. Во-первых, в ней
описан сегмент ABS0, содержащий вектор прерываний, с которым
программа имеет дело. Приведенная в примере программа заменяет как
прерывание вывода на печать INT 17H, так и прерывание от таймера
INT 8. Заметим также, что в сегменте ABS0 определяется адрес
PRINTER_BASE. В этой ячейке находится базовый адрес для устройства
печати 0. В данном примере предполагается, что все операции печати
производятся на системном устройстве печати.
Сегмент CODE - это та секция программы, которая остается
резидентной. При помощи команды ORG 100H мы составили эту программу
как файл типа .COM. Это означает, что для создания из выходного
файла редактора связей файла типа .COM, необходимо выполнить
описанную в гл.5 последовательность действий. Для хранения исходных
значений вектора печати и вектора таймера в программе используются
области памяти PRINT_VECTOR и TIMER_VECTOR. Хотя программа заменяет
значения этих векторов, при выводе на печать в ней должны быть
известны их исходные значения.
Первая часть сегмента CODE, начиная с метки START, является
инициализирующей частью программы. В ней считываются исходные
значения векторов прерываний и сохраняются в области данных
сегмента CODE. В процедуре инициализации векторы прерываний в
нижних адресах памяти заменяются новыми, используемыми в процедурах
буферизации и вывода на печать. Обратите внимание на команду CLI,
которая блокирует прерывания перед выполнением этой операции.
Поскольку программа изменяет прерывание таймера, она не может
допустить обработку его шага в этот момент времени. Если бы
прерывание от таймера произошло в тот момент, когда программа
изменила только одно из двух слов вектора прерываний от таймера, то
микропроцессор продолжил бы выполнение с непредсказуемого адреса
памяти. Разумнее запретить прерывания, чем допустить возможность
перехода по неизвестному адресу.
Прежде чем разблокировать прерывания, программа изменяет
текущее значение счетчика таймера. Обычно прерывания от таймера
происходят примерно 18 раз в секунду. Устройство печати может
печатать по 80 символов в секунду. Если бы процедура вывода на
печать выдавала по одному символу при каждом прерывании от таймера,
то максимальная скорость печати составила бы 18 символов в секунду.
Если ускорить таймер, прерывания от таймера будут происходить чаще.
Это позволит программе выдавать на печать все 80 символов в
секунду. В приведенном примере в таймер загружается значение
счетчика 256, оно в 256 раз меньше стандартного значения.
Компенсируется это увеличение скорости при помощи процедуры
TIMER_HANDLER.
Процедура инициализации возвращает управление в DOS при помощи
прерывания INT 27H. Перед выходом из процедуры в регистр DX
загружается указатель на байт, сразу следующий за последнм байтом
всей программы. Заметим, что все процедуры и буфер печати мы
расположили в пределах этой области памяти. В соответствии с
правилами действия прерывания INT 27H DOS не затронет эту
область.
Приведенная программа зря расходует часть памяти.
Инициализирующая ее часть выполняется только один раз, поэтому нет
смысла оставлять ее в памяти. Можно оптимизировать программу
поместив часть кода от команды START до INT 27H после метки
BUFFER_END. В этом случае при прерывании INT 27H инициализирующая
часть программы оказалась бы за пределами защищаемой области
памяти, и следующая загружаемая DOS программа перекрыла бы
процедуру инициализации. Экономия около 90 байт из более чем 10000
байт в нашем примере не впечетляет, но она вполне доступна в случае
необходимости.
Далее следует процедура PRINT_HANDLER. Эта подпрограмма
вместо базовой системы ввода-вывода осуществляет управление
принтером при каждом обращении программ к прерыванию INT 17H для
вывода данных на печать. Первые три команды управляют перехватом
управления у BIOS. Наша процедура работает только тогда, когда
должен быть напечатан символ (AH = 0). При любом другом коде
функции работу выполняет BIOS, поэтому программа производит
проверку, не равен ли регистр AH нулю. Если нет, то производится
косвенный переход с использованием сохраненного значения исходного
вектора печати. В результате управление передается процедуре
входящей в BIOS, которая выполняет требуемую функцию. Сказанное
означает, что в нашей процедуре обработки прерывания достаточно
написать только поддержку сделанных изменений.
Относительно рассмотренного способа управления печатью следует
сделать два замечания. Во-первых, передача дальше всех функций
печати кроме случая AH = 0 - не блестящая идея. Если какая-либо
программа инициализирует принтер (AH = 2) во время работы механизма
буферизации, то BIOS берет управление на себя и выдает на принтер
команду RESET. Эта команда обрывает ту строку, которая в это время
выводится на печать, что в большинстве случаев приводит к потере
одного или нескольких символов. Если вы хотите сделать эту
программу более защищенной от ошибок, то вам придется рассмотреть
вопрос об управлении всеми функциями печати.
Второе, на что следует обратить внимание - это использование
сохраненного вектора прерываний печати. Можно было бы обратиться к
листингу BIOS, приведенному в техническом справочнике, и найти
начальный адрес процедуры печати. Затем включить этот адрес
непосредственно в код программы так же, как это делается для других
абсолютных адресов. Однако в результате программа оказалась бы
жестко к этому адресу в системе BIOS. Если фирма IBM изменит
процедуры BIOS и, таким образом, - адрес процедуры печати, то
рассмотренная программа не сможет больше работать. Конечно, если
пишите эту программу для своей собственной машины, а покупать новую
или продавать свою программу не собираетесь, то указанных проблем
не возникнет. Однако в общем случае надо избегать использования
абсолютных адресов, если есть выбор. В приведенном примере
процедура инициализации легко может использовать вектор
прерываний печати для определения адреса процедуры печати
BIOS в ПЗУ.
В оставшейся части процедуры PRINT_HANDLER символ помещается в
буфер печати. Перед тем, как поместить символ программа проверяет,
есть ли в буфере место. Если буфер полон, программа ждет, пока
освободится место. Это ожидание не вызовет проблем, поскольку и
стандартная процедура BIOS ждет, чтобы принтер был готов принять
символ. Из соображений безопасности в регистре CX накапливается
число проходов по ветви "занято". Если это число становится равным
64K, а буфер по-прежнему полон, то это может означать какой-то
сбой. В этом случае процедура PRINT_HANDLER так же, как и BIOS,
выдает сообщение о превышении допустимого времени ожидания.
В приведенном примере процедура печати использует также
внутреннюю процедуру ADVANCE_POINTER. Эта несложная процедура
делает буфер печати циклическим. Если указатель сдвигается за
пределы буфера, подпрограмма переносит его на начало буфера. Она
аналогична процедуре BIOS для буфера клавиатуры. Только в данном
случае в буфер помещается 10000 символов, а не 16.
Интересно рассмотреть работу процедуры TIMER_HANDLER из
приведенного примера. Инициализирующая процедура связывает эту
подпрограмму с аппаратным прерыванием от таймера, поэтому на каждом
цикле таймера она получает управление. Помимо пересылки кодов на
принтер, эта процедура должна обеспечивать, через компенсацию
ускоения таймера, выполнение его обычных функций, таких как
ведение времени дня.
Сначала процедура работы с таймером проверяет, имеются ли
предназначенные для вывода на печать символы. Нет смысла пытаться
переслать символы на принтер, если пересылать нечего. Если в буфере
нет символов, процедура проходит на метку TIMER_RETURN. Этот
фрагмент процедуры обслуживает ускорение таймера.
Метка TIMER_RETURN указывает часть программы, обеспечивающую
нормальное функционирование таймера. При каждом прерывании от
таймера значение байта TIMER_COUNT увеличивается на единицу. Если
этот байт не нулевой, то процедура выходит из прерывания после
выдачи сигнала о завершении прерывания на контроллер прерываний.
Если этот байт равен нулю, то выход из программы осуществляется
посредством косвенного перехода по сохраненному вектору прерывания
от таймера TIMER_VECTOR. При этом управление передается процедуре
BIOS для определения текущего времени и выключения дисковода.
Дублировать эти операции в нашей программе не требуется. Переход в
BIOS происходит только один из 256 раз выполнения подпрограммы
работы с таймером. Но поскольку скорость таймера была увеличена в
256 раз, процедура реакции на прерывание от таймера базовой системы
ввода-вывода по-прежнему будет получать управление 18,2 раза в
секунду. Это означает, что текущее время будет поддерживаться
правильно, и мотор дисковода будет выключен вовремя. Именно поэтому
и было выбрано ускорение таймера в 256 раз, хотя и ускорения в 5
раз было бы достаточно, чтобы обеспечить работу устройства печати с
максимальной скоростью.
Ускорение таймера в 256 раз было выбрано потому, что это было
просто сделать. Однако если брать в расчет производительность, то
лучше было бы ускорить работу таймера в 5 раз, поскольку на
обработку каждого прерывания от таймера тратится по меньшей мере 10
микросекунд, и даже больше, если в буфере печати есть символы.
Время, затраченное на обработку прерываний, идет в ущерб выполнению
системой других заданий, например ассемблирования. При такой
частоте прерываний от таймера, становится заметным замедление
работы. Для оптимизации производительности следует ускорять таймер
менее, чем в 256 раз.
Что же происходит в процедуре работы с таймером, когда в буфере
есть символы, предназначенные для печати? Программа считывает порт
состояния, чтобы определить, готов ли принтер к приему символа.
Поскольку в процедуре используется базовый адрес из области данных
BIOS, то наша подпрограмма будет работать и с автономным адаптером
устройства печати, и с портом адаптера монохромного дисплея. Если
устройство печати не готово, процедура возвращает управление на
метку TIMER_RETURN, где в случае необходимости поддерживаются
стандартные функции таймера. Процедура вывода на печать не ждет,
когда устройство печати освободится, если оно занято. Мы знаем, что
прерывание от таймера очень скоро повторится, тогда мы и повторим
попытку вывода. Ожидание готовности устройства печати здесь
связывало бы бы всю систему. Результат был бы таким же, как и в
случае отсутствия буферизации печати.
Если принтер готов, программа извлекает символ из буфера и
передает его на принтер. И в данном случае программа вновь не
делает всего, что следовало бы. Подпрограмма, входящая в BIOS,
делает проверку на ситуацию ошибки при передаче каждого символа. То
же самое следовало бы делать и в нашей процедуре. Но что же
произойдет в случае сбоя? Если процедура вывода обнаружила ошибку,
то как она сможет сообщить программе, что это произошло во время
печати? В некоторых случаях к этому моменту программа передававшая
даные для печати уже завершила свою работу. Наилучший выход может
состоять в проверке ошибок при каждой пересылке символа на принтер
процедурой работы с таймером. При обнаружении ошибки процедура
PRINT_HANDLER должна выдать сообщение об ошибке, что далее все
программы будут производить вывод на печать через прерывание INT
17H. Возможно, это не идеальный вариант, но, вероятно, лучший.
Прежде чем закончить рассмотрение примера, следует обратить
внимание еще на одну проблему. Существуют и другие процедуры,
изменяющие частоту прерываний от таймера. BASICA - расширенная
версия интерпретатора Бейсика, для ускорения таймера используется
прием, во многом аналогичный приведенному. При вызове программы
BASICA после установки буферизованной печати, процедура
TIMER_HANDLER получает прерывания уже не с той частотой, которая
предполагается. Поскольку процедура TIMER_HANDLER ограничивает
передачу управления прерыванием от таймера процедуре BIOS, текущее
время замедлится в 256 раз. BASICA осуществляет также инициализацию
устройства печати, что, как мы уже видели, мешает выводу на печать.
Это означает, что программа буферизации печати будет работать не
для всех приложений. Однако она иллюстрирует использование
прерывания INT 27H для создания постоянной системной функции.
Приведенный пример иллюстрирует также метод переопределения
векторов BIOS для подцепления новой функции к уже имеющимся
программам.
Вставка короткой программы
Вставка короткой программы
В предыдущем примере рассматривалась довольно большая программа
на языке ассемблера, хранящаяся в собственном объектном файле и
загружаемая в память интерпретатором Бейсика. А как в случае очень
маленькой программы. Представляется, что для такой программы
тратилось бы слишком много усилий на одну только загрузку ее из
собственного файйла. В приложении C справочника по языку Бейсик
приведен способ "упаковки" программы на машинном языке в область
памяти за пределами рабочей области интерпретатора. Приведем пример
применения другого способа.
На Фиг. 10.8 показана программа, написанная на языке
ассемблера, которой мы воспользуемся. Эта программа обращается к
BIOS для сдвига изображения на экране. Рассмотрев параметры,
хранящиеся в регистрах CX и DX, можно увидеть, что сдвигаемое окно
отображает лишь часть экрана. Мы будем исползовать приведенную
программу для разбиения экрана на несколько окон, в каждом из
которых сдвиг может производиться независимо. Поскольку средства
реализации этого в языке Бейсик отсутствуют, понадобится процедура
на языке ассемблера.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:07:03
Фиг. 10.8 Программа прокрутки окон на дисплее Page 1-1
PAGE ,132
TITLE Фиг. 10.8 Программа прокрутки окон на дисплее
0000 CODE SEGMENT
ASSUME CS:CODE
0000 SCROLL PROC FAR
0000 55 PUSH BP
0001 8B EC MOV BP, SP
0003 8B 76 06 MOV SI, [BP+6] ; Загрузка адреса параметра
0006 8B 0C MOV CX, [SI] ; Загрузка параметра
0008 0A C0 OR AL, AL
000A B7 07 MOV BH, 7
000C B8 0601 MOV AX, 601h
000F 75 0C JNZ WINDOW1 ; Определение требуемого окна
0011 B9 0200 MOV CX, 200h ; Окно 1
0014 BA 1010 MOV DX, 1010h
0017 DO_SCROLL:
0017 CD 10 INT 10h
0019 5D POP BP
001A CA 0002 RET 2
001D WINDOW1:
001D B9 0514 MOV CX, 514h ; Окно 2
0020 BA 1224 MOV DX, 1224h
0023 EB F2 JMP DO_SCROLL
0025 SCROLL ENDP
0025 CODE ENDS
END
Фиг. 10.8 Процедура сдвига изображения для Бэйсика
Как можно увидеть на листинге ассемблирования на Фиг. 10.8, для
определения, в каком из двух окон должен производиться сдвиг,
используется входной параметр. Программа, написанная на языке
Бейсик, передает этот параметр в ассемблерную подпрограмму
оператором CALL. На Фиг.10.9(а) показано содержимое стека в момент
вызова в Бэйсике процедуры SCROLL. Оператор CALL помещает в стек
адрес параметра перед выполнением дальнего вызова (FAR CALL)
подпрограммы на машинном языке. Адрес в стеке является смещением
параметра относительно регистра DS. Первые команды процедуры SCROLL
извлекают этот адрес из регистра SI для того, чтобы загрузить
истинное значение в регистр CX. На Фиг.10.9(b) показано содержимое
стека после того, как процедура SCROLL поместила содержимое
регистра BP в стек, а затем переслала содержимое регистра SP в
регистр BP. Обратите внимание, что параметр находится в шести
байтах от вершины стека. Если бы программа на языке Бейсик
передавала более одного параметра, перед вызовом они были бы
аналогичным образом помещены в стек. Забегая вперед, заметим, что
перед возвратом процедура, используя команду RET 2, извлекает
параметры из стека. Интерпретатор Бейсика предполагает, что перед
возвратом подпрограмма удаляет параметры из стека.
ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ
SPДДДДД>і Смещение і SPДДДД>і Старое зна-і
і возврата і і чение BP і<ДДДДBP
ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ
і Сегмент і і Смещение і [BP+2]
і возврата і і возврата і
ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ
і Смещение і і Сегмент і [BP+4]
і аргумента і і возврата і
ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ
і Смещение і [BP+6]
і аргумента і
ГДДДДДДДДДДДДґ
(a) (b)
Фиг. 10.9 Стек при вызове процедуры
Подпрограмма SCROLL в зависимости от значения параметра
обрабатывает одно из двух окон экрана. Если параметр равен нулю,
то изображение в окне, заданном координатами (2, 0) и (16, 16)
сдвигается вверх на одну строку. Если параметр не равен нулю, то
на одну строку вверх сдвигается изображение в окне (5, 20), (18,
36). Перемещается текст только в заданном окне, остальной текст
или данные на экране остаются неподвижными. Реализация такого
оконного режима входит в функцию сдвига BIOS. Для ее использования
требуется лишь вызвать BIOS с правильно заданными параметрами.
На Фиг. 10.10 представлена программа на языке Бейсик,
обращающаяся к процедуре SCROLL. В этом простом примере в каждое
окно записывается строка символов, а затем вызывается процедура
сдвига текста вверх. Эта Бэйсик-программа не более чем
иллюстрирует использование сдвига окон.
Первое, на что следует обратить внимание, это - способ загрузки
программы на машинном языке в систему. Программа содержится в
символьной строке P$. Каждый символ в строке соответствует одному
байту объектного кода из Фиг. 10.8. В программу на Бэйсике эта
программа вводится с клавиатуры по листингу ассемблирования. Это -
одна из причин, по которой применение рассмотренного способа
ограничено лишь короткими программами. При вводе программы таким
способом очень легко сделать ошибки.
A
1 CLS
5 DEFINT A-Z
10 P$=CHR$(&H55)+CHR$(&H8B)+CHR$(&HEC)+CHR$(&H8B)+CHR$(&H76)+CHR$(&H6)
20 P$=P$+CHR$(&H8B)+CHR$(&HC)+CHR$(&HA)+CHR$(&HC9)+CHR$(&HB7)+CHR$(&H7)
30 P$=P$+CHR$(&HB8)+CHR$(&H1)+CHR$(&H6)+CHR$(&H75)+CHR$(&HC)+CHR$(&HB9)
40 P$=P$+CHR$(&H0)+CHR$(&H2)+CHR$(&HBA)+CHR$(&H10)+CHR$(&H10)+CHR$(&HCD)
50 P$=P$+CHR$(&H10)+CHR$(&H5D)+CHR$(&HCA)+CHR$(&H2)
60 P$=P$+CHR$(&H0)+CHR$(&HB9)+CHR$(&H14)
70 P$=P$+CHR$(&H5)+CHR$(&HBA)+CHR$(&H24)+CHR$(&H12)+CHR$(&HEB)+CHR$(&HF2)
100 ENTRY!=(PEEK(VARPTR(P$)+1))+(PEEK(VARPTR(P$)+2))*256
110 IF ENTRY!>32768! THEN ENTRY%=ENTRY!-65536! ELSE ENTRY%=ENTRY!
120 A$="АБВГДЕЖЗИК"
130 L=0:R=1
140 LOCATE 1,1:PRINT "Пример сдвига окна . . .э
200 LOCATE 15,1:PRINT A$;
210 CALL ENTRY%(L)
220 LOCATE 18,21:PRINT A$;
230 CALL ENTRY%(R)
240 A$=RIGHT$(A$,9)+LEFT$(A$,1)
250 GOTO 200
Фиг. 10.10 Бэйсик-программа для сдвига окон
Поскольку программа на машинном языке задана в строке P$, то
для определения адреса этой строки программа на языке Бейсик
использует функцию VARPTR. Для оператора CALL необходим адрес
подпрограммы, поэтому для его нахождения и используется функция
VARPTR. Воспользовавшись информацией из приложения C справочника
по Бейсику, можно найти адрес строки во втором и третьем байтах
дескриптора строки. Возвращаемое функцей VARPTR значение является
адресом дескриптора строки для P$. Программа извлекает адрес
строки из дескриптора и присваивает его значение переменной ENTRY!.
Поскольку это значение может находиться в диапазоне от 0 до 65536,
подпрограмма должна преобразовать его в целое значение длиной в
одно слово, со значением от от -32768 до 32767. Это слово
помещается в переменную ENTRY%. В остальных строках программы в
сдвигаемые окна записывается символьная строка, а затем для
перемещения текста вызывается подпрограмма SCROLL.
При запуске этой программы вы увидите, что данные в двух окнах
перемещаются независимо. Такой прием позволяет задать два
различных окна на экране и перемещать в них текст независимо друг
от друга. Если написать немного более длинную программу, можно
было бы ограничить каждое окно рамкой, чтобы деиствительно отделить
их друг от друга. Применение подобных методов построения окон
позволяет писать довольно симпатичные программы с одновременным
выводом на экран наскольких фрагментов текста.
Прежде чем покончить с этой программой, давайте просмотрим
через отладчик часть программы, написанную на машинном языке. Для
этого надо иметь готовую к выполнению программу ДОС DEBUG. Это
достигается следующим образом: сначала загружается программа
DEBUG, а затем загружается BASIC.COM (или BASICA.COM, если
используется расширенный Бейсик). После загрузки программы Бейсик
замените первый символ в P$ (и соответственно, первый байт
программы на машинном языке), на CHR$($HCC). Это - код для
прерывания INT 3 прерывания по точке прерывания. Теперь, когда во
время выполнения программы на языке Бейсик она вызывает
подпрограмму на машинном языке, управление получает программа
DEBUG. Теперь можно вновь заменить код 0CCH на исходное значение
(в данном случае 055H). Программу DEBUG можно использовать для
трассировки программы на машинном языке. Конечно, если программа
на языке ассемблера хорошо написана и коротка, то такая отладка не
так необходима. На самом же деле вы, вероятно, заметите, что в
большинстве случаев из-за ошибок при вводе с клавиатуры программы
на машинном языке в строку интерпретатора Бейсик возникает
множество проблем.
Загрузка в верхнюю часть памяти
Загрузка в верхнюю часть памяти
Применение прерывания DOS INT 27H является предпочтительным
способом включения в систему постоянных функций типа драйверов
устройств. Это - удобный способ сделать программу постоянной частью
системы. Пользователь может включить программу в файл AUTOEXEC.BAT,
тогда она будет загружаться автоматически. Такую автоматическую
загрузку можно использовать, когда в вашей системе имеется
специальное устройство ввода-вывода. DOS будет загружать драйвер
этого устройства при каждой загрузке системы. Вы можете даже
предпочесть собственную версию процедуры буферизации печати,
поскольку вы хотите, чтобы она постоянно загружалась в систему.
Однако выход в DOS с фиксацией программы в ОЗУ работает не
всегда. Фирма IBM предлагает три операционные системы для
персональных ЭВМ: DOS, которая и рассматривается в данной книге,
CP/M-86 фирмы Digital Research и UCSD p-System фирмы SofTech
Microsystems. Кроме указанных систем, предлагаемых фирмой IBM,
несколько независимых разработчиков распространяют свои системы.
Чтобы создать драйвер устройства, который работал бы со всеми этими
системами, нужно использовать нечто отличного от метода,
применяемого для DOS.
Допустим, у вас имеется специализированное устройство печати,
которое вы хотите продавать как приспособление к IBM PC. Поскольку
ваш принтер - отноительно дешевое устройство, для него потребуется
больше управления со стороны BIOS, чем для принтера фирмы IBM. Вы
конструируете принтер и устройство подсоединения и пишете BIOS
программу для поддержки его работы. Если вы пользуетесь
прерыванием INT 27H, то ваше устройство можно передавать только
пользователям, имеющим на своей персональной ЭВМ DOS. Необходим
такой способ загрузки драйвера устройства, который бы работал во
всех операционных системах.
Способ загрузки, годный не только для DOS, называется загрузкой
в верхние адреса оперативной памяти. При этом управление системой
перехватывается непосредственно после процедуры самоконтроля при
включении питания. Это может быть реализовано при помощи
специальной дискеты загрузки. Программа будет записана на дискету,
которая вставляется в дисковод перед включением питания.
Подпрограмма загрузки, входящая в BIOS, загружает драйвер
устройства с дискеты в верхнюю часть оперативной памяти. Затем
можно изменить размер области данных сообщаемый BIOS в соответствии
с имеющимся объемом оперативной памяти. При загрузке программы в
верхние адреса размер доступной оперативной памяти уменьшается.
Если после этого загрузить стандартную операционную систему, будет
восстановлено нормальное функционирование ЭВМ. Все операционные
системы фирмы IBM учитывают объем памяти BIOS при определении
границ оперативной памяти. Указанные системы не затрагивают
программ, загруженных в верхние адреса. Если система удовлетворяет
указанным требованиям, то можно пользоваться загрузкой в верхние
адреса оперативной памяти.
Приведем пример для иллюстрации описанного приема. На Фиг. 10.2
представлен листинг ассемблирования двух подпрограмм. Первая
подпрограмма осуществляет инициализацию и загрузку драйвера
устройства. Вторая подпрограмма является собственно драйвером
устройства. Позже станет ясным, почему удобнее было разделить эту
программу на две части.
A
Microsoft (R) Macro Assembler Version 5.00 1/1/80 01:21:50
Фиг. 10.2(а) Загрузчик для создания псевдо-диска Page 1-1
PAGE ,132
TITLE Фиг. 10.2(а) Загрузчик для создания псевдо-диска
0000 NEW_DISK SEGMENT
0000 DISK_BIOS LABEL FAR
0003 ORG 3
0003 OLD_VECTOR LABEL WORD
0003 NEW_DISK ENDS
0000 ABS0 SEGMENT AT 0
004C ORG 13H*4
004C DISK_VECTOR LABEL WORD
0410 ORG 410H
0410 EQUIPMENT LABEL WORD
0413 ORG 413H
0413 MEMORY_SIZE LABEL WORD
= 00A0 DISK_SIZE EQU 160
7C00 ORG 7C00H ; Место,в которое заносится загрузчик ДОС
7C00 BOOT_RECORD LABEL FAR
7C00 ABS0 ENDS
0000 CODE SEGMENT
ASSUME CS:CODE,DS:ABS0
7C00 ORG 7C00H
7C00 8C C8 MOV AX,CS
7C02 8E D8 MOV DS,AX
7C04 8E C0 MOV ES,AX
7C06 8D 36 7C00 R LEA SI,BOOT_RECORD
7C0A 8D 3E 7A00 R LEA DI,BOOT_RECORD-200H ; Место,на которое переносится
7C0E B9 0200 MOV CX,512 ; загрузчик ДОС
7C11 F3/ A4 REP MOVSB ; Перенесение загрузчика
7C13 E9 7A16 R JMP NEXT_LOCATION-200H
7C16 NEXT_LOCATION:
Фиг. 10.2 программа создания псевдо-диска (начало)
7C16 83 06 0410 R 40 ADD EQUIPMENT,40H ; Увеличение числа дисководов
7C1B A1 0413 R MOV AX,MEMORY_SIZE
7C1E 2D 00A0 SUB AX,DISK_SIZE
7C21 A3 0413 R MOV MEMORY_SIZE,AX ; Уменьшение доступной ДОС памяти,необхо-
7C24 B1 06 MOV CL,6 ; димое для размещения псевдо-диска
7C26 D3 E0 SHL AX,CL ; Умножение на 1024/16
7C28 8E C0 MOV ES,AX ; Сегментная часть адреса нового диска
7C2A B8 0201 MOV AX,201H ; Чтение сектора в эту область
7C2D BB 0000 MOV BX,0
7C30 B9 0002 MOV CX,2
7C33 BA 0000 MOV DX,0
7C36 CD 13 INT 13H
7C38 72 1A JC BOOT_ERROR
ASSUME ES:NEW_DISK
7C3A A1 004C R MOV AX,DISK_VECTOR
7C3D 26: A3 0003 R MOV OLD_VECTOR,AX
7C41 A1 004E R MOV AX,DISK_VECTOR+2 ; Сохранение старого вектора пре-
7C44 26: A3 0005 R MOV OLD_VECTOR+2,AX ; рывания 13h
7C48 C7 06 004C R 0000 MOV DISK_VECTOR,0 ; Установка вектора прерывания 17h
7C4E 8C 06 004E R MOV DISK_VECTOR+2,ES ; на новое место
7C52 EB 07 JMP SHORT REBOOT ; Чтение загрузчика с другой дискеты
7C54 BOOT_ERROR:
7C54 8D 36 7A93 R LEA SI,ERROR_MSG-200H ; Печать сообщения об ошибке
7C58 E8 7C81 R CALL PRINT_MSG
7C5B REBOOT:
7C5B 8D 36 7AA5 R LEA SI,BOOT_MSG-200H ; Печать сообщения о загрузке ДОС
7C5F E8 7C81 R CALL PRINT_MSG
7C62 WAIT_BOOT:
7C62 B4 00 MOV AH,0
7C64 CD 16 INT 16H ; Ожидание ввода с клавиатуры
7C66 3C 20 CMP AL,' ' ; Ожидается ввод пробела
7C68 75 F8 JNE WAIT_BOOT
7C6A B8 0201 MOV AX,201H
7C6D BB 7C00 MOV BX,7C00H
7C70 B9 0001 MOV CX,1
7C73 BA 0000 MOV DX,0
7C76 8E C2 MOV ES,DX ; Ввод на стандартное место загрузчика
7C78 CD 13 INT 13H
7C7A 72 D8 JC BOOT_ERROR
7C7C EA 7C00 ---- R JMP BOOT_RECORD
7C81 PRINT_MSG PROC NEAR
7C81 2E: 8A 04 MOV AL,CS:[SI] ; Взять символ для печати
7C84 46 INC SI
7C85 3C 24 CMP AL,'$' ; Проверка на символ конца вывода
7C87 75 01 JNE OUTPUT
7C89 C3 RET
7C8A OUTPUT:
7C8A B4 0E MOV AH,14
7C8C BB 0000 MOV BX,0
7C8F CD 10 INT 10H ; Вывод на дисплей через BIOS
7C91 EB EE JMP PRINT_MSG
7C93 8E E8 A8 A1 AA A0 20 ERROR_MSG DB 'Ошибка загрузки',13,10,'$'
A7 A0 A3 E0 E3 A7 AA
A8 0D 0A 24
Фиг. 10.2 программа создания псевдо-диска (продолжение)
7CA5 82 E1 E2 A0 A2 EC E2 BOOT_MSG DB 'Вставьте новую дискету с ДОС',13,10
A5 20 AD AE A2 E3 EE
20 A4 A8 E1 AA A5 E2
E3 20 E1 20 84 8E 91
0D 0A
7CC3 A8 20 AD A0 A6 AC A8 DB 'и нажмите на пробел',10,13,'$'
E2 A5 20 AD A0 20 AF
E0 AE A1 A5 AB 0A 0D
24
7CD9 PRINT_MSG ENDP
7CD9 CODE ENDS
END
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:06:49
Фиг. 10.2(б) Программа обслуживания псевдо-диска Page 1-1
PAGE ,132
TITLE Фиг. 10.2(б) Программа обслуживания псевдо-диска
0000 CODE SEGMENT
ASSUME CS:CODE
;--------------------------------------------
; Эта программа находится в секторе 1 трека 0
; псевдо-диска. Чтение и запись на устройство 2
; переадресуется на эту программу
;--------------------------------------------
0000 DISK PROC FAR
= 0140 DISK_SIZE EQU 320 ; Размер псевдо-диска в сектрах
0000 EB 05 90 JMP START_BIOS
0003 ???????? ORIGINAL_VECTOR DD ?
0007 START_BIOS:
0007 80 FA 02 CMP DL, 2 ; Программа обрабатывает только обращения
000A 74 05 JE L1 ; к устройству (дисководу) 2
000C OLD_BIOS:
000C 2E: FF 2E 0003 R JMP ORIGINAL_VECTOR ; Переход на стандартную программу
0011 L1:
0011 3C 01 CMP AL, 1
0013 76 F7 JBE OLD_BIOS
0015 80 FC 04 CMP AH, 4
0018 72 06 JB READ_WRITE ; Обрабатываются только команду чтения и
; записи
001A OK_RETURN:
001A B4 00 MOV AH, 0 ; Код возврата - 0
001C F8 CLC ; Сброс C-флага - нет ошибки
001D CA 0002 RET 2
0020 READ_WRITE:
0020 53 PUSH BX ; Сохранение регистров
0021 51 PUSH CX
0022 52 PUSH DX
0023 56 PUSH SI
0024 57 PUSH DI
0025 1E PUSH DS
0026 06 PUSH ES
Фиг. 10.2 программа создания псевдо-диска (продолжение)
;----- Вычисление адреса расположения требуемой записи в псевдо-диске
0027 50 PUSH AX ; Сохранение кода требуемой операции
0028 B0 08 MOV AL, 8 ; Число секторов на треке
002A F6 E5 MUL CH
002C B5 00 MOV CH, 0
002E 03 C1 ADD AX, CX ; Прибавление номера сектора
0030 80 FE 00 CMP DH, 0 ; Проверка на номера стороны
0033 74 03 JE HEAD_0
0035 05 0140 ADD AX, 320 ; Переключение на второю сторону
0038 HEAD_0:
0038 48 DEC AX
0039 3D 0140 CMP AX, DISK_SIZE ; Вычисленное значение правильно?
003C 76 0E JBE DISK_OK
003E RECORD_NOT_FOUND:
003E 58 POP AX ; Восстановление регистров
003F 07 POP ES
0040 1F POP DS
0041 5F POP DI
0042 5E POP SI
0043 5A POP DX
0044 59 POP CX
0045 5B POP BX
0046 B4 04 MOV AH, 4 ; Ошибка: сектор не найден
0048 F9 STC
0049 CA 0002 RET 2 ; Возврат с указанием об ошибке
004C DISK_OK:
004C B1 05 MOV CL, 5
004E D3 E0 SHL AX, CL ; Определение расположения данных на
0050 8C C9 MOV CX, CS ; псевдо-диске
0052 03 C8 ADD CX, AX ; В регистре CX сегментная часть адреса
; данных на диске
0054 51 PUSH CX
0055 8B D3 MOV DX, BX ; В регистре DX адрес передачи
0057 B1 04 MOV CL, 4
0059 D3 EA SHR DX, CL
005B 8C C1 MOV CX, ES
005D 03 D1 ADD DX, CX ; В регистре DX сегментная часть адреса
; передаваемых данных
005F 59 POP CX
0060 83 E3 0F AND BX, 0Fh ; Выделение младших 4 разрядов
0063 58 POP AX ; Восстановление код требуемой операции
0064 80 FC 02 CMP AH, 2
0067 74 11 JE READ_OPN
0069 WRITE_OPN:
0069 8C CE MOV SI, CS
006B 3B CE CMP CX, SI ; Проверка на запись поверх этой программы
006D 74 1B JE ALL_DONE
006F 8E C1 MOV ES, CX
0071 BF 0000 MOV DI, 0
0074 8E DA MOV DS, DX
0076 8B F3 MOV SI, BX ; Установка параметров передачи
0078 EB 09 JMP SHORT DO_MOVE
007A READ_OPN:
007A 8E D9 MOV DS, CX
Фиг. 10.2 программа создания псевдо-диска (продолжение)
007C BE 0000 MOV SI, 0
007F 8E C2 MOV ES, DX
0081 8B FB MOV DI, BX
0083 DO_MOVE:
0083 8A E8 MOV CH, AL ; Число слов в секторе
0085 B1 00 MOV CL, 0
0087 FC CLD
0088 F3/ A5 REP MOVSW ; Пересылка данных
008A ALL_DONE:
008A 07 POP ES ; Восстановление регистров
008B 1F POP DS
008C 5F POP DI
008D 5E POP SI
008E 5A POP DX
008F 59 POP CX
0090 5B POP BX
0091 B4 00 MOV AH, 0 ; Нормальное окончание
0093 F8 CLC
0094 CA 0002 RET 2
0097 DISK ENDP
0097 CODE ENDS
END
Фиг. 10.2 (а) Процедура загрузки для виртуального диска;
(b) Программа драйвера виртуального диска.
Драйвер устройства, приведенный в рассматриваемом примере,
реализует модель диска в оперативной памяти. Мы возьмем 160К
памяти системы и будем исполльзовать ее не как оперативную
память, а как дискету. Мы выбрали именно 160К потому, что это
минимальный объем дискеты фирмы IBM. Очевидно, при большем объеме
оперативной памяти можно моделировать дискету большего объема.
Подпрограмму псевдо-диска можно использовать для повышения
производительности программ, производящих интенсивный обмен с
диском. Например, если поместить на псевдо-диск ассемблер и
исходный код программы, ассемблирование будет произведено не за
минуты, а за секунды. Производительность некоторых программ может
быть повышена более чем на порядок. Платой за такое повышение
производительности являются 160K байт оперативной памяти, отводимые
под псевдо-диск. Если в системе, которая в основном используется
для редактирования и ассемблирования, имеется 256 кбайт памяти, то
в действительности для ассемблера достаточно всего лишь 96 кбайт.
Оставшиеся 160 кбайт можно использовать для моделирования диска в
оперативной памяти. Следует помнить, что содержимое такого диска
теряется при отключении питания, поэтому, прежде чем окончить
работу, убедитесь, что информация скопирована на настоящую
дискету.
Первая подпрограмма на Фиг. 10.2 - процедура загрузки. Ее
код находится в секторе 1 дорожки 0 загрузочной дискеты. Как
поместить программу туда, будет объяснено позже. Подпрограмма POST
при завершении считывает содержимое сетора 1 дорожки 0 в память,
по адресу 0:7C00H. Затем POST передает управление по первому
адресу этой записи. Таким образом система фирмы IBM загружает в
память DOS или любую другую операционную систему. А мы как раз и
собираемся, загружать свою собственную простую операционную
систему.
Сегмент NEW_DISK определяет адрес подпрограммы-драйвера
устройства, также представленной на втором листинге (см. Фиг.
10.2). Поскольку наши подпрограммы ассемблируются отдельно, этот
сегмент для связи процедуры загрузки и драйвера устройства во время
выполнения. Сегмент ABS0 локализует векторы прерываний, заменяемые
в процедуре загрузки. В сегменте CODE, содержатся команды,
загружаемые с дискеты. Сегмент CODE - единственная часть
приведенной программы, находящаяся на загрузочной дискете.
Первое, что делает программа инициализации - пересылает себя по
адресу 0:7A00H. Затем, в процессе инициализации, процедура
перезагружает систему, чтобы загрузить настоящую операционную
систему. Эта загрузка производится по адресу 0:7C00H. Если бы
процедура инициализации не переносила себя на другое место, она бы
считывала следующую запись загрузки в ту область памяти, где
находится сама.
С адреса NEXT_LOCATION процедура инициализации инсталирует
драйвер устройства. Она изменяют флаги оборудования для указания на
наличие дополнительного дисковода по сравнению с установкой внешних
переключателей. Это "убеждает" операционную систему, что диск в
оперативной памяти является частью технического обеспечения. При
инициализации значение MEMORY_SIZE уменьшается на 160 кбайт,
которые резервируются для моделирования диска. Это предотвращает
использование предназначенной для него памяти. Кроме того,
программа подсчитывает значение сегмента для этой области в 160
кбайт, чтобы знать, куда загружать драйвер устройства. Когда это
выполнено, подпрограмма инициализации загружает в зарезервиро-
ванную память содержимое сектора 2 дорожки 0 загрузочной дискеты.
Как поместить драйвер устройства в сектор 2 будет описано при
размещении программы загрузки в секторе 1.
После чтения процедуры драйвера устройства, подпрограмма
инициализации изменяет вектор прерывания BIOS дискеты BIOS (INT
13H), чтобы он указывал на новый драйвер устройства. Как и в
предыдущем примере, эта процедура сохраняет старый вектор. Новому
драйверу этот вектор нужен чтобы при необходимости считывать данные
с настоящей дискеты, а не с ее модели. Наконец, наша программа
загружает систему. Она предлагает пользователю вставить системную
дискету, ждет утвердительного ответа и считывает запись загрузки.
(Если бы процедура предварительно не произвела пересылку программы,
то сейчас она была бы испорчена). Если все идет нормально, то
процедура осуществляет переход по первому адресу записи загрузки, в
результате чего управление получает стандартная операционная
система.
Прежде чем двинуться дальше, рассмотрим, как поместить
процедуру загрузки на новую загрузочную дискету. Во-первых,
необходима пустая отформатированная дискета. Она и станет
загрузочной. Листинг на Фиг. 10.3 показывает, что ассемблирование и
редактирование связей процедуры загрузки происходят, как обычно.
Вызовите программу DOS DEBUG и загрузите процедуру инициации.
Она загружается со смещением 7C00H, установленным программой DEBUG.
Регистры устанавливаются таким образом, чтобы использовать BIOS для
записи одного сектора дискеты. Это выполняет трехбайтовая
программа, находящаяся по адресу 200H. Если после записи нет
состояния ошибки, то запись инициализации уже на дискете.
Для записи драйвера устройства в сектор 2 выполните следующие
шаги, показанные на Фиг. 10.3. С помощью программы DEBUG мы
загружаем в память драйвер псевдодиска. Команда записи программы
DEBUG помещает код драйвера в сектор с относительным номером 1
(сектор 2 дорожки 0) дискеты, находящейся на дисководе A:.
Аналогичный способ можно было бы применить и для занесения на
дискету записи инициализации.
Такой способ формирования вызова BIOS в программе DEBUG
для записи на дискету может использоваться почти для всех функций
BIOS. Проследить, что именно происходит при вызове BIOS, можно с
помощью программы DEBUG. Можно установить регистры для вызова и
написать несложную трехбайтовую программу, осуществляющую
программное прерывание и производящую возврат в DEBUG. Этот прием
удобен также для тестирования собственного драйвера устройства.
Вернемся к процедуре драйвера псевдо-диска во второй части Фиг.
10.2. Заметим, что процедура загрузки сохранила исходный вектор
дискеты (INT 13H) в этом сегменте со смещением 3. Подпрограммы-
драйвера используют этот вектор для реализации всех функций
дискеты, которые не реализуются псевдо-диском. В приведенной
подпрограмме предполагается, что псевдо-диск находится на дисководе
2. На запрос любого другого дисковода процедура передает управление
BIOS, используя приэтом сохраненный в ORIGINAL_VECTOR исходный
вектор. Аналогично и запрос на смену дискеты передается BIOS. Если
функция, запрашиваемая для псевдо-дисковода, не считывание и не
запись, то драйвер псевдо-диска не производит никаких действий, и
происходит возврат с нормальным кодом завершения. Псевдо-диск
не требует форматирования, а поскольку у нас нет контроля ошибок,
то не остается ничего проверять.
Если запрашиваемой операцией является считывание или запись,
драйвер вычисляет адрес соответствующего псевдо-сектора в памяти.
При обращении за границу диска поцедура возвращает запись об ошибке
отсуствия адреса. Код драйвера устанавливает регистры источника и
назначения в соответствии с направлением операции. Наконец,
команда REP MOVSW передает данные между псевдо-диском и буфером
пользователя. Рассматриваемая программа всегда устанавливает
нормальный код завершения и производит возврат в вызывающую
программу.
Данный пример показывает, как реализовать моделирование диска,
однако он не готов для продуктивного использования. Для того, чтобы
стать утилитой общего назначения, эта программа должна быть
преобразована для обеспечения работы с любым прсевдоустройством, а
не только со вторым. Программу можно было бы изменить для работы с
сектором любой длины, хотя обычно этого не требуется. Фактически,
если моделирование диска применяется только при работе с DOS,
процедура инициализации должна форматировать дискету, записав
A
A>MASM BOOT,,,;
The IBM Personal Computer MACRO Assembler
Version 1.00 (C)Copyroght IBM Corp 1981
Warning Severe
Errors Errors
0 0
A>B:LINK BOOT,,,;
IBM Personal Computer Linker
Version 1.00 (C)Copyroght IBM Corp 1981
Warning: No STACK segment
Therhe was 1 error detected
A>MASM DISK,,,;
The IBM Personal Computer MACRO Assembler
Version 1.00 (C)Copyroght IBM Corp 1981
Warning Severe
Errors Errors
0 0
A>B:LINK DISK,,,;
IBM Personal Computer Linker
Version 1.00 (C)Copyroght IBM Corp 1981
Warning: No STACK segment
Therhe was 1 error detected
A>DEBUG BOOT.EXE
-R
AX=0000 BX=0000 CX=7CD3 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=06D7 ES=06D7 SS=06E7 CS=06E7 IP=0000 NV UP DI PL NZ NA PO NC
06E7:0000 0000 ADD [BX+SI],AL DS:0000=CD
-U7C00 7C05
06E7:7C00 8CC8 MOV AX,CS
06E7:7C00 8CD8 MOV DS,AX
06E7:7C00 8CC0 MOV ES,AX
-RAX
AX 0000
:301
-RBX
BX 0000
:7C00
-RCX
CX 7CD3
:1
-RDX
DX 0000
:
-RES
ES 06D7
:6E7
-E200
O6D7:0200 OO.CD 00.13 00.CC ;*** Здесь вставьте загрузочную дискету
-g=100
AX=0000 BX=7C00 CX=0001 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=06D7 ES=06D7 SS=06E7 CS=06E7 IP=0102 NV UP EI PL NZ NA PE NC
06E7:0102 CC INT 3
-NDISK.EXE ;*** Здесь вставьте программную дискету
-L
-UD 10
06E7:0000 EB05 JMPS 0007
06E7:0002 90 NOP
06E7:0003 0000 ADD [BX+SI],AL
06E7:0005 0000 ADD [BX+SI],AL
06E7:0007 80FA02 CMP DL,02
06E7:000A 7405 CMP 0011
06E7:000C 2E SEG CS
06E7:000D FF2E0300 JMP L,[0003] ;*** Здесь вставьте загрузочную дискету
-W0 0 1 1
-Q
A>
A
Фиг. 10.3 Шаги подготовки загрузки в верхние
адреса памяти
справочник и таблицу размещения файлов FAT. При нынешнем виде этой
процедуры после загрузки DOS вы должны "форматировать" диск C:. Для
псевдо-диска не требуеися физического форматирования, но утилита
FORMAT записывает таблицу FAT и каталог, необходимые для
функционирования DOS.
Эта процедура обеспечивает также сохранение процедуры-драйвера
устройства в псевдо-секторе 1 на дорожке 0. Система DOS не
использует указанный сектор дисковода C:, однако другие системы
могут это делать. Вы вооозможно, заметили, что программа псевдо-
диска предотвращает запись в смоделированный сектор дорожки 0, так
что программа по крайней мере не уничтожит саму себя.
Вообще говоря, метод загрузки в верхние адреса оперативной
памяти довольно сложен. Необходима загрузка с двух дискет, что
требует от оператора дополнительных манипуляций. Если не
предполагается использование программы в каких-либо других
системах, кроме DOS, то гораздо удобнее использовать прерывание INT
27H. В противном случае загрузка в верхние адреса оперативной
памяти может оказаться единственно возможным способом.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования