, поскольку программа может заниматься манипуляциями с экраном, не заботясь о постоянной проверке статуса кнопок. Однако это существенно замедляет скорость выполнения программы, поскольку проверка статуса кнопок осуществляется после выполнения каждого оператора. По этой причине, STRIG работает только когда он преднамеренно включен, а он может включаться и выключаться по ходу программы. STRIG работает двумя способами. Во-первых, он может работать как функция, которая непосредственно читает текущие значения кнопок, в форме X = STRIG(n). Здесь n - кодовый номер: 0 Кнопка A1 нажата со времени последнего вызова 1 Кнопка A1 в данный момент отпущена 2 Кнопка B1 нажата со времени последнего вызова 3 Кнопка B1 в данный момент отпущена 4 Кнопка A2 нажата со времени последнего вызова 5 Кнопка A2 в данный момент отпущена 6 Кнопка B2 нажата со времени последнего вызова 7 Кнопка B2 в данный момент отпущена Во всех случаях функция возвращает -1, если описание применимо и 0 - если нет. Второй способ использования STRIG это форма, в которой он автоматически переключает на процедуру при нажатии кнопки. Надо написать ON STRIG(n) GOSUB номерстроки. Номер строки дает началь- ный номер строки процедуры. Число n относится к кнопке, где 0 = A1, 2 = B1, 4 = A2 и 6 = B2. Каждая кнопка может обрабатываться своей процедурой или может быть одна процедура для всех кнопок. Для активизации функции STRIG включите в программу оператор STRIG(n) ON. В качестве значения n используются четыре перечис- ленных кода. Чтобы отменить его (ускоряя работу программы) напи- шите STRIG(n) OFF. Имеется также третья возможность. STRING(n) STOP приводит к тому, что нажатие кнопки запоминается, но никаких действий не предпринимается до очередного оператора STRING(n) ON. Это свойство предохраняет от нежелательных перерывов из-за опера- тора ON STRING GOSUB. Тем не менее, при выполнении условия STRIG(n) STOP программа замедляется. Следующий пример показывает действие ON STRIG GOSUB. Пример пункта [7.3.3] содержит строки, показывающие форму X = STRIG. 100 ON STRIG(0) GOSUB 5000 'переход на 5000 при нажатии . 'кнопки A1 200 STRIG(0) ON 'включаем проверку нажатия кнопки . 300 STRIG(0) STOP 'отменяем уход на процедуру . 400 STRIG(0) ON 'возобновляем уход на процедуру . 500 STRIG(0) OFF 'отменяем проверку нажатия кнопки . 5000 '''здесь находится процедура обработки нажатия кнопки A1 . 5500 RETURN 'возврат к тому месту, откуда попали сюда Средний уровень. Только AT предоставляет поддержку джойстика на уровне опера- ционной системы. Функция 84H прерывания 15H возвращает установку кнопок в битах 4-7 регистра AL, как показано ниже. При входе DX должен содержать 0; когда DX содержит 1, то вместо этого возвра- щаются координаты джойстика [7.3.3]. При возврате регистр перено- са устанавливается, когда машина не имеет игрового порта. ;---проверяем кнопку #2 джойстика B (бит 7) MOV AH,84H ;номер функции MOV DX,0 ;запрос состояния кнопок INT 15H ;вызов функции JC NO_JOYSTICK ;если нет джойстика, то на выход TEST AL,10000000B ;проверяем бит 7 JNZ BUTTON_DOWN ;переход если кнопка нажата Низкий уровень. Биты 7-4 порта с адресом 201H содержат статус кнопок, связан- ных с игровым портом. Значение битов меняется в зависимости от того, присоединены ли джойстики или весла: Бит Джойстик Весло 7 Кнопка #2 джойстика B Кнопка весла D 6 Кнопка #1 джойстика B Кнопка весла C 5 Кнопка #2 джойстика A Кнопка весла B 4 Кнопка #1 джойстика A Кнопка весла A Программе нужно просто прочитать значение из порта и проверить установку соответствующих битов: MOV DX,201H ;адрес порта игрового адаптера IN AL,DX ;получаем значение из него TEST AL,0010B ;проверяем бит 1 (кнопка A2 нажата?) JNZ BUTTON_A2 ;если да, то на процедуру обработки Программа имеет обычно более важные дела, чем постоянно проверять игровой порт, однако настолько же быссмысленно время от времени проверять порт, рассовывая процедуру по разным частям программы. Чтобы получить эффект отлова нажатия кнопок, аналогичный описан- ному в Бейсике, Вам придется создать дополнение к прерыванию времени суток, как описано в [2.1.7]. Прерывание обычно выпол- няется 18.2 раза в секунду и каждый раз Вы можете проверять игро- вой порт и предпринимать нужные действия при необходимости. Приложения. Приложение А. Двоичные и шестнадцатиричные числа и адресация памяти. Основной единицей хранения данных в компьютере является бит. В большинстве микрокомпьютеров восемь битов объединены в байт, при этом каждый бит байта может быть установлен или "включен" (= 1) или сброшен или "выключен" (= 0), допуская 256 разных вариантов. Таким образом, в одном байте можно представить 256 разных симво- лов (расширенный набор кодов ASCII) или целое число в диапазоне от 0 до 255. Хотя мы привыкли записывать эти числа в десятичной форме, они могут записываться также в двоичной или шестнадцати- ричной форме - их значения при этом не изменяются, а программы могут с одинаковой легкостью читать эти значения как в той, так и в другой форме. Вместо того, чтобы говорить, что в одном байте могут храниться числа от 0 до 255, можно сказать, что могут хра- ниться двоичные числа от 00000000 до 11111111 или шестнадцатирич- ные числа от 00 до FF. Поскольку можно перепутать разные формы, то двоичные и шестнадцатиричные числа отмечаются специальным образом. В языке ассемблера за двоичными числами следует буква B, а за шестнадцатиричными числами - буква H, например, 11111111B или FFH. Бейсик фирмы Microsoft предваряет шестнадцатиричные числа символами &H, например &FFH; к сожалению, он не распознает числа в двоичной форме. Двоичные числа: Когда содержимое байта представляется в двоичной форме, то требуется 8 цифр. Каждая цифра соответствует одному биту, которые нумеруются от 0 до 7. Как и в десятичных числах цифры распола- гаются справа налево, от младших к старшим разрядам. В отличии от десятичных чисел, в которых каждая последующая цифра весит в 10 раз больше своей соседки справа, двоичные цифры имеют только вдвое больший вес. Таким образом, самая правая цифра считает единицы, вторая - двойки, третья - четверки и т.д., до значения 128 для восьмой цифры байта. Это означает, что если первая цифра равна 1, то прибавление к ней 1 приводит к тому, что она станет 0, а 1 будет перенесена во вторую цифру, как для десятичных чисел 9 + 1 = 0 и перенос единицы в следующий разряд. Вот как числа первого десятка представляются в двоичной форме: 00000000 0 00000001 1 00000010 2 00000011 3 00000100 4 00000101 5 00000110 6 00000111 7 00001000 8 00001001 9 00001010 10 В этой последовательности большинство нулей слева необязатель- ны, т.е. эту последовательность можно записать и в виде 0, 1, 10, 11, 100, 101 и т.д. Нули включены только для того, чтобы напом- нить Вам, что байт составляется восемью цифрами, соответствующими битам. В то время как набор нулей и единиц может быть несколько утомительным, Вы можете легче работать с двоичными числами, если будете представлять их себе следующим образом: бит 7 6 5 4 3 2 1 0 значение 128 64 32 16 8 4 2 1 Когда Вы встречаете двоичное число 10000001, то установлены биты 7 и 0. Бит 7 соответствует 128, а бит 0 - 1, поэтому десятичное значение байта равно 129. Если этот байт представляет символ, то он соответствует коду ASCII 129, который представляет букву u с умляутом (в альтернативной кодировке ГОСТа - букву Б). Наоборот, чтобы определить цепочку битов для буквы A, которая равна ASCII Приложение Б. Битовые операции в Бейсике. В Бейсике нельзя использовать числа в двоичной форме. Он расс- матривает цепочку битов 11000000 как 11 миллионов, а не как 192. Однако манипуляции с цепочками битов часто необходимы при прог- раммировании, поскольку требуется читать и изменять содержимое статусных байтов и статусных регистров. В большинстве случаев к цепочкам битов применяются две логи- ческие операции. Это операции ИЛИ (OR) и И (AND) и обе они дос- тупны в Бейдиапазоне от 0 до 255. Хотя мы привыкли записывать эти числа в десятичной форме, они могут записываться также в двоичной или шестнадцати- ричной форме - их значения при этом не изменяются, а программы могут с одинаковой легкостью читать эти значения как в той, так и в другой форме. Вместо того, чтобы говорить, что в одном байте могут храниться числа от 0 до 255, можно сказать, что могут хра- ниться двоичные числа от 00000000 до 11111111 или шестнадцатирич- ные числа от 00 до FF. Поскольку можно перепутать разные формы, то двоичные и шестнадцатиричные числа отмечаются специальным образом. В языке ассемблера за двоичными числами следует буква B, а за шестнадцатиричными числами - буква H, например, 11111111B или FFH. Бейсик фирмы Microsoft предваряет шестнадцатиричные числа символами &H, например &FFH; к сожалению, он не распознает числа в двоичной форме. Двоичные числа: Когда содержимое байта представляется в двоичной форме, то требуется 8 цифр. Каждая цифра соответствует одному биту, которые нумеруются от 0 до 7. Как и в десятичных числах цифры распола- гаются справа налево, от младших к старшим разрядам. В отличии от десятичных чисел, в которых каждая последующая цифра весит в 10 раз больше своей соседки справа, двоичные цифры имеют только вдвое больший вес. Таким образом, самая правая цифра считает единицы, вторая - двойки, третья - четверки и т.д., до значения 128 для восьмой цифры байта. Это означает, что если первая цифра равна 1, то прибавление к ней 1 приводит к тому, что она станет 0, а 1 будет перенесена во вторую цифру, как для десятичных чисел 9 + 1 = 0 и перенос единицы в следующий разряд. Вот как числа первого десятка представляются в двоичной форме: 00000000 0 00000001 1 00000010 2 00000011 3 00000100 4 00000101 5 00000110 6 00000111 7 00001000 8 00001001 9 00001010 10 В этой последовательности большинство нулей слева необязатель- ны, т.е. эту последовательность можно записать и в виде 0, 1, 10, 11, 100, 101 и т.д. Нули включены только для того, чтобы напом- нить Вам, что байт составляется восемью цифрами, соответствующими битам. В то время как набор нулей и единиц может быть несколько утомительным, Вы можете легче работать с двоичными числами, если будете представлять их себе следующим образом: бит 7 6 5 4 3 2 1 0 значение 128 64 32 16 8 4 2 1 Когда Вы встречаете двоичное число 10000001, то установлены биты 7 и 0. Бит 7 соответствует 128, а бит 0 - 1, поэтому десятичное значение байта равно 129. Если этот байт представляет символ, то он соответствует коду ASCII 129, который представляет букву u с умляутом (в альтернативной кодировке ГОСТа - букву Б). Наоборот, чтобы определить цепочку битов для буквы A, которая равна ASCII блоки. Набору блоков предшествует "лидер", который состоит из 256 байтов ASCII 1. Лидер завершается нулевым битом синхронизации. Затем следует байт синхронизации со значением 16H, а затем 256 байтов данных. После этого идут 2 байта контроля ошибок, а затем новый блок данных, сопровождающийся парой байт проверки ошибок и т.д. Вся последовательность завершается четырехбайтным "хвостом", содержащим коды ASCII 1. Для чтения данных с кассеты на до использовать функцию 2 пре- рывания 15H. Нет необходимости открывать файл, как это делается при дисковых операциях. ES:BX указывают на буфер в памяти, куда будут посылаться данные, а CX - число байтов, которые надо счи- тать. При возврате DX сообщит сколько байтов прочитано на самом деле, а ES:BX будут указывать на последний считанный байт плюс 1. Флаг переноса будет равен 0, если чтение прошло успешно, а в противном случае AH будет содержать 1, если проблема с контролем ошибки, 2 - при ошибке чтения данных и 3 - при отсутствии данных на ленте. Функция 3 прерывания 15H записывает данные на кассету. ES:BX указывают на первый байт данных, а CX содержит число байтов, которое надо записать. При возврате ES:BX указывают на байт, следующий за последним записанным. Мотор управляется функциями 0 (включение) и 1 (выключение) прерывания 15H. Для этих функций нет выходных регистров. 7.3.2 Чтение позиции светового пера. Хотя очень немногие компьютеры оснащены световым пером, тем не менее это одно из немногих вспомогательных устройств, которое поддерживается как оборудованием, так и операционной системой. Световое перо работает с помощью небольшого оптического детектора на кончике пера. По ходу сканирования экрана электронным лучом инициируется импульс оптического детектора, когда пучок достигает точки экрана, над которой находится перо. Время возникновения этого импульса, относительно сигналов горизонтальной и вертикаль- ной синхронизации, позволяет определить позицию светового пера. Высокий уровень. Бейсик может определять позицию светового пера двумя способа- ми. При первом программа непрерывно определяет статус пера. При втором, когда перо используется, то управление временно передает- ся процедуре, обеспечиваемой Вашей программой. Для непрерывного контроля за пером надо использовать оператор PEN как функцию в форме X = PEN(n), где n - кодовый номер, определяющий какую ин- формацию Вы хотите получить о пере и его позиции. Возможные зна- чения n такие: 0 возвращает -1, если перо было выключено со времени послед- него запроса, 0 - если нет 1 возвращает последнюю координату x (0-319 или 0-639), в ко- торой перо было включено (оно могло быть впоследствии передвинуто, если оставалось включенным) 2 возвращает последнюю координату y (0-199), в которой перо было включено. 3 возвращает -1, если перо включено и 0 - если нет 4 возвращает текущую x координату (0-319 или 0-639) пера 5 возвращает текущую y координату (0-199) пера 6 возвращает позицию - номер строки (1-24), в которой перо было последний раз активизировано 7 возвращает позицию - номер столбца (1-40 или 1-80), в ко- торой перо было последний раз активизировано 8 возвращает текущую позицию - номер строки (1-24) 9 возвращает текущую позицию - номер столбца (1-40 или 1-80) В данном примере определяется включено ли перо, и если это так, то берется текущее его положение: 100 IF NOT PEN(3) THEN 130 'проверяем включено ли перо 110 X = PEN(4) 'получаем координату точки по оси x 120 Y = PEN(5) 'получаем координату точки по оси y 130 ... 'продолжаем программу Более гибкие возможности использования светового пера предос- тавляются оператором ON PEN GOSUB. Этот оператор указывает номер строки, в которой начинается процедура, активизируемая при вклю- чении пера. Бейсик достигает этого проверкой состояния пера после выполнения каждой его инструкции. Процедура может получить пози- цию пера и предпринять требуемые действия. Когда процедура закан- чивается, то программа продолжается с того места, где она была при включении пера. ON PEN GOSUB не работает до тех пор, пока она не активизирова- на оператором PEN ON. PEN OFF отменяет ее работу. Смысл этого состоит в том, что постоянная проверка статуса пера замедляет работу программы, поэтому ее надо осуществлять только когда это необходимо. Если программа начинает выполнять критичекую часть кода, когда нельзя использовать процедуру ON PEN GOSUB, напишите PEN STOP. В этом случае будет продолжаться проверка статуса пера, и если перо будет включено, то этот факт будет запомнен. Однако пока не будет встречен оператор PEN ON, управление не будет пере- даваться процедуре ON PEN GOSUB. Данный пример вызывает остановку программы, когда нажата кноп- ка на световом пере. Точка в позиции светового пера включается процедурой, обрабатывающей включение ERROR ;на обработку ошибки INC BX ;увеличиваем указатель LOOP NEXT_CHAR ;выводим следующий символ Стандартное прерывание MS DOS для вывода на принтер это функ- ция 5 прерывания 21H. Просто поместите символ в DL и выполните прерывание. Эта функция всегда выводит на LPT1 и у нее нет возв- ращаемых регистров. ;---вывод данных на LPT1 MOV AH,5 ;номер функции MOV DL,CHAR ;готовим печатаемый символ INT 21H ;посылаем его на пр ;N,1)*2^(N-1) 2040 NEXT 2050 RETURN Приложение В. Основные сведения об языке ассемблера. Читатель этой книги, не знакомый с языком ассемблера, скоро поймет, что многие программистские трюки не могут быть достигнуты другими средствами. Хотя изучение языка ассемблера требует от- дельной книги, в этом приложении приводятся основные понятия, которые помогут новичкам разобраться в примерах на этом языке. Внимательный просмотр разделов, посвященных среднему и низкому уровням, даст Вам возможность получить представление о том, как работает ассемблер, после чего намного легче изучить разные част- ные вопросы. Здесь обсуждаются не все ассемблерные инструкции, встречающиеся в программах, но Вы обнаружите, что около 95 % инструкций, встреченных Вами в программах, описаны здесь, а зна- чение остальных может быть понято благодаря комментариям к прог- раммам. Микропроцессор 8088 имеет 13 16-разрядных регистров, каждый из которых имеет свои функции. В то время как в языках высокого уровня Вы можете поместить два числа в переменные, а затем сло- жить эти переменные, то в языке ассемблера эти числа помещаются в регистры микропроцессора, а затем складываются значения, содержа- щиеся в регистрах. Все операции в языке ассемблера состоят в обмене данных с регистрами, а затем выполнении операций на ре- гистрах, таких как изменение отдельных битов, выполнение арифме- тических операций и т.д. Одной из причин высокой эффективности языка ассемблера является хранение данных в регистрах микропро- цессора; компиляторы имеют тенденцию возвращать все значения в память после выполнения операции, а доступ к памяти требует боль- шого времени. На рис. В-1 показаны 13 регистров микропроцессоров 8088 и 80286 (последний имеет дополнительные средства для много- задачной работы, которые мы не будем рассматривать здесь). Регистры AX, BX, CX и DX являются регистрами общего назначе- ния. Их особенность состоит в том, что операции могут произво- диться не только над содержимым всего регистра, но также и над половиной. Каждый из четырех регистров делится на старшую и млад- шую части, например, AH обозначает старшую половину регистра AX, а AL - младшую. Точно так же ассемблерная программа может иметь доступ к BH, BL, CH, CL, DH и DL. Это свойство очень полезно, поскольку часто программе приходится работать с байтными величи- нами. Регистры BP, SI и DI также достаточно удобны, хотя они могут принимать только 16-битные значения. Каждый бит регистра флагов сообщает о соответствующем статусе процессора, например, о том, что при выполнении арифметической операции был перенос за разрядную сетку. В общем случае значения помещаются в регистры с помощью инст- рукции MOV. MOV AX,BX пересылает содержимое регистра BX в AX, затирая ранее содержащееся в AX значение. MOV AH,BL приводит к пересылке байта из регистра в регистр, но MOV AX,BL - недопусти- мая инструкция, так как значения должны иметь одинаковый размер. Инструкция MOV можеть также передавать значения из памяти, напри- мер, MOV AX,ACCT_NUMBER. Здесь ACCT_NUMBER - имя переменной, которую создал программист, совсем как в языке высокого уровня. Переменная создается оператором вида ACCT_NUMBER DW 0. Этот опе- ратор оставляет место для слова (двух байтов), присваивая им значение 0. Другие допустимые символы в этом операторе это DD - для двойного слова и DB - для байта или строк. Ассемблер следит за адресами переменных, поэтому при ассемблировании оператора MOV AX,ACCT_NUMBER имя переменной заменяется на ее адрес. Работа с именами переменных - самый простой способ идентифика- ции данных в программах на языке ассемблера. Но имеются различные способы хитрой адресации, которые позволяют программе хранить массивы или использовать указатели. Например, MOV AX,[BX][SI] посылает в AX значение, которое содержится по смещению, равному сумме значений регистров BX и SI. Но от чего отсчитывать смеще- ние? Ответ заключается в том, что все данные собраны в одну часть программы, а весь исполняемый код - в другую. Часть, отведенная под данные, называется сегментом данных, а под программу - кодо- вым сегментом. Все переменные, отведенные для хранения данных, адресуются через смещение относительно начала сегмента данных. Позиция в памяти, с которой начинается сегмент данных, хранит- ся в регистре DS, одном из четырех сегментных регистров. Как и все остальные регистры микропроцессора он 16-разрядный, поэтому он не может содержать числа, большие чем 65535. Каким же образом сегмент даных может указывать на ячейки памяти, расположенные в верхней части мегабайтного адресного пространства? Ответ состоит в том, что сегментные регистры автоматически умножаются на 16, а результат указывает на место в памяти, с которого начинается сегмент. Таким образом, сегменты всегда выравнены на 16-байтную границу. После того как сегмент установлен, все остальные регист- ры могут содержать смещения, указывающие на любой из следующих 65535 байтов. Регистр дополнительного сегмента (ES) также исполь- зуется для указания на данные, хранящиеся в памяти. Среди ассемблерных инструкций, которые Вы часто будете встре- чать в этой книге, есть инструкции загрузки сегментных и относи- тельных адресов переменных. MOV AX,SEG ACCT_NUMBER помещает зна- чение сегментного регистра, в котором расположен ACCT_NUMBER в AX, а впоследствии это значение будет переслано в DS. MOV BX,OFF- SET ACCT_NUMBER помещает в BX смещение переменной ACCT_NUMBER в сегменте данных. После выполнения этих операций DS:BX будут ука- зывать на ACCT_NUMBER. Если ACCT_NUMBER является одномерным мас- сивом, то для указания на определенный элемент массива может использоваться добавочное смещение. Вы часто будете встречать также инструкцию LEA, предоставляющую другой способ загрузки смещения. Кодовый сегмент содержит последовательность машинных инструк- ций, составляющих программу. Например, инструкция MOV существует в виде нескольких байтов машинного кода, значение байтов которого определяет в какой регистр идет пересылка и откуда. Регистр IP (счетчик команд) содержит величину смещения, которая указывает на ту инструкцию в кодовом сегменте, которая сейчас должна выпол- няться. После выполнения инструкции IP увеличивается таким обра- зом, чтобы он указывал на следующую инструкцию. В простейшей программе счетчик команд будет передвигаться от первого байта кодового сегмента к последнему, где программа и завершится. Но, как и другие программы, программа на языке ассемблера может быть разбита на процедуры (подпрограммы), поэтому счетчик команд может прыгать из одного места кодового сегмента в другое. Когда счетчик команд прыгает в другое место кодового сегмента, то его старое значение должно быть запомнено, с тем чтобы можно было вернуться в нужное место, так как это делает оператор RETURN в Бейсике, возвращая управление в то место, откуда была вызвана процедура. В языке ассемблера процедуре присваивается имя, напр- имер, COMBINE_DATA, и оператор CALL COMBINE_DATA передает управ- ление в процедуру. Процедура завершается инструкцией RET (возв- рат). При вызове процедуры процессор запоминает текущее значение счетчика команд, заталкивая его на стек. Стек это область, используемая для временного хранения данных. После завершения процедуры старое значение счетчика команд берет- ся из стека и выполнение программы продолжается. Стек также со- держится в отдельном сегменте, который, совершенно естественно, называется сегментом стека. Ему соответствует сегментный регистр SS. В регистре SP хранится указатель стека, который всегда указы- вает на вершину стека и изменяется при засылке на стек и выборке из стека. На первый взгляд стек кажется достаточно неуклюжим способом хранения информации, но у него есть два преимущества. Во-первых, доступ к его содержимому намного быстрее, чем к переменным, хра- нящимся в памяти, а, во-вторых, стек может использоваться для многих целей. Он может хранить адреса возврата из процедуры, вложенной в другую процедуру. Впоследствии, то же самое прост- ранство может использоваться программистом для хранения данных, которые должны сейчас обрабатываться, но для которых не хватает места в регистрах микропроцессора. Программа выталкивает содержи- мое регистра на стек командой PUSH, а позднее забирает его оттуда командой POP. В ассемблерных программах, приведенных в этой кни- ге, Вы не раз встретитесь с инструкциями типа PUSH BX и POP DX. Неправильный порядок обмена данными со стеком - лучший способ привести ассемблерную программу к краху. После того как программист на ассемблере установил три сег- ментных регистра (CS, DS и SS) и загрузил данные в регистры мик- ропроцессора он имеет широкий набор встроенных средств, которыми процессор может помочь программисту на ассемблере. Вот наиболее распространенные из них: ADD AX,BX Прибавляет BX к AX. Существует также инструкция вычи- тания (SUB), а также варианты обеих этих инструкций. MUL BL Умножает BL на AX. Имеется также инструкция деления (DIV), а также варианты обеих этих инструкций. INC BL Увеличивает BL на 1. Имеется также инструкция умень- шения (DEC). LOOP XXX Возвращает программу назад к строке помеченной XXX, повторяя процесс столько раз, какое число содержится в CX (аналогично инструкции FOR .. TO .. NEXT в Бей- сике). OR AL,BL Выполняет операцию логического ИЛИ над содержимым регистров AL и BL, причем результат помещается в AL. Имеются также инструкции AND, XOR и NOT. SHL AX,1 Сдвигает все биты, содержащиеся в AX, на одну позицию влево. Это эквивалентно умножению содержимого AX на 2. Другие инструкции сдвигают биты вправо или осу- ществляют циклический сдвиг. Все эти инструкции очень полезны для битовых операций, таких как установка точек экрана. IN AL,DX Помещает в AX байт, обнаруженный в порте, адрес кото- рого указан в DX. Имеется также инструкция OUT. JMP Передает управление в другое место программы, как инструкция GOTO в Бейсике. JMP YYY передает управле- ние на строку программы, имеющую метку YYY. CMP AL,BL Сравнивает содержимое AL и BL. За инструкцией CMP обычно следует инструкция условного перехода. Напри- мер, если за инструкцией CMP следует инструкция JGE, то переход произойдет только если BL больше или равно AL. Инструкция CMP достигает того же результата, что и инструкция IF .. THEN в Бейсике (на самом деле инструкция IF .. THEN переводится интерпретатором Бейсика в инструкцию CMP). TEST AL,BL Проверяет есть ли среди битов, установленных в BL, такие, которые установлены также и в AL. За этой инструкцией обычно следует команда условного перехо- да, так же как за CMP. TEST очень полезен при провер- ке статусных битов (битовые операции очень просто реализуются в языке ассемблера). MOVS Пересылает строку, длина которой содержится в CX, с места, на которое указывает SI, на место, на которое указывает DI. Имеется еще несколько других инструк- ций, связанных с пересылкой и поиском строк. Язык ассемблера обеспечивает несколько вариантов этих инструкций, а также ряд других специальных инструкций. Имеется также целый класс инструкций, называемых псевдооператорами, которые помещают- ся в текст программы с целью указания ассемблеру как обрабатывать данную программу. Например, один из типов псевдооператоров авто- матически вставляет часто используемый кусок кода по всей прог- рамме. Такая порция кода называется макросом и именно это свойст- во ассемблера дало ему название "макроассемблер". И, наконец, ассемблер имеет возможность, которой завидуют (или, по крайней мере, должны завидовать) все кто программирует только на языках высокого уровня. Имеется ввиду возможность опти- мальным образом использовать прерывания операционной системы. Ведь это ничто иное, как готовые процедуры. Однако вместо того, чтобы вызывать их по CALL, они вызываются инструкцией INT. INT21H вызывает прерывание с шестнадцатиричным номером 21. Имеется ряд таких прерываний, как в базовой системе ввода/вывода ПЗУ, так и в операционной системе, причем некоторые из этих процедур необычай- но мощны. На самом деле некоторые из них настолько тесно связаны с системой, что Вы практически не можете сами написать эквива- лентную процедуру. Языки высокого уровня позволяют использовать многие из этих прерываний. Они используют их для вывода на экран, приема ввода с клавиатуры и доступа к дискам. Но многие действи- тельно полезные прерывания игнорируются языками высокого уровня, например такие, которые позволяют запустить из одной программы другую. Некоторые трансляторы (такие как Lattice C или Turbo Pascal) позволяют доступ к этим прерываниям, если Вы знаете как их готовить и Вы можете использовать разделы среднего уровня этой книги для этой цели. Перед вызовом прерывания некоторая информация должна быть помещена в регистры процессора. Например, прерывание, верикально сдвигающее экран, должно знать размеры сдвигаемого окна, число строк на которое его надо сдвинуть и т.д. Эти значения часто называют входными регистрами. Снова и снова Вы будете встречать слова "при входе BX должен содержать ...", описывающие специфика- цию входных регистров. Аналогично, при возврате из прерывания некоторые регистры возвращают значения или статусную информацию. Они называются выходными регистрами и мы описываем их словами "при выходе AX содержит ...". Зачастую одно прерывание содержит много функций. В частности, операционная система впихнула практи- чески все свои возможности в прерывание 21H. Поэтому при вызове прерывания необходимо указывать номер функции. Все прерывания (как BIOS так и DOS) передают номер функции в AH (иногда в AL содержится номер подфункции). Все сказанное в основном служит только чтобы дать первое представление о предмете. Но если Вы будете внимательно просмат- ривать простейшие примеры, содержащиеся в этой книге, то Вы пой- мете стоящую за ними логику. Язык ассемблера имеет репутацию трудного языка. Но то, что Вы только что прочитали - настоящая чепуха. Имеется достаточно сложностей и в языках высокого уровня. И если ошибки в ассемблерной программе бывает очень сложно обна- ружить, то в основном это связано с тем, что сам текст программы намного длиннее, чем эквивалентный текст на языке высокого уров- ня (однако ассемблерный код намного плотнее). В настоящее время многие профессионалы пишут программы на языке C, затем анализи- руют эффективность и переписывают критические кусочки программы, которые расходуют много времени, на языке ассемблера. Невозмож- ность написания таких ассемблерных процедур может иногда свести усилия программиста к нулю. Поэтому найдите хороший букварь по ассемблеру и приступайте! Возможно самой большой наградой для Вас станет момент, когда Вы наконец действительно станете понимать как же работает компьютер. Приложение Г. Включение ассемблерных процедур в программы на Бейсике. Процедуры на языке ассемблера состоят из строк байтов машинно- го кода. При выполнении этой процедуры Бейсик передает управление из последовательности инструкций, составляющих программу на Бей- сике, в то место, где хранятся инструкции, которые могут быть декодированы в последовательность инструкций языка ассемблера. При завершении ассемблерной процедуры управление возвращается в то место бейсиковской программы, откуда была вызвана процедура. В этой книге ассемблерные процедуры, используемые в программах на Бейсике, приведены в двух видах. В обоих видах процедуры вклю- чены в программу, а не хранятся в виде отдельного дискового фай- ла. При первом способе требуется, чтобы коды процедуры находились в отдельном месте в памяти, а при втором, менее принятом, этого не требуется. В первом способе процедура помещается в операторы DATA и прог- рамма пересылается в неиспользуемую часть памяти, а затем вызы- вается оператором CALL. Надо позаботиться о том, чтобы код проце- дуры не накладывался на какие-либо данные и наоборот. Обычное решение этой проблемы состоит в том, что процедура помещается в те адреса памяти, к которым Бейсик не может получить доступ. Поскольку интерпретатор Бейсика не может иметь доступ за пределы 64K, то для системы, скажем, с памятью 256K, нужно поместить процедуру в старшие 64K. Для систем с памятью 128K Вы должны вычислить сколько памяти требуется операционной системе, Бейсику и драйверам устройств. Допустимо, чтобы они занимали 25K плюс 64K, используемых Бейсиком. В системах с 64K используйте при старте команду CLEAR, которая ограничивает объем памяти доступный для Бейсика. CLEAR,n ограничивает Бейсик n байтами. Затем помес- тите процедуру в самые верхние адреса памяти. Для указания начала области, куда будет помещена процедура, используйте оператор DEF SEG, а затем с помощью оператора READ считываются байты процедуры и помещаются в память до тех пор, пока вся процедура не будет помещена на место. Например: 100 DATA &Hxx, &Hxx, &Hxx, &Hxx, &Hxx '10-байтная процедура 110 DATA &Hxx, &Hxx, &Hxx, &Hxx, &Hxx . . 300 '''помещаем процедуру в память 310 DEF SEG = &H3000 'указываем на область памяти 320 FOR N = 0 TO 9 'для каждого из 10 байтов 330 READ Q 'читаем байт данных 340 POKE N,Q 'помещаем его в память 350 NEXT После того как процедура загружена в память и Вы хотите ее использовать, необходимо чтобы последний оператор DEF SEG указы- вал на начало процедуры. Затем присвойте целой переменной значе- ние 0 и напишите оператор CALL с именем этой переменной. Если процедуре передаются параметры, то они должны быть указаны в скобках в конце оператора CALL. Например: 500 DEF SEG = &H3000 'указываем на начало процедуры 510 DOGS = 12 'у нее 3 параметра 520 CATS = 44 ' 530 POSSUMS = 1 ' 540 CASUALTIES = 0 'начинаем выполнение с 1-го байта 550 CALL CASUALTIES(DOGS,CATS,POSSUMS) 'выполняем процедуру Имеется намного более простой и экономичный способ создания ассемблерных процедур, который избегает проблемы распределения памяти. Надо просто создать процедуру в виде строковой переменной внутри программы. Каждый байт может быть закодирован с помощью CHR$. Затем используйте функцию VARPTR для определения положения этой строки в памяти. Смещение по которому находится эта перемен- ная хранится в двух байтах, которые идут за тем, на который ука- жет VARPTR (в первом байте содержится длина строки). Затем этот адрес используется для вызова процедуры. Отметим способ, которым используется оператор DEF SEG, для указания на сегмент данных Бейсика, с тем чтобы полученное смещение указывало на адрес стро- ки для оператора CALL. Например: 100 DEF SEG 'устанавливаем сегмент на данные Бейсика 110 X$ = "CHR$(B4)+..." 'код процедуры 120 Y = VARPTR(X$) 'получаем дескриптор строки 130 Z = PEEK(Y+1)+PEEK(Y+2)*256 'вычисляем ее адрес 140 CALL Z Многие значения, выражаемые через CHR$() могут быть представлены и в виде символов ASCII. Вы можете писать ROUT = CHR$(12) + "AB" вместо ROUT = CHR$(12) + CHR$(65) + CHR$(66). На самом деле боль- шинство символов ASCII могут вводиться путем нажатия клавиши Alt, наборе номера кода на дополнительной клавиатуре, а затем отпуска- ния клавиши Alt. Однако коды от 0 до 31 не могут быть введены таким образом для наших целей. Приложение Д. Использование драйвера устройства ANSI.SYS. ANSI.SYS это небольшая программа, входящая в состав операцион- ной системы, которая может быть загружена в память, с тем чтобы увеличить возможности MS DOS. Она не сделана частью COMMAND.COM с целью экономия памяти, когда она не используется. Средства, пре- доставляемые ANSI.SYS, могут быть использованы для удобства прог- раммирования, но они могут также служить средством достижения некоторой программной совместимости с не IBM-овскими машинами, использующими MS DOS. Этот драйвер не предоставляет никаких доба- вочных возможностей, которых нельзя было бы добиться другим обра- зом, но он делает некоторые возможности управления клавиатурой и терминалом намного более простыми (и обычно более медленно). Все свойства драйвера ANSI.SYS описаны в этой книге под соответствую- щим заголовком. ANSI.SYS может быть загружен только во время загрузки опера- ционной системы. Начиная с версии 2.0 система автоматически ищет файл CONFIG.SYS, так же как и файл AUTOEXEC.BAT. Файл CONFIG.SYS содержит различные параметры, такие как число создаваемых буферов для файлов. Но он содержит также и имена тех драйверов устройств, которые должны быть загружены и включены в COMMAND.COM. ANSI.SYS как раз и является таким драйвером. Надо просто включить в этот файл строку DEVICE = ANSI.SYS. Она может быть единственной стро- кой в файле. Для создания этого файла можно воспользоваться ко- мандой COPY. Надо просто ввести с терминала такие строки: COPY CON: CONFIG.SYS <CR> DEVICE = ANSI.SYS <CR> <F6> <CR> Нажатие клавиши F6 записывает символ Ctrl-Z (ASCII 26), отмечаю- щий конец файла. Приложение Е. Набор инструкций микропроцессора 8088. Число тактов, которое надо добавить для вычисления эффективно- го адреса следующее: компоненты адреса операнды такты (а) база или индекс [BX],[BP],[DI],[SI] 5 (б) смещение метка или смещение 6 (в) база + индекс [BX][SI], [BX][DI] 7 [BP][SI], [BP][DI] 8 (г) смещение + база или индекс [BX],[BP],[DI],[SI] + смещ. 9 (д) смещение + база + индекс [BX][SI],[BX][DI] + смещ. 11 [BP][SI],[BP][DI] + смещ. 12 Необходимо добавить также 2 такта при пересечении сегмента. Вот времена инструкций: инструкция такты байты AAA 4 1 AAD 60 2 AAM 83 1 AAS 4 1 ADC регистр, регистр 3 2 ADC регистр, память 9(13) + EA 2-4 ADC память, регистр 16(24) + EA 2-4 ADC регистр, значение 4 3-4 ADC память, значение 17(25) + EA 3-6 ADC аккумулятор, значение 4 2-3 ADD регистр, регистр 3 2 ADD регистр, память 9(13) + EA 2-4 ADD память, регистр 16(24) + EA 2-4 ADD регистр, значение 4 3-4 ADD память, значение 17(25) + EA 3-6 ADD аккумулятор, значение 4 2-3 AND регистр, регистр 3 2 AND регистр, память 9(13) + EA 2-4 AND память, регистр 16(24) + EA 2-4 AND регистр, значение 4 3-4 AND память, значение 17(25) + EA 3-6 AND аккумулятор, значение 4