4857

Программирование на языке ассемблера

Конспект

Информатика, кибернетика и программирование

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

Русский

2012-11-28

337.5 KB

188 чел.

Введение

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

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

Элементом подготовки программиста-профессионала обязательно является изучение ассемблера. Это связано с тем, что программирование на ассемблере требует знание архитектуры ПК, что позволяет создавать более эффективные программы на других языках и объединять их с программами на ассемблере.

В пособии рассматриваются вопросы программирования на языке ассемблера для компьютеров на базе микропрцессоров фирмы Intel.

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

  1.  Архитектура ПК.

Архитектура ЭВМ – это абстрактное представление ЭВМ, которое отражает ее структурную, схемотехническую и логическую организацию.

Все современные ЭВМ обладают некоторыми общими и индивидуальными свойствами архитектуры. Индивидуальные свойства присущи только конкретной модели компьютера.

Понятие архитектуры ЭВМ включает в себя:

  •  структурную схему ЭВМ;
  •  средства и способы доступа к элементам структурной схемы ЭВМ;
  •  набор и доступность регистров;
  •  организацию и способы адресации;
  •  способ представления и формат данных ЭВМ;
  •  набор машинных команд ЭВМ;
  •  форматы машинных команд;
  •  обработка прерываний.

Основные элементы аппаратных средств компьютера: системный блок, клавиатура, устройства отображения, дисководы, печатающие устройства (принтер) и различные средства связи. Системный блок состоит из системной платы, блока питания и ячеек расширения для дополнительных плат. На системной плате размещены микропроцессор, постоянная память (ROM), оперативная память (RAM) и сопроцессор.

 

  1.  Регистры.                

                       

  Внутри микропроцессора информация содержится в группе из 32 регистров (16 пользовательских, 16 системных), в той или иной мере доступных для использования программистом. Так как пособие посвящено программированию для микропроцессора 8088-i486, то логичнее всего начать эту тему с обсуждения внутренних регистров микропроцессора, доступных для пользователя.

Пользовательские регистры используются программистом для написания программ. К этим регистрам относятся:

  •  восемь 32-битных регистров (регистры общего назначения) EAX/AX/AH/AL, EBX/BX/BH/BL, ECX/CX/CH/CL, EDX/DX/DLH/DL, EBP/BP, ESI/SI, EDI/DI, ESP/SP;
  •  шесть 16 -,битовых регистров сегментов: CS,DS, SS, ES, FS,GS;
  •  регистры состояния и управления: регистр флагов EFLAGS/FLAGS, и регистр указателя команды EIP/IP.

Через наклонную черту приведены части одного 32-разрядного регистра. Приставка E (Extended) обозначает использование 32-разраядного регистра. Для работы с байтами используются регистры с приставками L (low)  и H(high), например, AL,CH - обозначающие младший и старший байты 16-разрядных частей регистров.

  1.  Регистры общего назначения.

EAX/AX/AH/AL(Accumulator register) –аккумулятор. Используются при умножении и делении, в операциях ввода-вывода и в некоторых операциях над строками.

 EBX/BX/BH/BLбазовый регистр (base register), часто используется при адресации данных в памяти.

 ECX/CX/CH/CLсчетчик (count register), используется как счетчик числа повторений цикла.

 EDX/DX/DH/DLрегистр данных (data register), используется для хранения промежуточных данных. В некоторых командах использование его обязательно.

Все регистры этой группы позволяют обращаться к своим «младшим» частям. Использование для самостоятельной адресации можно только младшие 16- и 8-битовые части этих регистров. Старшие 16 бит этих регистров как самостоятельные объекты недоступны.

Для поддержки команд обработки строк, позволяющих производить последовательную обработку цепочек элементов имеющих длину 32, 16 или 8 бит используются:

 ESI/SI (source index register) – индекс источника. Содержит адрес текущего элемента источника.

 EDI/DI (distination index register) – индекс приемника (получателя). Содержит текущий адрес  в строке приемнике.

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

 ESP/SP (stack poINTer register) –регистр указателя стека. Содержит указатель вершины стека в текущем сегменте стека.

 EBP/BP (base poINTer register) –регистр указателя базы стека. Предназначен для организации произвольного доступа к данным внутри стека.

1.1.2. Сегментные регистры

В программной модели микропроцессора имеются шесть сегментных регистров: CS, SS, DS, ES, GS, FS.  Их существование обусловлено спецификой организации и использования оперативной памяти микропроцессорами Intel. Микропроцессор аппаратно поддерживает структурную организацию программы состоящей из сегментов. Для указания сегментов доступных в данный момент предназначены сегментные регистры. Микропроцессор поддерживает следующие типы сегментов:

  1.  Сегмент кода. Содержит команды программы Для доступа к этому сегменту служит регистр CS (code segment register) – сегментный регистр кода. Он содержит адрес сегмента с машинными командами, к которому имеет доступ микропроцессор.
  2.  Сегмент данных. Содержит обрабатываемые программой данные. Для доступа к этому сегменту служит регистр DS (data segment register) – сегментный регистр данных, который хранит адрес сегмента данных текущей программы.
  3.  Сегмент стека. Этот сегмент представляет собой область памяти, называемую стеком. Микропроцессор организует стек по принципу – первый «пришел», первый «ушел». Для доступа к стеку служит регистр SS (stack segment register) – сегментный регистр стека, содержащий адрес сегмента стека.
  4.  Дополнительный сегмент данных. Обрабатываемые данные могут находиться еще в трех дополнительных сегментах данных. По умолчанию предполагается, что данные находятся в сегменте данных. При использовании дополнительных сегментов данных их адреса требуется указать явно с помощью специальных префиксов переопределения сегментов в команде. Адреса дополнительных сегментов данных должны содержаться в регистрах ES, GS, FS (extenSIon data segment registers).
    1.  Регистры управления и состояния

Микропроцессор содержит несколько регистров, которые содержат информацию о состоянии, как самого микропроцессора, так и программы, команды которой в данный момент загружены в конвейер. Это:

     -    регистр указателя команд EIP/IP;

  •  регистр флагов EFLAGS/FLAGS.

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

 EIP/IP (instruction poINTer register) –указатель команд. Регистр EIP/IP имеет разрядность  32 или 16 бит и содержит смещение следующей выполняемой команды относительно содержимого сегментного регистра CS в текущем сегменте команд. Этот регистр непосредственно недоступен, но изменение его производится командами перехода.

EFLAGS/FLAGS (Flag register) – регистр флагов. Разрядность 32/16 бит. Отдельные биты данного регистра имеют определенное функциональное назначение и называются флагами. Флаг - это бит, принимающий значение 1 ("флаг установлен"),  если выполнено  некоторое условие,  и  значение 0 ("флаг сброшен") в противном случае.  Младшая часть этого регистра полностью аналогична регистру FLAGS для i8086.

        1.1.3 Регистр флагов   

Регистр флагов является 32-разрядным, имеет имя EFLAGS (рис.1). Отдельные биты регистра имеют определенное функциональное назначение и называются флагами.  Каждому из них присвоено определенное имя (ZF, CF и т.д). Младшие 16 бит EFLAGS представляют 16-разрядный регистр флагов FLAGS, используемый при выполнении программ, написанных для микропроцессора i086 и i286.

 

31

.

18

17

16

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

 

AC

V

RF

NT

IOPL

OF

DF

IF

TF

SF

ZF

AF

PF

CF

   Рис.1 Регистр флагов

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

       Флаги условий:

   CF (carry flag)  -  флаг переноса.  Принимает значение 1, если при сложении целых чисел появилась единица переноса, не "влезающая" в разрядную сетку, или если при вычитании чисел без знака первое из них было меньше второго.  В командах сдвига в CF заносится бит,  вышедший за разрядную сетку. CF фиксирует также особенности команды умножения.

   OF (overflow flag) - флаг переполнения.  Устанавливается в 1, если при сложении или вычитании целых чисел со знаком  получился результат, по модулю превосходящий  допустимую  величину  (произошло переполнение мантиссы и она "залезла" в знаковый разряд).

   ZF (zero flag) - флаг нуля.  Устанавливается в 1,  если  результат команды оказался равным 0.

   SF (SIgn flag) - флаг знака.  Устанавливается в 1, если в операции над знаковыми числами получился отрицательный результат.

   PF (parity flag) - флаг четности.  Равен 1, если результат очередной команды  содержит  четное количество двоичных единиц.  Учитывается обычно только при операциях ввода-вывода.

   AF (auxiliary carry flag) - флаг дополнительного переноса.  Фиксирует особенности выполнения операций над двоично-десятичными числами.

       Флаги состояний:

   DF (direction flag)  - флаг направления. Устанавливает направление просмотра строк в строковых командах:  при DF=0 строки просматриваются "вперед" (от начала к концу), при DF=1 -  в обратном направлении.

   IOPL (input/output privilege level) – уровень привилегий ввода-вывода. Используется в защищенном режиме работы микропроцессора, для контроля доступа к командам ввода-вывода, в зависимости от привилегированности задачи.

  NT (nested task) – флаг вложенности задачи. Используется в защищенном режиме работы микропроцессора для фиксации того факта, что одна задача вложена в другую.

Системные флаг:

   IF (INTerrupt flag) - флаг прерываний. При IF=0 процессор перестает реагировать на поступающие к нему прерывания,  при IF=1  блокировка прерываний снимается.

   TF (trap flag) - флаг трассировки.  При TF=1 после выполнения каждой команды процессор делает прерывание (с номером 1),  чем можно воспользоваться при отладке программы для ее трассировки.

  RF (resume flag) – флаг возобновления. Используется при обработке прерываний от регистров отладки.

   VM (virtuAL 8086 mode) – флаг виртуального 8086. 1-процессор работает в режиме виртуального 8086. 0- процессор работает в реальном или защищенном режиме.

   AC (ALignment check) –флаг контроля выравнивания. Предназначен для разрешения контроля выравнивания при обращении к памяти.

  1.  Организация памяти.

Физическая память, к которой микропроцессор имеет доступ, называется оперативной памятью (или оперативным запоминающим устройством - ОЗУ).  ОЗУ представляет собой цепочку байтов, имеющих свой уникальный адрес (его номер), называемый физическим. Диапазон значений физических адресов от 0 до 4 Гбайт. Механизм управления памятью полностью аппаратный.

Микропроцессор аппаратно поддерживает несколько моделей использования оперативной памяти:

  •  сегментированную модель. В этой модели память для программ делится на непрерывные области памяти (сегменты), а сама программа может обращаться только к данным, которые находятся в этих сегментах;
  •  страничную модель. В этом случае оперативная память рассматривается как совокупность блоков фиксированного размера 4 Кбайта. Основное применение этой модели связано с организацией виртуальной памяти, что позволяет использовать для работы программ пространство памяти большее, чем объем физической памяти. Для микропроцессора Pentium размер возможной виртуальной памяти может достигать 4 Тбайта.

Использование и реализация этих моделей зависит от режима работы микропроцессора:

  1.  Режим реальных адресов (реальный режим). Режим аналогичный работе i8086 процессора. Необходим для функционирования программ, разработанных для ранних моделей процессоров.
  2.  Защищенный режим. В защищенном режиме появляется возможность многозадачной обработки информации, защиты памяти с помощью четырехуровнего механизма привилегий и ее страничной организации.
  3.  Режим виртуального 8086. В этом режиме появляется возможность работы нескольких программ для i8086. При этом возможна работа программ реального режима.

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

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

Каждая программа в общем случае может состоять из любого  количества   сегментов, но непосредственный доступ она имеет к трем основным: кода, данных и стека – и от одного до трех дополнительных сегментов данных. Операционная система размещает сегменты программы в оперативной памяти по определенным физическим адресам, после чего помещает значения этих адресов в соответствующие регистры. Внутри сегмента программа обращается к адресам относительно начала сегмента линейно, то есть начиная с адреса 0 и заканчивая адресом, равным размеру сегмента. Относительный адрес или смещение, который микропроцессор использует для доступа к данным внутри сегмента, называется эффективным.

Формирование физического адреса в реальном режиме

В реальном режиме диапазон изменения физического адреса от 0 до 1 Мбайт. Максимальный размер сегмента 64 Кбайт. При обращении к конкретному физическому адресу оперативной памяти определяется адрес начала сегмента и смещение внутри сегмента. Адрес начала сегмента берется из соответствующего сегментного регистра. При этом в сегментном регистре содержатся только старшие 16 бит физического адреса начала сегмента. Недостающие младшие четыре бита 20-битного адреса получаются сдвигом значения сегментного регистра влево на 4 разряда. Операция сдвига выполняется аппаратно. Полученное 20-битное значение и является настоящим физическим адресом, соответствующим  началу сегмента.  То есть физический адрес задается как пара "сегмент:смещение", где "сегмент" (segment) - это первые16 битов начального адреса сегмента памяти, которому принадлежит ячейка,  а "смещение" - 16-битовый адрес этой ячейки,  отсчитанный  от  начала  данного  сегмента  памяти  (величина 16*сегмент+смещение  дает абсолютный адрес ячейки). Если, например, в регистре CS хранится величина 1234h,  тогда адресная пара 1234h:507h определяет  абсолютный адрес, равный  16*1234h+507h  =12340h+507h = 12847h. Такая пара записывается в виде двойного слова,  причем (как и для чисел)  в "перевернутом" виде: в первом слове размещается смещение, а во втором - сегмент, причем каждое из этих слов в свою очередь  представлено в "перевернутом" виде. Например, пара 1234h:5678h будет записана так: | 78 | 56| 34 | 12|.

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

1.3. Представление данных

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

   Шестнадцатиричные числа записываются с буквой h на конце, двоичные числа - с буквой b.

   

        1.3.1 Типы данных

В общем случае  под целое число  можно отвести любое число байтов, однако,  система команд ПК  поддерживает только числа размером в байт, слово, двойное слово и учетверенное слово.  

Байт – восемь последовательно расположенных битов, пронумерованных от 0 до 7, при этом бит 0 является самым младшим значащим битом.

Слово – последовательность из двух байт, имеющих последовательные адреса. Микропроцессоры Intel имеют важную особенность – младший байт всегда хранится по меньшему адресу. Адресом слова считается адрес его младшего байта. Адрес старшего байта может быть использован для доступа к старшей половине слова.

Двойное слово – последовательность из четырех байтов (32 бита), расположенных по последовательным адресам. Адресом двойного слова считается адрес его младшего слова.

Учетверенное слово – последовательность из восьми байт (64 бита), расположенных по последовательным адресам.

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

   

        Целые числа без знака -    могут быть представлены в виде байта, слова или двойного слова - в зависимости от их размера. В виде байта представляются целые от 0 до 255 (=28-1),  в виде слова - целые от 0 до 65535 (=216-1), в виде двойного слова - целые от 0 до 4 294 967 295 (=232-1). Числа записываются в двоичной системе счисления,  занимая  все разряды ячейки. Например, число 130 записывается в виде байта 10000010b (82h).     Числа  размером в слово  хранятся в памяти  в "перевернутом" виде: младшие (правые) 8 битов числа  размещаются  в первом байте  слова,  а старшие 8 битов  -  во втором байте  (в 16-ричной системе:  две правые цифры  - в первом байте, две левые цифры - во втором байте). Например, число 130 (=0082h) в виде слова хранится в памяти так:  | 82 | 00 |. Отметим, однако, что в регистрах числа хранятся в нормальном виде: AX  | 00 82 |

"Перевернутое" представление  используется и при хранении в памяти целых чисел размером в двойное слово:  в первом его байте  размещаются младшие 8 битов числа, во втором байте - предыдущие 8 битов и т.д. Например, число 12345678h хранится в памяти так: | 78 | 56 | 34 | 12 |.

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

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

        Целые числа со знаком - также представляются в виде байта, слова и двойного слова.  В виде байта  записываются числа от -128 до 127,  в виде слова  -числа  от  -32768  до  32767,  а  в виде  двойного слова  -  числа  от -2147483648 до 2147483647.  При этом числа записываются в дополнительном коде: неотрицательное число записывается так же, как и беззнаковое число (т.е. в прямом коде), а отрицательное число -x (x>0) представляется беззнаковым числом 28-x (для  байтов),  216-x  (для  слов)  или 232-x (для двойных слов). Например, дополнительным кодом числа -6 является байт FAH (=256-6), слово FFFAH или двойное слово FFFFFFFAH. При этом  байт  10000000b (=80h) трактуется как -128, а не как +128 (слово 8000h понимается как -32678),  поэтому левый бит  дополнительного кода всегда  играет роль  знакового:  для неотрицательных чисел он равен 0, для отрицательных - 1.

   Знаковые числа размером в слово и двойное слово записываются в памяти в "перевернутом" виде (при этом  знаковый бит  оказывается в последнем байте ячейки).

   Иногда число-байт необходимо расширить до слова,  т.е. нужно получить такое же по величине число,  но размером в слово.  Существует два способа такого расширения - без знака и со знаком.  В любом случае исходное число-байт попадает во второй (до "переворачивания")  байт слова, а вот первый байт заполняется по-разному: при расширении без знака в него записываются нулевые биты  (12h -> 0012h),  а при расширении со знаком в первый байт записываются нули, если число-байт было неотрицательным, и записывается восемь двоичных единиц в противном случае (81h ->  FF81h).  Другими словами,  при расширении со знаком в первом байте слова копируется знаковый разряд числа-байта.

   Аналогично происходит расширение числа-слова до двойного слова.

Неупакованное двоично-десятичное число – байтовое представление десятичной цифры от 0 до 9. Неупакованные десятичные числа хранятся как байтовые значения без знака по одной цифре в каждом байте.

Упакованное двоично-десятичное число – представляет собой упакованное представление двух десятичных цифр от 0 до 9 в одном байте. Каждая цифра хранится в своем полубайте.

 1.3.2 Представление символов и строк

   На символ отводится один байт памяти,  в который записывается  код символа - целое от 0 до 255. В ПК используется система кодировки ASCII (American Standard Code for Information INTerchange).  Она, естественно, не содержит кодов русских букв, поэтому в нашей стране применяется некоторый вариант этой системы с русскими буквами  (обычно это альтернативная кодировка ГОСТа).

   Некоторые особенности этих систем кодировки:

 - код пробела  меньше кода любой буквы, цифры и вообще любого графически представимого символа;

 - коды цифр  упорядочены по величине цифр и  не содержат  пропусков, т.е. из неравенства код('0')<=код(c)<=код('9') следует, что c - цифра;

 - коды больших латинских букв упорядочены согласно алфавиту и не содержат пропусков; аналогично с малыми латинскими буквами;

 - (в альтернативной кодировке ГОСТа) коды русских букв (как больших, так и малых)  упорядочены согласно алфавиту,  но между ними  есть коды других символов.     Строка (последовательность символов) размещается в соседних байтах  памяти (в не перевернутом виде): код первого символа строки записывается в первом байте, код второго символа - во втором байте и т.п.  Адресом строки считается адрес ее первого байта.

   В ПК строкой считается также и последовательность слов (обычно это последовательность целых чисел).  Элементы таких строк располагаются в последовательных ячейках памяти,  но каждый элемент представлен в "перевернутом" виде.

   

  1.  Операторы программы на ассемблере

Программа на ассемблере представляет собой последовательность операторов, описывающих выполняемые операции. Оператором (строкой) исходной программы может быть или команда, или псевдооператор языка ассемблер.

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

2.1 Команды языка ассемблера

 

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

[Метка:] Мнемокод [Операнд] [;Комментарий] .

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

 Поле метки служит для присвоения имени команде языка ассемблер. По нему на эту команду могут ссылаться другие команды программы. Метка содержит до 31 символа и должна заканчиваться двоеточием (:). Метку можно начинать с любого символа, кроме цифры. Не используются как метки служебные слова.

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

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

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

На поле комментариев указывает точка с запятой (;), которая должна быть отдалена от предыдущего поля по крайне мере одним пробелом. Ассемблер игнорирует комментарии при трансляции, но сохраняет их в листинге программы.

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

Машинные команды  занимают от 1 до 6 байтов.     Код операции (КОП)  занимает один или два первых байта команды.  В ПК столь много различных операций, что для них не хватает  256 различных КОПов,  которые можно представить в одном байте. Поэтому некоторые операции объединяются в группу, и им дается один и тот же КОП,  во втором же байте этот КОП уточняется. Кроме того, во втором байте указываются типы и способ адресации операндов. Остальные байты команды указывают на операнды. У большинства команд  - один или два операнда.  Размер операндов  - байт  или  слово  (редко -двойное слово).  Операнд может быть указан в самой команде  (это  т.н. непосредственный операнд),  либо может находиться в одном из регистров  и тогда в команде указывается этот регистр, либо может находиться в ячейке памяти и тогда в команде тем или иным способом указывается  адрес этой ячейки.  Некоторые команды требуют, чтобы операнд находился в фиксированном месте (например, в регистре AX), тогда операнд  явно  не указывается в команде.  Результат выполнения команды  помещается в регистр или ячейку памяти, из которого (которой),  как правило,  берется первый операнд. Например, большинство команд с двумя операндами реализуют действие         op1 := op1  op2, где op1  - регистр или ячейка,  а op2  - непосредственный операнд, регистр или ячейка.

Адрес операнда разрешено модифицировать по одному или двум регистрам.  В первом случае  в качестве регистра-модификатора  разрешено использовать регистр BX, BP, SI или DI (и никакой иной).  Во втором случае один из модификаторов обязан быть регистром BX или BP,  а другой - регистром SI или DI;  одновременная модификация по BX и BP или SI и DI недопустима.

В  ассемблере  адреса  в командах записываются в виде одной из следующих конструкции:

          A, A[M] или A[M1][M2],

где A - адрес, M - регистр BX, BP, SI или DI,  M1 - регистр BX или BP, а M2 - регистр SI или DI. Во втором и третьем варианте A может отсутствовать, в этом случае считается, что A=0.

 При выполнении команды  процессор прежде всего вычисляет  т.н. эффективный (исполнительный) адрес  -  как сумму адреса, заданного в команде, и текущих значений  указанных  регистров-модификаторов,  причем все  эти  величины  рассматриваются как неотрицательные и суммирование ведется по модулю 216 ([r] означает содержимое регистра r):

      A        :    Aисп = A

      A[M]     :    Aисп = A+[M] (mod 216)

      A[M1][M2]:    Aисп = A+[M1]+[M2] (mod 216)

Полученный таким образом 16-разрядный адрес определяет т.н. смещение - адрес, отсчитанный от начала некоторого сегмента (области) памяти.  Перед обращением к памяти процессор еще добавляет к смещению  начальный адрес этого сегмента, (он хранится в некотором сегментном регистре),  в результате чего получается  окончательный  20-разрядный  адрес, по которому и происходит реальное обращение к памяти.

   

        2.2. Режимы адресации  и форматы машинных команд

Микропроцессор Intel предоставляет множество способов доступа к операндам. Операнды могут содержаться в регистрах, самих командах, в памяти или в портах ввода-вывод. Режимы адресации разделяются на семь групп:

  1.  Регистровая адресация.
  2.  Непосредственная адресация.
  3.  Прямая адресация.
  4.  Косвенная регистровая адресация.
  5.  Адресация по базе.
  6.  Прямая адресация с индексированием.
  7.  Адресация по базе с индексированием.

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

MOV AX,BX

То ассемблер закодирует оба операнда (AX, BX)  для регистровой адресации. Если же поместить регистр BX в квадратные скобки:

 MOV AX,[BX]

То Ассемблер закодирует операнд-источник для косвенной регистровой адресации.

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

                          

                                   Табл. 1

    Режим адресации

    Формат операнда

     Регистр сегмента

 Регистровый   

      Регистр

 Не используется

 Непосредственный

      Данное

 Не используется

 Прямой

       Метка

        Сдвиг

                DS

                DS

  Косвенный регистровый

          [BX]

          [BP]

          [DI]

          [SI]

                 DS

                  SS

                  DS

                  DS

  По базе

     [BX]+сдвиг

     [BP]+сдвиг

                  DS

                  CS

Прямой с индексированием

     [DI]+сдвиг

      [SI]+сдвиг

                  DS

                  DS

По базе с индексированием

   [BX][SI]+сдвиг

   [BX][DI]+сдвиг

   [BP][SI]+сдвиг

   [BP][DI]+сдвиг

                  DS

                  DS

                  SS

                  SS

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

   

   1) Формат "регистр-регистр" (2байта):

            КОП   d  w   11   reg1  reg2

         7  ...    2  1  0  7 6  5 4  3 2 1 0

Команды  этого формата описывают обычно действие  reg1:=reg1reg2  или reg2:=reg2reg1. Поле КОП первого байта указывает на операцию (), которую надо выполнить.  Бит w определяет размер операндов, а бит d указывает, в какой из регистров записывается результат:

      w = 1 - слова      d = 1 - reg1:=reg1reg2

        = 0 - байты        = 0 - reg2:=reg2reg1

Во втором байте  два левых бита  фиксированы (для данного формата),  а трехбитовые поля reg1 и reg2 указывают на регистры, участвующие в операции, согласно следующей таблице:

        Табл.2

reg

W=1

W=0

Reg

W=1

W=0

000

AX

AL

100

SP

AH

001

CX

CL

101

BP

CH

010

DX

DL

110

SI

DH

011

BX

BL

111

DI

BH

   2) Формат "регистр-память" (2-4 байта):

      

        КОП  |d|w|  |mod|reg|mem|  |адрес (0-2 байта)

      

Эти команды описывают операции  reg:=regmem  или  mem:=memreg. Бит w первого байта определяет размер операндов (см. выше), а бит d указывает, куда записывается результат:  в регистр (d=1)  или в ячейку памяти (d=0).  Трехбитовое поле reg второго байта  указывает  операнд-регистр (см. выше),  двухбитовое поле mod определяет, сколько байтов в команде занимает операнд-адрес  (00 - 0 байтов, 01 - 1 байт, 10 - 2 байта),  а трехбитовое поле mem указывает способ модификации этого адреса. В следующей таблице указаны правила вычисления исполнительного адреса в зависимости от значений полей mod и mem (a8 - адрес размером в байт, a16 - адрес размером в слово):

      Табл.3

  

 mem   mod           00                    01                       10

    

     000           [BX]+[SI]     [BX]+[SI]+a8      [BX]+[SI]+a16

     001           [BX]+[DI]    [BX]+[DI]+a8      [BX]+[DI]+a16

     010           [BP]+[SI]     [BP]+[SI]+a8       [BP]+[SI]+a16

     011           [BP]+[DI]     [BP]+[DI]+a8      [BP]+[DI]+a16

     100           [SI]               [SI]+a8                 [SI]+a16

     101           [DI]              [DI]+a8                 [DI]+a16

     110           a16               [BP]+a8                [BP]+a16

     111           [BX]             [BX]+a8               [BX]+a16

   Замечания. Если в команде не задан адрес, то он считается нулевым.

Если адрес задан в виде байта (a8), то он автоматически расширяется со знаком до слова (a16). Случай mod=00 и mem=110 указывает на отсутствие регистров-модификаторов, при этом адрес должет иметь размер слова (адресное выражение [BP] ассемблер транслирует в  mod=01  и  mem=110  при a8=0). Случай mod=11 соответствует формату "регистр-регистр".

   

   3) Формат "регистр-непосредственный операнд" (3-4 байта):

      

         КОП |s|w|  |11|КОП"|reg|  |непосред.операнд (1-2 б)

      

Команды этого формата описывают операции  reg:=regimmed  (immed - непосредственный операнд).  Бит w указывает на размер операндов,  а поле reg - на регистр-операнд (см. выше). Поле КОП в первом байте определяет лишь класс операции (например, класс сложения),  уточняет же операцию поле КОП" из второго байта.  Непосредственный операнд  может занимать 1 или 2 байта - в зависимости от значения бита w,  при этом  операнд-слово записывается в команде в "перевернутом" виде. Ради экономии памяти в ПК предусмотрен случай,  когда в операции над словами  непосредственный операнд может быть задан байтом  (на этот случай указывает 1 в бите s при w=1), и тогда перед выполнением операции байт автоматически расширяется (со знаком) до слова.

    4) Формат "память-непосредственный операнд" (3-6 байтов):

 КОП |s|w|  |mod|КОП"|mem|  |адрес (0-2б)|  |непоср.оп (1-2б)|

Команды этого формата  описывают операции типа  mem:=memimmed.  Смысл всех полей - тот же, что и в предыдущих форматах.

          Помимо рассмотренных  в ПК используются и другие форматы команды с двумя операндами;  так,  предусмотрен  специальный формат  для команд, один из операндов которых  фиксирован (обычно  это регистр AX).  Имеют свои форматы и команды с другим числом операндов.

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

        MOV op1,op2     (op1:=op2)

Анализируя типы операндов,  ассемблер сам выбирает подходящую машинную команду.

Регистры указываются своими именами, например:

        MOV AX,SI      ;оба операнда - регистры

   Непосредственные операнды  задаются  константными выражениями  (их значениями являются константы-числа), например:

        MOV   BH,5                ;5 - непосредственный операнд

        MOV DI,SIZE_X  ;SIZE_X (число байтов, занимаемых переменной                  X) - непосредственный операнд

Адреса описываются адресными выражениями  (например, именами переменных),  которые могут быть модифицированы по одному или двум регистрам; например, в следующих командах первые операнды задают адреса:

        MOV X,AH

        MOV X[BX][DI],5

        MOV [BX],CL

   При записи команд в символьной форме  необходимо  внимательно следить за правильным указанием типа (размера) операндов,  чтобы  не было ошибок. Тип обычно определяется по внешнему виду одного из них, например:

        MOV AH,5        ;пересылка байта, т.к. AH - байтовый регистр

        MOV AX,5      ;пересылка слова, т.к. AX - 16-битовый регистр;(операнд 5 может быть байтом и словом, по нему  ;нельзя определить размер пересылаемой величины)

        MOV [BX],300  ;пересылка слова, т.к. число 300 не может быть байтом.

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

        MOV  DS,AX   ;оба операнда имеют размер слова

        MOV  CX,BH   ;ошибка: регистры CX и BH имеют разные размеры

        MOV  DL,300   ;ошибка: DL - байтовый регистр, а число 300 не

                                ;может быть байтом

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

       MOV  [BX],5

Здесь  число 5  может быть и байтом, и словом,  а адрес из регистра BX может указывать и на байт памяти, и на слово. В подобных ситуациях ассемблер фиксирует ошибку.  Чтобы избежать ее, надо уточнить тип одного из операндов с помощью оператора с названием PTR:

        MOV BYTE PTR [BX],5  ;пересылка байта

        MOV WORD PTR [BX],5  ;пересылка слова

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

Оператор PTR  необходим и в том случае,  когда надо изменить  тип, предписанный имени при его описании. Если, например, X описано как имя переменной размером в слово:

         X DW 999

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

MOV AH,X

нельзя, т.к. ее операнды имеют разный размер.  Эту команду следует записать несколько иначе:

         MOV AH,BYTE PTR X

Здесь конструкция BYTE PTR X  означает адрес X, но уже рассматриваемый не как адрес слова, а как адрес байта.  (Напомним, что с одного и того же адреса  может начинаться байт, слово и двойное слово;  оператор PTR уточняет, ячейку какого размера мы имеем в виду.)

  3. Псевдооператоры

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

 

        3.1 Директивы определения данных

Для того чтобы в программе на ассемблере  зарезервировать  ячейки памяти под константы и переменные, необходимо воспользоваться директивами определения данных - с названиями DB (описывает данные размером в байт), DW (размером в слово) и DD (размером в двойное слово). (Директивы, или команды ассемблеру, - это предложения программы, которыми ее автор сообщает какую-то информацию ассемблеру или просит что-то сделать дополнительно, помимо перевода символьных команд на машинный язык.)

В простейшем случае в директиве DB,  DW  или  DD  описывается одна константа,  которой дается имя для последующих ссылок на нее.  По этой директиве ассемблер формирует машинное представление константы (в частности, если надо, "переворачивает" ее) и записывает в очередную ячейку памяти. Адрес этой ячейки становится значением имени: все вхождения имени в программу ассемблер будет заменять на этот адрес.  Имена, указанные в директивах DB, DW и DD, называются именами переменных  (в отличие от меток - имен команд).

   В ассемблере числа записываются в нормальном (неперевернутом) виде в cистемах счисления с основанием 10, 16, 8 или 2.  Десятичные числа записываются как обычно, за шестнадцатиричным числом ставится буква h (если число начинается с "цифры" A, B, ..., F,  то вначале обязателен 0), за восьмиричным числом - буква q или o,  за двоичным числом - буква b.

   Примеры:

       A  DB 162       ;описать константу-байт 162 и дать ей имя A

       B  DB 0A2h    ;такая же константа, но с именем B

      С DW -1      ;константа-слово -1 с именем С

      D  DW 0FFFFh  ;такая же константа-слово, но с именем D

      E  DD -1      ;-1 как двойное слово

   Константы-символы описываются в директиве DB  двояко:  указывается либо код символа (целое от 0 до 255), либо сам символ в кавычках (одинарных или двойных);  в последнем случае ассемблер сам заменит  символ на его код. Например, следующие директивы эквивалентны (2A - код звездочки в ASCII):

       CH DB 02AH

       CH DB '*'

      Константы-адреса, как правило, задаются именами. Так, по директиве

       ADR DW CH

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

 По любой из директив  DB, DW и DD  можно описать переменную,  т.е. отвести ячейку, не дав ей начального значения.  В этом случае в правой части директивы указывается вопросительный знак:

          F DW ?  ;отвести слово и дать ему имя F, ничего в этот байт не                записывать

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

         G  DB 200, -5, 10h, ?, 'F'

Имя,  указанное в директиве,  считается именующим  первую из констант. Для ссылок на остальные в ассемблере используются выражения вида  <имя>+<целое>; например, для доступа к байту с числом -5 надо указать выражение G+1, для доступа к байту с 10h - выражение G+2 и т.д.

   Если в директиве DB перечислены только символы, например:

       S  DB 'a','+','b'

тогда эту директиву можно записать короче,  заключив все эти символы в одни кавычки:

       S  DB 'a+b'

   И, наконец, если в директиве описывается несколько одинаковых констант (переменных), то можно воспользоваться конструкцией повторения         

K  DUP(a,b,...,c)

которая эквивалентна  повторенной k раз последовательности  a,b,...,c.

Например, директивы

     V1 DB 0,0,0,0,0

     V2 DW ?,?,?,?,?,?,?,?,?,'a',1,2,1,2,1,2,1,2

можно записать более коротко таким образом:

     V1 DB  5 DUP(0)

     V2 DW  9 DUP(?), 'a', 4 DUP(1,2)

     3.2 Структура программы на ассемблере

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

     3.2.1 Программные сегменты. Директива ASSUME

Для того чтобы указать, что некоторая группа предложений программы на ассемблере образуют единый сегмент памяти,  они оформляются как программный сегмент: перед ними ставится директива SEGMENT с операндами, после них - директива ENDS, причем в начале обеих этих директив должно быть указано одно и то же имя, играющее роль имени сегмента.  Программа  же  в  целом представляет собой  последовательность таких программных сегментов,  в конце которой указывается директива конца программы END, например:

   DT1 SEGMENT    PARA PUBLIC ‘DATA’  ; сегмент данных с именем DT1

     A DB 0

     B DW ?

   DT1 ENDS

  CODE SEGMENT      ; кодовый сегмент CODE

       ASSUME CS:CODE, DS:DT1

  MAIN   PROC

       MOV  AX,DT1   ;инициализация сегмента

       MOV DS,AX   ;данных

        MOV AX,A       

          ...

MOV AX,4C00H  ; выход из

 INT 21H   ; программы

 MAIN   ENDP   ; конец процедуры MAIN

  CODE  ENDS    ; конец сегмента кода

       END MAIN         ;конец  программы

Важно отметить, что функциональное назначение сегмента несколько шире, чем просто разбиение программ на блоки кода, данных и стека. Сегментация является частью более общего механизма, связанного с концепцией модульного программирования, она предполагает унификацию оформления объектных модулей, создаваемых компилятором, в том числе с разных языков программирования. Это позволяет объединять программы, написанные на разных языках. Именно для реализации различных вариантов такого объединения и предназначены операнды в директиве SEGMENT:

 SEGMENT <тип выравнивания><тип комбинирования><класс><тип размера сегмента>

Первый операнд в директиве – атрибут выравнивания сегмента (тип выравнивания) сообщает компановщику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Допустимые значения атрибута:

 BYTE – выравнивание не выполняется. Сегмент может начинаться с любого адреса.

 WORD – сегмент начинается по адресу, кратному двум.

DWORD – сегмент начинается по адресу кратному четырем.

PARA  - сегмент начинается по адресу, кратному 16. Принимается по умолчанию.

 PAGE  -сегмент начинается по адресу кратному 256.

NEMPAGE – сегмент начинается по адресу, кратному 4 Кбайт.

Следующий атрибут комбинирования сегментов(комбинаторный тип) сообщает компоновщику, как нужно комбинировать сегменты различных модулей, имеющих одно и то же имя. По умолчанию атрибут комбинирования принимает значение PRIVATE.  Атрибуты комбинирования могут быть следующими:

PRIVATE – сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля.

PUBLIC – компоновщик соединит все сегменты с одинаковыми именами.

COMMON – располагает все сегменты с одним и тем же именем по одному адресу.

AT xxxx – располагает сегмент по абсолютному адресу параграфа. Абсолютный адрес параграфа задается выражением xxxx.

STACK – определение сегмента стека. Заставляет компоновщик соединять все одноименные сегменты и вычислять адреса этих сегментов относительно регистра SS.

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

Все ссылки  на предложения одного программного сегмента  ассемблер сегментирует по умолчанию по одному и тому же сегментному регистру. По какому именно - устанавливается специальной директивой ASSUME. В нашем примере эта директива определяет, что все ссылки на сегмент CODE должны, если явно не указан сегментный регистр, сегментироваться по регистру CS,  все ссылки на DT1 - по регистру DS,  а все ссылки на DT2 - по регистру ES.

Встретив в тексте программы ссылку на какое-либо имя (например, на имя C в команде  MOV AX,A),  ассемблер определяет, в каком программном сегменте оно описано (у нас - в DT2). Затем по информации из директивы ASSUME узнает, какой сегментный регистр поставлен в соответствие этому сегменту (у нас - это ES),  и далее образует адресную пару  из данного регистра и смещения имени (у нас - ES:0),  которую и записывает в формируемую машинную команду.

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

Директива ASSUME должна быть указана перед первой командой программы. В директиве ASSUME следует каждому сегменту ставить в соответствие сегментный регистр.  

   

  1.  Начальная загрузка сегментных регистров

Директива ASSUME сообщает ассемблеру о том,  по каким регистрам он должен сегментировать имена  из каких сегментов,  и  "обещает",  что в этих регистрах будут находиться начальные адреса этих сегментов. Однако  загрузку этих адресов в регистры  сама директива  не осуществляет.

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

   Поскольку в ассемблере  нет команды пересылки непосредственного операнда в сегментный регистр  (а имя, т.е. начало, сегмента - это непосредственный операнд),  то такую загрузку приходится делать через какой-то другой, несегментный, регистр (например, AX):

        MOV AX,DT1   ;AX:=начало сегмента DT1

        MOV DS,AX    ;DS:=AX

Аналогично загружается и регистр ES.

   Загружать регистр CS в начале программы не надо: он, как и счетчик команд IP, загружается операционной системой перед тем, как начинается выполнение программы (иначе нельзя было бы начать ее выполнение).  Что же касается регистра SS, используемого для работы со стеком, то он может быть загружен так же, как и регистры DS и ES.

Это называется стандартными директивами сегментации.    

  1.  Упрощенная директива сегментации

Для простых программ, содержащих по одному сегменту кода, данных и стека возможно использовать упрощенную директиву сегментации. Совместно с упрощенными директивами сегментации используется директива указания модели памяти MODEL, которая управляет размещением сегментов и выполняет функцию директивы ASSUME.  Эта директива связывает сегменты с предопределенными именами.

Существуют модели памяти:

    tiny      и код и данные программы должны  помещаться   внутри

              одного 64  Кбайтного  сегмента.  И  код  и  данные -

              ближние.

    small     код программы  должен   помещаться   внутри   одного

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

              другого 64 К сегмента. И код и данные - ближние.

    medium    код программы может быть больше   64 К,   а   данные

              должны  помещаться внутри 64   К   сегмента.   Код -

              дальний, а данные - ближние.

    compact   программный код  должен   помещаться   внутри   64 К    сегмента, а данные могут быть больше   64 К.   Код -      ближний, а данные - дальние. Любой массив данных  не                может быть больше 64 К.

    large     и код и данные могут быть больше 64 К,  но  ни  один        массив данных не может быть больше 64 К.   И  код  и     данные - дальние.

    huge      и  код  и  данные  могут  быть больше 64 К, и массив  данных может быть больше 64 К.   И  код  и  данные -дальние. Указатели  на   элементы  внутри  массива - дальние.

    Заметим, что  с  точки  зрения  ассемблера,   large   и   huge идентичны. Модель huge не поддерживает автоматически массивы данных больше 64 К.

    Немногие ассемблерные  программы  требуют  более 64 К кода или данных, поэтому модель smALl оптимальна для  большинства  программ. Вы должны использовать модель smALl, где это возможно, т.к. дальний код (модели medium, large и huge) делают выполнение программы более медленным; дальние   данные   (модели   compact,   large   и  huge) значительно труднее обрабатывать на ассемблере.

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

Директива MODEL требуется,  если Вы  используете  упрощенные  директивы сегментации, иначе  Турбо-Ассемблер  не  будет знать, как установить сегменты, определенные   с   .CODE   и   .DATA.    MODEL    должна предшествовать директивам .CODE, .DATA, .STACK.

    Приведем заготовку    программы,    использующей    упрощенные директивы сегментации:

 Пример:

   MASM                                            ;  режим работы TASM

   MODEL       SMALL                      ;модель памяти

.DATA                                                ;сегмент     данных

     A DB 0

     B DW ?

.STACK    ;сегмент стека

    DB 256 DUP(?)

.CODE     ; сегмент кода

  MAIN PROC

    MOV AX,@DATA  ; инициализация сегмента

    MOV DS,AX   ; данных

  .  .   .

  MOV AX,4C00H   ;выход

    INT 21H    ;из программы

 MAIN ENDP   ;конец процедуры

        ENDMAIN     ;конец программы с точкой выхода main

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

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

    .FARDATA позволяет Вам определить дальний сегмент данных, т.е. сегмент данных,   отличный   от   стандартного   сегмента    @data, используемого всеми   модулями.  .FARDATA  позволяет  ассемблерному модулю определить его собственный сегмент данных размером  более 64 К. Если .FARDATA была использована, @fardata - это имя для дальнего сегмента данных, указанного этой директивой, так же как @data - это имя сегмента данных, указанного .DATA.

    .FARDATA? во многом подобна .FARDATA за исключением  того, что она   определяет  неинициализированный  дальний  сегмент.  Как  для .FARDATA  и  @fardata,  если  была  задана   директива   .FARDATA?, @fardata?  - это имя для дальнего сегмента данных,  указанного этой директивой.

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

    Когда используются упрощенные директивы  сегментации, доступны некоторые предопределенные    метки.    @FileName    -    это   имя ассемблируемого файла.  @curseg - это имя сегмента,  который сейчас ассемблируется Турбо-Ассемблером.  @CodeSIze - 0 для моделей памяти с ближними кодовыми сегментами (tiny,  smALl и compact)  и  единица для моделей памяти с дальними кодовыми сегментами (medium,  large и huge). Аналогично @DataSIze -  0  для  моделей  памяти  с  ближними сементами данных (tiny,  smALl и medium), 1 в compact и large и 2 в huge модели.

4. Ассемблирование  и компоновка  программы.

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

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

    Чтобы ассемблировать программу PROG.ASM наберите

    TASM PROG

Если  не  указать  другого  имени  файла,  PROG.ASM  будет ассемблироваться в   PROG.OBJ.  (Заметим,  что  не  требуется набирать расширение имени  файла;  Турбо-Ассемблер  добавляет  .ASM сам). На экране появится

    Turbo Assembler  version  1.0  Copyright  (C)  1988 by Borland

International, Inc.

    Assembling file: prog.asm

    Error messages: none

    Warning messages: none

Если в программе содержатся ошибки,  они появятся  на экране вместе с номером строки,  в которой найдена эта ошибка. После исправления ошибок   отассемблируйте  программу снова.

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

    TLINK PROG

 Не нужно вводить расширение имени,  TLINK  добавит .OBJ сам.  Когда  компоновка  завершится редактор автоматически создаст .EXE файл с тем же именем, что и имя объектного файла, если Вы не укажете другого. На экране появится сообщение:

    Turbo Linker  version 2.0 Copyright (C) 1987,  1988 by Borland

International Inc.

Для отладки программы можно использовать программу TD – турбо-debuger, которая обеспечивает пошаговую трассировку и просмотр значений регистров, стека и оперативной памяти.

 

 5. Команды пересылки данных

Команды пересылки данных осуществляют обмен данными и адресами между ячейками памяти и регистрами или портами ввода-вывода. Эти команды разделены на четыре группы: команды общего назначения, команды ввода-вывода, команды пересылки адресов и команды пересылки флагов.

5.1 Команды общего назначения

Основная команда общего назначения MOV (MOVe – переслать) может переслать байт или слово между регистром и ячейкой памяти или между двумя регистрами. Она может также переслать непосредственно адресуемое значение в регистр или ячейку памяти.

Команда MOV имеет следующий формат:

 MOV приемник,источник.

Примеры с использованием команды MOV рассматривались в главе 2.2.

Отметим лишь исключающие сочетания операндов в команде MOV:

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

MOV AX,TAB1      ; пересылает данные из ячейки TAB1

 MOV TAB2,AX      ; в ячейку TAB2

2. Нельзя загрузить непосредственно адресуемый операнд в регистр сегмента. Например, инициализировать сегмент данных (DATAS):

 MOV  AX,DATAS

MOV  DS,AX.

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

  1.  Нельзя использовать регистр CS в качестве приемника.

5.2 Команды работы со стеком

Для временного сохранения данных (содержимого регистра или ячейки памяти) удобно использовать стек. Для работы со стеком есть две команды PUSH (поместить слово в стек) и POP (извлечь слово из стека).

Команда PUSH помещает содержимое регистра или ячейки памяти размером в слово на вершину стека. Команда POP, наоборот, снимает слово с вершины стека и помещает его в регистр или ячейку памяти.

Команды PUSH и POP имеют следующие форматы:

 PUSH источник

 POP    приемник.

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

 Вершина стека – это ячейка в сегменте стека, адрес которой содержится в указателе стека SP. Регистр SP всегда указывает на слово, помещенное в стек последним. Следовательно, команда PUSH  вычитает 2 из значения указателя стека, а затем пересылает операнд-источник в стек.

  4.3 Команды ввода-вывода

Команды ввода-вывода используются для взаимодействия с периферийными устройствами системы. Они имеют формат:

 IN     аккумулятор,порт   

 OUT  порт,аккумулятор

Где аккумулятор – регистр AL при обмене байтами или регистр AX при обмене словами. Операндом порт может быть десятичное число от 0 до 255, что позволяет адресоваться к 256 устройствам.

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

 IN       AL,200                 ;ввести байт из порта 200

 IN       AL,PORT_VAL  ;или из порта указанного константой

 OUT   30H,AX     ; вывести слово в порт 30H

 OUT    DX,AX     ; или в порт, указанный в DX.

  

5.4 Команды пересылки адреса

Команды пересылки адреса передает не содержимое переменных, а их адреса. Команда LEA (load effective address – загрузить эффективный адрес) пересылает смещение ячейки памяти в любой 16-битовый регистр общего назначения, регистр указателя или индексный регистр. Она имеет формат:

 LEA   регистр16,память16

Где операнд память16 должен иметь атрибут WORD.

Операнд память16 в команде LEA может быть индексирован, что дает возможность осуществить гибкую адресацию. Например, если регистр DI содержит 2, то команда

 LEA  BX,TABLE[DI]

Загрузит смещение адреса TABLE+2 в регистр BX.

Команда LDS (load poINTer uSIng DS – загрузить указатель с использованием регистра DS) считывает из памяти 32-битовое двойное слово и загружает первые 16 битов в заданный регистр, а следующие 16 битов – в регистр сегмента данных DS. Она имеет формат:

 LDS регистр16,память32,

Где регистр16 – любой 16-битовый регистр общего назначения, а память32 ячейка памяти с атрибутом типа DOUBLEWORD.

Команда LES (load poINTer uSIng ES – загрузить указатель с использованием регистра ES) идентична команде LDS, но загружает номер блока в регистр ES, а не в DS.

  5.5 Команды пересылки флагов

Команды пересылают содержимое регистра флагов в стек и обратно. PUSHF сохраняет флаги в стеке, POPF извлекает флаги из стека. Как и в случае с PUSH и POP эти команды всегда используются парами.

6. Арифметические команды

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

  1.  Арифметические операции над целыми двоичными числами.

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

6.1.1 Сложение и вычитание.

Для сложения и вычитания  используются   инструкции  ADD  и  SUB . ADD прибавляет содержимое операнда-  источника  (правый)  к  содержимому  операнда приемнике и   сохраняет   результат  в  операнде  приемнике.  SUB работает так же, за  исключением  того,  что  он  вычитает  операнд источника из операнда приемника. Например,

       ...

      .DATA

     BaseVAL   DW  99

     Adjust      DW 10

            ...

          .CODE

            ...

           MOV  DX,[BaseVAL]

     ADD   DX,11

      SUB    DX,[Adjust]

    ...

В начале  в DX загружается значение 99,  хранящееся в BaseVAL, затем к нему добавляется константа 11,  в результате в DX  значение 110,  и  наконец  из DX вычитается значение 10,  хранящееся в Ajust. Результат - в DX хранится 100.

ADD и SUB работают с восьми или шестнадцатибитными операндами. Если Вы  хотите  складывать  или  вычитать  32-битные операнды,  Вы должны разбить операцию на ряд пословных  операций  и  использовать ADC или SBB.

 Когда Вы складываете 2  операнда,  микропроцессор  сохраняет  статус  во флаге переноса (бит СF в регистре флагов) который указывает,  был ли перенос в назначении;  это происходит, когда результат сложения был слишком большой,  чтобы  уместиться  в  назначении.

ADC аналогичен ADD за исключением того, что он использует флаг переноса,  (установленный  предыдущим  сложением)  при  вычислении. Когда  Вы  складываете  два значения,  которые больше ,  чем слово, сложите младшие слова вместе,  с использованием ADD,  затем сложите оставшиеся  слова  значений,  одной  или  более  инструкциями  ADC, складывая наибольшие значащие слова последними. Например, следующий код  складывает  значение типа двойное слово,  хранящееся в CX:BX с значением типа двойное слово, хранящееся в DX:AX:

     ...

    ADD AX,BX

    ADC  DX,CX

     ...

SBB работает  во  многом  аналогично ADС.  Когда SBB выполняет вычитание, она учитывает флаг переноса,  который устанавливается во время предыдущего   вычитания.  Например,  следующий  код  вычитает двойное слово в CX:BX  из двойного слова DX:AX:

    ...

    SUB AX,BX

    SBB DX,CX

    ...

 Используя ADC и SBB, Вы должны быть уверены, что флаг переноса не изменялся после последнего вычитания или сложения,  иначе статус переноса,  хранящийся во флаге переноса,  будет потерян.

                  6.1.2  Команды приращения и уменьшения приемника на единицу

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

    В соответствии с частым использованием инкремента и декремента ассемблер предоставляет инструкции INC и  DEC.  Как  можно,  ожидать INC прибавляет к регистру или переменной 1 и DEC вычитает 1 из регистра или переменной.

    Например, следующий  код заполняет 10 байтный массив TempArray числами 0,1,2,3,4,5,6,7,8,9:

    ...

    .DATA

TempArray DB 10 DUP (?)

FillCount DW ?

    ...

    .CODE

    ...

    MOV AL,0                ;первое значение запоминается в TempArray

          MOV BX,OFFSET TempArray ;BX указывает на TempArray

    MOV [FillCount],10  ;# элемента для заполнения

M1:

    MOV [BX],AL             ;устанавливает текущий элемент TempArray

    INC BX          ;указатель на следующий элемент TempArray

    INC AL          ;следующее значение для запоминания

    DEC [FillCount]           ;уменьшить # элементов

    JNZ M1         ;делать другой элемент, если мы не

                             ;заполнили все элементы

    ...

Инструкция ADD - длиной  3  байта,  а  INC  только  1  байт  и работает быстрее.  В   действительности,  короче  использовать  две инструкции INC, чем прибавить 2 к регистру типа слово. (Инкремент и декремент регистра или памяти размера в байт занимают 2 байта - это короче, чем сложение или вычитание).

    Короче, INC и DEC более эффективны для инкремента и декремента регистров и переменных в памяти.

             6.2 Умножение и деление.

Инструкция MUL  умножает  два  8  или  16-битных  беззнаковых сомножителя, генерируя  16  или  32- битное число.  

Один из сомножителей при умножении  8-битовых чисел  должен быть запомнен в AL; другой может быть в любом 8-битном регистре  общего  назначения или быть операндом памяти. MUL всегда сохраняет 16 битный результат в AX. Например,

    ...

    MOV AL,25

    MOV DH,40

    MUL DH

    ...

умножает AL на DH,  сохраняя результат 1000 в AХ. Заметим, что MUL требует  только  один  операнд;  другой сомножитель всегда в AL (или в AX в случае умножения слов).

 При умножении слов  один сомножитель должен быть сохранен в AX,  в то время как другой может быть  любым  16-битным регистром общего  назначения или операндом памяти.  MUL помещает 32-битный результат в DX:AX с младшими 16 битами  результата  в  AX  и старшими 16 битами результата в DX. Например,

    ...

    MOV AX,1000

    MUL AX

    ...

загружает в AX 1000 и затем возводит  AX  в  квадрат,  помещая результат 1000000 в DX:AX.

В отличие от сложения и вычитания умножение зависит  от  того, знаковые операнды   или   беззнаковые,   поэтому   введена   вторая инструкция умножения IMUL для перемножения байтов и слов. За исключением того,  что  она  обрабатывает знаковые  значения,  IMUL аналогична MUL:

    ...

    MOV AL,-2

    MOV AH,10

    IMUL AH

    ...

    запоминает значение -20 в AX.

Команда DIV (divide –разделить) выполняет деление чисел без знака, а команда IDIV выполняет деление чисел со знаком. Команды имеют формат

DIV   источник

IDIV источник

Где источник делитель размеров в байт или слово, находящееся в регистре общего назначения или в ячейке памяти. Делимое должно иметь двойной размер; оно извлекается из регистра AX (при делении на 8-битовое число) или из регистров DX и AX при делении на 16-битовое число).  Результат  возвращается следующим образом:

Если операнд-источник представляет собой байт, то частное возвращается в регистр AL, а остаток в AH.

Если операнд-источник слово, то частное возвращается в AX, а остаток – в регистр DX.

Обе команды оставляют состояние флагов неопределенными, но если частное не помещается в регистре приемнике (AL или AX), то генерируется прерывание типа 0 (деление на 0).

    ...

    MOV AX,51

    MOV DL,10

    DIV DL

    ...

Результат 5 в AL и остаток 1 в AH.  Пример:

    ...

    MOV AX,0ffffh

    MOV BL,1

    DIV B1

    ...

генерирует прерывание  0. (Как Вы ожидаете, прерывание деления на 0 будет так же генерироваться, если делитель равен 0).

Как и   умножение   деление  зависит  от  того,  знаковые  или беззнаковые операнды используются. DIV используется для беззнаковых операндов, а IDIV - для знаковых операндов. Например,

    ...

    .DATA

TDiv DW 100

    ...

    .CODE

    ...

    MOV AX,-667

    CDW        ;установить DX:AX в -667

    IDIV [TDiv]

    ...
    сохраняет -6 в AX и -67 в DX.

                      6.3  Изменение знака.

 Инструкции NEG  может изменить знак    содержимого   регистра   общего   назначения   или переменной. Например,

    ...

    MOV AX,1     ;устанавливает AX в 1

    NEG  AX        ;изменяет AX в -1

    MOV BX,AX ;копирует AX в BX

    NEG  BX       ;изменяет BX в 1

    ...

В результате получим  в AX -1, а в BX 1.

                   7.   Логические операции

Ассемблер поддерживает  полный набор инструкций, которые производят логические операции,  включая END,  OR,  XOR и NOT.  Эти инструкции полезны  для  манипуляции отдельными битами внутри байта или слова и для реализации булевской алгебры.

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

     AND AX,DX

производит логический AND с битом 0 AX и с битом 0 в DX, как с битами  источника,  и  битом 0 в AX, как битом назначения.  И то же самое, с битами 1,2 и т.д. до 15 бита.

    

Логические операции                        Табл.4

    Исходный бит А   Исходный бит В   A AND B    A OR B   A XOR B

              0                               0                       0                  0              0

              0                               1                       0                  1              1

              1                               0                       0                  1              1

              1                               1                       1                  1              0

 

Инструкция AND обрабатывает  два  операнда  в  соответствии  с  правилами, показанными   в  табл. . ,  устанавливая  каждый  бит  в назначении в 1 только тогда,  когда соответствующие биты источников - 1.  AND  позволяет  Вам  выделить  указанный  бит  или установить указанные биты в 0. Например,

    ...

    MOV DX,3dAH

    IN AL,DX

    AND AL,1

    ...

выделяет бит 0 из байта статуса цветного графического адаптера (CGA). Этот код устанавливает AL в 1, если видеопамять на CGA может быть обновлена без снега и устанавливает в 0, в противном случае.

Инструкция OR   обрабатывает   2  операнда  в  соответствии  с правилами, показанными  в  табл.4.2  устанавливая  каждый   бит   в назначении в   1  если  хотя  бы  1  соответствующий  исходный  бит установлен в 1. OR позволяет Вам устанавливать исходный бит(ы) в 1.

Например,

    ...

    MOV AX,40h

    MOV ds,AX

    MOV bx,10h

    OR  WORD PTR [bx],0030h

    ...

устанавливает 4-й и 5-й биты слова,  флага аппаратуры BIOS,  в 1, заставляя BIOS поддерживать монохромный дисплейный адаптер.

Инструкция XOR  обрабатыват  2  операнда  в   соответствии   с правилами,   приведенными   в  табл.4,  устанавливая  каждый  бит назначения в 1,  только если 1 из соответствующих  битов  источника был 0, а другой 1. Например,

    ...

    MOV AL,01010101b

     XOR AL,11110000b

    ...

    устанавливает AL в 10100101b или A5h.  Главное здесь  то,  что когда  выполняется  "исключающее  или"  AL  с  11110000b  или 0F0h, единичные биты 0F0h переключают значения соответствующих  битов,  в то  время  как нулевые биты в 0F0h оставляют соответствующие биты в AL неизменными.  В результате этого, все биты в старшей половине AL изменяются, а биты в младшей половине AL остаются неизменными.      Поэтому XOR,  удобный способ обнулить регистр.  Например, этот код установит AX в 0:

    XOR AX,AX

 NOT  переключает  каждый  бит  операнда  в противоположное состояние, как, если  бы  была  выполнена   XOR   с операндом источника 0FFh. Например,

    ...

    MOV bl,10110001b

    NOT bl             ;переключает BL в 01001110b

    XOR bl,0fffh      ;возвращает BL в  10110001b

    ...

                 8. Сдвиги и циклические сдвиги

Микропрцессор Intel обеспечивает   ряд  способов  для  передвижения  битов  в регистре или переменной памяти вправо или влево.  Простейший из них - логический сдвиг.

SHL (сдвиг влево, так же известный как SAL) передвигает каждый бит  в  назначении  на  1 разряд влево или в направлении к старшему значащему биту.  Значение 1010110b (96h или 150d),  хранящееся  в  AL  сдвигается  влево  при  помощи SHL AL,1. Результат - значение 00101100b (2Ch или 44d) , которое возвращается в AL. Флаг переноса устанавливается в 1.

    Старшие значащие биты сдвигаются за пределы операнда и во флаг переноса, а в младшие значащие биты устанавливаются нули.

    Для чего   используется   сдвиг    влево?    Наиболее    общее использование  SHL  -  это  выполнение  быстрого  умножения  на  2, поскольку каждый SHL умножает операнд на 2. Например, следующий код умножает DX на 16:

    ...

    SHL DX,1     ;DX * 2

    SHL DX,1     ;DX * 4

    SHL DX,1     ;DX * 8

    SHL DX,1     ;DX * 16

    ...

   Умножение при  помощи сдвига много быстрее,  чем использование инструкции MUL. Заметьте, что  второй  операнд  SHL  в  этом  примере,   имеет значение  1.  Это  означает,  что DX должен быть сдвинут влево на 1 бит.   Для сдвига на число бит большее чем 1 можно использовать CL; например,

    ...

    MOV CL,4

    SHL DX,CL

    ...

    умножает DX на 16, как в предыдущем примере.

Если существует  левый  сдвиг,  то  наверное,  должен  быть  и правый.  SHR (сдвиг вправо) во многом подобен SHL: он сдвигает биты операнда вправо,  либо на 1,  либо на  CL  бит,  при  этом  младший значащий  бит  сдвигается во флаг переноса,  а старший значащий бит устанавливается в 0.  SHR - это быстрый способ беззнакового деления на 2.

SAR - арифметический  сдвиг  вправо,  такой  же  как  SHR,  за исключением   того,   что  в  SAR  старший  значащий  бит  операнда сдвигается вправо на следующий бит и затем  возвращается  в  самого себя.  Значение 10010110b (90h или -106d), хранящееся в AL сдвигается вправо с помощью SAR AL,1.  Результат  - значение 11001011b (0CBh или -53d) сохраняемый в AL.  Флаг переноса устанавливается в 0.

    В результате этого, сохраняется  знак  операнда,  поэтому  SAR полезен для знакового деления на 2. Например,

    ...

    MOV bx,-4

    SAR bx,1

    ...

    сохраняет -2 в BX.

 Существует 4 инструкции циклического сдвига:  ROR,  ROL, RCR и RCL.  ROR подобен SHR,  за исключением,  что младший  значащий  бит кроме  сдвига  во флаг переноса сдвигается так же обратно в старший значащий бит.  Рис.4.10 показывает, как значение 10010110b (96h или 150d),  хранящееся в AL, циклически сдвигается вправо с помощью ROR  AL,1.  Результат -  значение  01001011b  (04Bh  или  75d),  которое сохраняется в AL. Флаг переноса установлен в 0.

ROL выполняет   действие,   обратное   ROR,   сдвигая  операнд циклическим образом,  но влево,  помещая  старший  значащий  бит  в  младший значащий бит. Например,

    ...

    MOV SI,49f1h

    MOV CL,4

    ROR SI,CL

    ...

    помещает 149fh в SI, сдвигая биты 3-0 в биты 15-12, биты 7-4 в биты 3-0 и т.д.

    RCR и RCL несколько отличаются.  RCR подобен сдвигу вправо,  в котором старший значащий бит сдвигается из флага переноса. Значение 10010110b (96h или 150d), хранящееся в AL, циклически сдвигается вправо через флаг переноса,  который содержит значение 1,  с помощью RСR AL,1.  Результат  -  значение  11001011b (0cbh или 203d) которое сохраняется в AL.  Флаг переноса установлен в 0.

    RCL похож  на  сдвиг  влево,  в  котором  младший значащий бит заполняется из флага  переноса.  RCR  и  RCL  полезны  для  сдвигов многословных операндов.  Например, значение, типа двойного слова, в DX:AX умножается на 4:

    ...

    SHL AX,1    ;бит 15 AX сдвигается в перенос

    RCL DX,1    ;перенос сдвигается в бит 0 DX

    SHL AX,1    ;бит 15 AX сдвигается в перенос

    RCL DX,1    ;перенос сдвигается в бит 0 DX

    ...

    Инструкции циклического сдвига,  как и инструкции сдвига могут сдвигать операнд либо на 1 бит, либо на число бит, указанных в CL.

 9. Строковые операции

   

В ассемблере под строкой понимается последовательность соседних байтов или слов.  В связи с этим  все строковые команды имеют две разновидности - для работы со строками из байтов (в мнемонику операций входит буква B) и для работы со строками из слов (в мнемонику входит W).    Имеются следующие операции над строками:

  - пересылка элементов строк (в память, из памяти, память-память);

  - сравнение двух строк;

  - просмотр строки с целью поиска элемента, равного заданному.

   Каждая из этих операций  выполняется  только  над одним  элементом строки,  однако,  одновременно  происходит  автоматическая настройка на следующий  или предыдущий элемент строки.  Имеются специальные команды повторения (REP и др.), которые заставляют следующую за ними строковую команду многократно повторяться (до 216 раз), в связи с чем, такая пара команд позволяет обработать всю строку, причем намного быстрее, чем запрограммированный цикл.

Кроме того, строки можно просматривать вперед (от их начала к концу) и назад.  Направление просмотра  зависит  от флага направления DF, значение  которого  можно  менять  с  помощью команд STD (DF:=1) и CLD (DF:=0). При DF=0 все последующие строковые команды программы просматривают строки вперед, а при DF=1 - назад.    В строковых командах операнды явно не указываются,  а подразумеваются.  Если команда работает с одной строкой, то адрес очередного, обрабатываемого сейчас элемента строки задается  парой регистров DS и SI или парой ES и DI,  а если команда работает с двумя строками, то адрес элемента одной из них определяется парой DS:SI,  а адрес элемента другой  -  парой ES:DI.  После выполнения операции  значение  регистра SI и/или DI увеличивается (при DF=0) или уменьшается (при DF=1) на 1 (для байтовых строк) или на 2 (для строк из слов).

 Начальная установка всех этих регистров,  а также флага DF  должна быть выполнена до начала операции над строкой. Если сегментный регистр DS  уже имеет нужное значение,  тогда загрузить регистр SI можно с помощью команды   LEA SI,<начальный/конечный адрес строки> Если же  надо загрузить сразу  оба регистра DS и SI,  тогда можно воспользоваться командой

        LDS SI,m32

которая в регистр SI заносит  первое слово,  а в регистр DS  -  второе слово из двойного слова, имеющего адреc m32  (таким образом, по адресу m32+2 должен храниться сегмент, а по адресу m32  - смещение начального или конечного элемента строки).  Начальную загрузку  регистров ES и DI обычно осуществляют одной командой

        LES DI,m32

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

Перечислим вкратце строковые команды ассемблера.

Команда  загрузки элемента строки в аккумулятор  (LODSB или LODSW) пересылает в регистр AL или AX  очередной элемент строки,  на  который указывает пара DS:SI,  после чего увеличивает (при DF=0) или уменьшает (при DF=1) регистр SI на 1 или 2.

Команда записи аккумулятора в строку (STOSB или STOSW) заносит содержимое регистра AL или AX в тот элемент строки, на который указывает пара ES:DI, после чего изменяет регистр DI на 1 или 2.     Команда пересылки строк (MOVSB или MOVSW) считывает элемент первой строки, определяемый парой DS:SI,  в элемент второй строки, определяемый парой ES:DI, после чего одновременно меняет регистры SI и DI.     Команда  сравнения строк  (CMPSB или CMPSW)  сравнивает  очередные элементы строк, указываемые парами DS:SI и ES:DI,  и результат сравнения (равно, меньше и т.п.) фиксирует в флагах, после чего меняет регистры SI и DI.

Команда сканирования строки  (SCASB или SCASW)  сравнивает элемент строки, адрес которого задается парой ES:DI,  со значением регистра AL или AX и результат сравнения фиксирует в флагах, после чего меняет содержимое регистра DI.

Перед  любой строковой командой  можно поставить  одну из двух команд, называемых "префиксами повторения", которая заставит многократно повториться эту строковую команду.  Число повторений (обычно это длина строки)  должно быть  указано  в регистре CX.  Префикс повторения REPZ (синонимы - REPE, REP)  сначала заносит 1 в флаг нуля ZF,  после чего, постоянно  уменьшая CX  на 1,  заставляет повторяться следующую за ним строковую команду до тех пор, пока в CX не окажется 0 или пока флаг ZF не изменит свое значение на 0.  Другой префикс повторения REPNZ (синоним  -  REPNE)  действует аналогично,  но только вначале устанавливает флаг ZF в 0,  а при при изменении его на 1 прекращает повторение строковой команды.

    Пример.  Пусть надо переписать 10000 байтов  начиная с адреса A  в другое место памяти начиная с адреса B.  Если оба этих имени относятся к сегменту данных, на начало которого указывает регистр DS,  тогда эту пересылку можно сделать так:

     CLD           ;DF:=0 (просмотр строки вперед)

     MOV CX,1000   ;CX - число повторений

     MOV AX,DS

     MOV ES,AX     ;ES:=DS

     LEA SI,A      ;ES:SI - "откуда"

     LEA DI,B      ;DS:DI - "куда"

     REP MOVSB     ;пересылка CX байтов

   

                  10.   Логика и организация программ

   

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

Абсолютный адрес команды, которая должна быть выполнена следующей, определяется парой CS:IP, поэтому выполнение перехода означает изменение  этих регистров,  обоих  или  только одного (IP).  Если изменяется только счетчик команд IP, то такой переход называется внутрисегментным или близким (управление остается в том же сегменте команд), а если меняются оба регистра CS и IP,  то это межсегментный или дальний переход (начинают выполняться команды из другого сегмента команд).  По способу изменения счетчика команд переходы делятся на абсолютные и относительные.  Если в команде перехода указан адрес (смещение) той команды, которой надо передать управление,  то это абсолютный переход.  Однако  в команде  может быть указана  величина (сдвиг), которую надо добавить к текущему значению регистра IP, чтобы получился адрес перехода, и тогда это будет относительный переход; при этом сдвиг может быть положительным и отрицательным, так что возможен переход вперед и назад. По величине сдвига относительные переходы делятся на короткие (сдвиг задается байтом) и длинные (сдвиг - слово).  Абсолютные же переходы  делятся на прямые и косвенные:  при прямом переходе адрес перехода задается в самой команде, а при косвенном - в команде указывается регистр или ячейка памяти, в котором (которой) находится адрес перехода.

   

            10.1 Безусловные переходы

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

             JMP op

но в зависимости от типа операнда, ассемблер формирует разные машинные команды.

   1) Внутрисегментный относительный короткий переход.

             JMP i8        (IP:=IP+i8)

Здесь i8 обозначает непосредственный операнд размеров в байт,  который интерпретируется как знаковое целое от -128 до 127. Команда прибавляет это число к текущему значению регистра IP, получая в нем адрес (смещение) той команды, которая должна быть выполнена следующей.  Регистр CS при этом не меняется.

   Необходимо учитывать следующую особенность регистра IP. Выполнение любой команды начинается с того, что в IP заносится адрес следующей за ней команды, и только затем выполняется собственно команда. Для команды относительного перехода это означает,  что операнд i8  прибавляется не к адресу этой команды, а к адресу команды, следующей за ней, поэтому, к примеру, команда JMP 0  -  это переход на следующую команду программы.

   При написании машинной программы  сдвиги для относительных переходов приходится вычислять вручную,  однако  MASM избавляет от этого неприятного  занятия:  в MASM  в командах относительного перехода всегда указывается метка той команды, на которую надо передать управление,  и ассемблер сам вычисляет сдвиг, который он и записывает  в машинную команду.  Отсюда следует, что в MASM команда перехода по метке воспринимается не как абсолютный переход, а как относительный.     По короткому переходу  можно передать управление только на ближайшие команды программы  -  отстоящие от команды,  следующей за командой перехода, до 128 байтов назад  или до 127 байтов вперед.  Для перехода на более дальние команды используется    

   2) Внутрисегментный относительный длинный переход.

             JMP i16       (IP:=IP+i16)

Здесь i16 обозначает непосредственный операнд размером в слово,  который рассматривается как знаковое целое от -32768 до 32767.  Этот переход аналогичен короткому переходу.

   Отметим, что, встретив команду перехода с меткой, которой была помечена одна из предыдущих (по тексту) команд программы,  ассемблер вычисляет разность между адресом этой метки и адресом команды перехода и по этому сдвигу определяет,  какую машинную команду относительного перехода  -  короткую или длинную - надо сформировать. Но если метка еще не встречалась в тексте программы, т.е. делается переход вперед, тогда ассемблер, не зная еще адреса метки, не может определить, какую именно машинную команду относительного перехода формировать,  поэтому  он  на всякий случай выбирает команду длинного перехода.  Однако эта машинная команда занимает 3 байта,  тогда как команда  короткого перехода  -  2 байта,  и если автор программы на MASM  стремится к экономии памяти  и знает заранее, что переход вперед будет на близкую метку, то он должен сообщить об этом ассемблеру,  чтобы тот сформировал  команду короткого перехода. Такое указание делается с помощью оператора SHORT:

             JMP SHORT L

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

   

   3) Внутрисегментный абсолютный косвенный переход.

             JMP r16        (IP:=[r])

      или    JMP m16        (IP:=[m16])

Здесь r16 обозначает любой 16-битовый регистр общего назначения, а m16 - адрес слова памяти. В этом регистре (слове памяти) должен находиться адрес,  по которому и будет произведен переход.  Например,  по команде JMP BX осуществляется переход по адресу, находящемуся в регистре BX.

   

   4) Межсегментный абсолютный прямой переход.

        JMP seg:ofs     (CS:=seg, IP:=ofs)

Здесь seg - начало (первые 16 битов начального адреса) некоторого сегмента памяти, а ofs - смещение в этом сегменте. Пара seg:ofs определяет абсолютный адрес,  по которому делается переход.  В MASM  эта  пара всегда задается конструкцией  FAR PTR <метка>,  которая "говорит", что надо сделать переход по указанной метке, причем эта метка - "дальняя", из другого сегмента.  Отметим, что ассемблер сам определяет, какой это сегмент, и сам подставляет в машинную команду его начало, т.е. seg.     

   5) Межсегментный абсолютный косвенный переход.

         JMP m32       (CS:=[m32+2],  IP:=[m32])

Здесь под m32 понимается адрес двойного слова памяти,  в котором находится пара seg:ofs, задающая абсолютный адрес,  по которому данная команда должна выполнить переход. Напомним, что в ПК величины размером в двойное слово хранятся в "перевернутом" виде, поэтому смещение ofs находится в первом слове двойного слова m32,  а смещение seg - во втором слове (по адресу m32+2).

   Команды межсегментного перехода используются тогда,  когда команды программы размещены не в одном сегменте памяти, а в нескольких (например,  команд столь много,  что в совокупности они занимают более 64Кб, т.е. более максимального размера сегмента памяти). При переходе из одного такого сегмента в другой необходимо менять  не только счетчик команд IP,  но и содержимое регистра CS,  загружая в последний начальный адрес второго сегмента.  Такое одновременное изменение  обоих этих регистров и делают команды межсегментного перехода.     

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

         JMP A

- как  переход по метке A  или  как переход по адресу,  хранящемуся  в ячейке с именем A?  Кроме того,  какой это переход  - внутрисегментный или межсегментный? Ответ зависит от того, как описано имя A,  и от того, когда описано имя A - до или после команды перехода.

Пусть A описано до команды перехода ("ссылка назад").  Если именем A помечена некоторая команда текущего сегмента команда  (т.е. A - метка),  тогда ассемблер формирует машинную команду внутрисегментного относительного перехода.  Если же A -  имя  переменной,  тогда ассемблер формирует  машинную команду косвенного перехода  -  внутрисегментного, если A описано в директиве DW,  или межсегментного, если  A  описано в директиве DD.

В случае же,  если имя A описано  после команды перехода  ("ссылка вперед"), ассемблер всегда формирует машинную команду внутрисегментного относительного длинного перехода.  С учетом этого имя A обязательно должно метить команду из текущего сегмента команд,  иначе будет зафиксирована ошибка.  Если такая трактовка ссылки вперед  не удовлетворяет автора программы, тогда он обязан с помощью оператора  SHORT  или  PTR уточнить тип имени A:

    JMP SHORT A      ;внутрисегментный короткий переход по метке

    JMP WORD PTR A   ;внутрисегментный косвенный переход

    JMP DWORD PTE A  ;межсегментный косвенный переход

Отметим,  что переход по метке A из другого сегмента команд всегда должен указываться с помощью FAR PTR (независимо от того, описана метка A до или после команды перехода):

     JMP FAR PTR A   ;межсегментный переход по метке

10.2 Условные переходы

 

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

    ...

    MOV AH,1    ;Функция Dos чтения клавиатуры

    INT 21h     ;получить следующий символ

    CMP AL,'A'  ;был нажат 'A'?

    JE  AWasTyped;да, обработать его

    MOV [TempByte],AL ;нет, запомнить символ

    ...

AWasTyped:

    PUSH AX     ;сохранить символ в стеке

    ...

В начале,  этот  код  читает  символ  с  клавиатуры.  Затем он использует  инструкцию  CMP  для  сравнения  введенного  символа  с символом 'A'.  Инструкция CMP подобна SUB, только без действия; вся цель CMP - это позволить Вам сравнить 2 операнда без  изменения их.

Однако CMP, так же как и SUB, устанавливает флаги. Так в предыдущем коде, флаг нуля устанавливается в 1, только если AL содержит символ 'A'.

Сейчас мы пришли к сути примера. JE - это инструкция условного  перехода, которая выполняет переход, только если флаг нуля 1, иначе будет выполняться инструкция,  следующая после JE,  в этом случае - инструкция MOV.  Только если был нажат символ 'A',  флаг нуля будет установлен и только в этом случае,  Ассемблер будет выполнять переход на инструкцию с меткой AWasTyped - инструкцию PUSH.

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

    Таблица 5.  суммирует инструкции условных переходов.

             Инструкции условных переходов.        Табл.5

  Имя                   Значение                                          Проверяемые флаги

 JB/JNAE       переход, если меньше/переход, если                  CF=1

                       не больше или равно

  JAE/JNB      переход, если больше или равно/                       CF=0

                       переход, если не меньше

   JBE/JNA     переход, если меньше или равно/                 CF=1 или ZF=1

                       переход, если не больше

   JA/JNBE     переход, если больше/

                       переход, если не меньше или равно               CF=0 и ZF=0

   JE/JZ           переход, если равно                              ZF=1

   JNE/JNZ     переход, если не равно                            ZF=0

   JL/JNGE     переход, если меньше, чем/             SF не равно OF

                       переход, если не больше, чем или

                       равно

   JGE/JNL     переход, если больше, чем или равно/                 SF=OF

                       переход, если не меньше, чем

     JLE/JNG    переход, если меньше, чем или равно/          ZF=1 или

                         переход, если не больше, чем                      SF не равно OF

     JG/JNLE    переход, если больше, чем/

                        переход, если не меньше, чем или               ZF=0 или SF=OF

                         равно

     JP/JPE         переход, если паритет/

                         переход, если четный паритет                               PF=1

     JNP/JPO     переход, если нет паритета/

                         переход, если нечетный паритет                            PF=0

     JS                 переход, если знак                                                   SF=1

     JNS              переход, если нет знака                        SF=0

     JC                переход, если перенос                     CF=1

     JNC             переход, если нет переноса               CF=0

     JO                переход, если переполнение              OF=1

     JNO             переход, если нет переполнения           JF=0

Здесь      CF= флаг переноса; SF= флаг знака; OF= флаг переполнения;

    ZF= флаг нуля; PF= флаг паритета

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

    Например, Ассемблер не может ассемблировать:

    ...

JumpTarget:

    ...

    DB 1000 DUP (?)

    ...

    DEC AX

    JNZ JampTarget

    ...

    Поскольку JumpTarget  находится  в пределах более 1000 байт от инструкции JNZ, его нужно изменить так:

    ...

JumpTarget:

    ...

    DB 1000 DUP (?)

    ...

    DEC AX

    JZ SkipJump

    JMP JampTarget

SkipJump:

    ...

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

10.3 Циклы

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

 Циклы  используются для обработки массивов, проверки  статуса  портов  ввода-вывода,  пока  не  будет достигнуто определенное  состояние, очистки  блоков памяти,  чтения строк с клавиатуры, вывода строк на экран и т.п. Циклы - это основа для обработки   чего-либо,   что  требует  повторяющихся  действий. Поэтому они часто используются,  и Ассемблер предоставляет несколько специальных инструкций для циклов:  LOOP, LOOPE, LOOPNE и JCXZ. В  начале  посмотрим  LOOP.  Предположим,   что   Вы   хотите распечатать 17 символов строки TestString. Вы можете сделать это

    ...

    .DATA

TestString DB   'this is a test...'

    ...

    .CODE

    ...

    MOV CX,17

    MOV bx,OFFSET TestString

 PrINTStringLOOP:

    MOV dl,[bx]          ;взять следующий символ

    INC bx               ;указать следующий символ

    MOV AH,2     ;#  функции  DOS  для  вывода

    INT  21h     ;вызов DOS для печати символа

    DEC CX      ;уменьшить счетчик

    JNZ PrINTStringLOOP   ;делать следующий символ

    ...

    Однако есть лучший способ.  Мы отмечали,  как  Вы помните, что CX особенно полезен в циклах. Здесь

    LOOP PrINTStringLOOP

    делает то же, что  и

    DEC CX

    JNZ PrINTStringLOOP

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

Если   необходим  цикл  с  более  сложным  условием окончания, чем  простое  уменьшение  счетчика то можно воспользоваться   LOOPE   и   LOOPNE.

LOOPE делает то же самое,  что и LOOP за  исключением того, что LOOPE будет заканчивать цикл либо, если счетчик в CX достиг 0, либо если флаг  нуля  установлен  в  1.   (Вспомним,   что   флаг   нуля устанавливается в 1, если последний арифметический результат был 0, или если 2 операнда последнего  сравнения  были  неравны.) Подобно, LOOPNE будет  заканчивать  цикл,  если счетчик в CX достиг 0,  либо если флаг нуля установлен в 0.

Допустим, Вы хотите в цикле читать символы с клавиатуры до тех пор, пока не будет  нажат  ВВОД или   пока  не  будет  считано  128 символов. Следующий пример использует для этого LOOPNE:

    ...

    .DATA

KeyBuffer DB 128 DUP (?)

    ...

    .CODE

    ...

    MOV CX,128

    MOV bx,OFFSET KeyBuffer

KeyLOOP:

    MOV AH,1                         ; функции DOS для ввода

      INT 21h                            ;вызов DOS для чтения символа

    MOV [bx],AL                     ;сохранить символ

       INC bx                             ;указать на следующий символ

    CMP AL,0dh                      ;нажат ВВОД?

    LOOPne KeyLOOP            ;если нет, взять следующий символ,

                                           ;если не считано 128 символов

    ...

LOOPE так же известна как LOOPZ,  и LOOPNE так же  известна, как LOOPNZ (аналогично JE/JZ).

С циклами  связана  еще   одна   инструкция   -   JCXZ.   JCXZ осуществляет  переход,  только  если  CX=0;  это  полезный  способ, проверять CX в начала цикла.  Например,  следующий код в котором BX указывает  на блок байт,  устанавливаемых в 0,  а CX содержит длину блока, использует JCXZ для пропуска цикла, если CX - 0:

    ...

    jCXz SkipLOOP                       ;если CX=0, ничего не делать

CLearLOOP:

    MOV BYTE PTR [SI],0          ;установить следующий байт в 0

    INC SI                                    ;указать на следующий байт

    LOOP CLearLOOP                    ;обнулить следующий символ, если он есть

SkipLOOP:

    ...

Почему нужно пропустить цикл,  если CX - 0?  Если Вы выполните LOOP с CX=0,  то CX уменьшится до 0FFFFh и инструкция LOOP выполнит переход на  метку  назначения.  Тогда  цикл выполнится более 65,535 раз. Когда Вы устанавливали CX  в  0,  Вы  подразумевали,  что  нет байтов для обнуления,  а получилось 65,536 байт. JCXZ позволяет Вам проверить этот случай быстро и эффективно.

 Несколько интересных    примечаний    к   инструкциям   цикла. Во-первых,  запомните, что инструкция цикла как и условный переход, может  выполнять  переход на метку только в диапазоне 128 байт,  до или  после  инструкции  цикла.  Циклы,  больше  128  байт,  требуют использования   техники   "Условный   переход   перед   безусловным переходом",  описанный в разделе  "Условные  переходы".  Во-вторых, важно  представлять,  что  ни одна инструкция цикла не действует на флаги. Это означает, что  LOOP LOOPTop       не точно такая же, как

    DEC CX

    JNZ LOOPTop

поскольку DEC  изменяет  флаги  переполнения,   знака,   нуля, дополнительного переноса  и  четности  в  то  время,  как  LOOP  не изменяет флаги вообще.  По той же причине инструкция DEC  не  точно такая же, как

    SUB CX,1

    JNZ LOOPTop

поскольку SUB изменяет флаг переноса,  а инструкция  DEC  нет.

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

10.4  Процедуры в языке ассемблера

   При составлении и вызове подпрограмм  необходимо  следить  за тем, чтобы команды CALL и RET действовали согласовано  -  были одновременно близкими или дальними. В Ассемблере эта проблема снимается, если подпрограмму описать как процедуру. Процедуры имеют следующий вид:

     имя_процедуры PROC [NEAR или FAR]

                  ...

     имя_процедуры ENDP

   Хотя в директиве PROC после имени процедуры не ставится двоеточие, это имя относится к меткам  и его можно указывать в командах перехода, в частности в команде CALL,  когда надо вызвать процедуру.  Это же имя должно быть повторено в директиве ENDP,  заканчивающей описание процедуры. Предложения между этими двумя директивами образуют тело процедуры (подпрограмму).  Имя процедуры является фактически меткой первой из команд тела, поэтому данную команду не надо специально метить.

   Если  в директиве PROC  указан параметр NEAR или он вообще не указан,  то такая процедура считается "близкой"  и обращаться к ней можно только из того сегмента команд, где она описана.  Дело в том,  что ассемблер будет заменять все команды CALL, где указано имя данной процедуры, на машинные команды близкого перехода с возвратом, а все команды RET внутри процедуры  - на близкие возвраты.  Если же в директиве PROC указан параметр FAR, то это "дальняя" процедура: все обращения к ней и все команды RET внутри нее рассматриваются ассемблером как дальние переходы.  Обращаться к этой процедуре  можно из любых сегментов команд. Таким образом, достаточно лишь указать тип процедуры  (близкая она или дальняя), всю же остальную работу возьмет на себя ассемблер:  переходы на нее и возвраты из нее будут автоматически согласованы с этим типом. В этом главное (и единственное) достоинство описания подпрограмм в виде процедур.  (Отметим,  что метки и имена, описанные в процедуре,  не локализуются в ней.)

   Например,  вычисление AX:=SIgn(AX)  можно описать в виде процедуры следующим образом:

   SIng proc far     ;дальняя процедура

        CMP AX,0

        JE  sgn1     ;AX=0 - перейти к sgn1

        MOV AX,1     ;AX:=1  (флаги не изменились!)

        JG  sgn1     ;AX>0 - перейти к sgn1

       MOV AX,-1    ;AX:=-1

  sgn1:  RET         ;дальний возврат

   SIgn ENDP

         ...

   Возможный пример обращения к этой процедуре:

     ;CX:=SIgn(var)

        MOV AX,var

        cALl SIgn    ;дальний вызов

        MOV CX,AX

         …

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

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

      PUSH param1   ;запись 1-го параметра в стек

        ...

      PUSH paramk   ;запись последнего (k-го) параметра в стек

      CALL SUBr     ;переход с возвратом на подпрограмму

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

10.5 Прерывания INT

Команда  INT  прерывает  обработку   программы,  передает управление в DOS или BIOS для определенного действия и затем возвращает управление в прерванную программу для продолжения обработки.   Наиболее  часто  прерывание   используется  для выполнения  операций   ввода  или  вывода.

Формат команды

INT тип_прерывания

  Для  выхода  из программы  на  обработку   прерывания  и   для  последующего возврата команда INT выполняет следующие действия:

-    уменьшает указатель стека на 2 и заносит в вершину стека содержимое флагового регистра;

-    очищает флаги TF и IF;

-  уменьшает указатель стека на 2 и заносит содержимое      регистра CS в стек;

-    уменьшает указатель стека на 2 и заносит в стек      значение командного указателя;

-    вычисляет адрес вектора прерывания, умножая тип_прерывания на 4;

  •  загружает второе слово вектора прерываний в регистр CS;
  •  загружает в IP первое слово вектора прерывания;

-    обеспечивает выполнение необходимых действий;

-    восстанавливает из стека значение регистра и возвращает     управление в прерванную программу на команду, следующую      после INT.

  Этот процесс выполняется  полностью автоматически. Необходимо лишь  определить  сегмент стека достаточно  большим для записи в него значений регистров.

  В  данной  главе рассмотрим два  типа прерываний: команду BIOS INT 10H и команду DOS INT 21H  для  вывода  на  экран и ввода с клавиатуры.  В последующих примерах в зависимости от требований используются как INT 10H так и INT 21H.

  10.6 Системное программное обеспечение

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

Без знания конфигурации аппаратуры и операционной системы  программы не будут иметь возможности сделать ввод и  вывод  и  даже завершиться. Ниже   покажем некоторые основные возможности ПК.

Все ПК  имеют клавиатуру,  дисплейный адаптер,  монитор и накопитель на гибком диске.  Так же часто  устанавливаются  модемы, принтеры, мышка   и   жесткие   диски.  Каждое  из  этих  устройств управляется сложной серией  обращений  к  портам  ввода-вывода  или памяти (или обоим).  Например,  выбор нового видеорежима на цветном  графическом адаптере (CGA) требует более  30  инструкций  OUT;  для управления модема  или  диска  управляющие  последовательности  еще более сложны. Системное  программное обеспечение ПК уже делает большинство этой работы.

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

В частности,    системные    программы    управляют    сложными интерфейсами к  отдельным устройствам.  Например,  для того,  чтобы ПК обработал нажатие одной клавиши, требуется несколько сотен команд на  языке  ассемблера,  но  Ваша ассемблерная программа может получить символ от клавиши за вызов только одной системной функции. Это обеспечивают  два  главных системных компонента ПК:  DOS и BIOS (базовая система ввода-вывода).

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

                    10.6.1    DOS

DOS (дисковая  операционная  система  - так же известна как MS DOS и PC DOS) - это программа,  которая управляет Вашим компьютером с момента  чтения  диска  при  включении  компьютера  и  до момента выключения. DOS занимает часть памяти. DOS так же обеспечивает  широкий набор функций, используемых каждой прикладной программой. Это через функции DOS прикладная программа читает и пишет файлы,  работает  с клавиатурой, распределяет память, выполняет другие программы и даже устанавливает и получает время суток. Например, код

    ...

    MOV AH,2      ; функция DOS для отображения символа

    MOV DL,'a'    ; a - символ для отображения

       INT 21h       ; вызов DOS для выполнения функции

    ...

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

     Функции  DOS используются для  выполнения   таких операций, как чтение с клавиатуры или из файла, вывода на экран или в файл и вывода на принтер.  

                  10.6.1.1    Чтение клавиатуры.

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

Простейший   способ   чтения   клавиатуры    -    это использование функции DOS номер 1.  Функции DOS вызываются с помощью помещения номера функции в AH и  затем  вызова инструкции INT  21h.  Символ,  набранный с клавиатуры, возвращается в AL.

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

    ...

    MOV AH,1

    INT 21h

    ...

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

                    10.6.1.2 Вывод символов на экран.

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

Функция DOS 2 - это простейший способ вывести  символ.  Для этого нужно поместите в AH 2 и в DL - символ,  затем обратитесь к DOS через INT 21h. Следующий фрагмент прграммы отображает каждый введенный символ на экране.

    ...

    MOV AH,1

      INT 21h                    ; получить символ

    MOV AH,2

    MOV DL,AL             ; поместить символ из AL в DL

       INT 21h                   ; отобразить символ

    ...

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

                     10.6.1.3 Завершение программ.

    Для завершения программы выберем функцию  DOS  4Ch. Фрагмент завершения программы будет выглядеть следующим образом:  

 

MOV  AH,4Ch           ;функция DOS завершения программы

               INT  21h                 ;завершение прграммы

     END

                          10.6.2 BIOS.

В отличие от DOS и прикладных программ, BIOS (базовая система ввода-вывода) не загружается с диска. BIOS хранится в ПЗУ, в  части  адресного пространства микропроцессора,  зарезервированного для системных функций.

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

                     10.6.2.1 Выбор режимов дисплея.

Одна из  главных  причин  использовать  BIOS  для   управления дисплеем, поскольку  DOS  не  обеспечивает  поддержку  всех богатых возможностей дисплеев на ПК.  Только вызвав функции BIOS, можно установить режим экрана,  управлять цветами,  получить информацию о дисплейном адаптере и т.д.  Например, следующий код заставляет BIOS установить экран в четырехцветный графический режим на CGA:

    ...

    MOV AH,0   ; установка номера функции BIOS

    MOV AL,4   ; номер режима для 320х200 4-х цветной графики

        INT 10h    ; вызов прерывания BIOS для установки режима

    ...

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

Иногда  совершенно необходимо работать с аппаратурой напрямую. Например, коммуникационные программы  управляют  последовательным  портом  ПК напрямую через инструкции IN и OUT,  поскольку ни DOS  ни  BIOS  не обеспечивают поддержки    последовательного    порта.   Аналогично, высокопроизводительная графика должна производиться через  доступ к дисплейной памяти  напрямую,  поскольку  DOS вообще не поддерживает графики, а BIOS делает это крайне медленно.

11. Дисковая память

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

Каждая cторона 3 ½ дюймовой дискеты содержит 80-160 концентрических дорожек,  пронумерованных от 00 до 79-159. На каждой дорожке форматируется    девять  секторов  по  512 байтов каждый. На жестком диске вместо термина дорожки используются цилиндры.

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

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

11.1 Оглавление диска (каталог)

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

А) Запись начальной загрузки (один кластер);

Б) Таблицу распределения файлов (FAT);

В) Каталог;

Г) Файлы данных.

Размеры  FATа, каталога зависят от емкости диска. Записи заносятся сначала на 0-ю сторону n-й доpожки, затем  на 1-ю сторону n-й дорожки, затем на 0-ю сторону n+1-й дорожки   и  т.д.  Такая  особенность  заполнения  дисковой  памяти на противоположных  дорожках снижает  число перемещений головки  дисковода.  Данный метод используется  как для гибких, так и            для твердых дисков.

          При  использовании  утилиты  FORMAT/S  для форматизации          дискеты,  модули DOS  IBMBIO.COM и IBMDOS.COM записывается в         первые сектора области данных.

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

Элементы оглавления имеют следующий формат:

     Байт           Назначение

    0-7      Имя  файла,   определяемое  из  программы,  создавшей

               данный  файл.  Первый байт может  указывать на статус

               файла:   шест.00   обозначает,  что  данный  файл  не

               используется,  шест.E5  -  файл  удален,  шест.  2E -

               элемент подоглавления.

    8-10   Тип файла

      11     Атрибут файла, определяющий его тип:

               шест.00 - обычный файл;

               шест.01 - файл можно только читать;

               шест.02 - "спрятанный" файл;

               шест.04 - системный файл DOS;

               шест.08 - метка тома;

               шест.10 - подоглавление;

               шест.20 - архивный файл (для твердого диска).

12-21      Зарезервировано для DOS.

22-23     Время  дня,  когда файл был создан или  последний раз

             изменялся, в следующим двоичном формате:

                   ¦чччччммммммссссс¦

24-25     Дата создания или последнего изменения  файла, сжатая

             в два слова в следующем двоичном формате:

                   ¦гггггггм¦мммддддд¦

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

            от 0 до 119, месяц - от 1 до 12, а день - от 1 до 31.

26-27     Начальный   кластер    файла.   Относительный   номер

              последних двух секторов каталога.  Первый файл данных

              (без  COM-модулей  DOS)  начинается  на относительном

               кластере  002.  Текущая  сторона,  дорожка  и кластер

               зависят от емкости диска.

28-31      Размер  файла  в  байтах.   При  создании  файла  DOS

               вычисляет и записывает размер файла в это поле.

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

11.2 Таблица распределения файлов

Назначение  таблицы  распределения  файлов  (FAT  -  File Allocation Table) - распределение дискового пространства для файлов.   Если  вы  создаете   новый   файл   или  изменяете существующий,  то  DOS  меняет  элементы  таблицы  файлов  в соответствии  с   расположением   файла   на  диске. FAT  содержит  элементы  для  каждого кластера, длина элементов FAT зависит от устройства дисковой памяти.   Кластер  представляет собой один или несколько секторов.

Элементы  FAT  определяют кластеры.  Каждый  такой элемент  имеет  длину  12-16  битов. Два первых элемента  FAT, известные    как   относительные  сектора  000   и  001,  соответственно,   указывают на  два  последних  сектора  оглавления, определяя его  размер  и  формат.  Первый  файл  данных  начинается на   относительном секторе 002.  Каждый  элемент  FAT  состоит из трех  шестнадцатиричных цифр (для 12-битового FATа),  которые указывают  на характер использования конкретного сектора:

    000  свободный кластер,

    nnn  относительный номер следующего кластера для файла,

    FF7  неиспользуемый кластер (сбойная дорожка),

    FFF  последний кластер файла.

         Предположим, например, что дискета содержит только один файл              с именем TEST.ASM, занимающий относительные сектора  002,  003 и 004. Элемент оглавления для этого файла  содержит  имя файла TEST, тип - ASM, шест.00 для обычного  файла,  дату создания, 002 - номер первого относительного сектора файла и размер файла в битах. Таблица FAT в этом случае может выглядеть следующим образом (кроме того, что в каждой паре  байты в обратной последовательности):

            Элемент FAT:        ¦FDF¦FFF¦003¦004¦FFF¦000¦000¦...¦000¦

        Относительн.сектор:   0      1      2      3    4     5     6   ...конец

Первые  два элемента FAT  указывают расположение каталога на   относительных   секторах   000   и   001.   Для   ввода     рассматриваемого файла в память, система выполняет следующие    действия:

           1.   DOS получает  доступ  к дискете и ищет  в  каталоге имя

           TEST и тип ASM.

     2.   Затем  DOS  определяет  по  каталогу  положение первого        относительного сектора файла (002)  и загружает содержимое  этого  сектора  в  буферную  область   в  основной   памяти.

      3.   Номер второго сектора  DOS  получает  из  элемента FAT,  соответствующего   относительному   сектору   002.  Этот элемент             содержит 003.  Это обозначает,  что файл продолжается в относительном  секторе  003.  DOS  загружает содержимое  этого сектора в буфер в основной памяти.

4.   Номер третьего  сектора DOS получает  из  элемента FAT, соответствующего   относительному  сектору   003.  Этот элемент  содержит  004,   значит  файл  продолжается  в относительном  секторе  004.  DOS  загружает содержимое  этого сектора в буфер в основной памяти.

5.   Элемент  FAT для  относительного  сектора  004 содержит FFF,  что свидетельствует о  том,  что  больше нет   данных для этого файла.

Элемент  каталога содержит номер начального  кластера для каждого файла,  а FAT -  шест.трехзначные элементы, указывающие на  расположение каждого  дополнительного кластера, если он  имеется.  Для того,  чтобы  указать,  например, что файл содержит  все записи  только в первом кластере,  таблица FAT должна содержать FFF в  элементе, представляющем первый относительный кластер.

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

11.3 Операции ввода-вывода   на диск

Управление  вводом и  выводом осуществляется специальными прерываниями.  Запись файла на диск требует, чтобы прежде он был "создан"  и DOS смогла сгенерировать соответствующий элемент в оглавлении.  Когда  все записи  файла будут записаны, программа должна  "закрыть"  файл, так, чтобы DOS  завершила обработку  оглавления.  Чтение файла требует,  чтобы  он был сначала "открыт"  для того,  чтобы убедиться  в его существовании.  Так как записи  имеют фиксированную длину  и  в силу соответствующей  организации  оглавления,  обработка записей дискового файла  может  осуществляться  как последовательно, так и произвольно.

  Метод доступа к дисковой памяти,  поддерживающий использование оглавления,  "блокирование"  и  "разблокирование" записей,  обеспечивается  прерыванием DOS 21H.  Более низкий уровень, обеспечивающий абсолютную адресацию дисковых секторов, также  через DOS,  выполняется посредством прерываний  25H и 26H.  Самый низкий  уровень обеспечивается  прерыванием BIOS 13H,  которое  позволяет выполнить произвольную  адресацию в дисковой памяти по номеру дорожки и сектора.  Методы DOS осуществляют некоторую  предварительную  обработку  до передачи управления  в BIOS.

11.3.1 Запись  файла на диск

    11.3.1.1 Данные в формате ASCIIZ

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

            PATHNM1    DB    'B:\TEST.ASM',0

            PATHNM2    DB    'C:\UTILITY\NU.EXE',0

Обратная косая (или  прямая косая)  используются в качестве разделителя. Нулевой байт (zero) завершает строку (отсюда название ASCIIZ формата).  Для прерываний, использующих в качестве параметра ASCIIZ строку,  адрес этой строки загружается в регистр DX, например, командой  LEA  DX,PATHNM1.

 11.3.1.2 Файловый номер

Операции создания  и открытия  файла  требуют  загрузки в регистр AX двухбайтового числа,  представляющего собой файловый номер. Стандартные  устройства не  нуждающиеся  в  операции  открытия  и  могут  использовать непосредственно файловые номера:  0  -  ввод, 1 - вывод, 2 - вывод сообщений об ошибках, 3 - внешнее устройство, 4 - принтер.

Для доступа к диску при создании  или  открытии  файла используется ASCIIZ строка и функция    3Ch или  3Dh прерывания DOS INT21h . Успешная операция устанавливает флаг  CF в 0  и помещает файловый номер в регистр AX.  Этот номер  необходимо  сохранить в   элементе данных DW и использовать  его для  всех последующих операций над дисковым файлом.   При неуспешной операции флаг CF устанавливается в 1,  а в регистр AX помещается код ошибки, зависящий от операции.

 11.3.1.3 Создание дискового файла

Для создания нового файла или переписывания старого файла используется  функция 3Ch.  При этом  регистр  DX должен    содержать  адрес ASCIIZ-строки,  а регистр CX  - необходимый  атрибут; для обычного файла значение атрибута - 0.

          Рассмотрим пример создания обычного файла:

         MOV  AH,3CH               ; Запрос на создание

          MOV  CX,00                  ;    обычного файла

          LEA  DX,PATHNM1     ; ASCIIZ строка

              INT  21H                     ; Вызов DOS

            JC   error                        ; Переход по ошибке

          MOV  HANDLE1,AX    ; Сохранение файлового номера в DW

       При правильном  открытии  операция  создает элемент оглавления  с данным  атрибутом,  очищает флаг CF и устанавливает файловый номер в регистре  AX.  Этот  номер  должен использоваться для всех последующих операций.  Если создаваемый файл уже существует  (т.е.  имя файла присутствует в оглавлении), то длина этого файла устанавливается в 0 для перезаписи.

В случае возникновения ошибки операция устанавливает флаг CF в 1  и помещает в регистр AX код возврата:  03, 04 или 05.  Код 05  свидетельствует либо о переполнении оглавления,  либо  о  защите  существующего  файла атрибутом "только чтение".  При завершении операции необходимо сначала проверить  флаг  CF,  так  как при  создании  файла возможна установка в регистре AX файлового номера 0005, который можно легко спутать с кодом ошибки 05 (нет доступа).

 Для  записи  файла используется функция  40h. При этом в регистре  BX  должен быть установлен  файловый номер, в регистре CX -  число записываемых байт,  а в регистре DX - адрес области вывода.  В следующем примере происходит запись 256 байт из области OUTREC:

      HANDLE1 DW   ?

      OUTREC  DB   256 DUP (' ')

              MOV  AH,40H                 ; Запрос записи

              MOV  BX,HANDLE1     ; Файловый номер

              MOV  CX,256                 ; Длина записи

              LEA  DX,OUTREC        ; Адрес области вывода

                  INT  21H                     ; Вызов DOS

                 JC   error2                       ; Проверка на ошибку

              CMP  AX,256         ; Все байты записаны?

                JNE  error3

 Правильная операция записывает из памяти на  диск все данные (256  байт),  очищает флаг CF и устанавливает в регистре AX число действительно записанных байтов.  Если диск переполнен,  то число записанных  байтов может  отличаться от заданного числа.  В случае неправильной операции  флаг CF устанавливается в 1,  а в регистр AX заносится код 05 (нет доступа) или 06 (ошибка файлового номера).

После завершения записи файла необходимо установить файловый номер в регистр BX  и,  используя  функцию  3Eh, закрыть  файл.  Эта  операция записывает все  оставшиеся еще данные из буфера на  диск и корректирует оглавление  и таблицу FAT.

         MOV  AH,3EH                ; Запрос на закрытие файла

         MOV  BX,HANDLE1     ; Файловый номер

            INT  21H                       ; Вызов DOS

 В случае  ошибки  в регистре  AX  устанавливается  код 06 (неправильный файловый номер).

 

  1.  Чтение дискового файла

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

Эта операция проверяет  правильность имени файла  и его наличие на  диске.  При открытии регистр DX должен  содержать адрес необходимой ASCIIZ-строки, а регистр AL - код доступа:

 0  Открыть файл только для ввода

1  Открыть файл только для вывода

           2  Открыть файл для ввода и вывода

Обратите внимание,  что для записи файла используется     функция  создания  (3Ch),  но не функция открытия файла.

                     Ниже приведен пример открытия файла для чтения:

          MOV  AH,3DH              ; Запрос на открытие

              MOV  AL,00                  ; Только чтение

              LEA  DX,PATHNM1     ; Строка в формате ASCIIZ

                     INT  21H                 ; Вызов DOS

                  JC   error4         ; Выход по ошибке

              MOV  HANDLE2,AX     ; Сохранение номера в DW

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

           Если файл отсутствует,  то операция устанавливает флаг CF и заносит в регистр   AX код ошибки.  Не  забывайте проверять  флаг  CF.  При успешном создании файла система может установить в  регистре AX файловый номер  0005,  что легко можно спутать с  кодом ошибки 05 (нет доступа).

Для чтения записей файла используется  функция 3Fh.  При этом необходимо  установить в  регистре BX файловый номер, в регистре CX - число байтов и в регистре DX -  адрес области  ввода.  В следующем  примере  происходит считывание    512-байтовой записи:

                       HANDLE2     DW   ?

                       INPREC      DB   512 DUP (' ')

             MOV  AH,3FH                ; Запрос на чтение

             MOV  BX,HANDLE2     ; Файловый номер

             MOV  CX,512                  ; Длина записи

              LEA  DX,INPREC          ; Адрес области ввода

                      INT  21H                 ; Вызов DOS

                    JC   error5                 ; Проверка на ошибку

             CMP  AX,00                     ; Прочитано 0 байтов?

                JE   endfile

 Правильно выполненная операция считывает запись в память, сбрасывает флаг  CF  и  устанавливает  в  регистре  AX действительно прочитанных байтов.  Нулевое значение в регистре AX обозначает попытку чтения после конца файла. Ошибочная операция  устанавливает флаг  CF  и возвращает в  регистре AX код  ошибки 05  (нет доступа)  или 06 (ошибка файлового номера).

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


 

А также другие работы, которые могут Вас заинтересовать

71117. Подготовительные работы к реконструкции земляного полотна 479 KB
  Подготовительные работы к реконструкции земляного полотна В состав основных подготовительных работ входят создание геодезической разбивочной основы перенос коммуникаций; расчистка дорожной полосы и территорий отведенных под карьеры и резервы...
71118. Тепловой эффект ферментации и тепловой баланс ферментера (классификация ферментеров). Тепловой эффект процесса ферментации 84.5 KB
  Основой роста и размножения клеток является ассимиляция веществ из окружающей среды. Это – т.н. конструктивный (строительный) обмен или анаболизм. Он немыслим без расхода энергии. Он также невозможен без процессов противоположного типа - катаболизма.
71119. Отделение клеток для получения конечного продукта 87 KB
  Наиболее желательно фильтрование с образованием слоя осадка. В производственных условиях при эксплуатации установок систематически проводят промывку продувку и сушку осадка на фильтрах. В процессе фильтрования движущая сила и сопротивление осадка меняются поэтому скорость м с – величина...
71120. Некоторые особенности механического разделения культуральной жидкости 1.76 MB
  Для улучшения отделения жидкости от мицелиальной массы, с целью минимального содержания жидкости в массе предлагается обрабатывать ферментационное сусло на гидравлических прессах (рис. 2). Гидравлическому уплотнению под действием перепада давления подвергают фильтрующий слой...
71121. Экстракция и адсорбция. Флотация. Вакуум-выпарка 696 KB
  Жидкость каждый раз отделяется на решётке а к сырью подаются новые порции растворителя и газ. Культуральную жидкость с аминокислотой пропускают через колонну. В вертикальной конструкции культуральная жидкость подаётся на разных уровнях.
71122. СУШКА И СУШИЛКИ 1.6 MB
  Для сушки микробиологических суспензий и жидкостей применяют распылительные сушилки. Для сушки пастообразных веществ – вакуумные аппараты кипящего слоя вальцеленточные и сублимационные сушилки. Распылительные сушилки При распылении продукта на мелкие частицы создаётся большая...
71123. МОДЕЛИРОВАНИЕ И МАСШТАБИРОВАНИЕ 1.14 MB
  Моделированием называется исследование проблем применения к промышленному оборудованию результатов лабораторных исследований и данных по опытным установкам Это необходимо для правильного конструирования аппаратов. Некоторые такие вопросы уже были нами рассмотрены.
71124. Контрольно-измерительная аппаратура 4.23 MB
  Многие контрольно-измерительные приборы в биотехнологии не имеют отличий от работающих на химических предприятиях (для измерения скорости потока, температуры, влажности, давления и уровня жидкости).
71125. Количество и конструктивный расчет аппаратуры 80.5 KB
  Целью этих расчетов является определение оптимальных объемов и производительности основной и вспомогательной аппаратуры и количества аппаратов, необходимых для заданной производительности. Расчет основан на тех материальных потоках, которые должны проходить через аппараты за определенный...