Assembler для начинающих

         

Биты байты и слова



Биты, байты и слова


    Мы назвали "битом" двоичную цифру, еденичное значение 0 или 1.  Для
    удобства введем специальные названия для некоторых последо-
    вательностей битов.  Группу из 8 бит принято называть байтом.  Во
    всей документации IBM и в этой книге о любых 8 битах информации
    говорится как о байте.  Байт заслужил свое собственное имя по
    нескольким причинам.  Элементарная ячейка памяти имеет длину 8 бит.
    При каждом обращении к паамяти IBM PC для процессора запрашивыается
    ровно 8 бит информации.  Как мы увидим позднее, отдельные команды
    8088 могут производить арифметические и логические опреации над
    группами в 8 бит.  Байт - наименьшая еденица информации, с которой
    8088 может манипулировать непосредственно.  8088 может одной
    операцией сложить два 8-битовых числа, но не может этого проделать
    с 4-битовыми.  Кроме того IBM PC использует байт для представления
    одного символа.  Используя один байт можно представить 256 (2**8)
    отдельных элементов, таких, например, как графические символы.  В
    следующем пункте мы рассмотрим набор символов IBM PC.
 
      Поскольку байт является элементом памяти, мы должны иметь сред-
    ство определения в ней отдельных байтов.  Задача ассемблера


    фактически и будет состоять в определении содержимого памяти для
    выполнения программы.  В основном исходный текст ассемблера состоит
    из выполняемых инструкций.      Но для помещения определенного значения
    в байт памяти ассемблер располагает специальным механизмом -
    определением байта (dtfine byte) или псевдокомандой DB.  DB не
    является командой 8088.  Это команда ассемблеру поместить в память
    определенные значения.  Псевдокоманда
 
      DB   23
 
    дает ассемблеру задание сохранить десятичное значение 23 в текущий
    байт памяти.  А оператор
 
      DB   1,2,3,4
 
    сохраняет значения от 1 до 4 по четырем последовательным адресам в
    памяти.
 
      В программах на языке ассемблера оператор DB применяют для
    определения областей памяти.  В предыдущих примерах мы размещали в
    памяти определенные значения.  Это может быть поисковая таблица или
    информация для перекодировки чисел.  Мы составим несколько
    примеров, в которых используется определенная подобным образом
    информация.  Кроме того встречаются ситуации, когда программе
    требуется место в памяти для сохранения данных в процессе
    исполнения.  Во время ассемблирования программы содержимое этого
    участка памяти неизвестно:      собственно, это содержимое будет
    переменным во время исполнения программы.  Инструкция
 
      DB   ?
 
    сообщает ассемблеру о необходимости выделить один байт памяти, не
    изменяя его содержимое.  В Этом байте может оказаться любое
    случайное число, которое будет там оставаться пока какая-либо
    команда не поместит в него определенное значение.
 
      Нам может потребоваться выделить и большое количество байтов,
    например, чтобы оставить область памяти для массива.  Мы можем это
    сделать так:
 
      DB   25 DUP(?)
 
    Этой инструкцией выделяется 25 байт памяти.  Ключевое слово DUP в
    этой псевдокоманде означает повторить (duplicate).      Число 25
    указывает, сколько раз ассемблер повторит определение байта в
    памяти.  Значение или значения в скобках ассемблер использует для
    инициализации этой области памяти.    В данном случае это значение
    неизвестно.  Для инициализации области с одним и тем же значением
    выражение, например,
 
      DB 17 DUP(31)
 
    создает 17 байт со значением 31 каждый.  Наконец,
 
      DB 30 DUP(1,2,3,4,5)
 
    выделяет 30 байт со значениями от 1 до 5 в первых пяти байтах. Сле-
    дующие пять байт тоже имеют значения от 1 до 5 и т.д.  Ассемблер
    повторяет значения в скобках пока не будут заполнены все 30 байт.
 
      Иногда нам хочется обратиться к набору бит меньшему чем байт.
    Принят размер 4 бит.  В 4 битах мы можем представить все 10
    десятичных цифр.  Для значений такого размера мы будем
    пользоваться термином "полубайт".  Этот термин (в оригинале
    "nybble" - прим.  перев.), который достиг широкого применения,
    позволяет нам говорить о данных, меньших, чем "байт".
 
      Термин "Слово" имеет для программиста значение отличное от
    принятого в языке.  В применении к ЭВМ слово - это наибольшее
    количество бит, с которым машина может обращаться как с единым
    элементом.    Для системы IBM/370 слово составляет 32 бит, а для
    семейства Intel 8088 - 16.      Поэтому термин "слово" имеет
    неопределенный смысл, пока не известна конкретная машина.
 
      Размер слова в 8088 составляет 16 бит.    Этот размер
    определяется каналами передачи данных в процессоре.  Над числами до
    16 бит 8088 может призводить операции одной командой.  Любое более
    крупное число потребует более одной команды.  Существуют команды,
    которые манипулируют и с меньшими объемами памяти, как, например,
    команда сложения двух 8-битовых чисел.  Несколько инструкций
    позволяют манипулировать и с отдельными битами.  Но для сложения
    двух 32-битовых чисел потребуется уже две команды, складывающие по
    16 бит каждая.  Наибольшее число над которым мы можем производить
    элементарные операции типа сложения имеет размер машинного слова.
 
      Аналогично команде определения байта памяти, существует и
    инструкция для определения слова памяти.  Оператор ассемблера DW
    означает определение слова (defie word).  Первое утверждение DW на
    Фиг.  2.10 определяет 16 бит памяти со значением 1234H.  Как и в
    случае с байтами, мы можем использовать оператор DUP для
    определения больших областей памяти, разбитых на слова.  И точно
    также, для обозначения неинициируемых областей можно использовать
    операнд "?".

            Microsoft (R) Macro Assembler Version 5.00              10/29/88
              16:10:44
            Фиг. 2.10 Примеры определения слов                Page         1-1
 
                 1                                    PAGE    ,132
                 2                                    TITLE   Фиг. 2.10 Примеры определения слов
                 3
                 4 0000  1234                   DW      1234H
                 5 0002  0003[                        DW      3 DUP(5678H)
                 6            5678
                 7                         ]
                 8
                 9 0008  ????                   DW      ?
                10
                11                                    END
 
                                    Фиг. 2.10 Примеры определения слов
 
      Одна из обескураживающих черт 8088 - это его манера хранения
    слов в памяти.  На Фиг.  2.10, хоть мы и определяли значение слова
    как 1234Н, ассемблер сохранит в памяти значение 3412Н, по крайней
    мере так это выглядит.  Посмотрим, как это получается.
 
      Допустим, слово 1234Н сохранено в ячейках 100 и 101.  8088 тре-
    бует, чтобы ассемблер поместил значение 34Н в ячейку 100, а 12Н - в
    101.  Легче всего запомнить это так, что ассемблер сохраняет
    младший байт слова в ячейку памяти с меньшим адресом, а старший
    байт - с большим.  На Фиг.      2.11 показано, содержимое памяти после
    того, как ассемблер поместит в нее данные.  Пока вы не привыкнете к
    такому методу, вам
 
                  Адрес    Значение
                 -------------------------
                    .          .
                    .          .
                   100        34Н
                   101        12Н
                    .          .
                    .          .       Фиг. 2.11 Представление
                 -------------------------          в памяти  DW 1234H
 
    будет казаться, что все в памяти наоборот.  К счастью, если вы не
    будете смешивать операции над байтами и над словами одной и той же
    области в памяти, вам незачем беспокоиться об этом неожиданном
    "переключении" байтов.  Программа может спокойно работать со
    словами, а 8088 всегда разберется что к чему.  Только в том случае,
    если вы захотите обратиться к конкретному байту какого-либо слова,
    вам придется иметь дело с фактическим способом хранения слов в
    памяти семейства 8088.  Ассемблер обращает внимание на структуру
    слов в распечатке программы, то есть изображает слова в объектном
    коде как слова, а не как байты, которые выглядели бы перевернутыми
    наоборот.  Вы сможете различать слова благодаря тому, что ассемблер
    записывает их шестнадцатеричными цифрами без пробелов.
 
      Однако остался еще один тип данных, который постоянно использу-
    ется программах на языке ассемблера для микропроцессора 8088.  Это
    - двойное слово, значение в 32 бита длиной.  Программы пользуются
    двойными словами для хранения адресов и очень больших чисел.  Чтобы
    определить область, содержащую значение двойного слова, оператор
    ассемблера
 
      DD   значение
 
    генерирует поле размером в 4 байта.  DD означает операцию выделения
    двойнго слова (define doubleword).    Так же как в случае с DW -
    опратором, ассемблер размещает в памяти младший байт ниже, а
    старший - выше.  В таком же порядке сохраняются средние два байта.
    Аналогично операторам DB и DW вы можете пользоваться функцией DUP и
    применять операнд "?" для того чтобы оставить область
    неопределенной.
 
      Ассемблер может генерировать и другие структуры данных.  Их об-
    суждение мы отложим, пока не дойдем до некоторых свойств макроас-
    семблера и сопроцессора 8087.  Остальные структуры данных
    используются в программах прежде всего для очень больших чисел в
    Числовом сопроцессоре или для определения собственных структур
    данных.




Двоичная арифметика



Двоичная арифметика


    Все компьютеры используют для хранения информации двоичную систему.
    Это значит, что каждый элемент хранимой информации может иметь
    только два состояния.  Эти состояния обозначаются как "включен" и
    "выключен", "истина" и "ложь", или "1" и "0".  Компьютер хранит эти
    значения в виде уровней напряжения.  К счастью у нас нет нужды свя-
    зываться с напряжением.  При написании программ мы имеем дело
    только с числами.  Используя простейшие числа 0 и 1, можно
    выполнять очень сложные вычисления.  Из-за двоичного представления
    данных компьютеры используют в своих вычислениях арифметику с
    двоичным основанием.  Арифметика с основанием 2 пользуется только
    двумя цифрами:  0 и 1.  Мы обычно применяем систему исчисления по
    основанию 10.  В десятичной арифметике употребляется десять
    различных цифр - от 0 до 9.  Двоичную арифметику можно представить
    себе как систему для людей, имеющих только два пальца.
 
      Ограничение лишь десятью цифрами в десятичной арифметике не ме-
    шает нам представлять более крупные числа.  Мы пользуемся
    многозначными числами, в каждой позиции которых стоят разные
    степени 10.  Самая правая цифра любого числа обозначает число
    едениц, соседняя слева - количество десятков, следующая - число
    сотен и т.д.  Прогрессия справа налево выстраивается такая:
    10**0, 10**1, 10**2 и т.д.      Число 2368 в дейстительности
    представляет 2 тысячи, 3 сотни, 6 десятков и 8 едениц.  Фиг.  2.1
    показывает ,говоря матическим языком, разложение числа 2368.
 
      _____________________________________________________
      2368 = 2 * 10**3 + 3 * 10**2 + 6 * 10**1 + 8 * 10**0
           = 2000    +     300    + 60    +    8
      _____________________________________________________
                Фиг. 2.1 Десятичное представление
 
      Арифметика с основанием 2 или двоичная система аналогична деся-
    тичной, за исключением того, что разряды числа здесь соответствуют
    степеням 2 а не 10.  Числа больше 1 представляются многозначными
    числами, так же как в десятичной арифметике многозначное представ-
    ление получают числа больше 9.  Каждая цифра в двоичной системе
    называется бит от Binary digIT (двоичная цифра).  Позиция каждого
    бита в числе соответствует некоторой степени 2.  Фиг.  2.2
    показывает значение двоичного числа 101101B.
       101101B = 1*2**5 + 0*2**4 + 1*2**3 + 1*2**2 + 0*2**1 + 1*2**0
             =   32     +   0 +   8        +   4    +   0    +   1
             =  45
       ____________________________________________________________
              Фиг. 2.2 Двоичное представление
 
      Мы будем пользоваться суффиксом "B" для обозначения чисел в
    двоичном представлении.  Этим они будут отличаться от десятичных,
    не имеющих суффикса.  Например, 2368 - это десятичное число, а
    101101B - двоичное.  В математической литературе для обозначения
    системы исчисления обычно используется индекс.  Мы будем
    пользоваться символом "B", поскольку ассемблер IBM для обозначения
    двоичных чисел применяет именно его.
 
      В таблице Фиг.    2.3 для представления максимального десятичного
    числа требуется 4 бита.  Для более крупных чисел потребуется еще
    больше бит.  Двоичным числом состоящим из n бит можно изобразить
    число величиной 2**n-1.  То есть двоичное число в n бит длиной
    может
 
                       Десятичное   Двоичное
                      -----------------------
                         1          1
                         2           10
                         3           11
                         4          100
                         5          101
                         6          110
                         7          111
                         8         1000
                         9         1001
                        10         1010
                      -----------------------
                     Фиг. 2.3 Первые 10 целых
 
    единственным образом представить любое целое от 0 до 2**n-1.  Для
    4-х битового примера на Фиг 2.3 самое большое такое число равно 15
    (2**4-1).
 
      Для каждого конкретного микропроцессора сществует максмальный
    размер двоичных чисел, которые могут быть в нем представлены.  Для
    микропроцессора 8088, используемого в IBM PC, внутренние операции
    производятся над числами длиной 16 бит.  Максимальное целое, кото-
    рое можно представить в 16 битах, равно 2**16-1 или 65 535.  Однако
    такая беззнаковая арифметика допускает числа только от 0 до 65 535.
    Для обозначения отрицательных чисел нам потребуются изменения в
    этой схеме.





Двоичное дополнение



Двоичное дополнение


    Для изображения как положительных, так и отрицательных чисел 8088
    применяет арифметику двоичного дополнения.  В такой знакопеременной
    арифметике самый левый бит целого числа указывает на его знак.  По-
    ложительные числа имеют 0 в старшем бите, а отрицательные - 1.  По-
    ложительные числа имеют одинаковое значение в знаковой и без-
    знаковой арифметике.  У отрицательных же значение иное.  Для того
    чтобы сделать число отрицательным, изменить его знак на минус, оно
    дополняется и результат увеличиваетя на единицу.  В 4-х битовом
    примере 5 имеет значение 0101B, в то время как -5 равно 1011B.
    Пример на Фиг.  2.4 показывает этот метод.
 
      Пример на Фиг.    2.5 показывает единственность нуля в арифметике
    двоичного дополнения.  То есть -0 равен 0.  Для любого n-битового
    числа в системе с двоичным дополнением наибольшее значение сос-
    тавляет 2**(n-1)-1, а наименьшее -2**(n-1).  Ноль - единственен.  В
    4-х битовой системе наибольшее число, как показано на Фиг.    2.6,
    равно 7, а наименьшее -8.
 
    _________________________
      5        =       0101B
 
    Для изменения знака:
     Дополняем           1010B
     Добавляем 1       0001B
                   -----         __________________________________
     -5        =       1011B       0            =       0000B
 
    Для изменеия знака:          Для изменения знака:
     Дополняем           0100B          Дополняем           1111B
     добавляем 1       0001B      Добавляем 1       0000B
                   -----                        -----
      5     =            0101B          -0            =       0000B    =    0
    _________________________    __________________________________
    Фиг. 2.4 Двоичное допол-     Фиг. 2.5  То же самое с нулем
           нение 5
 
      Как мы увидим позднее, процессор 8088 может работать с одним и
    тем же целым как со значением имеющим или не имеющим знак.    Выбор
    оставлен программисту языка ассемблера.  Во всяком случае, когда
    программа работает в арифметике со знаком, процессор 8088 применяет
    при проведении операций двоичное дополнение.
 
      Десятичное  Двоичное        Десятичное    Двоичное
      -------------------------------------------------
          7      0111          -1   1111
          6      0110          -2   1110
          5      0101          -3   1101
          4      0100          -4   1100
          3      0011          -5   1011
          2      0010          -6   1010
          1      0001          -7   1001 Фиг. 2.6 Числа
          0      0000          -8   1000     с  двоичным
      -------------------------------------------------   дополнением




Assembler для начинающих



Глава 2


Основы компьютерных вычислений
    В этой главе разъясняются свойства компьютеров.  Она расскажет вам
    как компьютеры работают и почему они делают это именно так.  Неко-
    торые положения могут оказаться знакомыми вам.  Если у вас нет опы-
    та программирования на языке ассемблера, то многие операции будут
    для вас новыми.


Машинный язык и язык Ассемблера



Машинный язык и язык Ассемблера


    Мы уже видели, как из нулей и единиц, хранимых в ЭВМ, формируются
    числа.Теперь мы посмотрим как комбинации тех же значений 0 и 1
    могут быть использованы для программирования компьютера.
 
      Машинная программа представляет собой последовательность ко-
    манд (инструкций).  Эти команды "объясняют" компьютеру, что он
    должен делать.  Это похоже на рецепты в кулинарной книге.  В
    рецепте имеется описание действий, которые необходимы для
    приготовления определенного блюда.    Подобным образом, компьютер
    имеет последовательность команд, которые точно описывают ему
    последовательность действий.  Этот набор команд называется
    программой.  Процесс построения корректного набора команд
    называют программированием компьютера.  В нашей аналогии с рецептом
    рецепт является программой, а тот кто его написал - программистом.
    Роль компьютера здесь играет повар, готовящий еду.
 
      Реальная программа, которую выполняет компьютер, это последова-
    тельность едениц и нулей, связанных с памятью компьютера. Эту стро-
    ку бит принято называть машинным языком. Машинный язык  -  это  тот
    язык который машина понимает. Компьютер извлекает команды машинного
    языка из памяти точно определенным способом. Затем компьютер выпол-
    няет команду, обозначенную данной конфигурацией бит. Этот цикл изв-
    лечения и исполнения будет разобран в одном из последующих разделов
    данной главы.
 
      Однако машинный язык мало о чем говорит людям.  Если вы хотите
    сложить два числа в 8088 (например, содержимое регистров AX BX -
    краткое описание регистров сейчас последует), команда будет
    выглядеть таким образом:
 
      0000001111000011B  (или 03C3H)
 
    Эти два байта точно указывают компьютеру какую опреацию произвести.
    Аналогично, для вычитания двух чисел (вычитание регистра BX из ре-
    гистра AX) мы будем иметь в машинном языке
 
      0010101111000011B  (или 2BC3H)
 
    Здесь необходимо коротко пояснить, что такое регистры, поскольку в
    обсуждении основ работы 8088 с ними приходится чато сталкиваться.
    Регистр - это часть процессора, предназанченная для сохранения дан-
    ных.  К данным, сохраненным в регистре, процессор получает доступ
    очень быстро - намного быстрее, чем к данным, хранимым в памяти.
    Возможно еще специальное использование регистров в некоторых коман-
    дах.  В третьей главе будет дано полное описание регистров 8088.
      Хотя машинный язык - это действительно прекрасно, если вы явля-
    етесь компьютером, он труден для программистов - людей.  К счастью,
    существует более простой способ программирования.  Этим методом,
    более близким людям, чем машинам, является программирование на
    языке ассемблера.
 
      Язык ассемблера, как язык программирования, т.е. более понятный
    программисту, чем  машинный, язык, все  еще сохраняет все  значения
    машинного языка.  Компьютер читает программы на  языке ассемблера и
    переводит их в машинный язык, в ту форму, которая понятна ЭВМ. Этот
    процесс,   называемый   "ассемблированием"   программы,  фактически
    является переводом  с одного языка    на другой. Операцию  перевода с
    языка ассемблера  на машинный язык    выполняет программа, называемая
    ассемблером.
 
      Чтобы лучше понять разницу, давайте взглянем на примеры,
    которыми мы уже пользовались выше.    Ассемблерная команда для
    сложения содержимого регистров AX и BX проста:
 
      ADD   AX,BX
 
    Аналогично, для вычитания регистра BX из регистра AX мы напишем:
 
      SUB   AX,BX
 
    Ассемблер превращает эти предложения в форму, которую мы видели вы-
    ше.  Компьютер сам управляется с проблемой превращения файла с по-
    нятным человеку текстом в программу на машинном языке, которую мог
    бы выполнить процессор.
 
      Язык ассемблера не похож на языки типа Фортран, Кобол или
    Паскаль.  Эти языки, как и многие подобные им, являются языками
    высокого уровня.  Языки высокого уровня разработаны для того, чтобы
    можно было иметь дело непосредственно с задачей, решаемой
    программой.  В этом качестве они иногда называются процедурными
    языками, поскольку описывают процедуру, используемую для решения
    задачи.  Языки высокого уровня машинно-независимы.      Программа,
    написанная на Фортране для IBM PC будет правильно работать и давать
    те же самые результаты при выполнении на IBM/370.  язык
    программирования не зависит от машины.
 
      Програмы же на языке ассемблера непосредственно относятся к той
    машине, на которой они должны выполняться. Язык  ассемблера  машин-
    нозависимый. Язык  ассемблера для IBM  PC принципиально отличен  от
    языка ассемблера для IBM/370. Это  связано с тем, что команды языка
    ассемблера    почти один  к  одному  переводятся в  команды машинного
    языка  т.е. каждая  команда языка  ассемблера обычно  преобразуется
    точно  в одну  команду  машинного  языка. Поскольку  машинные языки
    разных  компьютеров различны,  то различаются  и языки  ассемблера.
    Обычно каждое утверждение языка  ассемблера генерирует одну команду
    машинного  языка.  В  некоторых  случаях  это  не  так,  потому что
    существуют команды, которые не являются частью выполняемой програм-
    мы, а предназачены для ассемблера. Они описывают действия ассембле-
    ра, который  должен выполнять их  во время ассемблирования.  Пример
    директивы  ассемблеру   (такой  предназначенной  только   для  него
    команды) -
 
      TITLE Пример Программы
 
    Эта инструкция сообщает ассемблеру заголовок программы.  После
    трансляции ассемблером прграммы этот заголовок - "Пример
    программы"- появляется в верхней части каждой страницы сообщений
    ассемблера.  Эта инструкция имеет смысл только для ассемблера.  В
    8088 нет команды, которая могла бы выполнить эту опреацию.




Набор символов



Набор символов


    Как мы заметили выше, мы можем рассматривать каждый байт информации
    не как двоичное число, а как символьное значение.  Каждое из
    двоичных чисел от 0 до 255 может представлять определенный символ.
    Фиг.  2.13 показывает множество символов IBM PC.  Колонки здесь
    соответствуют старшим 4 битам символьного кода, а ряды - младшим 4
    битам этого кода.  Так, позиция таблицы 41Н соответствует символу
    "A", а код 5ЕН представляет символ "^".

            ЪДДДВДДДТДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї   ЪДДДВДДДТДДДВДДДВДДДВДДДВДДДВДДДВДДДВДДДї
            ідесі= >є 0 і16 і32 і48 і64 і80 і96 і112і   ідесі= >є128і144і160і176і192і208і224і240і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і   ішстє 0 і 1 і 2 і 3 і 4 і 5 і 6 і 7 і   і   ішстє 8 і 9 і A і B і C і D і E і F і
            ЖНННШНННОНННШНННШНННШНННШНННШНННШНННШНННµ   ЖНННШНННОНННШНННШНННШНННШНННШНННШНННШНННµ
            і 0 і 0 єпусі > іпрбі 0 і @ і P і ` і p і   і 0 і 0 є А і Р і а і ° і А і Р і р і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 1 і 1 є   і < і ! і 1 і A і Q і a і q і   і 1 і 1 є Б і С і б і ± і Б і С і с і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 2 і 2 є   і     і " і 2 і B і R і b і r і   і 2 і 2 є В і Т і в і І і В і Т і т і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 3 і 3 є   і ! і # і 3 і C і S і c і s і   і 3 і 3 є Г і У і г і і і Г і У і у і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 4 і 4 є   і     і $ і 4 і D і T і d і t і   і 4 і 4 є Д і Ф і д і ґ і Д і Ф і ф і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 5 і 5 є   і     і % і 5 і E і U і e і u і   і 5 і 5 є Е і Х і е і µ і Е і Х і х і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 6 і 6 є   і     і & і 6 і F і V і f і v і   і 6 і 6 є Ж і Ц і ж і ¶ і Ж і Ц і ц і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 7 і 7 є   і     і ' і 7 і G і W і g і w і   і 7 і 7 є З і Ч і з і · і З і Ч і ч і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 8 і 8 є   і     і ( і 8 і H і X і h і x і   і 8 і 8 є И і Ш і и і ё і И і Ш і ш і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і 9 і 9 є   і     і ) і 9 і I і Y і i і y і   і 9 і 9 є Й і Щ і й і № і Й і Щ і щ і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і10 і A є   і     і * і : і J і Z і j і z і   і10 і A є К і Ъ і к і є і К і Ъ і ъ і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і11 і B є   і     і + і ; і K і [ і k і { і   і11 і B є Л і Ы і л і » і Л і Ы і ы і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і12 і C є   і     і , і < і L і \ і l і | і   і12 і C є М і Ь і м і ј і М і Ь і ь і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і13 і D є   і     і - і = і M і ] і m і } і   і13 і D є Н і Э і н і Ѕ і Н і Э і э і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і14 і E є   і     і . і > і N і ^ і n і ~ і   і14 і E є О і Ю і о і ѕ і О і Ю і ю і   і
            ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ   ГДДДЕДДДЧДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДЕДДДґ
            і15 і F є   і     і / і ? і O і _ і o і  і   і15 і F є П і Я і п і ї і П і Я і я і   і
            АДДДБДДДРДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ   АДДДБДДДРДДДБДДДБДДДБДДДБДДДБДДДБДДДБДДДЩ
 
                               Фиг. 2.13 Набор символов IBM
 
     Набор символов IBM PC является расширением набора символов
    ASCII (Американский стандартный код для обмена информацией).  В
    наборе ASCII значения символов от 20Н до 7ЕН представляют обычные
    символы латинского алфавита, числовые символы и знаки препинания.
    Коды от 0Н до 1FH обычно служат управляюшими символами.  На Фиг.
    2.14 показаны управляющие символы ASCII из этого диапазона.  Эти
    символы имеют значение при передаче на принтеры IBM или другие
    ASCII-принтеры.  Однако на Фиг.  2.13 видно, что эти управляющие
    символы могут также появляться на экране в виде графических симво-
    лов.  В IBM PC управляющая часть таблицы ASCII используется для
    графических изображений, с целью более полно реализовать возмож-
    ности видеоадапторов.  Поскольку видеоадапторы могут изобразить
    любой из 256 кодов, то нет оснований строго регламентировать
    применение какого-либо из кодов.  Разработчики рассматривали все 32
    символа из управляющей части таблицы как предназначенные главным
    образом для графического изображения и обычно не печатаемые
    принтером.    Короче говоря, первые 32 значения являются управляющими
    кодами при передаче их на принтер, но изображаются как графические
    символы при выводе их на дисплей.
 
      Символьные значения от 80Н до 0FFH являются расширением набора
    символов ASCII для IBM PC.      Эти символы подобраны разработчиками
    IBM так, чтобы расширить изобразительные возможности компьютера.
    Наборы иностранных, графических и научных символов позволяют
    использовать IBM PC в самых разнообразных приложениях.
 
        Код  Символ            Значение
      ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
         0  NUL   Пусто
         7  BEL   Сигнал
         9  HT    Горизонтальная табуляция
        0A  LF    Пропуск строки
        0B  VT    Вертикальная табуляция
        0C  FF    Прогон страницы
        0D  CR    Возврат каретки
        0E  SO    Шаг назад
        0F  SI    Шаг вперед
        11  DC1   Управление 1
        12  DC2   Управление 2
        13  DC3   Управление 3
        14  DC4   Управление 4
        18  CAN   Стоп
        1B  ESC   Выход
      ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД  Фиг. 2.14 Управляющие коды IBM
 
      В некоторых случаях вы захотите вводить символьные коды в па-
    мять для их дальнейшего использования программой.  Примером может
    служить сообщение, которое в определенный момент выполнения прог-
    раммы должно быть выдано оператору.  Вместо потска кодов символов в
    таблице, мы можем сразу ввести строку символов в текст программы.
    Ассемблер позволяет это сделать с помощю оператора DB.  В поле

          Microsoft (R) Macro Assembler Version 5.00              10/31/88 22:30:38
          Фиг. 2.15 Определение байтов для текста ASCII           Page  1-1
 
               1                              PAGE    ,132
               2                              TITLE   Фиг. 2.15 Определение байтов для текста ASCII
               3
               4 0000  9D E2 AE 20 E1 AE AE         DB          'Это сообщение',10,13
               5     A1 E9 A5 AD A8 A5 0A
               6     0D
               7
               8                              END
 
                            Фиг. 2.15 Определение байтов для текста ASCII
    операндов мы вместо ввода чисел (кодов) помещаем заключенную в
    кавычки строку символов.  Ассемблер подберет соответствующие
    значения кодов и поместит их в память - каждый символ в отдельный
    байт.  Так ассемблер может работать только с символами в диапазоне
    от 20Н до 0FFH.  в диапазоне от 0Н до 1FH символы должны вводиться
    в программу в виде чисел, а не ограниченной кавычками строки.  Это
    связано с тем, что в тексте исходного файла некоторые управляющие
    символы используются для обозначения начала и конца строки.
 
      Пример на Фиг.    2.15 показывает создание 15 байт данных в
    программе.    Первые 13 байтов соответствуют 13-ти символам текстовой
    строки заключенной в кавычки.  Первый байт имеет значение 9DH,
    второй 0E2H и т.д.  Последние два байта в 17-ти байтном сообщении -
    это коды возврата каретки и прогона строки.  Если мы отправим это
    17-байтное собщение на принтер, он напечатает заключенный в кавычки
    текст.  Управляющие символы предписывают принтеру перейти после
    этого на следующую строку документа.




Нумерация бит



Нумерация бит


    Иногда нам будет требоваться идентифицировать отдельные биты в бай-
    те или слове.  Для этого мы называем номер бита.  Индекс или номер
    каждого бита - это степень двойки, соответствующая позиции этого
    бита.  Самый младший бит - нулевой, поскольку он представляет два в
    нулевой степени.  Самый старший бит в байте - седьмой - 2**7.
    Самый старший бит в слове - 15-й.  Фиг.  2.12 показывает 16-битовое
 
            15 14 13 12 11 10  9  8  7  6  5    4  3  2  1  0
           ЪДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДї
           АДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДБДДЩ
 
                   Фиг. 2.12 Нумерация бит
 
    слово с пронумерованными битами.  Такой способ нумерации бит принят
    во всей документации IBM PC.




Прерывания



Прерывания


    Механизм прерываний - существенная часть любой вычислительной сис-
    темы.  Как мы увидим, он важен и для IBM PC.  Структура прерываний
    предоставляет эффективное средство для связи устройств вводоа-выво-
    да с процессором.  Нам прерывания интересны потому, что управление
    прерываниями - прерогатива программирования на языке ассемблера.  В
    языках высокого уровня отсутствуют средства для работы с
    прерываниями на машинном уровне.
 
      Прерывания обычно вызываются внешними устройствами.  Прерывание
    сигнализирует процесору о необходимости прервать текущие действия и
    ответить внешнему устройству.  В IBM PC клавиатура посылает сигнал
    прерывания всякий раз при нажатии любой клавиши.  Прерывание
    клавиатуры заставляет процессор прекратить текущую деятельность и
    считать набранный на клавиатуре символ.
 
      Легко понять, за что прерывания получили свое название.  Преры-
    вание сигнализирует о необходимости "прервать" текущее действие
    процессора.  Прерывания хороши тем, что избавляют процессор
    постоянного контроля за внешними устройствами.  Если бы, например,
    клавиатура пользователя не вызывала прерываний, то процессор был бы
    вынужден непрерывно проверять клавиатуру, чтобы обнаружить нажатие
    клавиши.  Каждая написанная для компьютера программа была бы
    вынуждена делать одно и то же, и им пришлось бы очень часто
    тестировать клавиатуру.  Но наличие прерываний снимают это
    требование, и программа может выполняться без постоянного
    тестирования клавиатуры.  Каждый раз, как клавиатура получает
    какую-либо информацию, она сигнализирует об этом процессору.  После
    того, как микропроцессор удовлетворит запрос клавиатуры, он может
    возобновить нормальный ход выполнения программы.
 
      Работа 8088 с прерываниями во многом напоминает его обращение с
    процедурами.  Прерывание не может прекратить работу процессора во
    время выполнения команды.  Сначала 8088 закончит выполнение текущей
    команды, но следующую уже проигнорирует.  Вместо ее выполнения про-
    цессор действует так, как будто следующая команда была вызовом про-
    цедуры.  Он сохраняет адрес очередной команды в стеке и переходит в
    специальную процедуру, которую называют программой обработки
    прерываний.  Эта процедура содержит команды для работы с вызвавшим
    прерывание устройством.  В случае с клавиатурой программа обработки
    прерывания считывает символ и сохраняет его для дальнейшего
    использования.  После того как она закончит работу с устройством,
    происходит возврат в точку прерывания.  Процессор извлекает из
    стека адрес возврата и продолжает выполнение программы как будто
    ничего не случилось.
 
      Поскольку прерывание вызывается внешним устройством, оно может
    произойти в любой момент выполнения программы.  Программа не может
    предпринять каких-либо действий чтобы подготовиться к прерыванию,
    так как не может предвидеть, когда пользователь нажмет на клавишу
    клавиатуры.  Отсюда следует, что прерывание не должно изменять
    данные в прерываемой программе.  Если прерывание иозменит
    какое-либо значение в программе, то она не сможет нормально
    работать когда к ней вернется управление.
 
      В ходе прерывания 8088 автоматически сохраняет некоторые уста-
    новленные программой значения в стек.  В свою очередь, программа
    обработки прерываний отвечает за сохранение любых других данных,
    которые она может изменить во время своего выполнения.  Эти данные
    обычно сохраняются в стеке.  Затем, перед возвращением управления в
    прерванную программу, программа обработки прерывания должна вернуть
    измененным данным те значения, которые они имели в момент
    прерывания.  Факт возникновения прерывания должен остаться
    "невидимым" для выполняемой программы.
 
      Поскольку сигнал прерывания могут посылать процессору многие
    устройстваэ, 8088 имеет механизм ориентации прерываний.  Это озна-
    чет, что 8088 определяет, какое устройство вызвало прерывание и пе-
    редает управление программе обработки прерывания, соответствующей
    этому устройству.  Процессор атоматически управляет веторизацией
    зап росов на прерывания.  Программе обработки прерывания не
    требуется перед обработкой прерывания определять, какое устройство
    его вызвало.  Это сокращает время реакции на прерывание и упрощает
    программирование прерываний.
 
      В программамах встречаются такие участки, выполнение которых не
    может быть прервано.  Например, это может быть кусок программы,
    который должен выполняться очень быстро, чтобы закончить выполнение
    специфической задачи, или момент работы с данными, которые могут
    быть изменены программой обработки прерывания.  В обоих случаях
    программа должна иметь возможность задержать или предотвратить
    прерывания, т.е.  программа должна уметь не допускать возникновения
    прерываний во время выполнения таких критических участков.    После
    прохождения этих участков программа должна восстановить способность
    системы прерываний вызывать прерывания.  Программа не может
    отключать прерывания на слишком долгое время, иначе с устройством,
    запросившим прерывание, может произойти какая-нибудь неприятность.
    Если прерывание клавиатуры не считает символ до того как оператор
    нажмет другую клавишу, второй симол может быть потерян.  В 8088
    имеется возможность блокировать все внешние прерывания.  IBM PC
    имеет более развитую возможность выбирать, каким из устройств можно
    вызывать прерывание, а каким нет.  Программа может использовать эту
    возможность для выбора наиболее важных устройств, которым можно
    разрешить прерывания, а менее критическим запретить.  Способы
    отключения прерываний мы обсудим в следующих главах.




Принципы работы Ассемблера



Принципы работы Ассемблера


    Рассмотрим теперь работу ассемблера в целом.  Детали будут
    обсуждены позднее, но сейчас нам нужно ввести новые термины и
    ознакомиться с реальным результатом работы ассемблера.
 
      Ассемблер берет программу, написанную на языке ассемблера, и
    превращает ее в машинный язык.  Файл, который содержит программу на
    языке ассемблера, называют исходным файлом.  Выход и ассемблера в
    действительности является не собственно машинным языком, а
    некоторым промежуточным представлением программы.  Этот выходной
    файл называют объектным файлом.  Данные в нем называются объектным
    кодом.  Для получения из него настоящего машинного кода объектный
    код должен быть несколько изменен.    Для IBM PC это делает программа
    редактор связей LINK.  Шаг преобразования объектных кодов в
    машинные принято называть построением связей или редактированием
    связей.  Как пользоваться редактором связей мы увидим в одной из
    следующих глав.
 
      Помимо преобразования исходного кода в объектный ассемблер
    создает несколько других выходных файлов.  Один из них -
    ассемблерный листинг.  Он содержит сообщение о действиях
    ассемблера.  Зтот файл содержит исходный код вместе с
    комментариями, а также объектный код, сформированный ассемблером.
    Фиг.  2.9 дает пример листинга ассемблера, иногда называемого
    распечаткой.
           Microsoft (R) Macro Assembler Version 5.00              10/28/88
             16:35:34
           Фиг. 2.9 Пример ассемблирования                        Page   1-1
 
 
                1                              PAGE    ,132
                2                              TITLE   Фиг. 2.9 Пример ассемблирования
                3 0000                   CODE    SEGMENT
                4                              ASSUME  CS:CODE
                5
                6 0000  03 C3            PART1:  ADD     AX,BX    ; Сложить с длиной буфера
                7
                8 0002                   CODE    ENDS
                9                              END
 
                                 Фиг. 2.9 Пример ассемблирования
 
      Взяв пример команды ассемблера, рассмотрим результаты работы
    ассемблера.  В правой части распечатки находятся исходные команды.
    В левой части - информация, сгенерированная ассемблером.  Первая
    колонка содержит номер каждой строки распечатки.  Ассемблер
    устанавливает эти номера для исходного файла.  Они строк не
    обязательно соотносятся с номерами строк в исходном файле
    сформированном текстовым редактором.
 
      Во второй колонке содержатся адреса инструкций.  Программа LINK
    может их изменить, но они являются лучшим предположением, которое
    может сделать ассемблер на шаге ассемблирования.  Следующая колонка
    - код команды на машинном языке.  Поскольу команды 8088 имеют длину
    от 8 до 56 бит, это поле будет изменяться в размере.  Кроме того,
    программа LINK может изменить некоторую информацию в поле объектных
    кодов.  Редактор связей может изменить любую группу команд,
    оперирующих с адресами.  Однако, за исключением адресов, листинг
    ассемблера дает верные машинные коды, которые и будут в дальнейшем
    исполняться.
 
      В большинстве примеров программ мы будем использовать листинг
    ассемблера.  Это позволит нам сразу видеть вырабатываемый ассембле-
    ром код.
 
      Другой создаваемый ассемблером файл - файл перекрестных сыылок.
    Этот файл описывает все связи между метками и командами, которые их
    используют.  Такая информация незаменима, когда вы пытаетесь
    изменить программу.  Вы можете воспользовваться перекрестными
    ссылками для того, чтобы выявить все команды, которые обращаются к
    определенному участку памяти.  Это позволяет программисту
    определить все команды, на которые может повлиять изменение в
    другой чассти программы.  Использование информации о перекрестных
    ссылках будет обсуждаться в главе 5.




Принципы работы компьютера



Принципы работы компьютера


    Ниже описаны некоторые основные принципы работы компьютера.  Эти
    принципы важны для понимания 8088 и его работы.  Все, что говорится
    в этом разделе, верно и для других компьютеов.  В соответствующих
    местах мы будем специально оговариваться, что речь идет об Intel
    8088, хотя основная часть сведений, относящихся только к 8088,
    появится в следующей главе.
 
      Работа компьютера состоит в выборке команд из памяти и их
    выполнении.  Каждая команда проходит через этот двухшаговый
    процесс.  Выборкой очередной порции в этом цикле управляет один из
    регистров процессора.  Этот регистр называют счетчиком программы
    или указателем команды.  Он является "маркером" текущей
    выполняемой команды.  То место в памяти, на которое указывает этот
    регистр, содержит следующую команду, которую должен будет выбрать и
    выполнить процессор.  Процессор читает в этом месте один или
    несколько байтов, интерпретирует их как комнду и выполняет ее.
    Затем процессор увеличивает указатель в соответствии с числом
    байтов в команде.  Теперь счетчик программы указывает на следующую
    команду.  Этот цикл повторяется для всех без исключения команд.
    Нормальное выполнение программы является последовательным, от одной
    команды к другой, расположенной следом.
 
      Процессор может изменить последовательный цикл выборки-исполне-
    ния при выполнении команды, которая помещает в указатель команд но-
    вое значение.  Такие команды являются командами передачи
    управления, поскольку выполнение программы переходит в новую
    область.  Инструкция перехода или выбора варианта является самым
    распространенным способом передачи управления.  Команда перехода
    задает адрес команды, которая должна выполняться следующей.  Цикл в
    программе является примером использования команды перехода.  Пример
    на Фиг.  2.16 на языке ассемблера 8088 показывает сохранение одного
    и того же значения в последовательных байтах памяти.  Команда
    перехода в конце цикла приводит к повторному выполнению его команд.
            Microsoft (R) Macro Assembler Version 5.00              11/2/88 21:30:42
            Фиг. 2.16 Команда перехода                        Page         1-1
 
 
                 1                                    PAGE    ,132
                 2                                    TITLE   Фиг. 2.16 Команда перехода
                 3 0000                   CODE    SEGMENT
                 4                                    ASSUME  CS:CODE
                 5
                 6 0000                   MEM     LABEL   BYTE
                 7
                 8 0000                   FIG2_16:
                 9 0000  2E: C6 87 0000 R 00          MOV     MEM[BX],0
                10 0006  43                     INC     BX
                11 0007  EB F7                        JMP     FIG2_16
                12
                13 0009                   CODE    ENDS
                14                                    END
 
 
                                    Фиг. 2.16 Команда перехода
 
      Обратите внимание, что в команде JMP для определения сле-
    дующего выполняемого адреса используется метка, в данном случае
    "FIG2_16".  Это - еще одна из возможностей ассемблера.  Хотя в
    машинном языке требуется абсолютный адрес следующей команды, язык
    ассемблера требует лишь программно определенную метку.  Ассемблер
    сам определяет абсолютный адрес и ставит правильное значение в
    команду машинного языка.
 
      Команда перехода не обязательно должна быть безусловной как в
    приведенном примере.  8088 располагает множством команд перехода,
    которые выполняются в соответствии с некоторым кодом условия.
    Значение кода условия устанавливают другие команды при их
    выполнении процессором.  Условие, указанное в команде условного
    перехода, сравнивается с кодом условия, сохраненного в регистре
    состояний.    Если условия совпадают, то процессор переходит по
    указанному адресу.  В противном случае процессор игнорирует
    переход, и выполнение программы продолжается в обычном
    последовательном порядке.  На Фиг.    2.17 предыдущий пример изменен.
    Цикл в этом примере прерывается, когда значение BX становится
    равным 1000.
 
      На Фиг.  2.17 появляется новая команда сравнения, которая
    устанавливает коды состояния.  Команда условного перехода (JNE
    (Jump if Not Equal) переход, если не равны) выполняет переход на
    "FIG2_17", если условие выполнено.  Если условие не выполняется,
    8088 выполняет команду, следующую за условным переходом, в данном
    случае команду NOP.  Команда условного перехода позволяет проверить
             Microsoft (R) Macro Assembler Version 5.00              11/2/88 22:31:33
             Фиг. 2.17 Команда условного перехода                    Page     1-1
 
 
                  1                              PAGE    ,132
                  2                              TITLE   Фиг. 2.17 Команда условного перехода
                  3 0000                         CODE    SEGMENT
                  4                              ASSUME  CS:CODE
                  5
                  6 0000                         MEM     LABEL   BYTE
                  7
                  8 0000                         FIG2_17:
                  9 0000  2E: C6 87 0000 R 00          MOV     MEM[BX],0
                 10 0006  43                     INC     BX
                 11 0007  81 FB 03E8                   CMP     BX,1000
                 12 000B  EB F3                        JMP     FIG2_17
                 13
                 14 000D                         CODE    ENDS
                 15                              END
 
 
                               Фиг. 2.17 Команда условного перехода
 
    значения данных в процессе выполнения программы.  Ход выполнения
    программы может меняться в зависимости от результатов этой
    проверки.




Процедуры



Процедуры


    Другая форма команды перехода - переход к подпрограмме.  Некоторая
    последовательность команд образует процедуру.  Эта последователь-
    ность реализует функцию, которая выполняется в программе
    неоднократно и в разных местах.  Вместо многократного повторения
    этой последовательности во всех необходимых местах, программист
    помещает эти команды в одном месте.  Такая часть программы
    становится подпрограммой или процедурой.
 
      Каждый раз как в программе потребуется выполняемая процедурой
    функция, она передает управление в эту процедуру командой перехода
    на нее.  Переход на процедуру называется вызовом процедуры или
    командой вызова.  Вызов процедуры отличается от команды перехода.
    Команда вызова сохраняет адрес следующей за ней команды.  Этот
    адрес, называемый адресом возврата, указывает дорогу обратно к
    исходной последовательности команд.
 
      Давайте посмотрим, как работает вызов процедуры.  Пусть, напри-
    мер, нам надо написать программу, которая складывает в нескольких
    местах 32-битовые числа.  У микропроцессора 8088 нет команд,
    которые выполняли бы такое сложение.  Мы можем написать короткую
    последовательность команд, которая будет выполнять сложение
    32-битовых чисел.  Эта часть программы будет процедурой.
 
      Программист пишет эту подпрограмму точно также как любую другую
    часть программы.  Она является частью программы на языке
    ассемблера.  При написании основной части прикладной программы
    программист будет иногда сталкиваться с необходимостью сложить два
    32-битовых числа.  Вместо того, чтобы писать команды для выполнения
    этого сложения, в программу включают вызов процедуры 32-битового
    сложения.  Сразу после него продолжаются команды основной части
    программы.    Вызов этой процедуры производит впечатление мощной
    команды 8088, так как один такой вызов выполняет 32-битовое
    сложение.
 
      При выполнении программы выозов процедуры выполняет не само
    сложение с двойной точностью, а передачу управления соответствующей
    процедуре.    Процессор выполняет команды процедуры, реализующей
    сложение.  Последняя команда процедуры является специальной
    командой для процедур и называется возвратом.  Команда возврата
    берет адрес, который был сохранен командой вызова и помещает его
    обратно в указатель команд.  Это заставляет программу вернуться к
    команде, следующей за вызовом процедуры.  Вызов процедуры как бы
    временно отводит течение программы в русло процедуры.  После
    выполнения процедуры выполнение возвращается к основной программе.
 
      Команды, которые обеспечивают выполнение процедуры - CALL и
    RETURN.  CALL - это переход на процедуру.  CALL сохраняет текущее
    значение указателя команд в специальном месте памяти.  Это
    сохраненное значение указателя команд является адресом возврата.
    Команда RETURN читает сохраненное значение указателя команд, поме-
    щает его в указатель команд процессора и возвращает управление в
    точку, следующую за командой CALL.    Пример на Фиг.    2.18 показывает
    процедуру, вызываемую из двух различных точек программы.
 
      Поскольку программа начинает свое выполнение с самого начала,
    она сразу же попадает на команду A1.  Команда CALL передает
    управление в точку SOBROUTINE.  Выполняя команду CALL, процессор
    в том числе сохраняет адрес точки A2.  После выполнения процедуры
    команда RET (от английского return - возврат) восстанавливает
    сохраненное значение A2.  Управление возвращается к главной
    программе.    Дальше в главной прграмме выполняется CALL в точке A3,
    что приводит к повторному выполнению подпрограммы.      На этот раз
    процессор сохраняет значение A4.  После выполнения процедуры во
    второй раз управление возвращается в A4.  Обратите внимание, что
    оба раза выполнялась одна процедура.  В первый раз возврат после ее
           Microsoft (R) Macro Assembler Version 5.00             5/11/80 16:25:59
           Фиг. 2.18 Использование процедуры                      Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 2.18 Использование процедуры
            0000                   CODE    SEGMENT
                                         ASSUME  CS:CODE
 
            0000  E8 0008 R              A1:     CALL    SUBROUTINE
 
            0003  40                     A2:     INC     AX
 
            0004  E8 0008 R              A3:     CALL    SUBROUTINE
 
            0007  43                     A4:     INC     BX
 
                                   ;-----  Здесь программа продолжается . . .
 
            0008                   SUBROUTINE      PROC    NEAR
 
            0008  B8 0000                      MOV     AX,0
            000B  BB 0000                      MOV     BX,0
            000E  C3                           RET
 
            000F                   SUBROUTINE      ENDP
 
            000F                   CODE    ENDS
                                         END
 
 
                       Фиг. 2.18 Использование процедуры
 
 
    выполнения осуществлялся на A2, во второй раз - на A4.
    Преимущество процедуры заключается в ее способности вызываться из
    множества различных мест и каждый раз правильно находоить точку
    возврата.
 
      Где же хранится адрес возврата во время выполнения процедуры?
    Существует множество возможностей, но микропроцессор 8088
    использует для хранения этого значения стек.




Шестнадцатиричное представление



Шестнадцатиричное представление


    Двоичная арифметика хороша для компьютера, поскольку он имеет дело
    только с еденицами и нулями.  Но человеческое восприятие требует
    более компактного представления.  Мы будем пользоваться шестнадца-
    теричным представлением данных для собственного удобства.
 
      Шестнадцатеричное представление чисел - это система исчисления
    по основанию 16.  Каждая цифра в числе может иметь значение от 0 до
    15.  Каждый разряд в числе является степенью 16.  Шестнадцатеричное
    представляение - удобный метод записи двоичной информации.    Каждая
    шестнадцатеричная цифра соответствует четырем битам.  Для преобра-
    зования двоичного числа в шестнадцатеричное разбейте его на группы
    по 4 бита и прочитайте каждую группу как шестнадцатеричную цифру.
    Это дает уплотнение записи один к четырем - очень удобно для
    разумного существа.
 
      Небольшая  трудность здесь  связанна с    тем, что  у нас имеются
    цифры только  от 0 до  9. Числа от    10 до 15  мы будем представлять
    первыми  шестью буквами  латинского алфавита:  от A  до F.    Таблица
    соответствия  между  десятичными,  шестнадцатеричными  и  двоичными
    цифрами приводится на Фиг. 2.7.
 
      Как показано в этой таблице, каждая шестнадцатеричная цифра со-
    ответствует точно 4-м битам какого-либо двоичного числа.
    Шестнадцатеричное представление обычно для машин, в которых
    размер слова кратен 4.  Поскольку слово в 8088 составляет 16 бит,мы
    будем пользо- ваться шестнадцатеричной записью.  Каждое 16-битовое
    значение пред- ставляется четырьмя шестнадцатеричными цифрами.  В
    этой книге числа в шестнадцатеричной записи будут обозначаться
    суффиксом "H", а двоичные числа - суффиксом "B".
 
    Десятичные Двоичные Шестнадцатер. Десятичные Двоичные Шестнадцатер.
    -------------------------------------------------------------------
      0     0000       0        8     1000     8
      1     0001       1        9     1001     9
      2     0010       2       10     1010     A
      3     0011       3       11     1011     B
      4     0100       4       12     1100     C
      5     0101       5       13     1101     D
      6     0110       6       14     1110     E
      7     0111       7       15     1111     F
    ------------------------------------------------------------------
                 Фиг. 2.7  Шестнадцатеричная нумерация
 
    Десятичные числа пишутся без суффикса или с суффиксом "D".  Это в
    точности соответствует записи чисел в языке ассемблера.  Для
    предсталения данных в ассемблерной программе можно пользоваться лю-
    бой из трех рассмотренных систем (десятичная, двоичная и шестнадца-
    теричная).
 
      При записи шестнадцатеричных чисел важно убедиться, что ассемб-
    лер воспримет их как числа.  Если вы ввели "FAH", то это может быть
    или шестнадцатеричное число FA, или имя переменной FAH.  Ассемблер
    предполагает, что число начинается с цифры и что метка начинается с
    буквы.  Поэтому "FAH" для ассемблера оказывается переменной.  Если
    мы имеем в виду не переменную а число, то его надо записать как
    "0FAH":  это число имеет желаемое значение и начинается заведомо с
    цифры.  Воизбежание путаницы каждому шестнадцатеричному числу,
    которое начинается со значений от A до F должен предшествовать 0.




Синаксис языка Ассемблера



Синаксис языка Ассемблера


    Прежде чем двигаться дальше, обсудим синтаксис команд языка ассем-
    блера.Мы должны выделить основные компоненты языка ассемблера,
    чтобы можно было затем обозначать эти компоненты с помощью
    сиандартных терминов.
 
      Команда языка ассемблера состоит из четырех частей.  Фиг.  2.8
    показывает типичную команду ассемблера и названия этих частей.
 
    --------------------------------------------------------
    PART1:    ADD     AX,BX      ;Добавить к длине буфера
    Метка     ОпКод   Операнды         Комментарий
    --------------------------------------------------------
                            Фиг. 2.8 Синтаксис языка ассемблера
 
    Единственная обязательная часть команды языка ассемблера - ОпКод
    (сокращение от ОПерационный КОД).  Программисты иногда называют ма-
    шинные команды кодами операций. Операционный код в утверждении язы-
    ка асемблера определяет, какую опреацию должен будет выполнить про-
    цессор, в нашем примере - операцию сложения (по английски - add -
    прим.  перев.).
 
      Поле операндов содержит дополнительную информацию о команде,
    например, какие значения участвуют в операции.  Поле операндов
    определяется операционным кодом.  Каждому коду операции должно
    соответствовать определенное число операндов.  Для команды ADD
    требуется два операнда; операция перемены знака (NEG) обходится
    лишь одним, а для некоторых команд, например, команды десятичной
    коррекции DAA, операнды не нужны.  В главе 4 описаны эти команды и
    их операнды.
 
      Метка и комментарий необязательны  в команде. Поле метки позво-
    ляет  обозначить какое-либо  конкретное место  в памяти компьютера.
    Собственный  адрес имеется      у  любого  участка памяти,  но выделить
    адрес  какой  либо  команды  трудно,  если  вообще      возможно. Метка
    позволяет  идентифицировать  определенное  место  в памяти заданным
    программистом  именем.  Говоря   технически,  поле      метки  содержит
    символический указатель  расположения команды. Если  мы хотим обра-
    титься к  этой команде позднее,  то мы делаем  это через символьное
    имя и  нам не требуетсся  указывать абсолютное расположение  данной
    инструкции. Использование меток - одна из причин предпочтительности
    языка  ассемблера  перед  машинным    языком.  Превращением же симво-
    лических имен в реальные адреса ведает ассемблер.
 
      Поле комментариев служит для удобства программиста.
    Программист может использовать это поле для сообщения
    дополнительной информации о команде.  Комментарий не обязательно
    жестко связан с командой.  Вы можете отвести под комментарий целую
    строку, поставив в ее начале символ ";".  Это позвляет программисту
    в ключить в листинг ассембле- ра блок собственной информации, к
    примеру, описание используемого алгоритма.
 
      У  каждого есть  собственное представление  о том,  как следует
    комментировать  программы, и  вы наверняка  тоже скоро  выработаете
    свое. Как  правило, вы будете  пытаться включать в      них информацию,
    которая  относится  непосредственно  к  решаемой  проблеме.  В при-
    веденном  примере  было   бы  бессмысленно  комментировать    команду
    чем-нибудь вроде  "сложить AX и  BX". Это не  более, чем повторение
    операционного кода и операндов (разве  что в переводе с английского
    - прим.перев.). Если  уж вы намерены связаться  с комментариями, то
    делайте их достойными труда их написания и чтения.




Стек



Стек


    Стек - это структура данных, которая используется для временного
    хранения информации.  Программа может поместить данные в стек
    (PUSH) или забрать их оттуда (POP).  Стековая структура данных
    предполагает упорядочивание помещенных в него данных специальным
    образом.  Во всех случаях первым из стека извлекается то, что было
    в нем сохранено последним.      Такая организация хранения данных
    сокращенно обозначается LIFO (last in, first out - последний
    введенный первым выводится).  Если мы поместили в стек сначала A,
    затем B, то первое, что мы извлечем из него будет B.  Следующая
    команда извлечения (POP) вернет A.    Информация возвращается из
    стека в порядке, строго противоположном порядку ее помещения в
    стек.
 
      Стек противоположен очереди.  Очередь - это обычная последова-
    тельность, подобная очередям на почте или в магазине.  Это
    структура данных типа "первым вошел - первым вышел" (first in,
    first out:    FIFO).      Тот, кто первым встал в очередь, первым и
    покинет ее.  Стек и очередь - очень разные вещи.
 
      Компьютер снабжает стек зарезервированным участком памяти и
    указателем, называемым указателем стека.  Программа использует ука-
    затель стека для того, чтобы фиксировать последние помещенные в
    стек данные, в отличие от почты, где сами элементы очереди продви-
    гаются вперед по мере движения очереди.  В компьютере намного легче
    использовать для слежения за данными указатель и при записи или
    считывании данных из стека изменять только его.  В ответ на
    выполнение операций POP и PUSH указатель стека соответственно
    увеличивается или уменьшается.
 
      Фиг.  2.19 дает пример стека.  В части (a) изображен стек после
    того как в него последовательно помещены значения A, B, C.    Ука-
    затель стека указывает на текущую вершину стека, в данном случае на
    C.      В части (b) в стек помещается еще одно значение:  D.  Операция
    PUSH уменьшает указатель стека SP (от Stack Pointer - указатель
    стека), который теперь указывает на новую вершину D.  Указатель
    стека всегда фиксирует то, что было последним помещено в стек.
 
      Фиг.  2.19(c) показывает состояние стека после операции POP.
    Этой операцией значение D было извлечено из стека.      Команда POP
    помещает извлеченное из стека значение в указанное место.  Если в
    части (c) выполнялась команда POP AX, то процессор поместил
    значение D в регистр AX (это уже дополнительный аспект, который мы
    обсудим в следующей главе).  POP увеличивает указатель стека.
    Теперь он указывает на новую вершину, C.  Заметим, что элементы
    извлекаются из стека по описанному принципу LIFO.  Последним
    помещенным в стек элементом был D и он же первым извлечен из стека.
 
      Обратите также внимание, что D так и осталось в памяти, однако
    теперь уже не является частью стека.  Логическая граница стека
    находится по адресу, хранящемуся в его указателе.  В данном случае
    вершина стека оказывается ниже ячейки со значением D.
 
      На Фиг.  2.19(d) видно, что происходит с D при помещении в сетк
    нового элемента E.  Элемент E записывается на место D и становится
    новой вершиной стека.  Мораль из этой истории такова, что хотя
    извлеченные из стека значения могут оставаться в памяти, полагаться
    на это не следует.
       ГДДДґ            ГДДДґ      ГДДДґ      ГДДДґ
       і   і            і D і<ДД SP    і D і        і E і<ДД SP
       ГДДДґ            ГДДДґ      ГДДДґ      ГДДДґ
       і C і<ДД SP    і C і        і C і<ДД SP    і C і
       ГДДДґ            ГДДДґ      ГДДДґ      ГДДДґ
       і B і            і B і      і B і      і B і
       ГДДДґ            ГДДДґ      ГДДДґ      ГДДДґ
       і A і            і A і      і A і      і A і
       ГДДДґ            ГДДДґ      ГДДДґ      ГДДДґ
      (a)          (b)        (c)        (d)
 
            Фиг. 2.19  Пример работы стека
 
    В приведенном примере подразумевался принцип построения стека
    процессора 8088.  Указатель стека постоянно указывает на текущую
    вершину стека.  Операция PUSH уменьшает указатель стека, POP
    увеличивает его.  Стек растет в направлении уменьшения адресов в
    памяти.  Основание стека располагается в памяти по большему адресу,
    чем его вершина.  Если вы нарисуете изображения стека с наименьшим
    адресом сверху, как на Фиг.  2.19, то вершина стека окажется в
    верхней части рисунка.
 
      Мы занялись обсуждением стека потому, что стек используется для
    хранения адреса возврата из процедуры.  Как это делается?
 
      Каждая команда CALL вызывает как бы выполнению команды PUSH для
    стека - сохраняет в стеке адрес возврата.  Команда RET извлекает из
    стека, подобно команде POP, адрес возврата и помещает его в
    указатель команд.  8088 использует стек для хранения адресов
    возврата потому, что это позволяет вкладывать процедуры одна в
    другую.  Что такое вложение?  На Фиг.  2.20 показан пример
    вложенных процедур.
 
      На Фиг.  2.20 показана абсурдная программа, которую мы
    используем как пример вложения процедур.  Часть (a) показывает
    стек перед выполнением программы.  Как только начинает выполняться
    процедура MAIN, она вызывает процедуру SUBROUTINE_A.  В это время
    процессор сохраняет в стек адрес возврата.  Часть (b) показывает
    адрес возврата 103 помещенным в стек.  SUBROUTINE_A в процессе
    своего выполнения вызывает SUBROUNINE_B.  Команда этого вызова
    сохраняет адрес возврата 108 в SUBROUNINE_A.  Когда SUBROUNINE_B
    заканчивается, команда возврата извлекает из стека значение 108,
    как показано в части (d).  Процессор помещает это значение в
    указатель команд, как требуется при команде возврата.  Как видно на
    листинге ассемблера, адрес 108 относится к SUBROUNINE_A и следует
    сразу за вызовом SUBROUNINE_B.  Затем SUBROUNINE_A заканчивается.
    Команда возврата извлекает из стека значение 103 для указателя
    команд.  Адрес 103 относится к процедуре MAIN и следует сразу за
    вызовом SUBROUNINE_A.
 
      Наиболее важным в примере на Фиг.  2.20 является вложение
    процедур.  Одна процедура может вызывать другую, а команда возврата
    всегда обеспечивает правильный возврат управления.      Единственное,
           Microsoft (R) Macro Assembler Version 5.00              11/10/88 23:18:17
           Фиг. 2.20 Вызов вложенных процедур                Page   1-1
 
 
                                         PAGE    ,132
                                         TITLE   Фиг. 2.20 Вызов вложенных процедур
            0000                   CODE    SEGMENT
 
                                         ASSUME  CS:CODE
            0100                         ORG     100H
 
            0100  E8 0104 R              MAIN:   CALL    SUBROUTINE_A
            0103  40                           INC     AX
 
                                   ;----- Здесь главная процедура продолжается . . .
 
 
            0104                   SUBROUTINE_A    PROC    NEAR
            0104  43                           INC     BX
            0105  E8 0109 R                    CALL    SUBROUTINE_B
            0108  C3                           RET
            0109                   SUBROUTINE_A    ENDP
 
            0109                   SUBROUTINE_B    PROC    NEAR
            0109  41                           INC     CX
            010A  C3                           RET
            010B                   SUBROUTINE_B    ENDP
 
            010B                   CODE    ENDS
                                         END
 
 
            ГДДДґ        ГДДДґ            ГДДДґ      ГДДДґ
            і   і        і   і            і108іДД SP    і108і
            ГДДДґ        ГДДДґ            ГДДДґ      ГДДДґ
            і   і        і103іДД SP    і103і        і103іДД SP
            ГДДДґ        ГДДДґ            ГДДДґ      ГДДДґ
            іxxxіДД SP    іxxxі          іxxxі      іxxxі
            ГДДДґ        ГДДДґ            ГДДДґ      ГДДДґ
 
             (a)        (b)          (c)        (d)
 
                       Фиг. 2.20 Вызов вложенных процедур
 
    что ограничивает глубину вложения процедур (сколько процедур может
    вызывать другие) - это размер стека.  Пока в стеке имеется место
    для очередного адреса возврата, можно производить вложенный вызов
    процедуры.    Структура стека LIFO дает гарантию правильной
    последовательности возвратов.
 
      Пример программы на Фиг.  2.20 показывает также использование
    еще одной псевдооперации ассемблера - PROC.  Оператор PROC
    используется ассемблером для идентификации процедур.  Как мы
    дальше увидим, ассемблер должен знать, как далеко располагается
    процедура и как возвращаться к точке ее вызова.  Операнд NEAR
    определяет процедуру как расположенную в пределах легкой
    досигаемости вызывающей программы.    Мы еще вернемся к оператору
    PROC, когда будем обсуждать реальное действие команд CALL и JMP.