Глава 4. Природа языка ассемблера.


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

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

Архитектура компьютера
——————————————————————

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

(В данный момент мы говорим о компютерах вообще; скоро мы
перейдем конкретно к системе 8088).

————————————-
?Подсистема арифметических операций ?
?(сложение, вычитание, умножение, ?
? деление, логическое И, ИЛИ, исклю-?
? чающее ИЛИ и т.д. ?
————————————-
°
?
—————- ——————- —————-
? Подсистема ? ? Подсистема ? ? Подсистема ?
? ввода ? ? управления ? ? вывода ?
?(С клавиатуры,?—>? (общее ?—>?(На дисплей, ?
? мыши, ? ? координирование)? ? принтер, ?
? джойстика, ? ? ? ? плоттер, ?
? диска) ? ? ? ? диск) ?
—————- ——————- —————-
°
?
—————————-
?Подсистема памяти ?
?(до 1 мегабайта ОЗУ (RAM) ?
? и/или ПЗУ (ROM)) ?
—————————-

Рис.4.1 Пять подсистем

Арифметическая подсистема компьютера это именно та
подсистема, которую обычно подразумевает большинство людей,
говоря о компьютере. В самом деле, что же такое компьютер, если
не машина для обработки чисел? Однако, как оказывается на самом
деле, большинство компьютеров занято обработкой чисел очень
малую часть времени, а большая чсть уходит на обработку строк
символов и операции ввода/вывода; какое отношение к арифметике
имеет, например, текстовый редактор? Тем не менее,
арифметическая подсистема очень важна, поскольку там
выполняются не только сложение, вычитание, умножение и деление,
но и логические операции (такие, как И, ИЛИ, исключающее ИЛИ).

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

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

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

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

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

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

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

Что представляет собой язык ассемблера ————————

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

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

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

Каждая инструкция имеет для данного процессора конкретный,
хорошо определенный смысл. Например, значение инструкции 4
сообщает 8088, что нужно сложить величину, хранимую в следующем
адресе памяти, с величиной в регистре AL. (Сейчас не важно, что
вы не знаете, что такое регистр AL — скоро мы подойдем к этому
вопросу). Таким образом, процессор можно заставить выполнить
желаемую последовательность действий соответствующей
последовательностью значений команд; практически программа это
ничто иное, как последовательность значений команд.

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

Прекрасно, но какое отношение это все имеет к языку
ассемблера? А вот какое: набор команд процессора и образует
язык ассемблера данного процессора. Или иначе говоря, язык
ассемблера это ориентированная на человеческое восприятие форма
набора команд процессора (также называемая машинным языком
процессора), которая затем преобразуется
программой-ассемблером, как например Turbo Assembler, на
машинный язык. При практической эквивалентности языка
ассемблера и машинного языка на языке ассемблера
программировать для человека гораздо проще. И наконец, вы
наверняка предпочтете писать программу при помощи команд типа

add a1,1

чем командами

4
1

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

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

i = j + k;

на языке ассемблера 8088 эта операция требует для записи
шесть строк:

mov ax,[j]
mov dx,[j+2]
add ax,[k]
adc dx,[k+2]
mov [i],ax
mov [i=2],dx

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

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

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

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

Процессоры 8088 и 8086
——————————————————————

8088 представляет собой процессор, используемый в
компьютерах IBM PC и XT, представляющих, вероятно, одно из
наиболее удачных семейств компьютеров. Однако, 8088 фактически
является лишь одним из семейства процессоров, известного под
именем iAPx86. В число прочих членов этого семейства входят:
процессор 8086, используемый в IBM моделей 25 и 30; процессор
80286, используемый в IBM AT и IBM PS/2 моделей 50 и 60, а
также процессор 80386, используемый в IBM PS/2 модели 80.
Каждый из этих процессоров несколько отличается от 8088. В
главе 11, «80386 и другие процессоры», дается подробное
обсуждение различных процессоров, входящих в семейство iAPx86.
Общее свойство процессоров семейства iAPx86 заключается в их
способность выполнять программы, написанные для процессоров
8086 и 8088.

8086 фактически лежит в основе всех процессоров семейства
iAPx86. 8088 это практически 8086, но имеющий внешнюю шину
данных с уменьшенными возможностями; в то время как 8086 может
обмениваться данными с памятью по 16 бит за один раз, 8088
может одновременно передать только 8 бит. Оба эти процессора
имеют один и тот же набор команд. Поэтому язык ассемблера,
использованный в программах для IBM PC и последующих машин
этого семейства, известен как язык ассемблера 8086, а не 8088.
Далее в этой главе подразумевается, что язык ассемблера 8086
включает в себя и 8088.

Возможности 8086 ———————————————-

По сегодняшним стандартам 8086 это процессор, обладающий
скромными возможностями. Кроме того, 8086 был разработан десять
лет назад, а за десять лет технологической эволюции в области
конструирования микросхем были сделаны существенные
усовершенствования. Тем не менее, 8086 остается важным
процессором. одна из причин здесь заключается в огромном
количестве существующих IBM PC и совместимых с ними; никто не
может позволить себе игнорировать компьютеры, число которых
превышает десять миллионов. Однако, есть и другая причина,
которая состоит в том, что 8086 даже сегодня отвечает
требованиям современных программных средств.

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

8086 работает на тактовой частоте 4.77 или 8 МГц; 80286
может работать на частоте 6,8,10,12,16 и 20 МГц; 80386 может
работать на частоте 16,20,25 и 33 МГц.

Ресурсы 8086, доступные программисту, работающему на языке
ассемблера, включают в себя память, интерфейсы ввода и вывода
(I/O), регистры и разумеется, команды. Далее изучаются эти
ресурсы.

Память ———————————————————

8086 позволяет адресовать за один раз любую из 1 Мб (1
мегабайт, или 2 в 20-й степени, то есть 1,048,576 , каждая
длиной 8 бит) ячеек оперативной памяти. Первый байт памяти
имеет адрес 0, а последний имеет адрес 0FFFFFh, как показано на
рис.4.2.

(Последний адрес, 0FFFFFh, записан в шестнадцатиричной
системе счисления с основанием 16, на которую указывает суффикс
h в записи числа; данное число эквивалентно 10,048,575 в
знакомой вам десятичной системе счисления, или системе с
основанием 10). Для программирования на языке ассемблера важно
хорошо ориентироваться в шестнадцатиричной записи. Эта система
записи рассматривается в главе 5, «Элементы программы на языке
ассемблера».

Шестнадцатиричный Десятичный
адрес адрес
————————
00000 ? ? 0
————————
00001 ? ? 1
————————
00002 ? ? 2
————————
00003 ? ? 3
————————
00004 ? ? 4
————————
00005 ? ? 5
————————
00006 ? ? 6
————————
00007 ? ? 7
————————
00008 ? ? 8
————————
00009 ? ? 9
————————
0000A ? ? 10
————————
0000B ? ? 11
————————
0000C ? ? 12
————————
0000D ? ? 13
————————
0000E ? ? 14
————————
0000F ? ? 15
————————
00010 ? ? 16
————————
.
.
.
————————
FFFEF ? ? 1048559
————————
FFFF0 ? ? 1048560
————————
FFFF1 ? ? 1048561
————————
FFFF2 ? ? 1048562
————————
FFFF3 ? ? 1048563
————————
FFFF4 ? ? 1048564
————————
FFFF5 ? ? 1048565
————————
FFFF6 ? ? 1048566
————————
FFFF7 ? ? 1048567
————————
FFFF8 ? ? 1048568
————————
FFFF9 ? ? 1048569
————————
FFFFA ? ? 1048570
————————
FFFFB ? ? 1048571
————————
FFFFC ? ? 1048572
————————
FFFFD ? ? 1048573
————————
FFFFE ? ? 1048574
————————
FFFFF ? ? 1048575
————————

Рис.4.2 Адресное пространство памяти 8086

Один байт, имеющий длину 8 бит, может содержать один
символ, либо одно целое в диапазоне от 0 до 255. Однако же, это
не значит, что 8086 не может работать с большими значениями.
Два байта, рассматриваемые вместе (они носят название «слово» —
word), могут содержать одно целое число в диапазоне от 0 до
65535; 8086 позволяет так же легко манипулировать словами, как
и байтами.

Четыре байта, рассматриваемые вместе (они носят название
«двойное слово» — dword), могут содержать одно целое число в
диапазоне от 0 до 4,294,967,295, либо одно число с плавающей
точкой одинарной точности. Восемь байт, рассматриваемых вместе
(они носят название «учетверенное слово» — qword), могут
содержать одно число с плавающей точкой двойной точности. 8086
не работает с этими двумя типами данных напрямую; однако
числовой сопроцессор 8087 может прямо обращаться к числам с
плавающей точкой и целым значениям увеличенной точности, и при
правильном программном обеспечении 8086 может работать с любыми
типами данных, хотя и несколько медленнее.

В любой момент программа для 8086 может считывать и
изменять содержимое любого из более чем 1,000,000 байтов
памяти. Например, фрагмент программы:

.
.
.
mov ax,0
mov ds,ax
mov bx,0
mov a1,[bx]
.
.
.

загружает содержимое байта с адресом 0 в регистр AL. Пока
не обращайте внимание на детали; смысл здесь в том, что
адресное пространство оперативной памяти 8086 обеспечивает
хранение более чем 1,000,000 рабочих значений, а также быстрый
и гибкий доступ к этим значениям.

Один мегабайт (1 Мб) это существенный объем памяти,
значительно превышающий те 64 Кб (2 в степени 16, или 65,536
байт), что могли быть адресованы процессорами,
предшествовавшими 8086. С другой стороны, последний из
процессоров этого семейства, 80386, позволяет адресовать
примерно в 4,000 раз больше памяти, чем 8086, и адресное
пространство последнего может показаться вам несколько тесным.
Кроме того, в IBM PC только 640 Кб из 1 Мб адресного
пространства доступно для использования в качестве памяти
общего назначения; остальная часть адресного пространства
предназначена для использования системным программным
обеспечением и в качестве дисплейной памяти. Кроме того, не
следует забывать, что в памяти, помимо данных, храмятся и
команды, и тем самым суммарно программные коды и данные не
должны превышать 640 Кб памяти PC.

Хотя 8086 и позволяет адресовать 1 Мб памяти,
одновременная адресация более 64 Кб затруднена специфическим
средством, известным как сегментация. Сегментация будет нами
рассмотрена в последующем разделе, «Сегментные регистры».

Ввод и вывод —————————————————

8086 поддерживает устройства ввода и вывода двумя способами:
при помощи команд ввода/вывода (I/O) и через адреса памяти.
Некоторые устройства ввода и вывода управляются через порты,
которые представляют собой специальные адреса I/O в адресном
пространстве размером 64 К, отдельном от адресного пространства
памяти 1 Мб, как показано на рис.4.3.

Адреса Адреса I/O
памяти (порт)
————— —————
00000 ? ? 0000 ? ?
————— —————
00001 ? ? 0001 ? ?
————— —————
00002 ? ? 0002 ? ?
————— —————
00003 ? ? 0003 ? ?
————— —————
00004 ? ? 0004 ? ?
————— —————
00005 ? ? 0005 ? ?
————— —————
00006 ? ? 0006 ? ?
————— —————
00007 ? ? 0007 ? ?
————— —————
00008 ? ? 0008 ? ?
————— —————
00009 ? ? 0009 ? ?
————— —————
0000A ? ? 000A ? ?
————— —————
. .
. .
. .
————— —————
FFFF5 ? ? FFF5 ? ?
————— —————
FFFF6 ? ? FFF6 ? ?
————— —————
FFFF7 ? ? FFF7 ? ?
————— —————
FFFF8 ? ? FFF8 ? ?
————— —————
FFFF9 ? ? FFF9 ? ?
————— —————
FFFFA ? ? FFFA ? ?
————— —————
FFFFB ? ? FFFB ? ?
————— —————
FFFFC ? ? FFFC ? ?
————— —————
FFFFD ? ? FFFD ? ?
————— —————
FFFFE ? ? FFFE ? ?
————— —————
FFFFF ? ? FFFF ? ?
————— —————

Рис.4.3 Отдельные адреса оперативной памяти и ввода/вывода
(I/O) 8086

Адресов I/O в 8086 существенно меньше, чем адресов
оперативной памяти; хотя технически в PC имеется 64 К адресов I/O,
практически доступны из них только 4 К. Следовательно, адреса
ввода/вывода не используются для хранения значений, а
исключительно для обеспечения каналов управления и данных к
устройствам ввода и вывода. Например, последовательные устройства,
как например модемы, полностью управляются через несколько адресов
I/O.

Доступ к адресам I/O возможен только при помощи двух
специальных команд, IN и OUT, используемых исключительно для
данной цели. Например,

out dx,al

посылает содержимое регистра AL в порт ввода/вывода,
выбираемый в регистре DX.

Далее мы вернемся к командам IN и OUT, а также к вопросу
ввода/вывода в целом, в Главе 5.

Некоторые устройства выхода являются управляемыми памятью;
это означает, что управление этими устройствами происходит не
через I/O, а через нормальную адресацию памяти. Это в частности
относится к дисплейным адаптерам, битовые массивы (растры) которых
могут занимать до 16 к, 32 К или даже 256 К адресного пространства
оперативной памяти 8086 (битовый массив, или растр, это массив
байтов, содержащих описание точек, отображаемых адаптером на
экране дисплея).

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

Регистры ———————————————————

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

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

Номер бита
15 0
————————-
FLAGS ? ? Флаговый регистр
————————-

————————- —
AX ? AH ? AL ? ?
————————- ?
————————- ?
BX ? BH ? BL ? ?
————————- ?
————————- ?
CX ? CH ? CL ? ?
————————- ?
————————- ? Регистры
DX ? DH ? DL ? ? общего
————————- ? назначения
————————- ?
SI ? ? ?
————————- ?
————————- ?
DI ? ? ?
————————- ?
————————- ?
BP ? ? ?
————————- ?
————————- ?
SP ? ? ?
————————- —

————————-
IP ? ? Указатель команд
————————-

————————- —
CS ? ? ?
————————- ?
————————- ?
DS ? ? ? Сегментные
————————- ? регистры
————————- ?
ES ? ? ?
————————- ?
————————- ?
SS ? ? ?
————————- —
15 0
Номер бита

Рис.4.4 Регистры 8086

Флаговый регистр
—————-
16-битовый флаговый регистр содержит всю информацию,
относящуюся к состоянию 8086 и результатам выполнения последних
инструкций, как показано на рис.4.5.

Номер бита
15 0
——————————-
?????????O?D?I?T?Z???A???P???C?
——————————-

Флаговые биты:
O = флаг переполнения
D = флаг направления
I = флаг внешнего прерывания
T = флаг внутреннего прерывания
S = флаг знака
Z = флаг нуля
A = флаг внешнего переноса
P = флаг четности
C = флаг переноса

Рис.4.5 Флаговый регистр 8086

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

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

Флаговый регистр не позволяет выполнять его прямую
модификацию или чтение. Для выполнения таких операций используются
специальные управляющие им команды (такие, как CLD, STI и CMC) и
арифметические и логические команды, которые могут изменять
состояния конкретных флагов. К примеру, содержимое конктерных
битов флагового регистра зависит от работы таких команд, как JZ,
RCR и MOVSB. Флаговый регистр в действительности никогда не
используется как ячейка памяти, а скорее как устройство,
определяющее состояние и управление сетью 8086.

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

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

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

.
.
.
mov ax,5
mov dx,9
add ax,dx
.
.
.

загружает в AX число 5, загружает в DX число 9 и складывает
эти две величины, помещая результат сложения, 14, обратно в
регистр AX. Вместо AX можно было подставить в данном фрагменте CX,
SI, или любой друго из регистров общего назначения, с тем же
успехом.

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

Регистр AX
———-

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

Младшие 8 битов регистра AX известны также как регистр AL
(мнемоника для A-Low, т.е. A-младший), а старшие 8 битов регистра
AX известны также как регистр AH (мнемоника для A-High, т.е.
A-старший). Это деление удобно использовать при работе с данными
размером в один байт, поскольку в этом случае AX может служить как
два отдельных регистра. Следующий фрагмент программы устанавливает
AH равным 0, копирует это значение в AL, а затем прибавляет к AL
единицу:

.
.
.
mov ah,0
mov al,ah
inc al
.
.
.

Конечным результатом этого фрагмента является установка
регистра AL в 1. Регистры BX,CX и DX также могут служить либо как
один 16-битовый регистр, либо как 2 8-битовых регистра.

Регистр BX
———-

Регистр BX может служить указателем на ячейки памяти. Более
подробно это его свойство будет рассмотрено в главе 5, , а в
кратком изложении, 16-битовое значение, хранимое в BX, может быть
использовано как часть адреса ячейки памяти, к которой выполняется
доступ. Например, следующий фрагмент программы загружает в AL
содержимое ячейки памяти с адресом 9:

.
.
.
mov ax,0
mov ds,ax
mov bx,9
mov al,[bx]
.
.
.

Регистр BX можно рассматривать как два 8-битовых регистра, BH
и BL.

Обратите внимание на то, что перед обращением к ячейке
памяти, на которую указывает BX, мы загрузили в регистр DS 0
(через AX). Это вызвано сегментированной природой памяти 8086, о
которой мы упомянули выше и к которой вернемся далее, в разделе
«Сегментные регистры». По умолчанию при использовании регистра BX
в качестве указателя памяти адресуемая им память зависит от
содержимого регистра DS.

Регистр CX
———-

Специализация регистра CX заключается в его использовании в
качестве счетчика. Предположим, что вы хотите повторить выполнение
некоторого блока команд 10 раз. Это можно сделать в виде:

.
.
.
mov cx,10
BeginingOfLoop: ;начало цикла
.
.
.
<повторяемые команды>
.
.
.sub cx,1
jnz BeginingOfLoop
.
.
.

Регистр CX можно рассматривать как два 8-битовых регистра, CH
и CL.

Не обращайте внимания на незнакомые вам элементы программы;
суть здесь заключается в том, что команды между меткой
BeginingOfLoop (НачалоЦикла) и командой JNZ повторяются до тех
пор, пока содержимое CX не станет равным 0. Отметим, что для
реверсивного счета регистром CX и для обратного перехода на метку
BeginingOfLoop, если содержимое его не равно 0 требуется две
команды — SUB CX,1 и JNZ BeginingOfLoop.

Реверсивный счет и зацикливание является часто встречающимся
элементом программ, поэтому 8086 имеет специальную команду,
позволяющую организовывать циклы более быстро и компактно. Не
удивительно, что эта команда называется LOOP (цикл). Команда LOOP
вычитает из CX единицу и выполняет переход, если его содержимое не
становится равным 0, все в одной команде. Ниже приводится
эквивалент предыдущему примеру:

.
.
.
mov cx,10
BeginingOfLoop:
.
.
.
<повторяемые команды>
.
.
loop BeginingOfLoop
.
.
.

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

Регистр DX
———-

Регистр ВX можно рассматривать как два 8-битовых регистра, ВH
и ВL.

Регистр DX является единственным регистром, который может
быть использован как указатель адресов ввода/вывода (I/O)
командами IN и OUT. Действительно, не существует способа адресации
портов I/O от 256 до 65,535, не используя DX. Например, следующий
фрагмент программы записывает в порт I/O 1000 значение 62:

.
.
.
mov al,62
mov dx,1000
out dx,al
.
.
.

Другие уникальные свойства DX связаны в операциями деления и
умножения. При делении 32-битового делимого на 16-битовый делитель
старшие 16 бит делимого должны быть помещены в DX; после деления
остаток записывается в DX. (Младшие 16 бит делимого должны
помещаться в AX, и частное также помещается в AX). Аналогичным
образом, при умножении двух 16-битовых множителей старшие 16 бит
произведения записываются в DX (младшие 16 бит помещаются в AX).

Регистр SI
———-

Как и регистр BX, регистр SI может использоваться как
указатель памяти. Например,

.
.
.
mov ax,0
mov ds,ax
mov si,20
mov al,[si]
.
.
.

загружает в AL 8-битовое значение, хранимое по адресу 20. При
работе со строковыми командами 8086 регистр SI становится
исключительно мощным средством. Например,

.
.
.
cld
mov ax,0
mov ds,ax
mov si,20
lodsb
.
.
.

не только загружает в AX значение ячейки памяти с адресом 20,
но и прибавляет к SI единицу. Это свойство может быть очень
эффективным при доступе к последовательности смежных ячеек памяти,
например, к строке текста. Еще лучше организовать автоматические
повторения строковых команд любое число раз, чтобы выполнить одной
командой сотни и даже тысячи действий. Подробно строковые команды
рассматриваются в главе 6, «Дополнительные сведения о
программировании на Turbo Assembler».

Регистр DI
———-

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

.
.
.
mov ax,0
mov ds.ax
mov di,1024
add bl,[di]
.
.
.

сложит 8-битовое значение, хранимое по адресу 1024, с
содержимым BL. Регистр DI несколько отличается от регистра SI при
работе со строковыми командами; если SI всегда служит для
строковых команд как указатель на исходную ячейку памяти, то DI
всегда служит указателем на ячейку памяти назначения результата.
кроме того, при работе со строковыми командами SI обычно адресует
память относительно сегментного регистра DS, тогда как DI всегда
адресует память относительно сегментного регистра ES. (При
использовании SI и DI как указателей памяти не-строковыми
командами они всегда выполняют адресацию относительно DS).
Например,

.
.
.
cld
mov dx,0
mov es,dx
mov di,2048
stosb
.
.
.

использует строковую команду STOSB для помещения значения из
регистра AL в адрес памяти, указываемый регистром DI, и для
прибавления к DI единицы. Здесь мы несколько забегаем вперед;
сначало вам нужно узнать о сегментах и сегментных регистрах, и
только после этого вы сможете начать изучение строковых команд.
Напоминаем, что строковые команды будут рассмотрены в главе 6,
«Дополнительные сведения о программировании на Turbo Assembler».

Регистр BP
———-

Как и регистры BX, SI и DI, регистр BP может быть использован
как указатель к памяти, однако с некоторым отличием. Если регистры
BX, SI и DI обычно действуют как указатели на ячейки памяти
относительно сегментного регистра DS (или, при использовании DI со
строковыми командами, относительно регистра ES), BP указывает
относительно SS, регистра стекового сегмента.

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

Как и почему Си использует для передаэи параметров стек, см.
в главе 7, «Связь Turbo Assembler с Turbo C».

Стек, или стековый сегмент, помещается в сегменте, на который
указателем служит регистр SS. С другой стороны, данные обычно
помещаются в сегменте данных, на который указывает регистр DS.
Поскольку BX, SI и DI обычно указывают на сегмент данных,
эффективного способа использования BX, SI или DI для указания на
параметры, передаваемые на стек, не существует, поскольку стек
обычно находится совсем в другом сегменте.

BP решает эту проблему, выполняя адресацию стекового
сегмента, Например,

.
.
.
push bp
mov bp,sp
mov ax,[bp+4]
.
.
.

обращается к стековому сегменту для загрузки в AX первого
параметра, переданного из Turbo C в подпрограмму на ассемблере.

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

Регистр SP
———-

Регистр SP, также известный как указатель стека, является
наименее универсальным из всех регистров общего назначения,
поскольку он практически всегда служит только для одной задачи:
обслуживание стека. Стек это область памяти, в которую данные
помещаются и извлекаются оттуда в режиме «последним вошел — первым
вышел»; то есть, последнее значение, помещенное на стек, будет
первым, получаемым оттуда при чтении стека. Классическим аналогом
стека служит стопка посуды. Тарелки можно помещать только на верх
стопки и брать только с ее верха, и ясно, что первая тарелка,
которая была туда помещена, будет последней, которую можно будет
извлечь.

Регистр SP в каждый момент времени указывает на вершину
стека; как и для примера со стопкой тарелок, вершина стека это
адрес, в который будет записано следующее значение, помещаемое на
стек. Действие, связанное с записью значение в стек, называется
«поместить на стек» и выполняется командой PUSH. Аналогичным
образом, действие извлечение значения из стека называется «снять
со стека» и выполняется командой POP.

Например, на рис.4.6 показано, как изменяются значения
регистров SP, AX и BX при выполнении следующего фрагмента
программы, предполагая, что в начале SP был установлен равным
1,000:

.
.
.
mov ax,1
push ax
mov bx,2
push bx
pop ax
pop bx
.
.
.

Хотя 8086 позволяет помещать в SP любые значения, а также
складывать и вычитать из значений, хранимых в SP, как и для всех
прочих регистров общего назначения, никогда не следует делать
этого, не зная в точности, к чему это приведет. Изменив значение
SP, вы тем самым измените адрес вершины стека, что быстро приведет
к нарушению работы машины.

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

В начале ? ?
———— ——————
AX ? ? ? 996 ? ? ?
———— ——————
BX ? ? ? 998 ? ? ?
———— ——————
SP ? 1000 ?—————>1000 ? ? ?
———— ——————
? ?

После команд mov ax,1 / push ax: ? ?
———— ——————
AX ? 1 ? 996 ? ? ?
———— ——————
BX ? ? ? ———>998 ? 1 ?
———— ? ——————
SP ? 998 ?——— 1000 ? ? ?
———— ——————
? ?

После команд mov bx,1 / push bx: ? ?
———— ——————
AX ? 1 ? ———>996 ? 2 ?
———— ? ——————
BX ? 2 ? ? 998 ? 1 ?
———— ? ——————
SP ? 996 ?——— 1000 ? ? ?
———— ——————
? ?

После команды pop ax: ? ?
———— ——————
AX ? 2 ? 996 ? ? ?
———— ——————
BX ? 2 ? ———>998 ? 1 ?
———— ? ——————
SP ? 998 ?——— 1000 ? ? ?
———— ——————
? ?

После команды pop bx: ? ?
———— ——————
AX ? 2 ? 996 ? ? ?
———— ——————
BX ? 1 ? 998 ? 1 ?
———— ——————
SP ? 1000 ?—————>1000 ? ? ?
———— ——————
? ?

Рис.4.6 Регистры AX, BX, SP и стек

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

Указатель команд
—————-

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

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

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

Всякий раз, когда мы говорили об адресации памяти, мы
упоминали о сегментах, и всякий раз откладывали полное объяснение
этого вопроса, пока не наступит время поговорить о сегментах.
Теперь это время пришло.

Сегментные регистры
——————-

Теперь мы перейдем к рассмотрению самого необычного аспекта
8086 — сегментации памяти. Основная причина сегментации памяти
такова: 8086 может адресовать 1 Мб памяти. Для того, чтобы
адресовать ячейку памяти в адресном пространстве объемом 1 Мб,
требуются 20-битовые адреса памяти. Однако 8086 использует только
16-битовые указатели памяти; например, вспомните, что для
адресации памяти может служить 16-битовый регистр BX. Каким же
образом 8086 обходится 16-битовыми указателями в адресном
пространстве, определяемом 20 битами ?

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

Сегменты и смещения объединяются следующим образом: значение
сегмента сдвигается на 4 бита влево (умножается на 16) и затем
складывается со смещением, как показано на рис.4.7.

————— —————
? 16-битовый ? ? 16-битовое ?
? сегментный ? ? смещение ?
? регистр ? ? ?
————— —————
? ?
——————- ?
?умножается на 16 ? ?
?(сдвигом влево на? ?
? 4 бита) ? ?
——————- ?
? ?
——————— ?
? значение сегмента,? ?
? умноженное на 16, ? ?
? равно 20-битовому ? ?
? значению ? ?
——————— ——— ?
? ? ? ?
————->? + ?<---------- ? ? --------- ? --------------------------- ? 20-битовый адрес памяти ? --------------------------- Рис.4.7 20-битовый адрес памяти Итак, рассмотрим для примера следующий фрагмент: . . . mov ax,1000h mov ds,ax mov si,201h mov dl,[si] . . . Здесь сегментный регистр DS установлен равным 1000h, а SI равным 201h, что может быть представлено парой сегмент:смещение 1000:201h. (Эффективно вычислить сегмент:смещение можно только в шестнадцатиричной системе счисления - одна из причин для ознакомления с шестнадцатиричной записью чисел). Адрес, из которого загружается DL, вычисляется как ((DS*16)+SI), или (1000h*16)+201h): 1000h x 16 ------- 10000h + 201h ------- 10201h На рис.4.8 иллюстрируется приведенный пример. -------------- -------------- DS ? 1000h ? SI ? 201h ? -------------- -------------- ? ? ------------------- ? ?умножается на 16 ? ? ?(сдвигом влево на? ? ? 4 бита) ? ? ------------------- ? ? ? --------------------- ? ? 10000h ? ? --------------------- --------- ? ? ? ? ? ------------->? + ?<---------- ? ? --------- ? --------------------------- Адрес памяти ? 10201h ? --------------------------- Рис.4.8 Вычисление адреса памяти для команды mov Другой способ вычисления полного адреса состоит в том, чтобы просто сдвинуть значение сегмента на 4 бита влево, либо на 1 шестнадцатиричный разряд, что эквивалентно умножению на 16: 1000 + 201 ----- 10201 Теперь вы видите, что доступ ко всему 1 Мб памяти 8086 возможен только при помощи пар сегмент:смещение. Дествительно, для доступа к памяти вы всегда используете пары сегмент:смещение; все команды и режимы адресации 8086 по умолчанию работают относительно того или другого сегментного регистра, хотя при желании можно явно указать команде сегментный регистр, который она должна использовать. Фактическая загрузка числа в сегментный регистр выполняется редко. Вместо этого туда, как правило, загружаются имена сегментов, преобразуемые в числовые значения уже в ходе ассемблирования, компоновки и запуска программы. Это происходит потому, что не существует способа определить заранее , где именно будет размещен в памяти данный сегмент; это зависит от версии DOS, количества и размеров резидентных программ и требований к памяти остальных программ. Использование имен сегментов позволяет Turbo Assembler и DOS решить эту сложную задачу. Наиболее распространенным именем сегмента является @data, которое обозначает сегмент данных по умолчанию, для которого используются упрощенные сегментные директивы. Например, .MODEL small .DATA Var1 DW 0 . . . .CODE mov ax,@data mov ds,ax . . . END загружает DS указателем на сегмент данных по умолчанию, в котором расположена переменная Var1. И снова мы немного забежали вперед; в следующей главе мы рассмотрим упрощенные сегментные директивы и загрузку значений сегментных регистров. Использование сегментов в 8086 имеет два интересных следствия. Во-первых, одновременно адресовать относительно сегментного регистра можно только блок памяти, не превышающий 64 Кб, поскольку 16-битовое смещение позволяет адресовать не более 64 Кб. это означает, что работать с большими (более 64 Кб) блоками данных в системе 8086 очень неудобно, поскольку тогда возникает необходимость часто изменять значения как сегментного регистра, так и величину смещения. Адресация больших блоков памяти затруднена в 8086 еще и тем, что в отличие от регистров общего назначения сегментные регистры не могут служить источником или назначением при работе арифметических или логических команд. Действительно, единственной операцией, которая может выполняться над сегментными регистрами, является операция копирования значений между сегментными регистрами и либо регистрами общего назначения, либо памятью. Например, чтобы сложить с содержимым сегментного регистра число 100, нужно сделать следующее: . . . mov ax,es add ax,100 mov es,ax . . . Вывод из этого состоит в том, что 8086 лучше приспособлен для работы с участками памяти, не превышающими 64 Кб. Второе следствие из использования сегментов заключается в том, что любой заданный адрес памяти адресуется многими возможными комбинациями сегмент:смещение. Например, адрес памяти 100h адресуется значениями сегмент:смещение 0:100h, 1:F0h, 2:Eoh и так далее, поскольку все эти пары сегмент:смещение в результате дают адрес 100h. Как и регистры общего назначения, каждый из сегментных регистров играет особую роль. Регистр CS указывает на программные коды, регистр DS указывает на данные, регистр SS является указателем на стек, а регистр ES это регистр универсального назначения ("избыточный") и может служить указателем на все, что может потребоваться. Теперь рассмотрим сегментные регистры более подробно. Регистр CS ---------- Регистр CS указывает на начало блока памяти размером 64 Кб, или сегмент программы, в котором находится следующая выполняемая команда. Следующая выполняемая команда располагается в сегменте программы со смещением, определяемым регистром IP; то есть по адресу сегмент:смещение CS:IP. 8086 не может извлечь команду ни откуда, кроме сегмента, определяемого в CS. Регистр CS может быть изменен при соответствующем изменении номера команды, включая конкретные переходы, вызовы и возвраты из подпрограмм. Регистр CS не может быть напрямую загружен каким-либо значением ни при каких обстоятельствах. Никакие режимы адресации памяти или указатели памяти, за исключением IP, обычно относительно CS не работают. Регистр DS ---------- Регистр DS указывает на начало сегмента данных, представляющего собой блок памяти размером 64 Кб, в котором находится большая часть операндов памяти. Обычно относительно DS работают смещения памяти, определяемые регистрами BX, SI или DI, как прямые адреса памяти. В целом сегмент данных работает так, как подразумевается его названием: это сегмент, в котором обычно находится текущий набор данных программы. Адресация памяти рассматривается далее в главе 5, "Элементы программы на языке ассемблера". Регистр ES ---------- Регистр ES указывает на начало блока памяти размером 64 Кб, известного как избыточный сегмент. Как подразумевается его именем, этот сегмент не предназначен специально ни для одной цели, и доступен в случае необходимости. Иногда избыточный сегмент используется для организации дополнительного блока памяти размером 46 Кб для хранения данных, однако доступ к такой памяти обычно менее эффективен, чем к памяти сегмента данных; этот вопрос рассматривается в главе 9, "Расширенные средства программирования на Turbo Assembler". Однако при использовании строковых команд избыточный сегмент исключительно эффективен. Все строковые команды, выполняющие запись в память, используют для этого адрес ES:DI. Это означает, что ES очень полезен как сегмент назначения при копировании блоков, сравнении строк, сканирования памяти и очистке блоков памяти. Строковые команды и использование в связи с ними сегмента ES рассматривается в главе 6, "Дополнительные сведения о программировании на Turbo Assembler". Регистр SS ---------- Регистр SS указывает на начало стекового сегмента, который представляет собо блок памяти размером 64 Кб, где располагается стек. Все команды, которые неявно используют регистр SP - включая команды помещения на стек, снятия со стека, вызова и возврата - работают со стековым сегментом, поскольку SP может адресовать память только стекового сегмента. Как было отмечено ранее, регистр BP также работает относительно стекового сегмента. Это позволяет использовать BP для адресации параметров и переменных, хранящихся на стеке. И снова повторяем, что подробное изложение адресации памяти приводится в следующей главе. Набор команд 8086 --------------------------------------------- С точки зрения программиста основным ресурсом 8086 является набор команд. Как отмечалось выше, набор команд включает в себя все действия, которые программисту могут понадобиться от 8086. (Полный набор команд Turbo Assembler приводится в Кратком справочном руководстве). Вы увидите, что набор команд 8086 весьма велик. Эти команды выполняют широкий диапазон различных действий, от фиктивной операции (NOP) до копирования сразу 65,535 байт (REP MOVSB). Далее большая часть данной главы, а также главы 5, 6 и 9 посвящены подробному описанию набора команд 8086. IBM PC и XT ----------------------------------------------------------------- До сих пор мы рассматривали только язык ассемблера для 8086, однако на самом деле процессор 8086 это лишь часть системы компьютера, и конфигурация аппаратного обеспечения и операционная система компьютера существенно влияют на программирование на языке ассемблера. Подавляющее большинство программ, написанных для процессора 8086 (и вероятно, большинство программ, вообще созданных за всю историю существования вычислительной техники), предназначены для компьютеров IBM PC и XT и совместимых с ними, работающих в операционной системе MS-DOS. Сам Turbo Assembler работает под управлением операционной системы MS-DOS на IBM PC и XT и совместимых с ними (далее мы будем называть их просто IBM PC), поэтому вероятнее всего, что вы планируете использовать свою копию Turbo Assembler для разработки программ, которые будут работать в среде IBM PC. Без знания конфигурации аппаратного обеспечения и операционной системы, под управлением которой будут работать ваши программы, вы не сможете осуществлять ввод и вывод и даже не будете знать, как завершить работу программы. Мы не сможем в данной книге изложить все возможности IBM PC и их системного программного обеспечения, однако покажем вам некоторые базовые средства PC. Мы предполагаем, что вы самостоятельно ознакомитесь с этими вопросами более подробно по книгам и руководствам, предложенных вам в начале этой главы. Устройства ввода и вывода ------------------------------------- Все IBM PC имеют клавиатуру, дисплейный адаптер и монитор, а также дисковод для гибких дисков. Кроме того, в систему часто входят модемы, принтеры, устройства "мышь" и жесткие диски. Каждое из этих устройств управляется достаточно сложными последовательностями обращений к портам ввода/вывода (I/O) или памяти (либо и туда, и туда). Например, для выбора нового режима видеоизображения на цветном графическом адаптере (CGA) требуется более 30 команд OUT; управляющие последовательности обращений к клавиатуре, модему и дисководу еще более сложны. Означает ли это, что для создания полезных программ на языке ассемблера для IBM PC вы должны сначала в совершенстве изучить эти бесчисленные управляющие последовательности? Разумеется, это не так; системное программное обеспечение вашего PC делает за вас большую часть этой работы. Системное программное обеспечение для семейства IBM PC -------- Системное программное обеспечение это такое программное обеспечение, которое служит как промежуточный уровень управления и интерфейса между прикладными программами, например Turbo Assembler и Quattro, и аппаратным обеспечением вашего компьютера, как показано на рис.4.9. В частности, системное программное обеспечение берет на себя сложные задачи интерфейса с конкретными устройствами. Например, для обработки одного нажатия клавиши PC требуется несколько сот строк программы на языке ассемблера, однако в ваших ассемблерных программах вы будете использовать для этого просто обращение к одной системной функции. Это становится возможным благодаря двум главным компонентам системного программного обеспечения PC: DOS и BIOS (базовая система вводавывода). Как показано на рис.4.9, системные компоненты DOS и BIOS служат в качестве промежуточного уровня управления и интерфейса между прикладными программами и аппаратным обеспечением IBM PC. Прикладные программы всегда имеют возможность обращаться к устройствам напрямую, однако всегда, когда это возможно, следует использовать для этих целей функции DOS или BIOS. ------------------------------------------------------------ ? Прикладное программное обеспечение ? ------------------------------------------------------------ ? ? ? ? ------------------------------ ? ? ? DOS ? ? ? ?(доступ через функции DOS ? ? ? ?int 21h и другие прерывания)? ? ? ------------------------------ ? ? ? ? ? ------------------------------ ? ? BIOS ? ? ?(доступ через функции BIOS ? ? ?посредством нескольких ? ? ?прерываний) ? ? ------------------------------ ? ? ------------------------------------------------------------ ? Аппаратное обеспечение IBM PC ? ? Дисплейный адаптер, клавиатура, принтер, диск, мышь, ? ? джойстик и т.д. Доступ выполняется через порты I/O ? ? и/или ячейки оперативной памяти, в зависимости от ? ? конкретного аппаратного обеспеэения ? ------------------------------------------------------------ Рис.4.9 Системное программное обеспечение DOS и BIOS как промежуточный уровень управления и интерфейса DOS --- DOS (сокращение для дисковой операционной системы, также иногда называемой MS-DOS и PC-DOS) это программа, управляющая работой вашего компьютера, начиная с момента, когда она начинает чтение с диска при подаче питания, и пока компьютер не будет выключен. DOS занимает часть из 640 Кб вашей драгоценной доступной оперативной памяти, однако с этим приходится мириться, поскольку без DOS ваш PC превращается просто в дорогостоящий утиль. Именно DOS выдает вам приглашение A> (или C>, либо любое другое
приглашение вашего компьютера), и именно DOS принимает и выполняет
команды типа DIR.

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

.
.
.
mov ah,2 ;функция DOS для вывода на дисплей символа
mov dl,’A’ ;A это выводимый символ
int 21h ;обращение к DOS для выполнения функции
.
.
.

запускает выполнение функции DOS «Вывод на дисплей», чтобы
получить в текущей позиции курсора на экране символ A.

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

Основным справочником по функциям DOS служит руководство
«Технический справочник по IBM DOS».

Кроме того, для программ, написанных в обход DOS, может быть
затруднено сосуществование с прочими программами; хорошим примером
здесь служат резидентные в памяти программы, как то SideKick или
SuperKey. И наконец, зачем же тратить время на написание лишних
частей программы, если в DOS эта работа уже проделана за вас?
Короче говоря, если имеется функция DOS, выполняющая требуемую вам
операцию, ей необходимо пользоваться!

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

Прием нажатий клавиш
———————

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

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

Например, при выполнении фрагмента программы

.
.
.
mov ah,1
int 21h
.
.
.

DOS поместит в AL следующий символ, набранный на клавиатуре.
Отметим, что если ни одно нажатие клавиши не ожидает считывания,
то DOS будет ждать, пока не будет нажата какая-либо клавиша;
поэтому для выполнения данной команды требуется неопределенно
долгое время.

Вывод символов на экран дисплея
——————————-

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

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

.
.
.
mov ah,1
int 21h ;прием следующего вводимого символа
mov ah,2
mov dl,al ;переслать считанный символ из AL в DL
int 21; ;вывести этот символ на дисплей
.
.
.

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

Существует еще один момент, который мы хотим отметить в связи
с вводом и выводом для клавиатуры, экрана и файлов на языке
ассемблера. Те из вас, кто использовал команды scanf и printf на
языке Си или Readln и Writeln на языке Паскаль, будут удивлены
тем, что DOS (и следовательно, язык ассемблера) не поддерживает
средств форматированного ввода и вывода; DOS на вводе и выводе
работает исключительно с символами и строками символов. В Си для
печати целочисленной переменной i необходимо просто написать:

printf(«%d\n,i);

Си автоматически преобразует целое число, хранимое в
16-битовой ячейке памяти, в стоку ASCII-символов и напечатает эти
символы. В случае языка ассемблера ваша программа перед выводом на
дисплей должна в явном виде выполнить преобразование переменных в
строку символов. Точно так же, DOS умеет считывать с клавиатуры
только строки символов, поэтому вы должны сами в программе
преобразовывать символы и строки, вводимые пользователем, в другие
типы данных, с которыми работает ваша ассемблерная программа.

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

Выход из программы
——————

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

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

Имеется несколько функций DOS, предназначенных для завершения
программы, однако предпочтительной является функция DOS номер 4Ch
(для тех, кто предпочитает десятиричную систему сэисления, это
номер 76). Зная это, можно написать следующую программу:

.MODEL small
.STACK 100h
.CODE
EchoLoop:
mov ah,1 ;функция DOS ввода с клавиатуры
int 21h ;принять следующее нажатие клавиши
cmp al,13 ;была ли нажата клавиша Enter?
jz EchoDone ;если да, то отображение закончено
mov dl,al ;поместить символ в DL
mov ah,2 ;функция DOS вывода на экран
int 21h ;вывести символ на экран
jmp Echoloop ;перейти к отображению следующего символа
EchoDone:
mov ah,4ch ;функция DOS выхода из программы
int 21h ;конец программы
END

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

BIOS
—-

Иногда функции DOS не соответствуют вашим требованиям; в
таких случаях необходимо обратиться к базовой системе ввода/
вывода, или BIOS. В отличие от DOS и прикладного программного
обеспечения, BIOS не загружается с диска и не занимает никакого
участка в доступных вам 640 Кб оперативной памяти; BIOS хранится в
ПЗУ (ROM) в той части адресного пространства 8086, которая
резервируется для системных функций.

Первичным справочником по функциям BIOS может служить
Техническое справочное руководство по интерфейсу с BIOS IBM.

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

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

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

.
.
.
mov ah,0 ;функция BIOS установки режима
mov al,4 ;номер режима для 4-цветной графики 320×200
int 10h ;выполнить видео прерывание BIOS
;для установки режима
.
.
.

Если вы вспомните, что для установки видео режима требуется
свыше 30 команд OUT, вы поймете, что функция BIOS «Установка
режима» существенно экономит ваше время.

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

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

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

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

Прочие ресурсы ————————————————

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

* Драйвер ANSI.SYS обеспечивает улучшенное управление
дисплеем без обращения к функциям BIOS.

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

* Необязательно устанавливаемый математический сопроцессор
8087 на порядок ускоряет вычисления с плавающей точкой.