Программирование на языке Ассемблера
Почему
вас могло бы
заинтересовать
программирование на языке
ассемблера? Cегодня повсюду используются такие языки высокого
уровня как Бэйсик, Фортран и Паскаль . Возможно, вы уже знакомы по
крайней
мере с одним языком высокого уровня. Если вы постоянно
пльзуютесь персональным компьютером IBM, то вы
знаете, что
интерпритатор Бэйсика является частью
системы. Зачем же возиться
еще
с одним языком
программирования, тем более с таким, который
сулит определенные трудности? Очевидно, даже
располагая современ-
ными могучими языками, вы все еще
нуждаетесь в ассемблере из-за его
эффективности и точности.
Ассемблерные
программы могут быть
очень эффективными. Из
программистов, с равными навыками и способностями, работающий на
языке
ассемблера создаст программу
более компактную и быстро-
действущую, чем такая же программа,
написанная на языке высокого
уровня.
Это так практически для всех небольших
или средних
программ. К сожалению, по мере возрастания
размеров, программы на
языке
ассемблера теряют часть
своих преимуществ. Это происходит
из-за необходимого в ассемблерной программе внимания к деалям. Как
вы увидите,
язык ассемблера требует от вас
планирования каждого
действия компьютера. В небольших программах это позволяет оптими-
зировать работу программы с аппаратными
средствами. В больших же
программах огромное
количество деталей может помешать вам эффек-
тивно работать над самой программой, даже
если отдельные компоненты
программы окажутся очень неплохими.
Безусловно, программирование на
языке ассемблера отвечает потребностям не
каждой программы.
Программы на языке ассемблера очень точны. Поскольку этот язык
позволяет программисту
непосредственно работать со всем
аппаратным
обеспечением, ассемблерная программа может
делать то, что недоступ-
но никакой другой
программе . Несомненно, что в программировании
устройств
ввода-вывода, где требуется
контроль над отдельными
разрядами регистров устроиства, программирование
на языке
ассемблера - единственный подходящий
выбор.
Ясно, что эффективность и точность языка ассемблера
дают
определенные преимущества. Но его
детализированность создает и
некоторые
проблемы. Когда же стоит избирать для программирования
язык ассемблера?
Конечно вы должны
пользоваться программами на
языке
ассемблера, когда нет другого способа
написать программу. Например,
программисты фирмы IBM писали с использованием процедур ассемблера
все программы управления устройствами ввода-вывода для IBM PC. Для
управления
устройствами ввода-вывода и
системой прерываний,
потребовалась та точность языка
ассемблера которую не
может
обеспечить ни
один другой язык программирования. Аналогично, на
языке
ассемблера в фирме
IBM писались процедуры
диагностики,
которые должны проверять каждую деталь
аппаратуры.
Язык ассемблера необходим также и в тех
случаях, когда главными
являются
рабочие характеристики програмы.
Это может быть время
исполнения или конечный размер программы.
Библиотека математических
процедур
Фортрана - пример
программы, требующей хороших характе-
ристик
как в отношении времени, так
и размера. Математические
процедуры являтся частью любой программы на Фортране, поэтому они
должны занимать как можно меньше места.
Кроме того, эти процедуры
управляют всеми математическими функциями
в фортрановской программе
и часто используются. Следовательно, они
должны исполняться быстро.
Какая программа не подходит для языка ассемблера?
Конечно, вы
можете написать на нем любую программу,
однако с большой программой
лучше
работать в языке
высокого уровня, таком
как Бэйсик или
Паскаль. Эти языки позволяют вам
сосредоточиться на своей проблеме.
Вам
не приходится непосредственно иметь дело с
тонкостями
аппаратного оборудования и процессора. Языки высокого
уровня
позволяют вам отступить назад и за
деревьями увидеть лес.
Очевидно далее, что вы нуждаетесь
в соединении программ языка
ассемблера с программами языков высокого уровня. Эдесь мы ограни-
чимся
программированием на языке ассемблера для тех задач, для
которых
он хорошо подходит, таких как управление вводом-выводом.
Заключительная же глава книги посвящена прямо проблеме соединения
программ на языке ассемблера с другми
языками программирования. Эти
методы
предоставляют вам все
лучшее, что есть в обоих мирах. Вы
можете,
когда необходимы точность и
эффективность, использовать
процедуры
на языке ассемблера - и процедуры
высокого уровня для
программы в целом. Все что вы для этого
должны сделать - сцепить их
вместе.
И последняя причина для изучения программирования на языке
ассемблера. Только через написание
программ на этом
уровне
детализации вы можете понять как
работает машина на самом нижнем
уровне.
Если вы хотите узнать о компьютере все, вы должны быть
знакомы с его языком ассемблера.
Единственный способ добиться этого
- писать программы на этом языке. Простое
чтение этой книги дела не
сдвинет.
Персональный компьютер фирмы IBM (IBM PC)
Почему в этом тексте IBM PC берется в
качестве базовой для изучения
программирования на языке ассемлера?
Для этого есть несколько
причин. Во-первых, IBM PC - новая и мощная
машина. Как персональный
компьютер
она обладает расширенными возможностями выходящими за
рамки
возможностей более ранних ПК. Как
вы подробнее увидете
дальше, PC использует микропроцессор Intel
8088. Этот процессор мо-
жет выполнять 16-битовую арифметику и адресацию над более чем мил-
лионом
символов памяти. Эти
возможности ставят его
ближе по
мощности к большим ЭВМ, чем к ранним
персональным компьютерам.
Во-вторых, IBM PC располагает
всеми средствами разработки
программ,
которые понадобятся вам для программирования на языке
ассемблера. Кроме ассемблера, фирма
IBM поставляет текстовый
редактор, редактор связей и дисковую опеационную
систему для того
чтобы объединить их все вместе. Имеется даже отладчик чтобы помочь
вам расчленить программу и затем собрать в
правильном порядке.
Наконец, IBM PC - хорошая
система для изучения
языка
ассемблера из-за ее доступности. Это недорогая машина, которая тем
не менее
дает все возможности, которые
требуются для программиро-
вания
на языке ассемблера. Более того,
как "персональный"
компьютер, машина принадлежит вам по крайней мере на время испол-
нения программы. Это означает, что вы
можете попытаться выполнить
то,
что нельзя делать на более крупной машине, общей с другими
пользователями. Вы можете овладеть
оборудованием ввода-вывода и
запрограммировать их для
выполнения интересных вещей.
Вы можете
делать
что захотите с
любой частью системы
- даже если это
"вырубает" систему.
Поскольку это персональная машина, то при
возникновении проблемы вы просто выключаете машину и запускаете ее
снова. Единственный человек, которому вы
при этом можете помешать -
это вы сами. Как персональная машина, IBM PC является великолепным
инструментом для разработки программ.
Об этой книге
Данная книга является введением в персональный компьютер IBM и его
язык ассемблера. Хотя основное внимание здесь уделено программиро-
ванию на
языке ассемблера, этот текст
описыват также программные
аспекты
основных особенностей
аппаратного обеспечения машины.
Вы
узнаете
как работают устройства ввода-вывода и
как программа
заставляет их работать
правильно. Вы также
научитесь писать
собственные программы на языке ассемблера с использованием IBM PC.
После того как вы напишете эти программы,
эта книга покажет вам как
связать
их с программами высокого уровня
или встроить вашу
программу в систему.
Программирование на языке ассемблера дает исключительный опыт,
но
часто вызывает затруднения. В
этой книге используются примеры
для демонстрации действительно работающих программ.
Эти примеры
помогут вам начать. Однако единственный
способ научиться чему-либо
в программировании - это сделать программу самому. Вы должны
со-
вершать свои собственные ошибки чтобы
чему-либо научиться. Желаю
достигнуть успехов и получить
удовольствие.
Двоичная арифметика
Все компьютеры используют для хранения
информации двоичную систему.
Это значит, что каждый элемент хранимой
информации может иметь
только два состояния. Эти состояния обозначаются как
"включен" и
"выключен", "истина" и
"ложь", или "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
с двоичным
------------------------------------------------- дополнением
Шестнадцатиричное представление
Двоичная арифметика хороша для компьютера,
поскольку он имеет дело
только с еденицами и нулями. Но человеческое восприятие требует
более компактного представления. Мы будем пользоваться шестнадца-
теричным представлением данных для
собственного удобства.
Шестнадцатеричное представление чисел -
это система исчисления
по основанию 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.
Машинный язык и язык Ассемблера
Мы уже видели, как из нулей и единиц,
хранимых в ЭВМ, формируются
числа.Теперь мы посмотрим как комбинации
тех же значений 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 нет команды, которая могла бы
выполнить эту опреацию.
Синаксис языка Ассемблера
Прежде чем двигаться дальше, обсудим
синтаксис команд языка ассем-
блера.Мы должны выделить основные
компоненты языка ассемблера,
чтобы можно было затем обозначать эти
компоненты с помощью
сиандартных терминов.
Команда языка ассемблера состоит из
четырех частей. Фиг. 2.8
показывает типичную команду ассемблера и
названия этих частей.
--------------------------------------------------------
PART1:
ADD AX,BX
;Добавить к длине буфера
Метка
ОпКод Операнды
Комментарий
--------------------------------------------------------
Фиг. 2.8 Синтаксис языка ассемблера
Единственная обязательная часть команды
языка ассемблера - ОпКод
(сокращение от ОПерационный КОД). Программисты иногда называют ма-
шинные команды кодами операций.
Операционный код в утверждении язы-
ка асемблера определяет, какую опреацию
должен будет выполнить про-
цессор, в нашем примере - операцию
сложения (по английски - add -
прим.
перев.).
Поле операндов содержит дополнительную
информацию о команде,
например, какие значения участвуют в
операции. Поле операндов
определяется операционным кодом. Каждому коду операции должно
соответствовать определенное число
операндов. Для команды ADD
требуется два операнда; операция перемены
знака (NEG) обходится
лишь одним, а для некоторых команд,
например, команды десятичной
коррекции DAA, операнды не нужны. В главе 4 описаны эти команды и
их операнды.
Метка и комментарий необязательны в команде. Поле метки позво-
ляет
обозначить какое-либо конкретное
место в памяти компьютера.
Собственный адрес имеется у любого
участка памяти, но выделить
адрес
какой либо команды
трудно, если вообще возможно.
Метка
позволяет
идентифицировать
определенное место в памяти заданным
программистом именем. Говоря технически, поле метки содержит
символический указатель расположения команды. Если мы хотим обра-
титься к
этой команде позднее, то мы
делаем это через символьное
имя и
нам не требуетсся указывать
абсолютное расположение данной
инструкции. Использование меток - одна из
причин предпочтительности
языка
ассемблера перед машинным языком. Превращением же симво-
лических имен в реальные адреса ведает
ассемблер.
Поле комментариев служит для удобства
программиста.
Программист может использовать это поле
для сообщения
дополнительной информации о команде. Комментарий не обязательно
жестко связан с командой. Вы можете отвести под комментарий целую
строку, поставив в ее начале символ
";". Это позвляет
программисту
в ключить в листинг ассембле- ра блок
собственной информации, к
примеру, описание используемого алгоритма.
У
каждого есть собственное
представление о том, как следует
комментировать программы, и вы наверняка тоже скоро
выработаете
свое. Как
правило, вы будете пытаться
включать в них информацию,
которая
относится непосредственно к
решаемой проблеме. В при-
веденном
примере было бы
бессмысленно комментировать команду
чем-нибудь вроде "сложить AX и
BX". Это не более, чем
повторение
операционного кода и операндов (разве что в переводе с английского
- прим.перев.). Если уж вы намерены связаться с комментариями, то
делайте их достойными труда их написания и
чтения.
Принципы работы Ассемблера
Рассмотрим теперь работу ассемблера в
целом. Детали будут
обсуждены позднее, но сейчас нам нужно ввести новые термины и
ознакомиться с реальным результатом работы
ассемблера.
Ассемблер берет программу, написанную на
языке ассемблера, и
превращает ее в машинный язык. Файл, который содержит программу на
языке ассемблера, называют исходным
файлом. Выход и ассемблера в
действительности является не собственно
машинным языком, а
некоторым промежуточным представлением
программы. Этот выходной
файл называют объектным файлом. Данные в нем называются объектным
кодом.
Для получения из него настоящего машинного кода объектный
код должен быть несколько изменен. Для 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.
Биты, байты и слова
Мы назвали "битом" двоичную
цифру, еденичное значение 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. Остальные структуры данных
используются в программах прежде всего для
очень больших чисел в
Числовом сопроцессоре или для определения
собственных структур
данных.
Нумерация бит
Иногда нам будет требоваться
идентифицировать отдельные биты в бай-
те или слове. Для этого мы называем номер бита. Индекс или номер
каждого бита - это степень двойки,
соответствующая позиции этого
бита.
Самый младший бит - нулевой, поскольку он представляет два в
нулевой степени. Самый старший бит в байте - седьмой - 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.
Набор символов
Как мы заметили выше, мы можем
рассматривать каждый байт информации
не как двоичное число, а как символьное
значение. Каждое из
двоичных чисел от 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-байтное собщение на принтер, он
напечатает заключенный в кавычки
текст.
Управляющие символы предписывают принтеру перейти после
этого на следующую строку документа.
Принципы работы компьютера
Ниже описаны некоторые основные принципы
работы компьютера. Эти
принципы важны для понимания 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
использует для хранения этого значения
стек.
Стек
Стек - это структура данных, которая
используется для временного
хранения информации. Программа может поместить данные в стек
(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.
Прерывания
Механизм прерываний - существенная часть
любой вычислительной сис-
темы.
Как мы увидим, он важен и для IBM PC.
Структура прерываний
предоставляет эффективное средство для
связи устройств вводоа-выво-
да с процессором. Нам прерывания интересны потому, что
управление
прерываниями - прерогатива
программирования на языке ассемблера. В
языках высокого уровня отсутствуют
средства для работы с
прерываниями на машинном уровне.
Прерывания обычно вызываются внешними
устройствами. Прерывание
сигнализирует процесору о необходимости
прервать текущие действия и
ответить внешнему устройству. В IBM PC клавиатура посылает сигнал
прерывания всякий раз при нажатии любой
клавиши. Прерывание
клавиатуры заставляет процессор прекратить
текущую деятельность и
считать набранный на клавиатуре символ.
Легко понять, за что прерывания получили
свое название. Преры-
вание сигнализирует о необходимости
"прервать" текущее действие
процессора. Прерывания хороши тем, что избавляют процессор
постоянного контроля за внешними
устройствами. Если бы, например,
клавиатура пользователя не вызывала
прерываний, то процессор был бы
вынужден непрерывно проверять клавиатуру,
чтобы обнаружить нажатие
клавиши.
Каждая написанная для компьютера программа была бы
вынуждена делать одно и то же, и им
пришлось бы очень часто
тестировать клавиатуру. Но наличие прерываний снимают это
требование, и программа может выполняться
без постоянного
тестирования клавиатуры. Каждый раз, как клавиатура получает
какую-либо информацию, она сигнализирует
об этом процессору. После
того, как микропроцессор удовлетворит
запрос клавиатуры, он может
возобновить нормальный ход выполнения
программы.
Работа 8088 с прерываниями во многом
напоминает его обращение с
процедурами. Прерывание не может прекратить работу процессора во
время выполнения команды. Сначала 8088 закончит выполнение текущей
команды, но следующую уже
проигнорирует. Вместо ее выполнения
про-
цессор действует так, как будто следующая
команда была вызовом про-
цедуры.
Он сохраняет адрес очередной команды в стеке и переходит в
специальную процедуру, которую называют
программой обработки
прерываний. Эта процедура содержит команды для работы с вызвавшим
прерывание устройством. В случае с клавиатурой программа обработки
прерывания считывает символ и сохраняет
его для дальнейшего
использования. После того как она закончит работу с устройством,
происходит возврат в точку
прерывания. Процессор извлекает из
стека адрес возврата и продолжает
выполнение программы как будто
ничего не случилось.
Поскольку прерывание вызывается внешним
устройством, оно может
произойти в любой момент выполнения
программы. Программа не может
предпринять каких-либо действий чтобы
подготовиться к прерыванию,
так как не может предвидеть, когда
пользователь нажмет на клавишу
клавиатуры. Отсюда следует, что прерывание не должно изменять
данные в прерываемой программе. Если прерывание иозменит
какое-либо значение в программе, то она не
сможет нормально
работать когда к ней вернется управление.
В ходе прерывания 8088 автоматически
сохраняет некоторые уста-
новленные программой значения в стек. В свою очередь, программа
обработки прерываний отвечает за
сохранение любых других данных,
которые она может изменить во время своего
выполнения. Эти данные
обычно сохраняются в стеке. Затем, перед возвращением управления в
прерванную программу, программа обработки
прерывания должна вернуть
измененным данным те значения, которые они
имели в момент
прерывания. Факт возникновения прерывания должен остаться
"невидимым" для выполняемой
программы.
Поскольку сигнал прерывания могут посылать
процессору многие
устройстваэ, 8088 имеет механизм
ориентации прерываний. Это озна-
чет, что 8088 определяет, какое устройство
вызвало прерывание и пе-
редает управление программе обработки
прерывания, соответствующей
этому устройству. Процессор атоматически управляет
веторизацией
зап росов на прерывания. Программе обработки прерывания не
требуется перед обработкой прерывания
определять, какое устройство
его вызвало. Это сокращает время реакции на прерывание и упрощает
программирование прерываний.
В программамах встречаются такие участки,
выполнение которых не
может быть прервано. Например, это может быть кусок программы,
который должен выполняться очень быстро,
чтобы закончить выполнение
специфической задачи, или момент работы с
данными, которые могут
быть изменены программой обработки
прерывания. В обоих случаях
программа должна иметь возможность
задержать или предотвратить
прерывания, т.е. программа должна уметь не допускать возникновения
прерываний во время выполнения таких
критических участков. После
прохождения этих участков программа должна
восстановить способность
системы прерываний вызывать
прерывания. Программа не может
отключать прерывания на слишком долгое
время, иначе с устройством,
запросившим прерывание, может произойти
какая-нибудь неприятность.
Если прерывание клавиатуры не считает
символ до того как оператор
нажмет другую клавишу, второй симол может
быть потерян. В 8088
имеется возможность блокировать все
внешние прерывания. IBM PC
имеет более развитую возможность выбирать,
каким из устройств можно
вызывать прерывание, а каким нет. Программа может использовать эту
возможность для выбора наиболее важных
устройств, которым можно
разрешить прерывания, а менее критическим
запретить. Способы
отключения прерываний мы обсудим в
следующих главах.
Модель программирования 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.
Регистры общего назначения
В первую группу входят регистры, используемые
в основном для вы-
числений. Все эти общие регистры имеют размер 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.
Регистры адресации
В
процессоре 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, он не является здесь самим
операндом, а используется
для определения его адреса.
Прямая адресация
Простейший способ определить операнд в памяти - даать имя ячейке
памяти. В
дальнейшем программа использует это
имя в командах для
ссылки на соответствующий участок памяти. Вот как этот метод
используется в команде
INC
OPND
В примере
команда ассемблера
OPND
DW ?
объявляет
OPND участком памяти длиной
в слово. Когда программа
ипользует OPND в качестве операнда,
ассемблер помещает адрес OPND в
машинную команду. В данном примере вы можете видеть адрес 0123 как
часть
команды в объектном
коде. Этот способ
называют прямой
адресацией, поскольку команда
непосредственно в себе содержит адрес
операнда.
Вычисление адресов
В способе
прямой адресации памяти
привлекательна простота, но во
многих случаях программа вынуждена
вычислять действительный адрес в
памяти.
Простейший пример -
операции с вектором,
одномерным
массивом.
В программе на
языке Фортран такую структуру
можно
создать оператором
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 более
подробно.
Адресация через базу и смещение
Поскольку
вычисление для операнда адреса,
состоящего из базы и
индекса, встречается довольно часто, среди способов адресации 8088
есть
такие, что позволяют
автоматически производить идексирующее
сложение.
Вместо выполнения всех
вычислений, программа может
определить только величину 2*I и поместить
ее в регистр BX. Команда
INC
[OPND + BX]
вычисляет исполнительный адрес через
сложение адреса базы OPND со
значением
индекса в BX. Этой командой достигается тот же самый ре-
зультат что и в предыдущем случае, но
меньшим числом команд. Обра-
тите внимание, что в этой команде
ассемблеру не требуется подсказка
WORD
PTR, потому что ассемблер уже
знает, что OPND является пере-
менной типа WORD. Оператор PTR требуется
только в тех случаях, ког-
да ассемблер не может определить тип
операнда.
Любой из
четырех адресных регистров может
быть использован в
качестве индекса при базе. Фиг. 3.2
показывает возможные способы
адресации через базу и индекс. Вы видите,
что ассемблер допускает
несколько способов записи операции адресации.
В группе, состоящей
из пяти
команд на Фиг. 3.2,
во всех командах адрес базы
OPND
складывается с указанным рядом индексным
регистром.
Надо
отметить, что в
команде, содержащей базисный
адрес,
регистр
не обязательно должен
содержать именно значение индекса.
Действительно, поскольку BX называется базисным регистром, кажется
разумным воспользоваться
противоположной конфигурацией. В
качестве
примера
предположим, что программа
использует множество разных
векторов с одинаковой длиной и размером элементов. Такую структуру
может
иметь, например, классный
журнал, в котором
векторам
соответствуют наборы оценок за
каждую контрольную работу.
Программа, вычисляющая оценку пятого
ученика в классе по I-й работе
будет
иметь уже известное
значение индекса (5) а базу (вектор
данной
контрольной работы) -
вычисляемую в ходе
выполнения
программы.
Индексный регистр может содержать как
адрес базы вектора, так и
значение
индекса в векторе.
Поскольку константное значение
в
команде может оказаться и базой и
индексом (или вовсе чем-нибудь
известным только программисту), то это
значение называют смещением.
Оно соответствует расстояню или смещению от адреса в
регистре до
исполнительного адреса, по которому
происходит обращение.
База + индекс + смещение
Программа
может также комбинировать вычисляемый адрес базы
с
вычисляемым индексом. Как показано
на Фиг.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
позволяет складывать либо
регистр с ячейкой памяти, либо два
регистра. В одной команде нельзя
сложить две ячейки памяти. Это
и означает, что команда содержит
только один адрес памяти.
Байт MOD R-M
Как
же адресная информация
передается микропроцессору в машинном
языке?
8088 использует почти
для всех операций адресации байт
MOD-R/M
(байт режима адресации
и регистра/модификатора - прим.
перев.).
Фиг.3.4 показывает формат
этого байта команды.
Байт
MOD-R/M следует за байтом кода операции и
определяет один операнд
памяти команды 8088. Этот байт может
вместо ячейки памяти указывать
и регистр. Такое единство
структуры позволяет реализовать
все
возможности адресации операндов.
ЪДДДДДДДВДДДДДДДДДДДВДДДДДДДДДДДї
і і
і і
АДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
Режим Заданная Регистр-
команда модификатор
Режим Смещение
-------------------------------------------------
00
DISP=0, нет байтов смещения
01
-128<DISP<127, однобайтовое смещение
10
-32768<DISP<32767, двухбайтовое смещение
11
В поле r/m - регистр, а не адрес
Фиг. 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.
Физическая адресация
Все,
что до сих пор говорилось об адресации, относится к генерации
так называемого смещения (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-битовую границу.
Эти границы называются также границами
параграфов.
Сегментные регистры
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 для указания исходного и целевого
регистров.
Предназначение сегментов
Каждый из
сегментных регистров имеет свое,
отмеченное выше назна-
чение. В
некоторых случаях, однако, более
удобна связь с данными
вне
сегмента данных, например,
с небольшой областью
данных в
программе. В большинстве случаев программа
работает с данными в той
области,
на которую указывает
регистр 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 мы обсудим строковые
команды и их специфическое
пользование сегментами.
Оператор 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. В противном случае ассемблер
запутается и выдаст сообщение об
ошибке.
Оператор 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 мы будем использовать далее в
примерах.
Указатель команд
Указатель команд - это 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 и системы
вместе с обычными запросами самой
программы.
Регистр флагов
Последний
управляющий регистр -
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 и
будут влиять на режим выполнения
программы. Флаги в младшем байте -
это
коды условия и могут быть испольлзованы в командах условных
переходов для изменения порядка выполнения
программы.
Флаг знака
Флаг знака (SF) показывает, является результат последней арифмети-
ческой
операции положиельным или
отрицательным. Установка бита
знака отражает значение старшего бита
последнего результата. Если
последний
результат отрицателен, то
бит знака устанавливается в
еденицу, а если положительный или нулевой,
то - в ноль.
Флаг нуля.
Флаг
нуля (ZF) показывает, что результат последней
операции
равнялся
нулю. Этот флаг
используется в программах для проверки
двух
чисел на равенство.
Допустим, в программе вычитаются два
числа.
Результат будет нулевым, если значения идентичны и не
нулевым, если они различны.
Флаг четности
Флаг четности (PF) показывает, является ли число едениц результата
последеней операции четным. Четность - это
способ контроля значений
данных. Бит четности - это
дополнительный бит, который
проверяет
значения других бит. Программа может
использовать флаг четности для
определения, в какое значение
следует установить бит четности.
Четность
используется в оперативной памяти
IBM PC для контроля
сохраненных в ней данных.
Такой контроль четности выполняется
непосредственно аппаратными средствами и
не влияет на бит флага
четности.
Флаг
четности устанавливается в
"1", если результат операции
имеет четное число едениц и переустанавливается в "0", если число
едениц
результата нечетно. В
обычных арифметических и логических
операциях знак четности использкется мало.
Флаг переноса
Флаг переноса (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 из старшей части числа.
Микропроцессор устанавливает флаг переноса как индикатор заема
при расширенном вычитании. Наряду с расширением точности программа
может сипользовать флаг переноса для
определения соотношения двух
чисел.
Если флаг переноса
установлен, вычитаемое значение больше
уменьшаемого; если флаг переноса не
установлен, вычитаемое значение
меньше
или равно уменьшаемому. Это
означает, что флаг переноса
становится первичным индикатором при определении соотношения двух
чисел: после того, как программа вычитает два числа, флаг переноса
указывает, какое
из них
больше. Таким способом программа может
проверять
целые числа без знака,
включая такие приложения, как
сортировка строк символов. В случае чисел
со знаком для определения
соотношения чисел программе нужна
дополнительная информация. В
следующей
главе в разделе
"Условные
переходы" обсуждаются все
способы тестирования чисел.
Дополнительный флаг переноса
Возможно, вам никогда
не придется пользоваться флагом
дополнительного переноса (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. В следующей
главе
рассматривается использование
команд десятичной и
сомвольной
коррекции.
Флаг переполнения
Флаг переполнения 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.
Вообще говоря, операция сложения в процессоре
выполняется одинаково над числами и со
знаком, и без знака, а также
с десятичными числами. Флаги переноса,
дополнительного переноса и
переполнения содержат информацию о выполненной операции, позволяя
программе
определить верный результат
в используемой системе
счисления. Флаг
переполнения показывает, что
результат
арифметической операции выходит за
пределы диапазона чисел,
представленных в дополнительном виде.
Переполнение отличается от
переноса,
который показывает, что
произошел перенос из старшего
бита результата.
Флаг захвата
Флаг захвата (специального прерывания) TF
помогает при отладке
программ. Этот флаг устанавливается не в
результате работы
микропроцессора, а - программой, с помощью
специальной команды.
Этот флаг называется также флагом
трассировки или шага.
Когда этот флаг установлен, после
выполнения каждой команды
возникает прерывание. Эффект при этом
такой же, как если бы после
каждой команды некоторое внешнее
устройство запрашивало прерывание.
Прерывание по трассировке передает
управление в ячейку,
определенную вектором прерывания 4. Во
время процедуры прерывания
микропроцессор сбрасывает флаг
специального прерывания. Это
позволяет программе обработки прерывания
по трассировке избежать
прерывания после каждой команды. Когда
обработчик прерывания по
трассировке возвращает управление
программе пользователя, он
восстанавливает начальное состояние
регистра флагов, в котором флаг
трассировки установлен. Микропроцессор
выполняет следующую команду
пользователя, и снова возникает
специальное прерывание. Обработчик
прерываний по трассировке получает
управление после каждой команды
до тех пор, пока программа пользователя не
сбросит флаг захвата.
Отладчик DOS использует флаг трассировки.
Одной из функций
отладчика является пошаговое выполнение,
при котором перед каждым
возвращением управления к отладчику
выполняется одна команда
программы пользователя. Это прерывание
инициируется флагом захвата.
Полное описание процедуры прерывания дано
в разделе "Векторы
прерываний".
Флаг прерываний
Флаг прерываний IF управляет внешними
прерываниями. Во время
выполнения тех фрагментов программы
пользователя, где внешние
прерывания разрешать нежелательно,
программа может сбросить флаг
прерываний. Пока флаг прерываний сброшен в
0, никакие внешние
прерывания не смогут возникнуть. Когда
программа устанавливает флаг
прерываний равным 1, внешние устройства
могут порождать прерывания.
Управляет флагом прерываний программа
пользователя.
IBM PC использует несколько методов
обслуживания прерываний.
Флаг прерываний регистра состояния
блокирует все внешние
прерывания, за исключением прерываний,
вызванных ошибками памяти.
Для тех случаев, когда программе надо
заблокировать только
некоторые из прерываний, существует
отдельный регистр масок
прерываний. Этот регистр может запретить
или разрешить отдельные
внешние прерывания. В гл.8, описывающей
аппаратуру IBM PC, будет
рассмотрено использование этого регистра.
Флаг направления
Последним флагом в регистре флагов
является флаг направления 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 для пересылки
данных внутри буфера
дисплея. Когда программа передвигает
изображение на экране вверх,
команды пересылают данные в меньшие адреса
памяти. Когда программа
опускает символы на экране вниз, команды
пересылают данные в
большие адреса памяти. В каждом случае
программа устанавливает или
сбрасывает флаг направления в соответствии
с направлением
пересылаемых данных.
Векторы прерываний
Еще одна важная составная часть
микропроцессора 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 мы
приведем несколько
примеров, которые покажут, использование
такого подхода.