

                           Оглавление.

   Введение
      Соглашения о числах, принятые в этой книге.....................  1 
  Глава 1. Системные ресурсы.                   
      Раздел1.Ревизия системных ресурсов.............................  5
        Доступ к микросхеме интерфейса с периферией 8255.............  5
        Определение типа IBM PC......................................  7
        Определение версии MS DOS....................................  8
        Определение числа и типов адаптеров дисплея..................  8  
        Определение числа и типа дисковых накопителей................ 10 
        Определение числа и типа периферийных устройств.............. 11
        Ревизия количества памяти.................................... 13
      Раздел 2. Управление прерываниями.............................. 15
        Программирование контроллера прерываний 8259................. 16
        Запрет/разрешение отдельных аппаратных прерываний............ 17
        Написание собственного прерывания............................ 18
        Дополнение к существующему прерыванию........................ 21
      Раздел 3. Управление программами............................... 21
        Манипуляции с памятью........................................ 22
        Запуск одной программы из другой............................. 24
        Использование команд интерфейса с пользователем из
        программы.................................................... 27
        Сохранение программы в памяти после завершения............... 27
        Загрузка и запуск программных оверлеев....................... 29
        Преобразование программ из типа .EXE в тип .COM.............. 32
   Глава 2. Таймеры и звук.
      Раздел 1. Установка и чтение таймера........................... 34
        Программирование микросхемы таймера 8253/8254................ 34
        Установка/чтение времени..................................... 37
        Установка/чтение даты........................................ 38
        Установка/чтение часов реального времени..................... 39
        Задержка программных операций................................ 41
        Операции запрограммированные во времени...................... 42
        Управление работой в реальном времени........................ 43
        Генерация случайных чисел с помощью микросхемы таймера....... 46
      Раздел 2. Создание звука....................................... 47
        Программирование генератора звука 76496 (только PCjr)........ 47
        Генерация тона............................................... 49
        Генерация звука одновременно с другими действиями............ 51
        Гудок динамика............................................... 52
        Генерация набора тонов....................................... 53
        Генерация строки тонов, одновременно с другими операциями.... 56
        Создание плавного перехода тонов............................. 58
        Создание звуковых эффектов................................... 59
        Одновременная генерация разных звуков........................ 61
   Глава 3. Клавиатура.
      Раздел 1. Управление клавиатурой............................... 62
        Очистка буфера клавиатуры.................................... 63
        Проверка символов в буфере................................... 64
        Ожидать ввод символа и не выводить его на экран.............. 65
        Ожидание нажатия клавиши и эхо на экран...................... 67
        Прием символа без ожидания................................... 68
        Получение строки символов.................................... 70
        Проверка/установка статуса клавиш-переключателей............. 72
        Написание процедуры  ввода  с клавиатуры общего назначения... 73
        Перепрограммирование прерывания клавиатуры................... 75
      Раздел 2. Доступ к отдельным клавишам.......................... 79
        Использование клавиш  <BackSpace>,  <Enter>,  <Escape> и
        <Tab>........................................................ 79
        Использование клавиш-переключателей: <Shift>, <Ctrl>  и
        <Alt>........................................................ 79

     
    
                   
        Использование клавиш-переключателей: NumLock,  CapsLock,
        Ins и ScrollLock............................................. 80
        Использование цифровой дополнительной клавиатуры и  кла-
        виш перемещения курсора...................................... 81
        Использование функциональных клавиш.......................... 82
        Перепрограммирование отдельных клавиш........................ 83
        Создание макроопределений для отдельных клавиш............... 85
        Создание процедуры обработки Ctrl-Break...................... 86
        Перепрограммирование клавиши PrtSc........................... 87
      Раздел 3: Сводка кодов клавиш и применений..................... 88
        Предопределенное использование клавиш........................ 89
        Сводная таблица скан-кодов................................... 89
        Сводная таблица кодов ASCII.................................. 90
        Сводка кодов псевдографики для построения рамок.............. 93
        Сводная таблица расширенных кодов............................ 93
   Глава 4. Вывод на терминал.
      Раздел 1. Управление выводом на терминал....................... 94
        Программирование контроллера дисплея 6845.................... 95
        Установка/проверка режима дисплея............................ 97
        Установка атрибутов/цветов символов..........................101 
        Установка цвета границы экрана...............................107
        Очистка части/всего экрана...................................109
        Переключение между видеоадапторами...........................110
      Раздел 2. Управление курсором..................................112
        Установка курсора в абсолютную позицию.......................112
        Относительное позиционирование курсора.......................115
        Включение и выключение курсора...............................116
        Изменение формы курсора......................................117
        Чтение/сохранение/восстановление позиции курсора.............118
        Создание альтернативных типов курсора........................119
      Раздел 3. Вывод символов на экран..............................120
        Вывод на экран одного символа................................121
        Вывод строки символов на экран...............................126
        Чтение символа и его атрибутов в данной позиции..............128
        Создание специальных символов................................129
        Сводка данных для описания символов..........................131
      Раздел 4. Вывод точечной графики...............................134
        Установка цветов для точечной графики........................135
        Рисование точки на экране (монохромный, цветной и PCjr)......139
        Рисование точки на экране (EGA)..............................142
        Определение цвета точки экрана...............................148
        Рисование линий на экране....................................151
        Заполнение областей экрана...................................155
        Графический вывод с использованием символов псевдографики....158
      Раздел 5. Сдвиг экрана и страницы..............................160
        Вертикальный сдвиг текстового экрана.........................160
        Сдвиг текстового экрана горизонтально........................162
        Переключение между текстовыми страницами.....................163
        Сдвиг между страницами текста................................166
   Глава 5. Дисковые накопители.
      Раздел 1. Управление распределением диска......................167
        Чтение таблицы размещения файлов.............................168
        Определение доступного дискового пространства................171
        Получение/установка размера файла............................172
        Восстановление  после  ошибок,  связанных  с  нехваткой
        пространства на диске........................................174
      Раздел 2. Работа с каталогами диска............................175
        Чтение/изменение корневого каталога..........................176
        Создание/удаление подкаталога................................178
        Чтение/изменение подкаталога.................................179
        Получение/установка текущего каталога........................181
        Получение/установка времени  и даты последнего доступа к
        файлу........................................................182
        Спрятанные и защищенные от записи файлы......................183
        Чтение/изменение метки тома..................................184

          
      Раздел 3. Подготовка к работе с файлами........................186
        Установка/проверка накопителя по умолчанию...................187
        Создание/удаление файла......................................188
        Открытие/закрытие файла......................................191
        Переименование файла;  изменение позиции файла в каталоге....195
        Подготовка к файловым операциям..............................197
        Анализ информации командной строки...........................201
      Раздел 4. Чтение и запись файла................................202
        Программирование контроллера НГМД 765 и микросхемы пря-
        мого доступа к памяти 8237...................................203
        Чтение/запись определенных секторов..........................211
        Запись в последовательные файлы..............................213
        Чтение из последовательных файлов............................218
        Запись в файлы прямого доступа...............................222
        Чтение из файлов прямого доступа.............................226
        Проверка данных после операций чтения/записи.................229
        Определение дисковых ошибок и восстановление после них.......229
   Глава 6. Принтер.
      Раздел 1. Управление работой принтера..........................232
        Инициализация  порта  принтера/повторная  инициализация
        принтера.....................................................233
        Проверка того, что принтер связан с машиной..................234
        Интерпретация  ошибок принтера и  восстановление  после них..236
        Переключение между двумя или несколькими принтерами..........238
      Раздел 2. Установка спецификаций печати........................239
        Установка текстового и графического режимов..................240
        Управление расстоянием между строками........................241
        Управление движением бумаги..................................242
        Управление положением печатающей головки.....................243
        Установка позиций табуляции..................................244
        Изменение шрифта печати......................................244
        Сравнение возможностей принтеров IBM.........................246
      Раздел 3. Посылка данных на принтер............................248
        Вывод текстовых или графических данных на принтер............248
        Выравнивание правого поля....................................252
        Пропорциональная печать......................................254
        Печать специальных символов..................................255
        Копирование экрана на принтер (дамп экрана)..................258
   Глава 7. Ввод/вывод.
      Раздел 1. Доступ к последовательному порту.....................260
        Программирование микросхемы UART 8250........................261
        Инициализация последовательного порта........................262
        Установка текущего коммуникационного порта...................265
        Определение статуса коммуникационного порта..................266
        Инициализация и управление модемом...........................267
        Передача данных..............................................270
        Получение данных.............................................272
        Посылка/получение  данных с  помощью  коммуникационного
        прерывания...................................................275
        Сводка управляющих кодов, используемых при коммуникации......277
      Раздел 2. Создание драйвера устройства.........................277
        Создание заголовка драйвера..................................279
        Создание стратегии устройства................................280
        Создание обработчика прерывания устройства...................281
        Доступ к драйверу устройства.................................284
        Обнаружение и анализ ошибок устройства.......................285
      Раздел 3. Использование специальных устройств ввода/вывода.....289
        Чтение/запись с кассетного магнитофона.......................289
        Чтение позиции светового пера................................290
        Получение аналогового ввода через игровой порт...............292
        Получение цифрового ввода из игрового порта..................294





Приложения.
      Приложение А.  Двоичные и шестнадцатиричные числа и  адре-
      сация памяти...................................................296
      Приложение Б. Битовые операции в Бейсике.......................299
      Приложение В. Основные сведения об языке ассемблера............302
      Приложение Г. Включение ассемблерных процедур в программы
      на Бейсике.....................................................306
      Приложение Д. Использование драйвера устройства ANSI.SYS.......308
      Приложение Е. Набор инструкций микропроцессора 8088............308
      Приложение Ж. Набор инструкций микропроцессора 80286...........328
      Приложение З. Толковый словарь IBM PC..........................330























































                                      -1-
                                  Введение.
          Программисты  в наше время  являются одной из наиболее передо-
       вых групп.  К сожалению, их наиболее неудачные новшества включают
       и  несколько новых способов потери времени.   Бесконечны  ужасные
       истории о программах, для  отладки  которых  требуется в двадцать
       раз больше времени, чем для написания.  И Вы можете снова и снова
       слышать о программах,  к  которым  приходится  обращаться вновь и
       вновь,  так как они не были достаточно хорошо продуманы с  самого
       начала. Намного меньше сказано  о  том, что может оказаться самым
       надежным  и  емким способом пустой траты  времени  для  изучающих
       программирование: поиск информации о машине.  Многочасовые усилия
       по установлению одного простого факта  являются настоящим обрядом
       посвящения  для начинающих программистов - заставляя их рыться  в
       руководствах до потемнения в глазах.
          Типичное следующее утро после этого - это глаза, слезящиеся от
       терминала, метровая стопка смятых выдач и пара дюжин  руководств,
       рассыпанных по всему  полу.  Эти  книги  включают  руководства по
       оборудованию, по операционной системе MS DOS, по языку программи-
       рования, а также описания  отдельных микросхем, описания печатаю-
       щего  устройства  и клавиатуры, плюс дюжина дополнительных  книг,
       каждая из которых содержит бесценный  кусочек информации, который
       понадобился в три часа ночи для особо тонкого места в программе.
          Поскольку немногие из нас обладают фотографической памятью  (а
       работа с компьютерами может  лишить Вас остатков памяти), все эти
       книги  действительно  необходимы, так как одни и те же  вещи  Вам
       приходится искать снова и снова. На первых порах Вы можете затра-
       чивать  часы и не обнаружить требуемой информации.  Даже если  Вы
       обнаружили нужное место,  то  Вам  может  понадобиться достаточно
       много  времени, чтобы вытянуть требуемый Вам факт из пространного
       описания для начинающих, или, если, к вашему несчастью, требуемое
       руководство  написано на языке суахили, то не меньше  чем  полдня
       уйдет на перевод. Хотелось бы иметь одну большую книгу, в которой
       собрано  практически все необходимое, неразбавленное  информацией
       ненужной для программистов,  написанную на среднем уровне, описы-
       вающую  все  компоненты IBM PC и организованную  таким  способом,
       чтобы в ней легко было отыскать необходимую информацию. Но слыша-
       ли ли Вы когда-нибудь о такой книге?
          Поэтому я собрал вместе все эти руководства и описания для тех
       кто хочет писать нетривиальные  программы,  но не может позволить
       себе тратить массу времени (или 600 - 800 долларов, чтобы  купить
       все эти книги). Материал организован двумя способами.  Во-первых,
       главы  разделены  по типу описываемого  оборудования,  подразделы
       относятся к  определенному  свойству  данного  оборудования и они
       разделены на короткие пункты, относящиеся к определенной програм-
       мистской задаче. Например,  один  из  разделов главы, посвященной
       выводу  на дисплей, относится к курсору и содержит пункты, описы-
       вающие как позиционировать  курсор,  менять его форму, включать и
       выключать его и т.д.
          Во-вторых, каждый пункт разделен на четыре части (иногда мень-
       ше). Сначала идут несколько  абзацев,  описывающих основные поня-
       тия.   Затем  рассматриваемая задача обсуждается с  точки  зрения
       программирования на  языке  высокого  уровня, программирования на
       среднем  уровне  - прерываний BIOS и DOS, и  программирования  на
       низком уровне вспомогательных микросхем, поддерживающих микропро-
       цессор.   Кроме того, каждый из разделов главы начинается с  пары
       страниц, описывающих сведения  необходимые  для понимания данного
       раздела.   Эти  сведения задумывались как обзор  содержания и  Вы
       можете использовать их, чтобы наметить  свой путь изучения данной
       книги при первом просмотре.
          Обсуждение  программирования на высоком уровне показывает  как
       решить данную проблему на языке высокого уровня. Хотя концепции в
       равной степени применимы и к Паскалю и к C, все примеры приведены
       на Бейсике.  Бейсик  выбран  отчасти  из-за того, что он является
       


                                      -2-
       латынью  для микроЭВМ, отчасти потому, что каждый владелец IBM PC
       имеет его в своем распоряжении и отчасти потому, что Бейсик фирмы
       Microsoft  предоставляет наиболее полные  средства  использования
       возможностей оборудования IBM  PC  по сравнению с другими языками
       программирования.   Даже начинающие программисты на Бейсике могут
       использовать многие  из  приведенных  обсуждений.  Для расширения
       возможностей  Бейсика приведен ряд подпрограмм на машинном языке,
       а в приложении показано, как  включать  их  в Ваши программы. Ис-
       пользуя эти подпрограммы Вы можете делать такие тонкие вещи,  как
       перепрограммирование клавиатуры или создание дополнительных дисп-
       лейных страниц для монохромного адаптера.
          Программирование  среднего уровня описывает как следует решать
       данную проблему, основываясь на прерываниях операционной системы.
       Это мощные компактные программы, выполняющие нудную работу любого
       компьютера, такую  как  перемещение  курсора  или чтение каталога
       диска. Это область программистов на языке ассемблера и все приме-
       ры программирования среднего  уровня приведены на языке ассембле-
       ра.  Но теперь все больше и больше трансляторов с языков высокого
       уровня предоставляют доступ к  прерываниям,  позволяя  грамотному
       программисту проделывать операции, которые не позволяет сам язык,
       например, чтение абсолютного  сектора  диска. Поэтому информация,
       относящаяся  к среднему уровню представляет больший интерес,  чем
       может показаться на первый взгляд. Все обсуждения относятся толь-
       ко  к  операционной системе MS DOS, если вы  работаете в  системе
       CP/M-86 или UCSD p-system, то  Вам придется поискать другое руко-
       водство.
          Наконец,  примеры  программирования низкого уровня  показывают
       как данная проблема может быть  решена  на  уровне микросхем. Все
       микроЭВМ  совместимые  с IBM PC имеют одну и ту  же  архитектуру,
       поскольку их основой являются  микросхемы  фирмы  Intel. Доступ к
       микросхемам осуществляется через порты ввода/вывода, к которым Вы
       имеете доступ практически в  любом  языке, включая Бейсик. Обсуж-
       даются  все  важные для программиста микросхемы, включая  таймер,
       интерфейс с периферией,  контроллер  прерываний, контроллер дисп-
       лея,  контроллер  НГМД (накопителя на гибких магнитных дисках)  и
       микросхемы управления коммуникационным каналом. Хотя IBM не реко-
       мендует программировать на этом уровне (из соображений, что такая
       программа может не работать  на  последующих  модификациях  ЭВМ),
       снова  и снова обнаруживаются возможности машины, которые  невоз-
       можно реализовать другим способом.
          Не все задачи показаны на всех трех уровнях. Решение некоторых
       просто невозможно на Бейсике. Для решения других не предусмотрено
       средств операционной системы.  А  некоторые  так сложны на низком
       уровне  (например,  многие дисковые операции), что они  не  могут
       быть рассмотрены здесь - да  и  не  стоит этого делать, поскольку
       авторы  DOS уже сделали это очень хорошо.   Однако в  большинстве
       случаев показаны все три уровня. Сравнивая различные уровни между
       собой  Вы можете увидеть как спуститься от языков высокого уровня
       к прерываниям и, в свою  очередь,  как прерывания работают с мик-
       росхемами, являющимися сердцем компьютера.
          Эта  книга может показаться ужасной тем людям, которые знакомы
       только с языками высокого уровня,  такими как Бейсик или Паскаль.
       Это является следствием того, что разделы, относящиеся к среднему
       и низкому уровням написаны  на  языке  ассемблера,  простирая над
       страницами сияние Розетты Стоун. Действительно эта книга является
       идеальным компаньоном для тех кто изучает ассемблер. Но не думай-
       те, что Вам нужна только треть книги если Вы не знаете ассемблера
       и не собираетесь изучать его.  Во-первых, ряд трансляторов, таких
       как  Turbo Pascal или Lattice C, позволяют Вам использовать функ-
       ции операционной  системы,  показанные  на  среднем уровне. Кроме
       того,  многие из процедур низкого уровня могут быть на самом деле
       реализованы на языках высокого уровня. Чтобы позволить Вам разоб-
       раться, что же содержится в приведенных примерах на ассемблере, в
       


                                      -3-
       приложении Г дано краткое  введение  в язык ассемблера. Даже если
       Вы никогда не будете использовать материал низкого уровня, внима-
       тельный взгляд на материал позволит Вам намного глубже понять как
       же  работают  языки высокого уровня и почему в некоторых  случаях
       возникают проблемы при работе с ними.
          Практически каждый подраздел содержит  образец кода. Часто это
       всего  лишь несколько тривиальных строк.  Иногда приводятся явные
       наметки для реализации сложных процедур.  Очень редко встречаются
       самостоятельные  программы.   Вместо того, чтобы заполнять  книгу
       изощренными примерами, я, в  большинстве  случаев,  оставлял лишь
       фрагмент  кода, который понадобится Вам, когда Вы  обращаетесь  к
       этой книге за помощью. Ни в коей мере каждый пример не претендует
       на  самое  красивое решение проблемы.  Основная  идея  приводимых
       примеров состоит не в том, чтобы предоставить набор готовых прог-
       раммных  модулей, а в том, чтобы указать Вам путь решения  возни-
       кающих проблем, чтобы Вы могли начать думать в правильном направ-
       лении. Но если Вы хотите, то Вы можете прямо включать приведенные
       образцы в программы в качестве  функциональной  отправной точки и
       затем дорабатывать их до кондиций, удовлетворяющих Вашему эстети-
       ческому вкусу.  Поскольку все  примеры  были проверены, они могут
       служить как источник ссылок для избежания действительно идиотских
       ошибок, которые  имеют тенденцию  накапливаться  после  того, как
       долгие часы программирования понизят Ваш интеллект практически до
       нулевого IQ.
          Язык этой книги, мягко говоря, очень компактный. Но я старался
       избегать  жаргона,  насколько это возможно.  Кроме того, в  конце
       книги приведен  терминологический  словарь компьютерных терминов.
       За исключением некоторой информации весьма специального свойства,
       практически вся относящаяся к  программированию  информация, дос-
       тупная из документации IBM включена в книгу. Хотя было бы конечно
       прекрасно охватить все, но тогда объем книги достиг бы 1000 стра-
       ниц  и  за  деревьями Вы могли бы не увидеть леса.   Поэтому  для
       действительно  необычных программистских нужд - скажем, для слож-
       ных программ управления контроллером  НГМД или перепрограммирова-
       ния  клавиатуры AT - Вам придется обращаться к техническим  руко-
       водствам IBM или специальным описаниям  производителей микросхем.
       Но  99% программ не потребуют другой информации  об  оборудовании
       IBM PC, кроме содержащейся  в  данной  книге.   Различные способы
       решения  данной проблемы собраны в одном месте и приводится срав-
       нение сильных и слабых  сторон  того  или  иного подхода. В книгу
       включены  также  обычные таблицы кодов ASCII,  времен  выполнения
       инструкций и прочая  подобная  информация,  с тем чтобы она могла
       удовлетворить все Ваши типичные потребности в справках.
          Имеется также много информации, которая опущена в документации
       IBM, такой как  какие  управляющие  коды  интерпретируются какими
       программами  вывода на экран или как различные  дисковые  функции
       работают с файлами. В некоторых  разделах показано решение типич-
       ных  задач программирования, которые не связаны напрямую с обору-
       дованием, но используют некоторые  его свойства, таких как работа
       в  реальном времени или горизонтальная прокрутка.  Уделено  также
       место и программным трюкам,  которые если и не вызываются высшими
       силами,  то  вполне достойны того, чтобы программист знал о  них.
       При существующем положении вещей каждый программист должен откры-
       вать эти методы для себя (причем обычно не один раз). Как печаль-
       но,  что  высшие жрецы Века Информации тратят так  много  времени
       переизобретая колесо, как в давние  времена, когда папирус еще не
       сделал обмен информацией достаточно легким.
          Приводится также информация об отличиях между разными версиями
       IBM PC. Все рассмотрения базируются на стандартном IBM PC.  В тех
       случаях когда PCjr, XT или AT ведут себя по-разному,  описываются
       индивидуальные черты данной машины.  Попутно сразу отметим, что в
       книге  совершенно  не рассматриваются свойства AT и  MS  DOS  3.0
       направленные в стороны многопользовательских систем.  Эти вопросы
       


                                      -4-
       заслуживают  отдельной книги.  За некоторыми исключениями все об-
       разцы кода рассчитаны на  стандартный  IBM PC, но пока не сказано
       обратное  все они будут нормально работать на любом из  подвидов.
       Однако есть существенное ограничение. Все написанное в этой книге
       предполагает  использование MS DOS 2.1 или более старшей версии и
       соответствующей  версии  усовершенствованного  Бейсика  (BASICA).
       Пользователи, до  сих  пор не перешедшие на MS DOS 2.1, не  могут
       использовать многие преимущества машины.
          Если в этой книге что-то и содержится,  то это факты - мириады
       их  - и я искренне надеюсь, что все они верны.  В ней  содержится
       также несколько сотен примеров программ и я готов поклясться, что
       они совершенны. Но если Вы думаете, что такое огромное количество
       информации можно  оставить  неповрежденным  в длительном процессе
       подготовки  книги к изданию, то попробуйте.  Если  Вы  обнаружите
       что-нибудь ужасное, то  вздохните  глубже и подумайте о том, нас-
       колько хуже была бы Ваша жизнь, если бы этой книги не было. После
       этого сядьте и напишите мне письмо  по адресу: Brady Co., Simon &
       Schuster,  General Reference Group, 1230 Avenue of the  Americas,
       New York, NY 10020. Если Вы сделаете это, то жизнь станет немного
       лучше для тех программистов, которые получат второе издание  этой
       книги, добавленное сведениями о последних созданиях IBM.

                   Соглашения о числах, принятые в этой книге.

          Программисты  на ассемблере не найдут ничего необычного в спо-
       собе представления чисел и адресов, используемом в этой книге. Но
       многие  программисты  на языках высокого уровня  мало  знакомы  с
       системой адресации и недесятичными числами и они могут быть слег-
       ка сконфужены на первых порах.  Если Вы относитесь к этой катего-
       рии - не отчаивайтесь!  Данная книга  может  служить сравнительно
       безболезненным  способом знакомства с этой кабаллистикой,  а Ваше
       образование как программиста будет существенно ограничено без зна-
       комства  с этими вещами.  Чтобы помочь Вам в этом вопросе в книгу
       включены два приложения.  В  приложении  А обсуждаются двоичные и
       шестнадцатиричные  числа, а также как последние используются  при
       адресации памяти.  Приложение Б более подробно разбирает двоичные
       числа  и их использование в битовых операциях.  Даже если  Вы  не
       нуждаетесь в помощи в этом отношении обратите внимание на следую-
       щие правила:
       1. Для удобства менее классных программистов, все числа считаются
       десятичными, до тех пор пока за ними не следует H (для шестнадца-
       тиричных) или B (для двоичных). Иногда B опускается после двоичн-
       ых чисел, когда очевидно,что их значения описывают цепочку битов.
       2. Другое исключение - числа из восьми цифр вида  0000:0000.  Это
       шестнадцатиричные числа, дающие сегмент и смещение адреса памяти.
       Их значение объяснено в приложении А.
       3. Биты нумеруются от 0 до 7 (или от 0 до 15), где бит 0 соответ-
       ствует младшему биту (т.е.,когда установлен бит 0=1,а бит 7=128).
       4. Выражение  вида  "ASCII 5" относится  к символу номер 5 набора
       ASCII. Это означает,что оно относится к одному байту со значением
       5,а не к коду ASCII для символа 5 и не к двухбайтному целому,пред-
       ставляющему значение 5.
       5. Числа заключенные в квадратные скобки, напр. [2.1.3],  являются
       перекрестными ссылками на другие разделы данной книги.
          Приведенный  пример  подразумевает "Глава 2, Раздел 1, Пункт 3".
       [2.1.0] относится к общему обсуждению,начинающему раздел 1 главы 2.
       Вы обнаружите сотни  таких ссылок, рассеяных по всему тексту.  Они
       отсылают Вас к тем местам книги, в которых Вы можете найти информа-
       цию  об  упомянутом предмете. Их основное назначение-помощь начина-
       ющим. Если  Вы  понимаете о чем идет речь, игнорируйте перекрестные
       ссылки.
       6. Когда в текст включен текст программы, то он всегда выделен жир-
       ным шрифтом.
       


                                      -5-
                        Глава 1. Системные ресурсы.

          Раздел 1. Ревизия системных ресурсов.

          Одной  из первых задач после загрузки задачи является проверка
       куда мы попали: на каком  типе  IBM  PC  запущена  задача?... под
       какой версией MS DOS?... сколько имеется памяти?...  все ли необ-
       ходимое оборудование  присутствует? Имеется три способа получения
       этой информации.  Наименее элегантный способ - спросить об этом у
       пользователя (но знает ли он ответы?). Намного лучше получить всю
       доступную  информацию  из установки переключателей  на  системной
       плате.  Но эта установка не всегда соответствует реальности. Поэ-
       тому  лучше всего использовать третью возможность - получить пря-
       мой доступ к требуемому оборудованию  или прочитать нужную инфор-
       мацию из области данных BIOS.  Поскольку установка переключателей
       может служить отправной точкой  для  получения требуемой информа-
       ции, то этот раздел начинается с обсуждения микросхемы,  содержа-
       щей эту информацию - микросхемы интерфейса с периферией 8255.
          Программа может  получить  доступ  к оборудованию только двумя
       способами.  Она может обратиться к любому из портов ввода/вывода,
       соответствующему   присоединенному   оборудованию  (обычно бывает
       занята  лишь малая доля из 65535 возможных адресов портов).   Или
       программа может обратиться к любому из более чем миллиону адресов
       оперативной  памяти.  Сводная таблица адресов портов приведена  в
       [7.3.0]. На рис. 1-1  показано  как  распределены в памяти опера-
       ционная система и программы.

          1.1.1 Доступ к микросхеме интерфейса с периферией 8255.

          Микросхема  интерфейса с периферией Intel 8255 - лучшее место,
       с которого надо начинать, чтобы получить  информацию об имеющемся
       оборудовании. Эта микросхема предназначена для многих целей.  Она
       сообщает об  установке  переключателей  на  системной плате.  Она
       принимает для компьютера ввод с клавиатуры.  Она управляет  рядом
       периферийных устройств, включая микросхему таймера 8253. Из машин
       семейства  IBM  PC  только AT не использует микросхему  8255;  он
       хранит информацию об оборудовании  вместе с часами реального вре-
       мени  в специальной микросхеме с независимым питанием.  Однако AT
       использует те же адреса портов, что  и 8255, для работы с клавиа-
       турой и управления микросхемой таймера.
          Микросхема 8255 имеет три однобайтных регистра, называемых  от
       порта A до порта C. Адреса этих портов от 60H до 62H сответствен-
       но.  Все три порта можно читать, но писать можно только в порт B.
       Для PC, установка бита 7 порта B в  1 изменяет информацию, содер-
       жащуюся в порте A.  Аналогично для PC установка бита 2 определяет
       содержимое  четырех  младших  битов  порта  C, а установка бита 3
       делает то же самое для XT. Содержимое этих регистров следующее:

          Порт A (60H)
             когда в порте B бит 7=0
                биты 0-7 PC,XT,PCjr,AT: 8-битные скан-коды с клавиатуры
             когда в порте B бит 7=1 для PC
                бит 0    PC: 0 = нет накопителей на дискетах
                    1    PC: не используется
                  2-3    PC: число банков памяти на системной плате
                  4-5    PC: тип дисплея (11 = монохромный,
                             10 = цветной 80*25, 01 = цветной 40*25)
                  6-7    PC: число накопителей на дискетах

          Порт B (61H)
                бит 0    PC,XT,PCjr: управляет каналом 2 таймера 8253
                    1    PC,XT,PCjr: вывод на динамик
                    2    PC: выбор содержимого порта C
       


                                      -6-
                         PCjr: 1 = символьный режим, 0 = графический
                    3    PC,PCjr: 1 = кассетный мотор выключен
                         XT: выбор содержимого порта C
                    4    PC,XT: 0 = разрешение ОЗУ
                         PCjr: 1 = запрет динамика и мотора кассеты
                    5    PC,XT: 0 = разрешение ошибок щелей расширения
                    6    PC,XT: 1 = разрешение часов клавиатуры
                  5-6    PCjr: выбор динамика (00 = 8253, 01 = кассета,
                         10 = ввод/вывод, 11 = микросхема 76496)
                    7    PC: выбор содержимого порта A
                         PC,XT: подтверждение клавиатуры

          Порт C (62H)
             когда в порте B бит 2=1 для PC или бит 3=1 для XT
             биты 0-3    PC: нижняя половина переключателя 2 конфи-
                         гурации (ОЗУ на плате расширения)
                    0    PCjr: 1 = введенный символ потерян
                    1    XT: 1 = есть мат. сопроцессор
                         PCjr: есть карта модема
                    2    PCjr: есть карта НГМД
                  2-3    XT: число банков памяти на системной плате
                    3    PCjr: 0 = 128K памяти
                    4    PC,PCjr: ввод с кассеты
                         XT: не используется
                    5    PC,XT,PCjr: выход канала 2 8253
                    6    PC,XT: 1 = проверка ошибок щелей расширения
                         PCjr: 1 = данные с клавиатуры
                    7    PC,XT: 1 = контроль ошибок четности
                         PCjr: 0 = кабель клавиатуры подсоединен
             когда в порте B бит 2=0 для PC или бит 3=0 для XT
             биты 0-3    PC: верхняя половина переключателя 2 конфи-
                         гурации (не используется)
                  0-1    XT: тип дисплея (11 = монохромный,
                         10 = цветной 80*25, 01 = цветной 40*25)
                  2-3    XT: число накопителей НГМД (00 = 1 и т.д.)
                  4-7    PC,XT: то же, что и с установленными битами

          Отметим,  что 0 в одном из битов регистра соответствует  уста-
       новке переключателя "off".
          AT хранит  информацию  о  конфигурации  в  микросхеме MC146818
       фирмы  Motorola, вместе с часами реального времени.  Он вовсе  не
       имеет микросхемы 8255, хотя для управления  микросхемой таймера и
       приема  данных с клавиатуры используются те же самые адреса  пор-
       тов.  Микросхема имеет 64 регистра, пронумерованных от 00 до 3FH.
       Для  чтения  регистра  нужно сначала послать его номер в  порт  с
       адресом 70H, а затем  прочитать  его  через  порт  71H. Различные
       параметры  конфигурации  обсуждаются  на  последующих  страницах.
       Приведем здесь только краткую сводку:

          Номер регистра               Использование
              10H             тип накопителя НГМД
              12H             тип накопителя фиксированного диска
              14H             периферия
              15H             память на системной плате (младший байт)
              16H             память на системной плате (старший байт)
              17H             общая память (младший байт)
              18H             общая память (старший байт)
              30H             память сверх 1 мегабайта (младший байт)
              31H             память сверх 1 мегабайта (старший байт)

          Высокий уровень.

          В данной книге имеется  множество примеров доступа к этим пор-
       


                                      -7-
       там.  Ниже приводится программа на Бейсике, устанавливающая число
       дисковых накопителей, присоединенных  к IBM PC. Прежде чем прочи-
       тать два старших бита порта A, бит 7 порта B должен быть установ-
       лен в 1.  Существенно, что Вы  должны вернуть значение этого бита
       назад в 0 перед дальнейшей работой, иначе клавиатура будет запер-
       та и для  восстановления  работоспособности  машины  Вам придется
       выключить  ее.  Бейсик не позволяет двоичное представление чисел,
       что затрудняет работу  с  цепочками  битов.  Простая подпрограмма
       может  заменить любое целое вплоть до 255 (максимальное значение,
       которое может принимать номер порта) на восьмисимвольную двоичную
       строку.   После  этого строковая функция MID$ позволяет  вырезать
       нужные биты для анализа. Основы битовых операций в Бейсике описа-
       ны в приложении Б.

       100 A = INP(&H61)            'получаем значение из порта B
       110 A = A OR 128             'устанавливаем бит 7
       120 OUT &H61,A               'посылаем байт назад в порт B
       130 B = INP(&H60)            'получаем значение из порта A
       140 A = A AND 128            'сбрасываем бит 7
       150 OUT &H61,A               'восстанавливаем значение порта B
       160 GOSUB 1000               'преобразуем в двоичную строку
       170 NUMDISK$ = RIGHT$(B$,1)  'получаем нулевой бит
       180 IF D$ = 1 THEN NUMDISK = 0: GOTO 230 'нет дисков
       190 C$ = LEFT$(B$,2)         'берем два старших бита строки
       200 TALLEY = 0               'переменная для числа дисков
       210 IF RIGHT$(C$,1) = "1" THEN TALLEY = 2 'берем старший бит
       220 IF LEFT$(C$,1) = "1" THEN TALLEY = TALLEY + 1 'и младший
       230 TALLEY = TALLEY + 1      'счет начинается с 1, а не с 0
                                    'теперь имеем число накопителей
       1000 '''Подпрограмма преобразования байта в двоичную строку
       1010 B$ = ""                 'заводим строку
       1020 FOR N = 7 TO 0 STEP -1  'проверка очередной степени 2
       1030 Z = B - 2^N             '
       1040 IF Z >= 0 THEN B = Z: B$ = B$+"1" ELSE B$ = B$+"0"
       1050 NEXT                    'повторяем для каждого бита
       1060 RETURN                  'все закончено

          Низкий уровень.

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

          IN   AL,61H          ;получаем значение из порта B
          OR   AL,10000000B    ;устанавливаем бит 7 в 1
          OUT  61H,AL          ;заменяем байт
          IN   AL,60H          ;получаем значение из порта A
          MOV  CL,6            ;подготовка для сдвига AL
          SHR  AL,CL           ;сдвигаем 2 старших бита на 6 позиций
          INC  AL              ;начинаем счет с 1, а не с 0
          MOV  NUM_DRIVES,AL   ;получаем число накопителей
          IN   AL,61H          ;подготовка к восстановлению порта B
          AND  AL,01111111B    ;сбрасываем бит 7
          OUT  61H,AL          ;восстанавливаем байт

          1.1.2 Определение типа IBM PC.

          Имеются проблемы совместимости между различными типами IBM PC.
       Для  того чтобы программа могла работать на любом из IBM PC,  ис-
       пользуя все его возможности,  необходимо  чтобы она могла опреде-
       лить  тип машины, в которую она загружена.  Эта информация содер-
       жится во втором с конца байте памяти  по адресу FFFFE в ROM-BIOS,
       


                                      -8-
       с использованием следующих ключевых чисел.

                    Компьютер                Код
                       PC                     FF
                       XT                     FE
                       PCjr                   FD
                       AT                     FC

          Высокий уровень.

          В Бейсике надо просто использовать PEEK для чтения значения:

       100 DEF SEG = &HF000        'указываем на верхние 64K памяти
       110 X = PEEK(&HFFFE)        'читаем второй с конца байт
       120 IF X = &HFD THEN ...    '... тогда это PCjr

          Низкий уровень.

          В языке ассемблера:

       ;--- Определение типа компьютера:
          MOV  AX,0F000H           ;указывает ES на ПЗУ
          MOV  ES,AX               ;
          MOV  AL,ES:[0FFFEH]      ;получаем байт
          CMP  AL,0FDH             ;это PCjr?
          JE   INITIALIZE_JR       ;переходим на инициализацию

          1.1.3 Определение версии MS DOS.

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

          Средний уровень.

          Функция  30H  прерывания 21H возвращает номер версии  MS  DOS.
       Старший номер версии (2  из  2.10)  возвращается  в AL, а младший
       номер  версии (10 из 2.10) возвращается в AH (обратите  внимание,
       что младший номер .1 возвращает  значение  AH, а не 1H). AL может
       содержать 0, что указывает на версию MS DOS меньшую чем 2.0.  Это
       прерывание меняет содержимое  регистров  BX и CX, в которых возв-
       ращается значение 0.

       ;--- Определение версии MS DOS:
          MOV   AH,30H            ;номер функции получения версии
          INT   21H               ;получить номер версии
          CMP   AL,2              ;проверка на версию 2.х
          JL    WRONG_DOS         ;если меньше 2, то выдать сообщение

          1.1.4 Определение числа и типов адаптеров дисплея.

          Программе  может оказаться необходима информация о том,  будет
       ли она работать в  системе  с  монохромным  адаптером,  с цветной
       графической картой или с EGA, а также о наличии второго адаптера.
       В  пункте  [4.1.6]  объяснено как передать управление  от  одного
       адаптера к другому.  Байт  статуса оборудования, хранящийся в об-
       ласти  данных  ROM-BIOS  по адресу 0040:0010  сообщает  установку
       


                                      -9-
       переключателя 1,  который  показывает  какая  из  карт активна. В
       принципе должны иметь значение 11 для монохромной карты, 10 - для
       цветной карты 80*25, 01 - для  цветной  карты 40*25 и 00 для EGA.
       Однако  при наличии EGA он может установить биты отличными от 00,
       в зависимости от установки его собственных переключателей. Поэто-
       му Вы должны сначала другими средствами установить наличие EGA, а
       затем, если его нет, то  по  данным  BIOS  определить является ли
       активным  цветной или монохромный адаптер.  Для проверки  наличия
       EGA надо прочитать байт по адресу  0040:0087. Если он равен 0, то
       EGA отсутствует.  Если этот байт ненулевой, то когда бит 3=0, EGA
       является активным адаптером, а  когда он равен 1, то активен вто-
       рой адаптер.
          Когда  присутствует EGA, то проверка наличия монохромного  или
       цветного адаптера осуществляется записью значения в регистр адре-
       са курсора микросхемы 6845 [4.1.1] и последующего чтения значения
       и проверки их на совпадение. Для  монохромной карты пошлите 0FH в
       порт  3B4H, чтобы указать на регистр курсора, а затем прочитать и
       записать адрес курсора через порт 3B5H. Соответствующие порты для
       цветной карты 3D4H и 3D5H. Когда карта отсутствует, то порт возв-
       ращает значение 0FFH; но поскольку это значение может содержаться
       в регистре, то недостаточно простой проверки на это значение.
          Имеются два добавочных вопроса, на которые могут потребоваться
       ответы при наличии  EGA:  сколько  имеется  памяти на его карте и
       какой  тип  монитора  подсоединен? Для определения  типа  дисплея
       проверьте бит 1 по  адресу  0040:0087;  когда  он  установлен, то
       подсоединен  ммонохромный дисплей, а когда он равен нулю -  цвет-
       ной.  Если Ваша программа  использует цветной графический режим с
       350  строками,  то надо также определить присоединен  ли  дисплей
       IRGB или  R'G'B'RGB,  где  последняя  аббревиатура  соответствует
       улучшеному  цветному  дисплею IBM.  Это  определяется  установкой
       четырех переключателей на карте EGA.  Установка этих переключате-
       лей возвращается в CL при обращении к функции 12H прерывания 10H.
       Цепочка четырех младших битов  должна  быть  0110 для улучшенного
       цветного  дисплея.  Та же самая функция сообщает и наличие памяти
       на карте EGA.  Она возвращает  BL,  содержащий 0 для 64K, 1 - для
       128, 2 - для 192 и 3 - для полных 256K памяти дисплея.

          Высокий уровень.

          Приведенные фрагменты кода определяют тип текущего монитора  и
       режим его работы, а  также  определяют  какие типы видеоадаптеров
       имеются в машине:

       100 '''определение активного адаптера
       110 DEF SEG = &H40         'указываем на область данных BIOS
       120 X = PEEK(&H87)         'проверка на наличие EGA
       130 IF X = 0 THEN 200      'EGA отсутствует, идем дальше
       140 IF X AND 8 = 0 THEN... 'активный монитор EGA
        .
        .
       200 X = PEEK(&H10)         'читаем байт статуса оборудования
       210 Y = X AND 48           'выделяем биты 4 и 5
       220 IF Y = 48 THEN ...     '... тогда монохромный (00110000)
       230 IF Y = 32 THEN ...     '... тогда цветной 80*25 (00100000)
       240 IF Y = 16 THEN ...     '... тогда цветной 40*25 (00010000)

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

       100 '''проверка наличия монохромной карты
       110 OUT &H3B4,&HF          'адрес регистра курсора
       


                                     -10-
       120 X = INP(&H3B5)         'чтение и сохранение значения
       130 OUT &H3B5,100          'посылаем в регистр любое значение
       140 IF INP(&H3B5)<>100 THEN... 'если карта есть - вернется то же
       150 OUT &H3B5,X            'восстанавливаем значение регистра

          Низкий уровень.

          Приведенные примеры соответствуют примерам на Бейсике.

       ;--- Определение активного адаптера:
          MOV   AX,40H        ;указываем ES на область данных BIOS
          MOV   ES,AX         ;
          MOV   AL,ES:[87H]   ;проверяем наличие EGA
          CMP   AL,0          ;
          JE    NO_EGA        ;если 0040:0087 = 0, то EGA нет
          TEST  AL,00001000B  ;EGA есть, проверяем бит 3
          JNZ   EGA_NOT_ACTIVE;если бит 3=1, то EGA неактивен
           .
           .
       EGA_NOT_ACTIVE:
          MOV   AL,ES:[10H]   ;проверяем байт статуса дисплея
          AND   AL,00110000B  ;выделяем биты 4 и 5
          CMP   AL,48         ;это монохромная карта?
          JE    MONOCHROME    ;переход если да

          Предполагая  наличие монохромной карты проверим установлена ли
       цветная карта (неактивная):

       ;--- Установлена ли неактивная цветная карта?
          MOV   DX,3D4H       ;указываем на регистр адреса 6845
          MOV   AL,0FH        ;запрашиваем регистр курсора
          OUT   DX,AL         ;указываем на регистр
          INC   DX            ;указываем на регистр данных
          IN    AL,DX         ;получаем текущее значение
          XCNG  AH,AL         ;сохраняем значение
          MOV   AL,100        ;тестовое значение 100
          OUT   DX,AL         ;посылаем его
          IN    AL,DX         ;считываем его снова
          CMP   AL,100        ;сравниваем значения
          JNE   NO_CARD       ;переход если нет карты
          XCNG  AH,AL         ;иначе есть цветная карта
          OUT   DX,AL         ;тогда восстанавливаем значение

          1.1.5 Определение числа и типа дисковых накопителей.

          На  всех  машинах  кроме AT (который будет  обсуждаться  ниже)
       регистры микросхемы 8255 интерфейса  с периферией содержат инфор-
       мацию о том, сколько НГМД имеет машина.  В примерах [1.1.1] пока-
       зано как получить эту  информацию.   Информация  определяющая тип
       диска содержится в таблице размещения файлов (FAT) диска, которая
       следит за использованием дискового пространства.  Первый байт FAT
       содержит один из следующих кодов:

             Код                    Тип диска

              FF            двухсторонний, 8 секторов
              FE            односторонний, 8 секторов
              FD            двухсторонний, 9 секторов
              FC            односторонний, 9 секторов
              F9            двухсторонний, 15 секторов
              F8            фиксированный диск

          Сама таблица размещение файлов не является  файлом.  Она может
       


                                     -11-
       быть считана при помощи функций DOS  или BIOS непосредственно чи-
       тающих определенные сектора диска. В  пункте  [5.1.1]  содержится
       вся информация необходимая для нахождения и чтения FAT. К счастью,
       операционная  система  обеспечивает  функцию,  которая  возвращает
       идентификационный байт диска.
          Данные BIOS не показывают число  жестких дисков в системе, так
       как переключатели предназначены только для гибких дисков.  Однако
       Вы можете использовать указанную функцию операционной системы для
       поиска накопителей.  Она возвращает значение 0CDH, вместо  одного
       из упомянутых кодов, когда  накопители  отсутствуют.  Надо просто
       проверять  все  большие и большие номера накопителей, до тех  пор
       пока не будет обнаружено указанное значение.
          AT уникален в том смысле, что  его  информация  о конфигурации
       говорит  какой тип накопителя используется.  Эту информацию можно
       получить из порта с  адресом  71H,  предварительно  послав  номер
       регистра в порт 70H. Для НГМД номер регистра равен 10H.  Информа-
       ция о первом  накопителе  содержится  в битах 7-4, а о втором - в
       битах 3-0. В обоих случаях цепочка битов 0000 говорит об отсутст-
       вии накопителя, 0001 - о двухстороннем накопителе с плотностью 48
       дорожек  на дюйм, а 0010 - о накопителе большой емкости (96 доро-
       жек на дюйм).  Информация о  фиксированном диске содержится в ре-
       гистре 12H. И снова биты 7-4 и 3-0 соответствуют первому и второ-
       му накопителям.  0000 указывает на отсутствие накопителя.  Другие
       15 возможных значений описывают емкость и конструкцию накопителя.
       Эти коды сложные; если Вам  по  какой-то  причине потребуется эта
       информация, обратитесь к техническому руководству по AT.

          Средний уровень.

          Функция  1CH прерывания 21H возвращает информацию об указанном
       накопителе.  Поместите номер  накопителя в DL, причем 0 = накопи-
       тель  по  умолчанию, 1 = A, и т.д.  При возвращении  DX  содержит
       число кластеров в FAT, AL  -  число  секторов  в кластере, а CX -
       число байтов в секторе.  DS:BX указывает на байт, содержащий  код
       идентификации диска из FAT, согласно приведенной таблице.  В сле-
       дующем примере определяется тип накопителя A:

       ;---определение типа диска
          MOV   AH,1CH        ;функция MS DOS
          MOV   DL,1          ;выбор накопителя A
          INT   21H           ;получение информации
          MOV   DL,[BX]       ;получение типа накопителя
          CMP   DL,0FDH       ;двухсторонний, 9 секторов?
          JE    DBL_9         ;и т.д.

          BIOS AT имеет функцию, сообщающую общие параметры накопителей.
       Это функция 8 прерывания 13H.  Она возвращает число накопителей в
       DL, максимальное число сторон накопителя в DH, максимальное число
       секторов в CL и дорожек в CH, а  код  статуса ошибки накопителя в
       AH (см. пункт [5.4.8]).
          Другая функция BIOS AT возвращает тип накопителя.  Это функция
       15H прерывания 13H, которая требует номера накопителя в DL.  В AH
       возвращается  код,  причем 0 = нет накопителя,  1 =  дискета  без
       обнаружения изменений, 2 = дискета с обнаружением изменений и 3 =
       фиксированный диск. В случае фиксированного диска в CX:DX возвра-
       щается число секторов по 512 байт.

          1.1.6 Определение числа и типа периферийных устройств.

          При старте ROM-BIOS  проверяет   присоединенное  оборудование,
       сообщая  о  результатах своей проверки в регистр  статуса.   Этот
       регистр занимает два байта, начиная  с 0040:0010. Нижеприведенные
       значения  битов относятся ко всем машинам, пока не оговорено  об-
       


                                     -12-
       ратное:
          бит 0    если 1, то присутствует НГМД
          1        XT,AT:1 = есть мат. сопроцессор (PC,PCjr:не использ.)
          2-3      11 = базовая память 64K (AT:не используется)
          4-5      Активный видеоадаптер (11 = монохромный,
                   10 = цветной 80*25, 01 = цветной 40*25)
          6-7      число НГМД (если бит 0 = 1)
          8        PCjr:0 = есть DMA (PC,XT,AT:не используется)
          9-11     число адаптеров коммуникации
          12       1 = есть игровой порт (AT:не используется)
          13       PCjr:есть серийный принтер (PC,XT,AT:не использ.)
          14-15    число присоединенных принтеров

          Большая часть информация расшифровывается примитивно. Но обра-
       тите внимание, что информация о дисковых накопителях распределена
       между битами 0 и 6-7. Значение 0 в битах 6-7 указывает, что  име-
       ется один дисковый накопитель; чтобы узнать об отсутствии накопи-
       телей надо проверить бит 0.
          Число портов коммуникации может быть получено из области  дан-
       ных BIOS. BIOS отводит четыре 2-байтных поля для хранения базовых
       адресов вплоть до четырех  COM  портов  (MS DOS использует только
       два из них). Базовый адрес - это младший из адресов портов, отно-
       сящихся к группе портов, имеющих доступ к данному каналу коммуни-
       кации.  Эти четыре поля начинаются с адреса 0040:0008. Порту COM1
       соответствует адрес :0008, а COM2  - 000A. Если это поле содержит
       0, то соответствующий порт отсутствует. Таким образом, если слово
       по адресу :0008 отлично от  нуля,  а по адресу 000A - нулевое, то
       имеется один порт коммуникации.
          AT  хранит  информацию о периферии в регистре  14H  микросхемы
       конфигурации. Сначала запишите  14H в порт с адресом 70H, а затем
       прочитайте содержимое регистра через порт 71H. Вот значение битов
       этого регистра:

          биты 7-6   00 = 1 НГМД, 01 = 2 НГМД
               5-4   01 = вывод на цветной дисплей, 40 строк
                     10 = вывод на цветной дисплей, 80 строк
                     11 = вывод на монохромный дисплей
               3-2   не используется
                 1   1 = имеется мат. сопроцессор
                 0   0 = нет НГМД, 1 = имеется НГМД

          Высокий уровень.

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

       100 DEF SEG = 0          'указывыаем на дно памяти
       110 X = PEEK(&H410)      'получаем младший байт регистра
       120 IF X MOD 2 = 0 THEN 140 'он четный - нет накопителей
       130 PRINT "Имеется диск" 'иначе имеется накопитель
       140 GOTO 160             'идем ко второму сообщению
       150 PRINT "Нет накопителей"  'второе сообщение
       160 ...                  'продолжаем...

          Проверка наличия COM1:

       100 DEF SEG = 40H        'указываем на область данных BIOS
       110 PORT = PEEK(0) + 256*PEEK(1) 'получаем слово со смещением 0
       120 IF PORT = 0 THEN...  '... то нет адаптера COM1

       


                                     -13-
          Средний уровень.

          Прерывание 11H BIOS возвращает байт статуса оборудования в AX.
       На  входе ничего подавать не надо.  В примере определяется  число
       дисковых накопителей.

       ; ---получение числа дисковых накопителей:
          INT   11H         ;получаем байт статуса
          TEST  AL,0        ;имеются накопители?
          JZ    NO_DRIVES   ;переход, если нет
          AND   AL,1100000B ;выделяем биты 5-6
          MOV   CL,5        ;подготовка к сдвигу регистра
          SHR   AL,CL       ;сдвиг вправо на 5 битов
          INC   AL          ;добавляем 1, т.к. отсчет идет с 1

          Низкий уровень.

          Ассемблерная  программа  работает  так  же, как и программа на
       Бейсике.   В примере читается информация о конфигурации  для  AT,
       определяя установлен ли математический сопроцессор:

          MOV   AL,14H      ;номер регистра
          OUT   70H,AL      ;посылаем запрос
          IN    AL,71H      ;читаем регистр
          TEST  AL,10B      ;проверяем бит 1
          JZ    NO_COPROCESSOR ;если не установлен, то сопроцессора нет

          1.1.7 Ревизия количества памяти.

          Вопрос:  "Сколько  имеется памяти?",- может иметь три  смысла.
       О каком количестве памяти  сообщают  переключатели, установленные
       на  системной плате? Сколько микросхем памяти реально установлено
       в машине? И, наконец, сколько остается  свободной памяти, которую
       DOS  может  использовать для выполнения  Ваших  программ?  Машина
       может иметь 10 банков памяти по  64K, но переключатели могут ука-
       зывать  на наличие только 320K, оставляя половину памяти для  ка-
       ких-либо специальных целей.  А  как  может Ваша программа узнать,
       сколько  из доступных 320K она может использовать, учитывая,  что
       другое программное обеспечение может быть загружено резидентным в
       верхнюю или нижнюю часть памяти?
          Ответ на каждый вопрос можно получить своим способом. Для PC и
       XT установка  переключателей  может  быть  просто прочитана через
       порт B микросхемы интерфейса с периферией 8255.  В пункте [1.1.1]
       описано как это делается. BIOS  хранит  двухбайтную переменную по
       адресу  0040:0013, которая сообщает число  килобайт  используемой
       памяти. Для PCjr бит 3 порта  62H  (порт C микросхемы 8255) равен
       нулю,  когда  машина имеет добавочные 64K памяти.  AT дает  особо
       полную информацию о памяти.   Регистры 15H (младший) и 16H (стар-
       ший)  микросхемы информации о конфигурации говорят сколько памяти
       установлено на системной плате  (возможны  три  значения: 0100H -
       для  256K, 0200H - для 512K и 0280H для 512K плюс 128K  на  плате
       расширения). Память канала ввода/вывода для AT сообщается регист-
       рами  17H и 18H (с инкрементом 512K).  Память  сверх 1  мегабайта
       доступна через регистры  30H  и  31H  (опять  с инкрементом 512K,
       вплоть до 15 мегабайт).  Если AT имеет 128K на плате  расширения,
       то установлен бит 7 регистра  33.   Во  всех случаях надо сначала
       послать номер регистра в порт 70H, а затем прочитать значение  из
       порта 71H.
          Легко  написать  программу,  которая  прямо  тестирует наличие
       памяти через определенные интервалы адресного пространства.  Пос-
       кольку минимальная порция памяти 16  килобайт, то достаточно про-
       верить одну ячейку памяти в каждом 16-килобайтном сегменте, чтобы
       убедиться, что все 16K  присутствуют.  Когда данная ячейка памяти
       


                                     -14-
       отсутствует,  то  при чтении из нее получаем значение  233.   Для
       проверки можно записать в ячейку произвольное  число, отличное от
       233 и сразу же считать его.  Если вместо посланного числа возвра-
       щается 233, то соответствующий банк памяти отсутствует. Не приме-
       няйте  этот способ на AT, где при попытке писать в несуществующую
       память вступает в действие  встроенная  обработка  несуществующей
       памяти.   Диагностика AT настолько хороша, что Вы можете  целиком
       положиться на системную информацию о конфигурации.
          Память  постоянно  занимается  частями  операционной  системы,
       драйверами устройств, резидентными программами обработки прерыва-
       ний и управляющими блоками MS DOS.  При проверке банков памяти Вы
       не  должны  вносить необратимых  изменений в  содержимое  памяти.
       Сначала надо сохранить значение, хранящееся в тестируемой ячейке,
       затем проверить ее и восстановить первоначальное значение.
          Имеется еще одна проблема. Если Ваша процедура хотя бы времен-
       но модифицирует свой код, то это может привести к краху.  Поэтому
       для проверки надо выбирать такую ячейку из блока 64K, которая  не
       будет занята текстом Вашей процедуры.  Для этого поместите проце-
       дуру тестирования впереди программы, а для тестирования  выберите
       ячейку со смещением равным смещению для кодового сегмента. Напри-
       мер,  если  регистр кодового сегмента содержит 13E2,  то  сегмент
       начинается со смещения 13E2 во  втором  64K-байтном блоке памяти.
       Поскольку Ваша подпрограмма проверки не может находиться по этому
       адресу, то Вы можете безопасно  проверять  значение  3E2 в каждом
       блоке.   Запрет  прерываний [1.2.2] позволяет не  беспокоиться  о
       модификации  кода  из-за  аппаратных  прерываний,  которые  могут
       происходить во время проверки.
          Определение  количества памяти реально доступной  операционной
       системе также требует некоторого  фокуса.  Когда программа первый
       раз получает управление, то DOS отводит ей всю доступную  память,
       включая верхнюю область  памяти,  содержащую  нерезидентную часть
       DOS (которая автоматически перезагружается, если она была модифи-
       цирована). Для запуска другой  программы из текущей или для того,
       чтобы  сделать программу подходящей для многопользовательсой сис-
       темы, необходимо урезать программу до требуемого размера. В пунк-
       те [1.3.1] описано как это сделать с помощью функции 4AH прерыва-
       ния 21H.
          Эта же функция может быть использована для расширения отведен-
       ной  памяти.  Поскольку программе отводится вся доступная  память
       при загрузке, то такое расширение  невозможно при старте. Если Вы
       попробуете  сделать  это, то будет установлен  флаг  переноса,  в
       регистре AX появится код ошибки  8, а в регистре BX будет возвра-
       щено  максимальное  число доступных 16-байтных  параграфов.   Эта
       информация как раз и нужна.  Значит надо выдать запрос со слишком
       большим  значением  в регистре BX ( скажем, F000H параграфов),  а
       затем выполните прерывание.   Позаботьтесь о том, чтобы выполнить
       эту  функцию в самом начале программы, пока регистр ES еще  имеет
       начальное значение.

          Высокий уровень.

          Интерпретатор  Бейсика  использует  только 64K (хотя операторы
       PEEK  и POKE позволяют доступ к памяти за пределами  64K).   Доля
       памяти доступная в  настоящий  момент  возвращается функцией FRE.
       Эта функция имеет фиктивный аргумент, который может быть числовым
       или символьной строкой.  BYTES  =  FRE(x)  передает в BYTES число
       свободных байтов. BYTES = FRE(x$) делает то же самое.  Но строко-
       вый аргумент вынуждает очистку области данных перед тем как возв-
       ратить  число байтов.  Заметим, что если размер  рабочей  области
       устанавливается с помощью  оператора CLEAR, то количество памяти,
       сообщаемое  функцией  FRE  будет на от 2.5 до 4  килобайт  меньше
       из-за потребностей рабочей области интерпретатора.
          Транслятор Бейсика не накладывает ограничение 64K на суммарный
       


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

          Средний уровень.

          Прерывание 12H BIOS проверяет установку переключателей и возв-
       ращает в AX количество килобайт  памяти  в системе.  Эта величина
       вычисляется  из установки регистров микросхемы 8255 или, для  AT,
       микросхемы  конфигурации/часов.   Входных  регистров нет.  Имейте
       ввиду,  что  установка  переключателей может быть  неверной,  что
       ограничивает достоверность такого подхода.
          Для определения  числа  16-байтных  параграфов,  доступных для
       DOS, используйте функцию 4AH прерывания 21H.  ES должен иметь  то
       же значение, что при старте задачи:

       ;---определение числа параграфов доступных для DOS
          MOV   AH,4AH        ;указываем нужную функцию
          MOV   BX,0FFFFH     ;требуем слишком большую память
          INT   21H           ;BX содержит число доступных параграфов

          AT использует функцию 88H  прерывания 15H для проверки наличия
       расширенной памяти, которая ищет память вне адресного пространст-
       ва процессора в обычном режиме  адресации.  Говорят, что она ищет
       память за отметкой 1 мегабайта.  При этом на системной плате дол-
       жно  быть от 512 до 640 килобайт памяти, чтобы эта функция  рабо-
       тала.  Число килобайтных блоков расширенной памяти возвращается в
       AX.

          Низкий уровень.

          Первый пример проверяет  число  банков  памяти по 64K в первых
       десяти 64-килобайтных сегментах памяти.  Если Вы будете проверять
       старшие 6 банков памяти, то имейте ввиду, что имеются видеобуфер,
       начиная  с  B000:0000 (и, возможно, A000:0000) и ПЗУ,  начиная  с
       F000:0000 (и, возможно, C000:0000).

       ;---проверка каждого банка памяти:
          CLI                  ;запрет аппаратных прерываний
          MOV   AX,CS          ;получаем значение кодового сегмента
          AND   AX,0FFFH       ;сбрасываем старшие 4 бита
          MOV   ES,AX          ;помещаем указатель в ES
          MOV   DI,0           ;DI считает число банков памяти
          MOV   CX,10          ;будем проверять 10 банков
          MOV   BL,'X'         ;для проверки используем 'X'
       NEXT:
          MOV   DL,ES:[0]      ;сохраняем значение тестируемой ячейки
          MOV   ES:[0],BL      ;помещаем 'X' в эту ячейку
          MOV   DH,ES:[0]      ;читаем тестируемую ячейку
          MOV   ES:[0],DL      ;восстанавливаем значение
          CMP   DH,'X'         ;совпадает с тем, что писали?
          JNE   GO_AHEAD       ;если нет, то банк отсутствует
          INC   DI             ;увеличиваем число банков
       GO_AHEAD:
          MOV   AX,ES          ;готовим увеличение указателя
          ADD   AX,1000H       ;указываем на следующие 64K
          MOV   ES,AX          ;возвращаем указатель в ES
          LOOP  NEXT           ;обрабатываем следующий банк
          STI                  ;разрешаем аппаратные прерывания

                        Раздел 2. Управление прерываниями.
       


                                     -16-
          Прерывания  это готовые процедуры, которые компьютер  вызывает
       для выполнения определенной задачи. Существуют аппаратные и прог-
       раммные прерывания.  Аппаратные прерывания инициируются аппарату-
       рой, либо с системной платы,  либо с  карты расширения. Они могут
       быть  вызваны сигналом микросхемы таймера, сигналом от  принтера,
       нажатием клавиши на клавиатуре и множеством  других причин. Аппа-
       ратные прерывания не координируются с работой программного  обес-
       печения. Когда вызывается прерывание, то процессор оставляет свою
       работу,  выполняет  прерывание, а затем возвращается  на  прежнее
       место. Для того чтобы иметь  возможность вернуться точно в нужное
       место программы, адрес этого места (CS:IP) запоминается на стеке,
       вместе с регистром флагов.  Затем в CS:IP загружается адрес прог-
       раммы обработки прерывания и ей передается  управление. Программы
       обработки  прерываний иногда называют драйверами прерываний.  Они
       всегда завершаются  инструкцией  IRET  (возврат  из  прерывания),
       которая завершает процесс, начатый прерыванием, возвращая  старые
       значения CS:IP и регистра  флагов, тем самым давая программе воз-
       можность продолжить выполнение из того же состояния.
          С  другой стороны, программные прерывания на самом деле ничего
       не прерывают.  На самом деле это обычные процедуры, которые вызы-
       ваются  Вашими программами для выполнения рутинной работы,  такой
       как прием нажатия клавиши на клавиатуре или вывод на экран. Одна-
       ко  эти  подпрограммы содержатся не внутри Вашей  программы, а  в
       операционной системе и механизм  прерываний  дает Вам возможность
       обратиться к ним. Программные прерывания могут вызываться друг из
       друга. Например, все прерывания  обработки ввода с клавиатуры DOS
       используют прерывания обработки ввода с клавиатуры BIOS для полу-
       чения символа из буфера клавиатуры.  Отметим, что аппаратное пре-
       рываение  может получить управление при  выполнении  программного
       прерывания. При  этом  не  возникает  конфликтов,  так как каждая
       подпрограмма обработки прерывания сохраняет значения всех исполь-
       зуемых ею регистров и  затем  восстанавливает  их при выходе, тем
       самым не оставляя следов того, что она занимала процессор.
          Адреса программ прерываний называют векторами.  Каждый  вектор
       имеет длину четыре байта. В  первом слове хранится значение IP, а
       во втором - CS.  Младшие 1024 байт памяти содержат вектора преры-
       ваний, таким образом имеется место  для 256 векторов. Вместе взя-
       тые  они  называются таблицей векторов.  Вектор для прерывания  0
       начинается с ячейки 0000:0000, прерывания  1 - с 0000:0004, 2 - с
       0000:0008 и т.д. Если посмотреть на четыре байта, начиная с адре-
       са 0000:0020, в которых содержится вектор прерывания 8H (прерыва-
       ние  времени суток), то Вы обнаружите там A5FE00F0.  Имея  ввиду,
       что младший байт  слова  расположен  сначала и что порядок IP:CS,
       это  4-байтное значение переводится в F000:FEA5.   Это  стартовый
       адрес программы ПЗУ, выполняющей прерывание 8H. На рис. 1-2 пока-
       зана схема выполнения программой прерывания 21H.

          1.2.1 Программирование контроллера прерываний 8259.

          Для  управления аппаратными прерываниями во всех типах IBM  PC
       используется микросхема  программируемого  контроллера прерываний
       Intel 8259. Поскольку в ккаждый момент времени может поступить не
       один запрос, микросхема имеет схему  приоритетов. Имеется 8 уров-
       ней приоритетов, кроме AT, у которого их 16, и обращения к  соот-
       ветствующим уровням обозначаются сокращениями от IRQ0 до IRQ7 (от
       IRQ0 до IRQ15), что означает запрос на прерывание.   Максимальный
       приоритет соответствует уровню  0.   Добавочные  8 уровней для AT
       обрабатываются второй микросхемой 8259; этот второй набор уровней
       имеет приоритет между IRQ2  и  IRQ3.  Запросы  на  прерывание 0-7
       соответствуют векторам прерываний от 8H до 0FH; для AT запросы на
       прерывания 8-15 обслуживаются векторами  от 70H до 77H. Ниже при-
       ведены назначения этих прерываний:

       


                                     -17-
          Аппаратные прерывания в порядке приоритета.

          IRQ 0     таймер
              1     клавиатура
              2     канал ввода/вывода
                 8  часы реального времени (только AT)
                 9  программно переводятся в IRQ2 (только AT)
                10  резерв
                11  резерв
                12  резерв
                13  мат. сопроцессор (только AT)
                14  контроллер фиксированного диска (только AT)
                15  резерв
              3     COM1 (COM2 для AT)
              4     COM2 (модем для PCjr, COM1 для AT)
              5     фиксированный диск (LPT2 для AT)
              6     контроллер дискет
              7     LPT1

          Прерыванию  времени суток [2.1.0] дан максимальный  приоритет,
       поскольку если оно будет  постоянно  теряться, то будут неверными
       показания системных часов. Прерывание от клавиатуры [3.1.0] вызы-
       вается при  нажатии  или  отпускании  клавиши;  оно вызывает цепь
       событий,  которая обычно заканчивается тем, что код клавиши поме-
       щается в буфер  клавиатуры  (откуда  он  затем может быть получен
       программными прерываниями).
          Микросхема 8259 имеет три однобайтных регистра, которые управ-
       ляют восемью линиями аппаратных  прерываний.   Регистр запроса на
       прерывание  (IRR)  устанавливает соответствующий бит, когда линия
       прерывания сигнализирует о запросе. Затем микросхема автоматичес-
       ки проверяет не обрабатывается ли другое прерывание. При этом она
       запрашивает информацию регистра обслуживания  (ISR). Дополнитель-
       ная  цепь отвечает за схему приоритетов.  Наконец, перед  вызовом
       прерывания,  проверяется  регистр  маски  прерываний (IMR), чтобы
       узнать  разрешено ли в данный момент прерывание  данного  уровня.
       Как правило программисты  обращаются только к регистру маски пре-
       рываний  через порт 21H [1.2.2] и командному регистру  прерываний
       через порт 20H [1.2.3].

          1.2.2 Запрет/разрешение отдельных аппаратных прерываний.

          Программы на аасемблере могут запретить аппаратные прерывания,
       перечисленные в [1.2.1]. Это маскируемые прерывания; другие аппа-
       ратные прерывания, возникающие  при  некоторых ошибках (таких как
       деление  на ноль) не могут быть маскированы.  Имеются две причины
       для запрета аппаратных прерываний. В первом случае все прерывания
       блокируются  с  тем чтобы критическая часть кода  была  выполнена
       целиком, прежде чем машина произведет какое-либо другое действие.
       Например, прерывания запрещают при изменении вектора  аппаратного
       прерывания, избегая  выполнения  прерывания  когда вектор изменен
       только наполовину.
          Во  втором  случае маскируются только определенные  аппаратные
       прерывания.  Это делается когда некоторые определенные прерывания
       могут  взаимодействовать  с  операциями,  критичными к  временам.
       Например, точно рассчитанная по времени процедура ввода/вывода не
       может себе позволить быть прерванной длительным дисковым прерыва-
       нием.

          Низкий уровень.

          Выполнение прерываний  зависит  от  значения  флага прерывания
       (бит 9) в регистре флагов.  Когда этот бит равен 0, то  разрешены
       все прерывания, которые разрешает маска. Когда он равен 1, то все
       


                                     -18-
       аппаратные  прерывания  запрещены.  Чтобы  запретить  прерывания,
       установив этот флаг в 1, используется инструкция CLI. Для очистки
       этого  флага и восстановления прерываний - инструкция STI.  Избе-
       гайте отключения прерываний  на   длительный  период.  Прерывание
       времени  суток происходит 18.2 раза в секунду и если к этому пре-
       рыванию был более чем один  запрос  в  то время, когда аппаратные
       прерывания  были запрещены, то лишние запросы будут  отброшены  и
       системное время будет определяться неправильно.
          Имейте ввиду, что  машина  автоматически  запрещает аппаратные
       прерывания  при  вызове  программных  прерываний и  автоматически
       разрешает их при возврате.  Когда Вы пишете свои программные пре-
       рывания,  то Вы можете начать программу с инструкции STI, если Вы
       можете допустить аппаратные  прерывания.  Отметим также, что если
       за  инструкцией  CLI не следует STI, то это приведет к  остановке
       машины, так как ввод с клавиатуры будет заморожен.
          Для  маскирования  определенных  аппаратных  прерываний  нужно
       просто  послать  требуемую цепочку битов в  порт с  адресом  21H,
       который соответствует регистру  маски  прерываний (IMR).  Регистр
       маски  на  второй  микросхеме 8259 для AT (IRQ8-15)  имеет  адрес
       порта A1H.  Установите те  биты  регистра,  которые соответствуют
       номерам прерываний, которые Вы хотите маскировать.  Этот  регистр
       можно только записывать.  Нижеприведенный пример блокирует диско-
       вое прерывание.  Не забудьте очистить регистр в конце  программы,
       иначе обращение к дискам будет запрещено и после завершения прог-
       раммы.

       ;---маскирование 6-го бита регистра маски прерываний
          MOV   AL,01000000B   ;маскируем бит 6
          OUT   21H,AL         ;посылаем в регистр маски прерываний
           .
          MOV   AL,0           ;
          OUT   21H,AL         ;очищаем IMR в конце программы
          1.2.3 Написание собственного прерывания.

          Имеется  несколько причин для написания собственного  прерыва-
       ния. Во-первых, большинство из готовых прерываний, обеспечиваемых
       операционной системой, ничто иное, как обычные процедуры, доступ-
       ные для всех программ, и Вы  можете  пожелать добавить свое в эту
       библиотеку.   Например, многие Ваши программы могут  использовать
       процедуру, выводящую строки на  экран  вертикально.  Вместо того,
       чтобы  включать  ее  в каждую программу в качестве  процедуры  Вы
       можете установить ее как  прерывание,  написав программу, которая
       останется резидентной в памяти после завершения [1.3.4]. Тогда Вы
       можете использовать INT 80H  вместо WRITE_VERTICALLY (имейте вви-
       ду,  что вызов прерывания несколько медленней, чем вызов процеду-
       ры).
          Второй причиной написания прерывания  может быть использование
       какого-либо  отдельного  аппаратного прерывания.  Это  прерывание
       автоматически вызывается при возникновении  определенных условий.
       В  некоторых случаях BIOS инициализирует вектор этого  прерывания
       так, что он указывает  на  процедуру,  которая  вообще  ничего не
       делает (она содержит один оператор IRET). Вы можете написать свою
       процедуру и изменить вектор прерываний, чтобы он указывал на нее.
       Тогда при возникновении аппаратного прерывания будет  выполняться
       Ваша  процедура.   Одна из таких процедур это прерывание  времени
       суток [2.1.0], которое  автоматически  вызывается 18.2 раза в се-
       кунду. Обычно это прерывание только обновляет показание часов, но
       Вы можете добавить к нему любой код,  который Вы пожелаете.  Если
       Ваш код проверяет показания часов и вступает в игру в  определен-
       ные моменты времени,  то  возможны  операции  в реальном времени.
       Другие возможности - это написание процедур обработки  Ctrl-Break
       [3.2.8], PrtSC  [3.2.9]   и   возникновения   ошибочных  ситуаций
       [7.2.5].  Прерывания принтера [6.3.1] и коммуникационные  [7.1.8]
       


                                     -19-
       позволяют компьютеру быстро  переключаться  между операциями вво-
       да/вывода и другой обработкой.
          Наконец, Вы можете захотеть написать прерывание, которое  пол-
       ностью заменит одну из процедур  операционной системы, приспособ-
       ленное к Вашим программным нуждам.  В [1.2.4] показано как  напи-
       сать прерывание внутри  прерывания, которое позволяет Вам модифи-
       цировать существующие процедуры.

          Средний уровень.

          Функция  25H прерывания 21H устанавливает вектор прерывания на
       указанный адрес.  Адреса имеют  размер  два слова.  Старшее слово
       содержит  значение сегмента (CS), младшее содержит смещение (IP).
       Чтобы установить вектор,  указывающим  на одну из Ваших процедур,
       нужно  поместить сегмент процедуры в DS, а смещение в DX  (следуя
       порядку нижеприведенного примера). Затем поместите номер прерыва-
       ния  в AL и вызовите функцию.  Любая процедура прерывания  должна
       завершаться не обычной инструкцией RET, а IRET. (IRET выталкивает
       из стека три слова,  включая  регистр  флагов, в то время как RET
       помещает  на  стек только два.  Если Вы  попытаетесь  тестировать
       такую процедуру как обычную процедуру, но кончающуюся IRET, то Вы
       исчерпаете  стек.) Отметим, что функция 25H автоматически  запре-
       щает аппаратные прерывания в процессе  изменения вектора, поэтому
       не существует опасности, что посреди дороги произойдет аппаратное
       прерывание, использующее данный вектор.

       ;---установка прерывания
          PUSH  DS             ;сохраняем DS
          MOV   DX,OFFSET ROUT ;смещение для процедуры в DX
          MOV   AX,SEG ROUT    ;сегмент процедуры
          MOV   DS,AX          ;помещаем в DS
          MOV   AH,25H         ;функция установки вектора
          MOV   AL,60H         ;номер вектора
          INT   21H            ;меняем прерывание
          POP   DS             ;восстанавливаем DS

       ;---процедура прерывания
       ROUT  PROC  FAR
             PUSH  AX          ;сохраняем все изменяемые регистры
              .
              .
             POP   AX          ;восстанавливаем регистры
             MOV   AL,20H      ;эти две строки надо использовать
             OUT   20H,AL      ;только для аппаратных прерываний
             IRET
       ROUT  ENDP

          В конце кода каждого из Ваших аппаратных  прерываний Вы должны
       включить следующие 2 строчки кода:

                MOV   AL,20H
                OUT   20H,AL

          Это  просто совпадение, что числа (20H) одни и те  же в  обеих
       строках. Если аппаратное  прерывание не заканчивается этими стро-
       ками,  то микросхема 8259 не очистит информацию регистра обслужи-
       вания, с тем чтобы была  разрешена  обработка  прерываний с более
       низкими  уровнями, чем только что обработанное.  Отсутствие  этих
       строк легко может привести к  краху программы, так как прерывания
       от   клавиатуры   скорее  всего  окажутся  замороженными и   даже
       Ctrl-Alt-Del окажется  бесполезным.  Отметим,  что эта добавка не
       нужна для тех векторов прерываний, которые являются  расширениями
       существующих прерываний, таким как прерывание 1CH, которое добав-
       


                                     -20-
       ляет код к прерыванию времени суток [2.1.7].
          Когда программа завершается, должны быть восстановлены  ориги-
       нальные вектора прерываний. В  противном случае последующая прог-
       рамма может вызвать данное прерывание и передать управление на то
       место в памяти, в котором  Вашей  процедуры  уже нет.  Функция 35
       прерывания  21H возвращает текущее значение  вектора  прерывания,
       помещая значение сегмента в ES, а смещение в BX. Перед установкой
       своего  прерывания  получите текущее значение вектора,  используя
       эту функцию, сохраните эти  значения,  и  затем восстановите их с
       помощью функции 25H (как выше) перед завершением своей программы.
       Например:

       ;---в сегменте данных:
          KEEP_CS  DW    0        ;хранит сегмент заменяемого прерывания
          KEEP_IP  DW    0        ;хранит смещение прерывания
       ;---в начале программы
                   MOV   AH,25H     ;функция получения вектора
                   MOV   AL,1CH     ;номер вектора
                   INT   21H        ;теперь сегмент в ES, смещение в BX
                   MOV   KEEP_IP,BX ;запоминаем смещение
                   MOV   KEEP_CS,ES ;запоминаем сегмент
       ; ---в конце программы
                   CLI
                   PUSH  DS         ;DS будет разрушен
                   MOV   DX,KEEP_IP ;подготовка к восстановлению
                   MOV   AX,KEEP_CS ;
                   MOV   DS,AX      ;подготовка к восстановлению
                   MOV   AH,25H     ;функция установки вектора
                   MOV   AL,1CH     ;номер вектора
                   INT   21H        ;восстанавливаем вектор
                   POP   DS         ;восстанавливаем DS
                   STI

          Имеется  пара ловушек, которых следует избегать при  написании
       прерывания. Если новая процедура прерывания должна иметь доступ к
       данным,  то необходимо позаботиться, чтобы DS был правильно уста-
       новлен (обычно  прерывание  может  использовать  стек  вызывающей
       программы).  Другая неприятность может заключаться в том, что при
       завершении программы  по  Ctrl-Break  вектор  прерывания не будет
       восстановлен,  если только Вы не предусмотрите,  чтобы  программа
       реакции на Ctrl-Break выполняла эту процедуру [3.2.8].

          Низкий уровень.

          Описанные выше функции MS  DOS  просто  получают  или изменяют
       пару  слов в младших ячейках памяти.  Смещение вектора может быть
       вычислено простым умножением номера вектора на 4. Например, чтобы
       получить адрес прерывания 16H в ES:BX:

       ;---получение адреса прерывания 16H
          SUB   AX,AX         ;устанавливаем ES на начало памяти
          MOV   ES,AX         ;
          MOV   DI,16H        ;номер прерывания в DI
          SHL   DI,1          ;умножаем на 2
          SHL   DI,1          ;умножаем на 2
          MOV   BX,ES:[DI]    ;берем младший байт в BX
          MOV   AX,ES:[DI]+2  ;берем старший байт в ES
          MOV   ES,AX         ;

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


                                     -21-
          1.2.4 Дополнение к существующему прерыванию.

          Хотя и не часто, но иногда  бывает  полезно добавить код к су-
       ществующему прерыванию.  В качестве примера рассмотрим программы,
       которые преобразуют одно  нажатие  клавиши в длинные определяемые
       пользователем  символьные  строки (макроопределения  клавиатуры).
       Эти программы используют факт, что  весь ввод с клавиатуры посту-
       пает  поступает через функцию 0 прерывания 16H BIOS [3.1.3].  Все
       прерывания ввода с клавиатуры  DOS  вызывают  прерывание BIOS для
       получения символа из буфера клавиатуры.  Поэтому необходимо моди-
       фицировать лишь прерывание 16H,  таким образом, чтобы оно служило
       шлагбаумом для макроопределений, после чего любая программа будет
       получать макроопределения,  независимо  от того, какое прерывание
       ввода с клавиатуры она использует.
          Конечно,  модифицировать прерывания BIOS и DOS непросто,  пос-
       кольку BIOS расположена в ПЗУ, а DOS поступает без листинга и они
       ограничены  размерами  отведенной для них памяти.  Но  Вы  можете
       написать процедуру, которая  предшествует  и/или следует за соот-
       ветствующим  прерыванием,  и эта процедура может  вызываться  при
       вызове прерывания DOS  или  BIOS.  Например,  в случае прерывания
       16H,  Вам нужно написать процедуру и указать на нее вектором пре-
       рывания для 16H.  Оригинальное  значение вектора 16H тем временем
       переносится  в  какой-либо  неиспользуемый вектор,  скажем,  60H.
       Новая процедура просто вызывает  прерывание  60H, чтобы использо-
       вать  оригинальное прерывание 16H; поэтому когда программа  вызы-
       вает прерывание 16H, управление передается Вашей процедуре, кото-
       рая затем вызывает оригинальное прерывание 16H, которая по завер-
       шении опять возвращает  управление  Вашей процедуре, а из нее уже
       Вы  возвращаетесь  в то место программы, из  которого  был  вызов
       прерывания 16H. После того  как  это  сделано,  в новой процедуре
       может содержаться любой код, как до, так и после вызова  прерыва-
       ния 60H. На рис. 1-3 показана диаграмма этой процедуры. Вот крат-
       кая сводка необходимых действий:

          1. Создать новую процедуру, вызывающую прерывание 60H.
          2. Перенести вектор прерывания для 16H в 60H.
          3. Изменить вектор 16H, чтобы он указывал на новую процедуру.
          4. Завершить программу, оставляя ее резидентной [1.3.4].

          Раздел 3. Управление программами.

          Большинство  программ  загружаются  в память,  запускаются,  а
       затем удаляются операционной системой при завершении. Языки высо-
       кого уровня обычно не имеют альтернативы. Но для программистов на
       ассемблере имеется другая  возможность и данный раздел демонстри-
       рует  ее.   Некоторые программы действуют как драйверы  устройств
       или драйверы прерываний и  они  должны  быть  сохранены  в памяти
       ("резидентными")  даже  после их завершения  (вектора  прерываний
       обеспечивают механизм, посредством которого последующие программы
       могут  обращаться  к резидентным процедурам).   Иногда  программе
       необходимо запустить из себя другую программу.  На самом деле DOS
       позволяет программе загрузить в память вторую копию  COMMAND.COM,
       которая может использована как  средство интерфейса с пользовате-
       лем или выполнения команд типа COPY или DIR.
          Программы могут быть в двух форматах: .EXE или .COM. Программы
       первого типа могут  быть  больше  64K,  но  они требуют некоторой
       обработки перед тем, как DOS загрузит их в память.  С другой сто-
       роны COM программы существуют прямо  в том формате, который нужен
       для загрузки в память.  COM программы особенно полезны для корот-
       ких утилит. В обоих случаях  код, составляющий программу, предва-
       ряется  в памяти префиксом программного сегмента (PSP).  Это  об-
       ласть размером 100H байт, которая содержит информацию необходимую
       DOS для работы программы; PSP также обеспечивает место для файло-
       


                                     -22-
       вых операций ввода/вывода [5.3.5].  При загрузке EXE файла и DS и
       ES указывают на PSP. Для COM файлов CS также сначала указывает на
       PSP.   Отметим, что MS DOS 3.0 имеет функцию, которая  возвращает
       номер сегмента PSP. Это функция  62H прерывания 21H; ей ничего не
       надо подавать на входе, а в BX возвращается номер параграфа.
          Одна  из причин, по которой интересно положение PSP, состоит в
       том, что его первое слово содержит номер прерывания DOS,  которое
       будет приводить к завершению программы. Когда выполняется послед-
       ний  оператор RET программы, то значения на вершине стека  указы-
       вают счетчику команд  (регистр  IP)  на начало PSP, таким образом
       код  завершения  выполняется как следующая инструкция  программы.
       Дальнейшее обсуждение этого смотрите в пунктах [1.3.4] и [1.3.6].

          Для справки приводим значение полей PSP:

          Смещение  Размер поля        Значение
            0H       DW           номер функции DOS завершения программы
            2H       DW           размер памяти в параграфах
            4H       DW           резерв
            6H       DD           длинный вызов функции диспатчера DOS
            AH       DD           адрес завершения (IP,CS)
            EH       DD           адрес выхода по Ctrl-Break (IP,CS)
           12H       DD           адрес выхода по критической ошибке
           16H     22 байта       резерв
           2CH       DW           номер параграфа строки среды
           2EH     46 байтов      резерв
           5CH     16 байтов      область параметров 1 (формат FCB)
           6CH     20 байтов      область параметров 2 (формат FCB)
           80H    128 байтов      область DTA по умолчанию/получает
                                  командную строку программы

          1.3.1 Манипуляции с памятью.

          Когда MS DOS загружает  программу, то она помещается в младшую
       область  памяти, сразу же за COMMAND.COM и установленными драйве-
       рами устройств или другими утилитами,  которые резидентны в памя-
       ти.  В этот момент времени вся память за программой отведена этой
       программе. Если программе нужна  память для создания области дан-
       ных, то она может приближенно вычислить где в памяти кончается ее
       код и затем поместить требуемую  область  данных в любое место за
       концом  кода.  Для определения адреса конца программы поместите в
       конце программы псевдосегмент типа:

          ZSEG    SEGMENT
                  ;
          ZSEG    ENDS

          В ассемблере IBM PC ZSEG  будет  последним  сегментом, так как
       сегменты располагаются в алфавитном порядке.  С другими ассембле-
       рами нужно действительно  поместить эти строки в конце программы.
       В самой программе достаточно  поставить оператор MOV AX,ZSEG и AX
       будет указывать на первый свободный сегмент памяти за программой.
          Такой  подход  будет  работать до тех пор, пока  программа  не
       будет предполагать о наличии  памяти,  которой на самом деле нет.
       Он  не будет также работать в многопользовательской среде,  когда
       несколько программ могут делить  между собой одну и ту же область
       адресов.  Для решения этой проблемы MS DOS имеет возможность отс-
       леживать 640K системной памяти и отводить по требованию программы
       блоки памяти любого размера. Блок памяти - это просто непрерывная
       область памяти,  его  максимальный  размер  определяется размером
       доступной  памяти, в частности, он может быть больше одного  сег-
       мента (64K). Если затребован  слишком большой блок, то DOS выдает
       сообщение об ошибке. Любая возможность перекрытия блоков исключе-
       


                                     -23-
       на. Кроме того MS DOS  может  освобождать,  урезать или расширять
       существующие  блоки.  Хотя программа не обязана использовать  эти
       средства, но удобно  и  предусмотрительно  делать  это. Некоторые
       функции DOS требуют, чтобы были использованы средства  управления
       памятью DOS, например,  завершение  резидентной программы [1.3.4]
       или вызов другой программы из данной [1.3.2].
          Прежде  чем отвести память, существующий блок (вся  память  от
       начала программы до конца)  должен  быть обрезан до размера прог-
       раммы.  Затем, при создании блока, DOS создает 16-байтный  управ-
       ляющий блок  памяти,  который  расположен  непосредственно  перед
       блоком памяти. Первые 5 байтов этого блока имеют следующее значе-
       ние:

          байт 0     ASCII 90 - если последний блок в цепочке, иначе
                     ASCII 77.
          байты 1-2  0 если блок освобожден
          байты 3-4  размер блока в 16-байтных параграфах

          DOS обращается к блокам по цепочке.   Адрес первого блока хра-
       нится  во внутренней переменной.  Значение этой переменной позво-
       ляет DOS определить  положение  первого  отведенного  блока, а из
       информации,  содержащейся в нем, может быть найден следующий блок
       и т.д., как показано на рис.  1-4. Как только Вы начали использо-
       вать  систему  распределения памяти DOS, то Вы обязаны  придержи-
       ваться ее.  Если программа изменит содержимое управляющего блока,
       то  цепочка  будет разорвана и DOS начнет выдавать  сообщения  об
       ошибке.
          MS DOS обеспечивает три  функции  распределения памяти, номера
       от 48H до 4AH прерывания 21H.  Функция 48H отводит блок памяти, а
       49H - освобождает блок памяти.   Третья  функция ("SETBLOCK") ме-
       няет  размер памяти, отведенной для программы; эта функция должна
       быть использована перед  двумя  остальными.  После  ее выполнения
       можно  спокойно отводить и освобождать блоки  памяти.   Программа
       должна  освободить  все  отведенные  ею  блоки перед завершением.
       Иначе  эта память будет недоступной для последующего  использова-
       ния.

          Средний уровень.

          Все три функции распределения памяти прерывания 21H используют
       16-битный  адрес  начала блока памяти, с которым  они  оперируют.
       Этот адрес  соответствует  сегменту,  с  которого начинается блок
       (блок  всегда начинается со смещения 0 данного сегмента).   Таким
       образом реальный адрес  ячейки  начала  блока равен этому адресу,
       умноженному  на  16.  Также, для всех трех функций,  BX  содержит
       число 16-байтных  разделов  памяти  (параграфов),  которые  будут
       отводиться или освобождаться. Если функция не может быть выполне-
       на, то устанавливается  флаг  переноса,  а  в AX возвращается код
       ошибки, объясняющий причину. Возможны три кода ошибки:

          7   разрушен управляющий блок памяти
          8   недостаточно памяти для выполнения функции
          9   неверный адрес блока памяти

       Функция отведения блока использует коды 7 и 8, а освобождения - 7
       и 9, в то время как  функция  изменения  блока использует все три
       кода.  В следующем примере сначала отводится блок, размером  1024
       байта. При этом BX содержит  требуемое  число 16-байтных парагра-
       фов,  а  при завершении стартовый адрес блока  равен  AX:0  (т.е.
       смещение 0 в сегменте со  значением,  содержащимся в AX).  Вторая
       часть  примера освобождает этот же блок, как и требуется при  за-
       вершении программы.  В  данном  случае  значение  полученное в AX
       помещается в ES. DOS следит за размером блока и знает какое коли-
       


                                     -24-
       чество параграфов надо освободить.

       ;---отведение блока размером 1024 байта
          MOV   AH,48H      ;номер функции
          MOV   BX,64       ;требуем 64 параграфа
          INT   21H         ;пытаемся отвести блок
          JC    ERROR       ;обрабатываем ошибку в случае неудачи
          MOV   BLOCK_SEG,AX;иначе сохраняем адрес блока
           .
       ;---освобождаем тот же блок
          MOV   AX,BLOCK_SEG ;получаем стартовый адрес блока
          MOV   ES,AX        ;помещаем его в ES
          MOV   AH,49H       ;номер требуемой функции
          INT   21H          ;освобождаем блок памяти

          Наконец, приведем пример использования функции 4AH.  ES содер-
       жит  значение сегмента PSP, т.е.  самого первого байта памяти,  с
       которого загружена программа.  Это  значение присваивается ES при
       старте задачи.  Для использования SETBLOCK надо либо вызывать эту
       функцию в самом начале  программы  (прежде чем ES будет изменен),
       либо сохранить его начальное значение для последующего  использо-
       вания.
          BX содержит требуемый  размер  блока  в 16-байтных параграфах.
       Для определения этого размера поместите добавочный "искуственный"
       сегмент в конец программы.  В  макроасссемблере  IBM  PC сегменты
       располагаются  в алфавитном порядке, поэтому Вы можете  поместить
       его в любое место программы, при условии,  что его имя это что-то
       вроде  "ZSEG".  В других ассемблерах действительно помещайте фик-
       тивный сегмент в конец программы. Программа может прочитать пози-
       цию этого сегмента и, сравнивая ее со стартовым сегментом,  полу-
       чить количество памяти, требуемое самой программе.  В момент заг-
       рузки программы и ES и DS содержат номер параграфа самого  начала
       программы в  префиксе  программного  сегмента;  для COM файлов CS
       также указывает на эту позицию, но для EXE файлов это не так.
       ---освобождение памяти (ES имеет значение при старте)
          MOV   BX,ZSEG      ;получаем # параграфа конца программы + 1
          MOV   AX,ES        ;получаем # параграфа начала программы
          SUB   BX,AX        ;вычисляем размер программы в параграфах
          MOV   AH,4AH       ;номер функции
          INT   21H          ;освобождаем память
          JC    MEMORY_ERROR ;проверяем на ошибку

       ;---
          ZSEG      SEGMENT
          ZSEG      ENDS

          1.3.2 Запуск одной программы из другой.

          MS  DOS обеспечивает функцию EXEC (номер 4BH прерывания  21H),
       реализующую вызов одной  программы  из  другой.  Первая программа
       называется "родителем", а загружаемая и запускаемая - "потомком".

          Высокий уровень.

          В  Бейсик версии 3.0 введена команда SHELL.  Со  значительными
       ограничениями она  позволяет  бейсиковской  программе загрузить и
       выполнить другую программу. Формат этой команды SHELL ком_строка.
       Командная строка может быть просто именем программы или она может
       содержать кроме имени параметры, которые обычно следуют за именем
       программы в  командной  строке.  Если  ком_строка  не указана, то
       загружается  копия  COMMAND.COM и появляется запрос  операционной
       системы. В этот момент можно выполнить любую команду MS DOS, а по
       завершению  вернуть управление бейсиковской программе, введя  ко-
       


                                     -25-
       манду EXIT.

          Имеется ряд ограничений при  использовании  SHELL. Если загру-
       жаемая  программа  меняет  режим работы дисплея, то он  не  будет
       автоматически восстановлен при возврате. Перед загрузкой програм-
       мы все файлы должны быть закрыты, и это не может быть  программа,
       которая остается резидентной  после  завершения.  Обсуждение ряда
       других проблем содержится в руководстве по Бейсику.

          Средний уровень.

          Функция 4BH более сложна, чем остальные, требуя четырех подго-
       товительных шагов:

          1. Подготовить в памяти место, доступное программе.
          2. Создать блок параметров.
          3. Построить строку,  содержащую  накопитель, путь и имя прог-
       раммы.
          4. Сохранить значения регистров SS и SP в переменных.

          Поскольку  при загрузке программы MS DOS выделяет ей всю  дос-
       тупную память, то необходимо  освободить  место в памяти. Если не
       освободить  часть  памяти, то не будет места для загрузки  второй
       программы.  В [1.3.1] объяснено как это сделать с помощью функции
       SETBLOCK.   После  того как память освобождена, Вы должны  просто
       поместить в BX требуемое число 16-байтных параграфов, заслать 4AH
       в AH и выполнить прерывание 21H, делая доступным программе именно
       то число параграфов, которое ей требуется.
          Блок   параметров,  на  который  должны  указывать  ES:BX  это
       14-байтный блок блок памяти, в  который  Вы должны поместить сле-
       дующую информацию:

          DW   сегментный адрес строки среды
          DD   сегмент и смещение командной строки
          DD   сегмент и смещение первого FCB
          DD   сегмент и смещение второго FCB

          Строка среды - это строка, состоящая из одной или более специ-
       фикаций, которым следует MS DOS при выполнении программы. Элемен-
       ты  строки среды такие же, как и те что можно обнаружить в диско-
       вом файле  CONFIG.SYS.   Например,  в  строку может быть помещено
       VERIFY  = ON.  Просто начните строку с первого элемента, завершив
       его символом ASCII 0, потом запишите  следующий и т.д. За послед-
       ним элементом должны следовать два символа ASCII 0. Строка должна
       начинаться на границе параграфа (т.е.  ее адрес по модулю 16 дол-
       жен быть равен нулю). Это вызвано тем, что соответствующий вход в
       блоке параметров, указывающий на строку,  содержит только 2-байт-
       ное  сегментное значение.  Все это не нужно, если новая программа
       может работать с той  же  строкой  среды,  что и программа "роди-
       тель".  В этом случае надо просто поместить два символа ASCII 0 в
       первые 2 байта блока параметров.
          Следующие  4  байта  блока параметров указывают  на  командную
       строку для загружаемой  программы.  "Командная строка" - это сим-
       вольная строка, определяющая способ работы программы. При загруз-
       ке программы из DOS она может иметь вид вроде EDITOR  A:CHAPTER1\
       NOTES.MS. При этом вызывается редактор и ему передается имя файла
       в подкаталоге накопителя A для  немедленного  открытия.  Когда Вы
       подготавливаете командную строку для EXEC, то надо включать толь-
       ко последнюю часть информации,  но  не имя загружаемой программы.
       Перед командной строкой должен стоять байт, содержащий длину этой
       строки, и она должна завершаться символом <ВК> (ASCII 13).
          Последние 8 байтов блока  параметров  указывают на управляющие
       блоки  файлов (FCB).  FCB содержит информацию об одном  или  двух
       


                                     -26-
       файлах, указанных в командной  строке.   Если  открываемых файлов
       нет,  то  надо заполнить все 8 байт символом ASCII 0.  В  [5.3.5]
       объяснено, как работает FCB. Начиная с версии MS DOS 2.0, исполь-
       зование FCB необязательно и Вы можете не включать информацию FCB,
       вместо этого используя новую  конвенцию  дескриптора файлов (file
       handler),  в  которой доступ к файлу предоставляется по  кодовому
       номеру, а не через FCB (также обсуждается в [5.3.5]).
          Наконец, Вы должны построить  строку  с  указанием накопителя,
       пути  и  имени файла.  Эта строка именует загружаемую  программу.
       DS:DX указывает на эту строку  при  выполнении EXEC. Эта строка -
       стандартная  строка ASCIIZ, т.е.  ничего более,  чем  стандартная
       спецификация  файла,  завершаемая  кодом  ASCII 0.  Например, это
       может  быть B:\NEWDATA\FILER.EXE<NUL>, где символом <NUL> обозна-
       чен код ASCII 0.
          После того как вся указанная информация подготовлена, остается
       последняя задача.  Поскольку все регистры будут изменены вызывае-
       мой задачей, то надо сохранить сегмент стека и указатель стека, с
       тем  чтобы  они могли быть восстановлены, когда управление  будет
       возвращено вызвавшей задаче.  Для их сохранения создайте перемен-
       ные.  Поскольку значение регистра DS также будет изменено, то эти
       переменные не могут быть найдены, до тех пор пока не будут повто-
       рены операторы MOV AX,DSEG и  MOV  DS,AX.  После того как SS и SP
       сохранены,  поместите  0  в AL, для выбора операции  "загрузка  и
       запуск" (EXEC  используется  также  для  оверлеев [1.3.5]). Затем
       поместите 4AH в AH и вызовите прерывание 21H. В этот момент запу-
       щены две программы, причем программа "родитель" находится в оста-
       новленном состоянии.  MS DOS предоставляет возможность  программе
       потомку передать родителю код  возврата, таким образом могут быть
       переданы  ошибки и статус.  В [7.2.5] объяснено как это  сделать.
       Что касается самой функции  запуска,  то при возникновении ошибки
       устанавливается  флаг переноса, а регистр AX в этом случае  будет
       возвращать 1 - для неправильного номера функции, 2 - если файл не
       найден,  5  - при дисковой ошибке, 8 - при нехватке памяти, 10  -
       если неправильна строка среды и 11 - если неверен формат.
          Приводимый пример - простейший  из  возможных, но часто больше
       ничего  и не надо.  Здесь оставлен нулевым блок  параметров и  не
       создана строка среды. Это  означает, что загружаемой программе не
       будет  передаваться командная строка и что среда будет такой  же,
       как и для вызывающей программы. Вы должны только изменить распре-
       деление  памяти, создать имя и (пустой) блок параметров и  сохра-
       нить значения SS и SP.

       ;---в сегменте данных
       FILENAME     DB   'A:TRIAL.EXE',0  ;загружаем TRIAL.EXE
       PARAMETERS   DW   7DUP(0)          ;нулевой блок параметров
       KEEP_SS      DW   0                ;переменная для SS
       KEEP_SP      DW   0                ;переменная для SP

       ;---перераспределение памяти
          MOV   BX,ZSEG          ;получить # параграфа конца
          MOV   AX,ES            ;получить # параграфа начала
          SUB   BX,AX            ;вычислить размер программы
          MOV   AH,4AH           ;номер функции
          INT   21H              ;перераспределение
       ;---указываем на блок параметров
          MOV   AX,SEG PARAMETERS      ;в ES - сегмент
          MOV   ES,AX                  ;
          MOV   BX,OFFSET PARAMETERS   ;в BX - смещение
       ;---сохранить копии SS и SP
          MOV   KEEP_SS,SS       ;сохраняем SS
          MOV   KEEP_SP,SP       ;сохраняем SP
       ;---указываем на строку имени файла
          MOV   DX,OFFSET FILENAME     ;смещение - в DX
       


                                     -27-
          MOV   AX,SEG FILENAME        ;сегмент - в DS
          MOV   DS,AX                  ;

       ;---загрузка программы
          MOV   AH,4BH           ;функция EXEC
          MOV   AL,0             ;выбираем "загрузку и запуск"
          INT   21H              ;запускаем задачу
       ;---впоследствии, восстанавливаем регистры
          MOV   AX,DSEG          ;восстанавливаем DS
          MOV   DS,AX            ;
          MOV   SS,KEEP_SS       ;восстанавливаем SS
          MOV   SP,KEEP_SP       ;восстанавливаем SP

       ;---в конце программы создаем фиктивный сегмент
       ZSEG     SEGMENT          ;см. [1.3.1]
       ZSEG     ENDS

          1.3.3 Использование команд интерфейса с пользователем из прог-
       раммы.

          Программа может иметь в своем распоряжении полный набор команд
       интерфейса с пользователем DOS, таких  как DIR или CHKDSK.  Когда
       эти команды используются из программы, загружается и  запускается
       вторая копию  COMMAND.COM.  Хотя  такой  подход  может сэкономить
       много  усилий  при программировании, для его успешной  реализации
       требуется достаточное  количество  памяти для этой второй копии и
       Ваша программа может попасть в ловушку если памяти недостаточно.

          Высокий уровень.

          Бейсик  3.0 может загрузить вторую копию COMMAND.COM с помощью
       оператора SHELL.  SHELL обсуждается в [1.3.2]. COMMAND.COM загру-
       жается когда не указано имя файла, поэтому вводя просто SHELL, Вы
       получаете запрос MS DOS. В этот  момент  можно использовать любую
       из утилит DOS, включая командные файлы.  Для возврата в вызвавшую
       программу надо ввести EXIT.

          Средний уровень.

          В этом случае к примеру, приведенному в [1.3.2] нужно добавить
       командную  строку.  Обычно она начинается с байта  длины  строки,
       затем следует сама командная строка и, наконец, код ASCII 13. При
       передаче  команды COMMAND.COM Вы должны указать /C перед  строкой
       (см. пункт "Вызов вторичного  командного  процессора" руководства
       по MS DOS).  Вы должны также указать накопитель, на котором нахо-
       дится COMMAND.COM,  поместив  имя  накопителя  в начале командной
       строки.   Чтобы вывести каталог накопителя A:, а COMMAND.COM  при
       этом находится на накопителе B:, нужна строка:

          COMMAND_LINE   DB   12,'B: /C DIR A:',13

          Следующий  кусочек кода устанавливает адрес командной строки в
       блок параметров, используемый в примере [1.3.2]:

          LEA   BX,PARAMETERS            ;получение адреса блока пар-ров
          MOV   AX,OFFSET COMMAND_LINE   ;получение смещения ком. строки
          MOV   [BX]+2,AX                ;пересылка в 1-е 2 байта блока
          MOV   AX,SEG COMMAND_LINE      ;получение сегмента ком. строки
          MOV   [BX]+4,AX                ;пересылка во 2-е 2 байта блока

          1.3.4 Сохранение программы в памяти после завершения.

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


                                     -28-
       качестве утилит для других программ. Обычно такие программы вызы-
       ваются через неиспользуемый вектор  прерывания. MS DOS рассматри-
       вает  такие программы как часть операционной системы, защищая  их
       от наложения других программ, которые  будут загружены впоследст-
       вии. Резидентные программы обычно пишутся в форме COM, что обсуж-
       дается в пункте [1.3.6].  Программы, написанные в форме EXE оста-
       вить резидентными в памяти немного труднее.
          Завершение  программы прерыванием 27H оставляет ее резидентной
       в памяти.  CS должен указывать  на начало PSP для того, чтобы эта
       функция работала правильно. В программах COM, CS сразу устанавли-
       вается  соответствующим  образом,  поэтому  надо просто завершить
       программу  прерыванием 27H.  В программах EXE , CS  первоначально
       указывает на первый байт, следующий  за PSP (т.е. 100H). При нор-
       мальном завершении EXE программы последняя инструкция RET  вытал-
       кивает из стека  первые  положенные  туда значения: PUSH DX / MOV
       AX,0  / PUSH AX.  Поскольку DS первоначально указывает на  начало
       PSP, то при получении этих значений  из стека счетчик команд ука-
       зывает  на смещение 0 в PSP, где при  инициализации  записывается
       инструкция INT 20H. Поэтому INT  20H выполняется, а это стандарт-
       ная функция для завершения программы и передачи управления в DOS.
       На рис. 1-5 показан этот процесс.  Чтобы заставить прерывание 27H
       работать  в EXE программе надо поместить 27H во второй  байт  PSP
       (первый содержит машинный код  инструкции INT), а затем завершить
       программу обычным RET.  Для обоих типов файлов прежде чем  выпол-
       нить прерывание 27H, DX должен  содержать смещение конца програм-
       мы, отсчитываемое от начала PSP.

          Средний уровень.

          Вектор прерывания устанавливается с помощью функции 25H преры-
       вания 21H, как  показано  в  [1.2.3]  (здесь  используется вектор
       70H).   Позаботьтесь,  чтобы процедура оканчивалась IRET.   Кроме
       самой процедуры, устанавливаемая программа не должна делать ниче-
       го, кроме инициализации вектора прерывания, присвоения DX  значе-
       ния смещения конца процедуры и завершения.  Для COM файлов просто
       поместите  оператор  INT 27H в конец программы.  Для  EXE  файлов
       поместите этот оператор в первое  слово PSP и завершите программу
       обычным оператором RET. Для того чтобы выполнить процедуру, впос-
       ледствии загруженная программа должна вызвать INT 70H.
          Приведены примеры для обоих типов файлов (COM и EXE).  В обоих
       установлена  метка FINISH для отметки конца процедуры  прерывания
       (напоминаем, что знак $  дает  значение  счетчика  команд  в этой
       точке).  Для COM файлов FINISH дает смещение от начала PSP, как и
       требуется для прерывания  27H.  Для  EXE файлов смещение отсчиты-
       вается  от первого байта, следующего за PSP, поэтому к нему необ-
       ходимо прибавить 100H, чтобы пересчитать  на начало PSP. Заметим,
       что  поместив  процедуру в начало программы, мы  можем  исключить
       установочную часть кода из  резидентной  порции. Другой возможный
       фокус состоит в использовании инструкции MOVSB для пересылки кода
       процедуры вниз в  неиспользуемую  часть  PSP, начиная со смещения
       60H, что освобождает 160 байт памяти.

          Случай файла COM:
       ;---здесь процедура прерывания
       BEGIN:     JMP   SHORT SET_UP  ;переход на установку
       ROUTINE    PROC  FAR
                  PUSH  DS            ;сохранение регистров
                   .
             (процедура)
                   .
                  POP   DS            ;восстановление регистров
                  IRET                ;возврат из прерывания
       FINISH     EQU   $             ;отметка конца процедуры
       


                                     -29-
       ROUTINE    ENDP

       ;---установка вектора прерывания
       SET_UP:    MOV   DX,OFFSET ROUTINE  ;смещение процедуры в DX
                  MOV   AL,70H             ;номер вектора прерывания
                  MOV   AH,25H             ;функция установки вектора
                  INT   21H                ;устанавливаем вектор
       ;---завершение программы, оставляя резидентной
                  LEA   DX,FINISH          ;определяем треб. смещение
                  INT   27H                ;завершение

          Случай файла EXE:

       ;---здесь резидентная процедура
                  JMP   SHORT SET_UP   ;переход на установку
       ROUTINE    PROC  FAR
                  PUSH  DS             ;сохранение регистров
                   .
              (процедура)
                   .
                  POP   DS             ;восстановление регистров
                  IRET                 ;возврат из прерывания
       FINISH     EQU   $              ;отметка конца процедуры
       ROUTINE    ENDP

       ;---установка вектора прерывания
       SET_UP:    MOV   DX,OFFSET ROUTINE  ;смещение процедуры в DX
                  MOV   AX,SEG ROUTINE     ;сегмент процедуры в DS
                  MOV   DS,AX              ;
                  MOV   AL,70H             ;номер вектора прерывания
                  MOV   AH,25H             ;функция установки вектора
                  INT   21H                ;установка вектора
       ;---завершение программы
                  MOV   DX,FINISH+100H     ;вычисляем смещение конца
                  MOV   BYTE PTR ES:1,27H  ;посылаем 27H в PSP
                  RET                      ;завершаем процедуру

          Функция 31H прерывания 21H работает аналогично, за исключением
       того, что в DX  должно  содержаться  число 16-байтных параграфов,
       требуемых  процедуре  (вычисление размера процедуры,  начиная  от
       начала PSP - см.  в примере [1.3.1]).  Преимуществом этой функции
       является  то, что она передает родительской программе код выхода,
       дающий информацию о  статусе  процедуры.  Родительская  программа
       получает  этот  код с помощью функции 4DH прерывания  21H.   Коды
       выхода обсуждаются в [7.2.5].

          1.3.5 Загрузка и запуск программных оверлеев.

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

          Средний уровень.

          MS DOS использует  функцию  EXEC  для  загрузки  оверлеев. Эта
       


                                     -30-
       функция, номер 4BH прерывания 21H, используется также для загруз-
       ки и запуска одной программы из  другой,  если  поместить код 0 в
       AL  [1.3.2].  Если в AL поместить код 3, то тогда будет  загружен
       оверлей. В этом случае не создается PSP, поэтому оверлей не уста-
       навливается  как независимая программа.  Такая  процедура  просто
       загружает оверлей, не передавая ему управления.
          Имеется два способа обеспечить память для оверлея.  Может быть
       использована либо область внутри тела программы, либо  специально
       отведена область памяти за пределами головной программы.  Функции
       EXEC передается только сегментный адрес, в качестве позиции, куда
       будет загружен оверлей. Когда оверлей загружается в тело головной
       программы,  то программа должна вычислить номер  параграфа,  куда
       будет загружаться оверлей, сама. С другой стороны, при загрузке в
       специально отведенную память MS DOS обеспечивает программу  номе-
       ром параграфа.
          В нижеприведенном примере  используется  загрузка в отведенную
       память.  Поскольку DOS отводит программе всю доступную память, то
       сначала необходимо освободить память с помощью функции 4AH. Функ-
       ция  48H  отводит  блок памяти достаточно большой, чтобы  он  мог
       принять самый большой из оверлеев.  Эта функция возвращает значе-
       ние  сегмента блока в AX, и этот номер параграфа определяет  куда
       будет загружен оверлей, а также  по  какому  адресу оверлей будет
       вызываться головной программой.  Эти функции детально обсуждаются
       в [1.3.1].
          Кроме кода 3, засылаемого в  AL, Вы должны установить для этой
       функции еще два параметра. DS:DX должны указывать на строку, даю-
       щую путь к файлу оверлея, завершаемую байтом ASCII 0.  Необходимо
       указывать  полное  имя  файла,  включая расширение .COM или .EXE,
       поскольку DOS в данном случае не считает, что он ищет программный
       файл.
          Наконец,  ES:BX должны указывать на 4-байтный блок параметров,
       который содержит (1) 2-байтный номер параграфа, куда будет загру-
       жаться  оверлей  и (2) 2-байтный фактор привязки,  который  будет
       использоваться для привязки  адресов  в  оверлее (привязка объяс-
       няется в [1.3.6]).  В качестве номера параграфа надо использовать
       число,  возвращаемое в AX, для номера параграфа отведенного блока
       памяти.  Фактор привязки  дает  смещение,  по которому могут быть
       вычислены адреса требующих привязки параметров в оверлее. Исполь-
       зуйте номер параграфа, куда загружается  оверлей.  После того как
       он установлен, вызовите функцию и оверлей будет загружен.  Просто
       изменяя путь к  оверлейному  файлу,  можно вновь и вновь вызывать
       эту функцию, загружая все новые и новые оверлеи. Если при возвра-
       те установлен флаг переноса, то  была ошибка и ее код будет возв-
       ращен в AX.  Код равен 1, если указан неверный номер функции, 2 -
       если файл не найден, 5 - при дисковых  ошибках и 8 - при отсутст-
       вии достаточной памяти.
          После  того как оверлей загружен в память, к нему можно  полу-
       чить доступ как к  далекой  (far)  процедуре.  В  сегменте данных
       должен  быть установлен двухсловный указатель, определяющий  этот
       вызов.  Сегментная часть указателя просто равна текущему кодовому
       сегменту.   Смещение  оверлея должно быть  вычислено  нахождением
       разницы между сегментами кода и  оверлея  и умножением результата
       на 16 (переводя величину из параграфов в байты).  В нижеприведен-
       ном примере две  переменные  OVERLAY_OFFSET  и  CODE_SEG помещены
       одна за другой для правильной установки указателя. Однажды загру-
       женный, оверелей затем  можем  вызываться  инструкцией CALL DWORD
       PTR OVERLAY_OFFSET.
          Оверлей  может  быть  полной программой со  своими  сегментами
       данных и стека, хотя как  правило  используется  стековый сегмент
       вызывающей  программы.  При вызове оверлея значение сегмента  его
       собственного сегмента данных должно быть помещено в DS.

       ;---завершаем программу фиктивным сегментом (см. [1.3.1]):
       


                                     -31-
       ZSEG         SEGMENT
       ZSEG         ENDS

       ;---в сегменте данных
       OVERLAY_SEG    DW    ?
       OVERLAY_OFFSET DW    ?             ;смещение оверлея
       CODE_SEG       DW    ?             ;сегмент оверлея - должен
       PATH           DB    'A:OVERLAY.EXE' ;следовать за смещением
       0BLOCK         DD    0             ;4-байтный блок параметров

       ;---освобождаем память
          MOV   CODE_SEG,CS     ;создаем копию CS
          MOV   AX,ES           ;копируем значение сегмента PSP
          MOV   BX,ZSEG         ;адрес сегмента конца программы
          SUB   BX,AX           ;вычисляем разность
          MOV   AH,4AH          ;номер функции SETBLOCK
          INT   21H             ;освобождаем память
          JC    SETBLK_ERR      ;флаг переноса говорит об ошибке
       ;---отводим память для оверлея
          MOV   BX,100H         ;отводим для оверлея 1000H байт
          MOV   AH,48H          ;функция отведения памяти
          INT   21H             ;теперь AX:0 указывает на блок
          JC    ALLOCATION_ERR  ;флаг переноса говорит об ошибке
          MOV   OVERLAY_SEG,AX  ;запасаем адрес сегмента оверлея
       ;---вычисление смещения оверлея в кодовом сегменте
          MOV   AX,CODE_SEG     ;вычитаем значение сегмента оверлея
          MOV   BX,OVERLAY_SEG  ;из значения сегмента кода
          SUB   BX,AX           ;BX содержит число параграфов
          MOV   CL,4            ;сдвигаем это число на 4 бита влево
          SHL   BX,CL           ;чтобы получить величину в байтах
          MOV   OVERLAY_OFFSET,BX  ;запоминаем смещение
       ;---загрузка первого оверлея
          MOV   AX,SEG BLOCK    ;ES:BX указывает на блок параметров
          MOV   ES,AX           ;
          MOV   BX,OFFSET BLOCK ;
          MOV   AX,OVERLAY_SEG  ;помещаем адрес сегмента оверлея в
          MOV   [BX],AX         ;первое слово блока параметров
          MOV   [BX]+2,AX       ;сегмент оверлея - фактор привязки
          LEA   DX,PATH         ;DS:DX указывает на путь к файлу
          MOV   AH,48H          ;номер функции EXEC
          MOV   AL,3            ;код загрузки оверлея
          INT   21H             ;загружаем оверлей
          JC    LOAD_ERROR      ;флаг переноса говорит об ошибке

       ;---теперь программа занимается своими делами
           .
           .
          CALL  DWORD PTR OVERLAY_OFFSET  ;вызов оверлея
           .     ;нужно указывать DWORD PTR, так как оверлей -
           .     ;далекая процедура

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

       CSEG     SEGMENT     PARA PUBLIC 'CODE'
       OVERLAY  PROC FAR      ;всегда "далекая" процедура
                ASSUME CS:CSEG,DS:DSEG
                PUSH   DS     ;храним DS вызывающей программы
                MOV    AX,DSEG;устанавливаем DS оверлея
                MOV    DS,AX
       


                                     -32-
                 .
                 .
                POP    DS     ;восстанавливаем DS при завершении
                RET
       OVERLAY  ENDP
       CSEG     ENDS
                END

          1.3.6 Преобразование программ из типа .EXE в тип .COM.

          Программисты  на  ассемблере имеют  возможность  преобразовать
       свои программы из обычного  формата  EXE в формат COM.  Файлы EXE
       имеют заголовок, содержащий информацию для привязки; DOS привязы-
       вает некоторые адреса программы при  загрузке.  С другой стороны,
       файлы  COM существуют в таком виде, что привязка не  требуется  -
       они хранятся уже в том  виде,  в  котором  загружаемая  программа
       должна быть в памяти машины. По этой причине файлы EXE по меньшей
       мере на 768 байтов больше на  диске, чем их COM эквиваленты (хотя
       при загрузке в память они будут занимать одинаковое место). Файлы
       COM также быстрее загружаются,  поскольку  не требуется привязки.
       Других преимуществ у них нет, а некоторые программы слишком слож-
       ны и слишком велики, чтобы их можно было преобразовать в тип COM.
          Привязка - это процесс установки адресов, связанных с сегмент-
       ным  регистром.   Например, программа может указывать  на  начало
       области данных следующим кодом:
          MOV   DX,OFFSET DATA_AREA
          MOV   AX,SEG DATA_AREA
          MOV   DS,AX
       Смещение в DX связано с  установкой  сегментного регистра DS.  Но
       какое  значение должен принимать сам DS? Программа требует  абсо-
       лютный адрес, но номер  параграфа,  в котором будет располагаться
       DATA_AREA зависит от того, в какое место в памяти будет загружена
       программа - а это  зависит  от  версии  MS  DOS, а также от того,
       какие  резидентные  программы будут находиться в младших  адресах
       памяти. По этой причине во время компоновки программы можно толь-
       ко установить некоторые сегментные значения через смещения  отно-
       сительно начала программы.  Затем, когда DOS осуществляет привяз-
       ку, значение начального адреса программы  прибавляется к сегмент-
       ным  значениям, давая абсолютные адреса,  требуемые в  сегментном
       регистре. На рис. 1-6 показан процесс привязки.
          Файлы  COM  не нуждаются в привязке, поскольку они хранятся  в
       таком виде, что не нуждаются в фиксации сегмента. Все в программе
       хранится относительно начала кодового сегмента, включая все  дан-
       ные и стек. По  этой  причине  вся  программа  не может превышать
       65535  байт  по длине, что соответствует максимальному  смещению,
       которое существует  в  используемой  схеме  адресации  (поскольку
       верхняя часть этого блока занята стеком, то реальное пространство
       доступное для кода и данных  немного  меньше чем 65535 байт, хотя
       стековый  сегмент при необходимости может быть вынесен за границу
       64K байтного блока).  В файлах COM все сегментные регистры указы-
       вают на начало PSP; сравните с файлами EXE, где DS и ES инициали-
       зируются  аналогичным  образом,  но  CS  указывает на первый байт
       следующий за PSP.
          Для  представления программы в виде файла COM требуется соблю-
       дение следующих правил:

          1.  Не оформляйте программу в  виде  процедуры.  Вместо этого,
       поместите в самое начало метку, вроде START, и завершите програм-
       му оператором END START.
          2. Поместите в начале программы оператор ORG 100H. Этот опера-
       тор указывает начало кода (т.е.  устанавливает счетчик  комманд).
       Программы COM  начинаются  с  100H,  что  является первым байтом,
       следующим  за PSP, поскольку CS указывает на начало PSP,  которое
       


                                     -33-
       расположено на 100H байт ниже. Для того чтобы начать выполнение с
       любого другого места поместите по адресу 100H инструкцию JMP.
          3.   Оператор  ASSUME должен устанавливать DS, ES и  SS  таким
       образом, чтобы они совпадали со значением  для кодового сегмента,
       например, ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG.
          4.  Данные программы могут помещаться в любом месте программы,
       до тех пор, пока они не перемешаны с кодом.  Лучше начинать прог-
       раммы  с области данных, поскольку макроассемблер может  выдавать
       сообщения об ошибках при первом  проходе,  если имеются ссылки на
       идентификатор  данных, который еще не обнаружен.  Для перехода  к
       началу кода используйте в качестве первой команды программы инст-
       рукцию JMP.
          5.   Нельзя  использовать фиксацию сегментов типа  MOV  AX,SEG
       NEW_DATA. Достаточно указания одного смещения метки. В частности,
       нужно  опускать обычный код, используемый в начале программы  для
       установки сегмента данных, MOV AX,DSEG / MOV DS,AX.
          6. Стековый сегмент  полностью  опускается  в  начальном коде.
       Указатель стека инициализируется на вершину адресного пространст-
       ва 64K,  используемого  программой  (напоминаем,  что стек растет
       вниз в памяти). В программах COM он должен быть сделан меньше чем
       64K, SS и SP могут быть изменены. Имейте ввиду, что при компонов-
       ке программы компоновщик выдаст сообщение об ошибке, указывающее,
       что сегмент стека отсутствует. Игнорируйте его.
          7. Завершите программу либо  инструкцией RET, либо прерыванием
       20H.   Прерывание  20H - это стандартная функция  для  завершения
       программы и возврата управления  в  DOS. Даже когда программа за-
       вершается  инструкцией RET, на самом деле используется прерывание
       20H.  Это происходит потому, что  вершина стека первоначально со-
       держит 0.  При выполнении завершающей инструкции программы RET, 0
       выталкивается из  стека,  переназначая  счетчик  команд на начало
       PSP.  Находящаяся в этой ячейке функция 20H, выполняется как сле-
       дующая инструкция программы,  вызывая  передачу управления в DOS.
       Все это означает, что Вам не  надо  при старте программы помещать
       на стек DS и 0 (PUSH DS / MOV AX,0 / PUSH AX), как это  требуется
       для EXE файлов.
          После того как  программа  сконструирована  таким образом, ас-
       семблируйте и компонуйте ее как обычно.  Затем преобразуйте ее  в
       форму COM c помощью утилиты EXE2BIN, имеющейся в MS DOS. Если имя
       программы, построенной компоновщиком MYPROG.EXE, то просто введи-
       те команду EXE2BIN MYPROG. В  результате  Вы получите программный
       файл с именем MYPROG.BIN.  Все что Вам останется после этого сде-
       лать - переименовать этот  файл  в  MYPROG.COM.   Вы можете также
       сразу использовать команду EXE2BIN MYPROG MYPROG.COM, для получе-
       ния файла с расширением COM.

          Низкий уровень.

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

       CSEG          SEGMENT
                     ORG 100H
                     ASSUME CS:CSEG, DS:CSEG, SS:CSEG
       ;---данные
       START:        JMP  SHORT BEGIN  ;переход к коду
       MESSAGE1      DB   'The dip switches are set for $'
       MESSAGE2      DB   'disk drive(s).$'
       ;---печать первой части сообщения
       BEGIN:        MOV  AH,9    ;функция 9 прерывания 21H - вывод
                     MOV  DX,OFFSET MESSAGE1  ;строки
       


                                     -34-
                     INT  21H     ;выводим строку
                     PUSH AX      ;сохраняем номер функции на будущее
       ;---получаем установку переключателей из порта A микросхемы 8255
                     IN   AL,61H  ;получаем байт из порта B
                     OR   AL,10000000B  ;устанавливаем бит 7
                     OUT  61H,AL        ;заменяем байт
                     IN   AL,60H        ;получаем установку переключат.
                     AND  AL,11000000B  ;выделяем старшие 2 бита
                     MOV  CL,6          ;подготовка к сдвигу AL вправо
                     SHR  AL,CL         ;сдвигаем 2 бита в начало
                     ADD  AL,49         ;добавляем 1, чтобы считать с 1
                                        ;и 48 для перевода в ASCII
                     MOV  DL,AL         ;помещаем результат в DL
                     MOV  AL,61H        ;должны восстановить порт B
                     AND  AL,01111111B  ;сбрасываем бит 7
                     OUT  61H,AL        ;возвращаем байт
       ;---печать числа накопителей
                     MOV  AH,2          ;функция 2 прерывания 21H
                     INT  21H           ;печатаем число из DL
       ;---печать второй половины сообщения
                     POP  AX            ;берем номер функции со стека
                     MOV  DX,OFFSET MESSAGE2
                     INT  21H           ;выводим строку
                     INT  20H           ;завершение программы
       CSEG          ENDS
                     END START

                             Глава 2. Таймеры и звук.

          Раздел 1. Установка и чтение таймера.

          Все  IBM PC используют микросхему таймера 8253 (или 8254)  для
       согласования импульсов от микросхемы системных часов.  Число цик-
       лов  системных часов преобразуется в один  импульс, а  последова-
       тельность этих импульсов  подсчитывается для определения времени,
       или  они  могут быть посланы на громкоговоритель  компьютера  для
       генерации звука определенной частоты.   Микросхема 8253 имеет три
       идентичных независимых канала, каждый из которых может программи-
       роваться.
          Микросхема 8253 работает  независимо  от процессора. Процессор
       программирует микросхему и затем обращается к другим делам. Таким
       образом 8253 действует как часы  реального  времени - она считает
       свои  импульсы независимо от того, что  происходит в  компьютере.
       Однако, максимальный  программируемый интервал составляет прибли-
       зительно 1/12 секунды.  Для подсчета интервалов времени в часы  и
       минуты нужны какие-то другие  средства.   Именно  по этой причине
       импульсы  от нулевого канала микросхемы таймера  накапливаются  в
       переменной, находящейся в области данных BIOS. Этот процесс пока-
       зан на рис.  2-1. Это накопление обычно называется подсчетом вре-
       мени суток.  18.2 раза  в  секунду  выход канала 0 обрабатывается
       аппаратным  прерыванием (прерыванием таймера), которое  ненадолго
       останавливает  процессор  и  увеличивает  счетчик  времени суток.
       Число  0  соответствует  полночи 12:00; когда  счетчик  достигает
       значения эквивалентного 24 часам, он сбрасывается на ноль. Другое
       время  в  течение суток легко  определяется  делением  показателя
       счетчика на 18.2 для каждой  секунды.   Счетчик времени суток ис-
       пользуется в большинстве операций, связанных со временем.

          2.1.1 Программирование микросхемы таймера 8253/8254.

          Каждый  из трех каналов микросхемы таймера 8253 (8254 для  AT)
       состоит из трех регистров. Доступ к каждой группе из трех регист-
       ров  осуществляется через один порт; номера портов от 40H до  42H
       


                                     -35-
       соответствуют каналам 0 -  2.  Порт  связан  с 8-битным регистром
       ввода/вывода, который посылает и принимает данные для этого кана-
       ла.  Когда канал  запрограммирован, то через этот порт посылается
       двухбайтное значение, младший байт сначала.  Это число передается
       в 16-битный регистр задвижки (latch register), который хранит это
       число и из которого копия помещается в 16-битный регистр  счетчи-
       ка. В регистре счетчика число  уменьшается на единицу каждый раз,
       когда импульс от системных часов пропускается через канал.  Когда
       значение этого числа  достигает  нуля,  то  канал выдает выходной
       сигнал и затем новая копия содержимого регистра задвижки передви-
       гается в регистр счетчика,  после  чего процесс повторяется.  Чем
       меньше число в регистре счетчика, тем быстрее ритм. Все три кана-
       ла всегда активны: процессор не включает и не выключает их. Теку-
       щее  значение любого из регистров счетчика может быть прочитано в
       любой момент времени, не влияя на счет.
          Каждый канал имеет две входные и одну выходную линии. Выходная
       линия выводит импульсы, возникающие в результате подсчета. Назна-
       чение этих сигналов варьируется в зависимости от типа IBM PC:

          Канал 0 используется системными часами времени суток. Он уста-
       навливается BIOS при  старте  таким  образом, что выдает импульсы
       приблизительно  18.2 раза в секунду.  4-байтный счетчик этих  им-
       пульсов хранится в памяти по адресу  0040:006C (младший байт хра-
       нится первым).  Каждый импульс инициирует прерывание таймера (но-
       мер 8) и именно это  прерывание  увеличивает  показание счетчика.
       Это  аппаратное  прерывание, поэтому оно  обрабатывается  всегда,
       независимо от того, чем  занят  процессор,  если только разрешены
       аппаратные прерывания (см. обсуждение в [1.2.2]).  Выходная линия
       используется также для синхронизации некоторых дисковых операций,
       поэтому если Вы изменили ее значение, то Вам необходимо восстано-
       вить первоначальное значение перед обращением к диску.

          Канал  1  управляет обновлением памяти на всех  машинах  кроме
       PCjr, поэтому его лучше не  трогать.  Выходная линия этого канала
       связана  с микросхемой прямого доступа к памяти [5.4.2] и ее  им-
       пульс заставляет  микросхему  DMA  обновить  всю память.  На PCjr
       канал  1 служит для преобразования входных данных с клавиатуры из
       последовательной в параллельную форму. PCjr не использует микрос-
       хему  прямого  доступа к памяти, поэтому когда  он  вместо  этого
       прогоняет данные через процессор, то прерывание от таймера забло-
       кировано.  Канал 1 используется для подсчета заблокированных  им-
       пульсов часов времени  суток,  с  тем  чтобы  можно было обновить
       значение счетчика после завершения дисковых операций.

          Канал  2 связан с громкоговорителем компьютера и он производит
       простые прямоугольные импульсы для  генерации звука. Программисты
       имеют  больший контроль над вторым каналом, чем  над  остальными.
       Простые звуки могут  генерироваться  одновременно с другими прог-
       раммными операциями, а более сложные звуковые эффекты могут  быть
       достигнуты за счет использования  процессора.  Канал 2 может быть
       отсоединен  от громкоговорителя и использоваться для  синхрониза-
       ции. Наконец, выходная линия канала 2 связана с динамиком компью-
       тера.   Однако динамик не будет генерировать звук до тех пор пока
       не сделаны определенные установки микросхемы интерфейса с перифе-
       рией 8255.
          Две  входные линии для каждого канала состоят из линии  часов,
       которая передает сигнал от  микросхемы  системных  часов и линии,
       называемой  воротами (gate), которая включает и выключает  сигнал
       от часов. Ворота всегда открыты для сигналов часов по каналам 0 и
       1.  Но они могут быть закрытыми для канала 2, что позволяет неко-
       торые специальные манипуляции со звуком. Ворота закрываются уста-
       новкой  младшего  бита порта с адресом 61H, который является  ре-
       гистром микросхемы 8255; сброс этого бита снова открывает ворота.
       


                                     -36-
       Эта микросхема обсуждается в  [1.1.1].  Отметим что - как и выход
       канала  2 - бит 1 порта 61H связан с динамиком и также может  ис-
       поьзоваться для генерации звука. На рис.  2-2 приведена диаграмма
       микросхемы таймера 8253.
          Микросхема  таймера может использоваться  непосредственно  для
       временных операций, но это  редко  бывает  удобным.  Ввод с часов
       производится  1.19318  миллионов раз в секунду (даже на  AT,  где
       системные часы идут быстрее, микросхема таймера получает сигнал с
       частотой  1.19 Мгц).  Поскольку максимальное число, которое может
       храниться в 16 битах, равно  65535  и поскольку это число делится
       на  частоту импульсов от часов, равную 18.2, то максимальный воз-
       можный интервал между импульсами равен приблизительно 1/12 секун-
       ды.   Поэтому  большинство временных операций используют  счетчик
       времени суток BIOS. Для подсчета времени читается значение време-
       ни  суток и сравнивается с некоторым ранее запомненным  значением
       для определения числа  импульсов,  прошедших с того момента. Спе-
       циальный способ, описанный в [2.1.7], позволяет испоьзовать счет-
       чик времени суток для операций в реальном времени.
          8253 предоставляет разработчикам оборудования 6 режимов работы
       для  каждого канала.  Программисты обычно ограничиваются  третьим
       режимом, как для канала 0 при  синхронизации,  так и для канала 2
       для синхронизации или генерации звука.  В этом режиме, как только
       регистр задвижки получает число, он  немедленно загружает копию в
       регистр  счетчика.  Когда значение в счетчике достигает нуля  ре-
       гистр задвижки мгновенно перезагружает  счетчик и т.д.  В течение
       половины отсчета выходная линия включена, а в течение половины  -
       выключена. В результате  получаются  прямоугольные волны, которые
       одинаково пригодны как для генерации звука, так и для подсчета.
          8-битный командный регистр управляет способом загрузки чисел в
       канал.  Адрес порта для этого регистра равен 43H.  Командному ре-
       гистру передается байт, который говорит какой канал  программиро-
       вать, в каком режиме, а также один или оба байта регистра задвиж-
       ки  должны быть переданы.  Он показывает также будет ли  число  в
       двоичной или BCD (двоичнокодированной десятичной) форме. Значение
       битов этого регистра таково:

          бит   0    если 0, двоичные данные, иначе BCD
              3-1    номер режима, 1 - 5 (000 - 101)
              5-4    тип операции:
                       00 = передать значение счетчика в задвижку
                       01 = читать/писать только старший байт
                       10 = читать/писать только младший байт
                       11 = читать/писать старший байт, потом младший
              7-6    номер программируемого канала, 0 - 2 (00 -10)

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

          1.  Послать в  командный  регистр  (43H)  байт, представляющий
       цепочку  битов,  которые  выбирают канал,  статус  чтения/записи,
       режим операции и форму представления чисел.
          2.  Для канала 2 надо разрешить сигнал от часов, установив в 1
       бит 0 порта с адресом 61H. (Когда бит 1 этого регистра установлен
       в 1, то канал 2 управляет динамиком.  Сбросьте его в 0 для опера-
       ций синхронизации.)
          3. Вычислите значение счетчика  от 0 до 65535, поместите его в
       AX,  и  пошлите сначала младший, а затем старший  байт в  регистр
       ввода/вывода канала (40H - 42H).

          Каналы микросхемы 8253 работают всегда.  По этой причине прог-
       раммы всегда должны восстанавливать начальные установки регистров
       


                                     -37-
       8253 перед завершением. В частности, если при завершении програм-
       мы  генерируется звук, то он будет продолжаться даже после  того,
       как MS DOS получит управление и загрузит другую программу. Имейте
       это ввиду при написании процедуры выхода по Ctrl-Break [3.2.8].

          Низкий уровень.

          В  данном примере канал 0 программируется на другое  значение,
       чем установлено BIOS  при  старте.  Причина  изменения  установки
       состоит в том, чтобы изменить интервал изменения счетчика времени
       суток на большую величину, чем  18.2  раза в секунду. Частота об-
       новления  счетчика  изменяется, скажем, на 1000 раз в секунду,  с
       целью проведения точных лабораторных измерений. Значение задвижки
       должно  быть 1193 (1193180 тактов в секунду / 10000).  Как читать
       текущее значение регистра  счетчика  см. в примере [2.1.8]. Перед
       дисковыми  операциями оригинальное значение задвижки должно  быть
       восстановлено, поскольку канал  0  используется для синхронизации
       дисковых операций.  Максимально возможное значение - 65535 тактов
       часов между импульсами от канала - может быть достигнуто засылкой
       0 в регистр  задвижки  (0  немедленно  превращается  в  65535 при
       уменьшении на единицу.

       ;---установка регистров ввода/вывода
       COMMAND_REG  EQU   43H         ;адрес командного регистра
       CHANNEL_0    EQU   40H         ;адрес канала 0
                    MOV   AL,00110110B   ;установка битов для канала 2
                    OUT   COMMAND_REG,AL ;засылка в командный регистр
       ;---посылка счетчика в задвижку
                    MOV   AX,1193     ;счетчик для 100 импульсов/сек.
                    OUT   CHANNEL_2,AL   ;посылка младшего байта
                    MOV   AL,AH       ;готовим для посылки старший байт
                    OUT   CHANNEL_2,AL   ;посылка старшего байта

          2.1.2 Установка/чтение времени.

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

          Высокий уровень.

          TIME$ устанавливает или получает время в виде строки чч:мм:сс,
       где часы меняются от 0 до 23, начиная с полуночи. Для 5:10 дня:

          100 TIME$ = "17:10:00"  'установка времени
          110 PRINT TIME$         'вывод времени

          Поскольку TIME$ возвращает строку, то для выделения  отдельных
       частей показания часов можно использовать строковые функции MID$,
       LEFT$  и RIGHT$.  Например, чтобы преобразовать время 17:10:00  в
       5:10 Вы должны вырезать строку  символов,  соответствующую часам,
       преобразовать  ее в числовой вид (используя функцию VAL), вычесть
       12, а затем представить результат опять в виде строки:
       


                                     -38-
       100 T$ = TIME$                'получаем строку времени
       110 HOUR$ = LEFT$(T$,2)       'выделяем значение часов
       120 MINUTES$ = MID$(T$,4,2)   'выделяем значение минут
       130 NEWHOUR = VAL(HOUR$)      'преобразуем часы в число
       140 IF NEWHOUR > 12 THEN NEWHOUR = NEWHOUR - 12
       150 NEWHOUR$ = STR$(NEWHOUR)  'новое значение в строку
       160 NEWTIME$ = NEWHOUR$ + ":" + MINUTES$  'делаем новую строку

          Средний уровень.

          MS DOS предоставляет прерывания  для чтения и установки време-
       ни, производя необходимые преобразования между значением счетчика
       времени суток и часами-минутами-секундами.  Время выдается с точ-
       ностью  до 1/100 секунды, но поскольку счетчик времени суток  об-
       новляется с частотой в пять раз  меньшей,  то показания сотых се-
       кунд очень приближенные. Функция 2CH прерывания 21H выдает время,
       а  функция 2DH - устанавливает его.  В обоих случаях CH  содержит
       часы (от 0 до 23, где 0 соответствует полночи), CL - минуты (от 0
       до  59), DH - секунды (от 0 до 59) и DL - сотые доли секунд (от 0
       до 99).
          Кроме того при  получении  времени  функцией  2CH, AL содержит
       номер  дня недели (0 = воскресенье).  Значение дня  будет  верным
       только если была установлена дата. DOS вычисляет номер дня недели
       по дате.  Отметим также, что при установке времени функцией  2DH,
       AL отмечает  правильность  введенного  значения времени (0 = пра-
       вильно, FF = неправильно).

       ;---установка времени
          MOV   CH,HOURS       ;вводим значения времени
          MOV   CL,MINUTES     ;
          MOV   DH,SECONDS     ;
          MOV   DL,HUNDREDTHS  ;
          MOV   AH,2DH         ;номер функции установки времени
          INT   21H            ;устанавливаем время
          CMP   AH,0FFH        ;проверяем правильность значения
          JE    ERROR          ;переход на обработку ошибки

       ;---получение времени
          MOV   AH,2CH         ;номер функции получения времени
          INT   21H            ;получаем время
          MOV   DAY_OF_WEEK,AH ;получаем день недели из AH

          Низкий уровень.

          Если  Вы изменили скорость импульсов канала 1 микросхемы  8253
       для специальных приложений, то  Вам необходимо написать свою про-
       цедуру декодирования показаний счетчика времени суток.  BIOS поз-
       воляет диапазон значений  счетчика  от  0 до 1.573 миллиона и это
       может  быть изменено только путем изменения  прерывания  таймера.
       Поэтому часы, реально  показывающие  сотые доли секунды, не могут
       работать  24  часа без специально написанной программы.   Отметим
       также, что байт 0040:0070  устанавливается  в  ноль при старте, а
       затем увеличивается на 1 (не больше) по ходу часов.

          2.1.3 Установка/чтение даты.

          При  включении  компьютера MS DOS  запрашивает у  пользователя
       текущие  дату и время.  Время записывается в области данных BIOS.
       Дата же содержится  в  переменной  в  COMMAND.COM. Она хранится в
       формате трех последовательных байтов, которые содержат соответст-
       венно день месяца, номер месяца  и номер года, начиная с 0, где 0
       соответствует  1980 году.  В отличии от счетчика  времени  суток,
       адрес даты в памяти меняется с изменением версии DOS и положением
       


                                     -39-
       в памяти COMMAND.COM.  По этой причине для получения даты  всегда
       надо использовать готовые  утилиты Бейсика или MS DOS, а не обра-
       щаться к этой переменной напрямую.
          Машины,  оборудованные микросхемой календаря-часов,  автомати-
       чески устанавливают время и дату  с помощью специальной программы
       (обычно  запускаемой  при старте через файл  AUTOEXEC.BAT).   Как
       получить доступ к микросхеме календаря-часов,  см. [2.1.4]. Отме-
       тим  также, что когда счетчик времени суток BIOS переходит  через
       отметку 24 часов, MS DOS меняет дату.

          Высокий уровень.

          Оператор Бейсика DATE$ устанавливает  или получает дату в виде
       строки  формата  ММ-ДД-ГГГГ.  Можно использовать косую черту  (/)
       вместо дефиса (-).  Первые две цифры года могут быть опущены. Для
       31-го октября 1984 г.:

          100 DATE$ = "10/31/84"     'установка даты
          110 PRINT DATE$            'вывод даты

          ... и на дисплее будет выведено: 10-31-1984.

          Средний уровень.

          Функции  2AH  и  2BH прерывания 21H  получают и  устанавливают
       дату.  Для получения даты поместите в AH 2AH и выполните прерыва-
       ние.   При возврате CX будет содержать год в виде числа  от 0  до
       119, что соответствует  диапазону  лет 1980 - 2099 (можно сказать
       что  выдается смещение относительно 1980 г.).  DH содержит  номер
       месяца, а DL - день.

          MOV   AH,2AH       ;номер функции получения даты
          INT   21H          ;получение даты
          MOV   DAY,DL       ;день из DL
          MOV   MONTH,DH     ;месяц из DH
          ADD   CX,1980      ;добавляем базу к году
          MOV   YEAR,CX      ;получаем номер года

          Для установки даты поместите день, месяц и год в те же регист-
       ры  и выполните функцию 2BH.  Если значения, указанные  для  даты
       неверны, то в AL будет возвращено FF, в противном случае - 0.

          MOV   DL,DAY       ;помещаем день в DL
          MOV   DH,MONTH     ;помещаем месяц в DH
          MOV   CX,YEAR      ;помещаем год в CX
          SUB   CX,1980      ;берем смещение относительно 1980
          MOV   AH,2BH       ;номер функции установки даты
          INT   21H          ;установка даты
          CMP   AH,0FFH      ;проверяем успешность операции
          JE    ERROR        ;неверная дата, идем на обработку ошибки

          2.1.4 Установка/чтение часов реального времени.

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


                                     -40-
          Различные установки времени и даты  осуществляются через набор
       адресов  портов.  Многие многофункциональные платы расширения для
       IBM PC имеют часы реального  времени,  но, к сожалению, нет стан-
       дартной  микросхемы и диапазона адресов портов.   AT  оборудуется
       часами  реального  времени,  основанными  на  микросхеме MC146818
       фирмы  Motorola, которые используют те же регистры, что и микрос-
       хема, содержащая данные  о  конфигурации  системы.  Доступ к этим
       регистрам можно получить, послав сначала номер требуемого регист-
       ра в порт 70H, а затем прочитав значение регистра через порт 71H.
       Регистры, связанные с часами, следующие:

                  Номер регистра              Функция

                       00H                  Секунды
                       01H                  Секундная тревога
                       02H                  Минуты
                       03H                  Минутная тревога
                       04H                  Часы
                       05H                  Часовая тревога
                       06H                  День недели
                       07H                  День месяца
                       08H                  Месяц
                       09H                  Год
                       0AH                  регистр статуса A
                       0BH                  регистр статуса B
                       0CH                  регистр статуса C
                       0DH                  регистр статуса D

          Биты  четырех статусных регистров выполняют различные функции,
       из которых интерес для  программистов  могут представлять следую-
       щие:
          Регистр A: бит 7   1 = идет модификация времени (надо ждать
                                 значения 0, чтобы читать)
          Регистр B: бит 6   1 = разрешено периодическое прерывание
                     бит 5   1 = разрешено прерывание тревоги
                     бит 4   1 = разрешено прерывание конца модификации
                     бит 1   1 = часы считаются до 24, 0 = до 12
                     бит 0   1 = разрешено запоминание времени суток

          Часы  реального времени на AT могут вызывать аппаратное преры-
       вание IRQ8. Программа может установить вектор этого прерывания на
       любую процедуру, которую требуется выполнить в определенное время
       [1.2.3].  Используйте вектор  4AH.   Операции в реальном времени,
       производимые  таким  образом, менее хлопотны, чем  обсуждаемые  в
       [2.1.7] (хотя и ценой  компактности  программ).  Прерывание может
       вызываться одним из трех способов, каждый из которых запрещен при
       старте.   Периодическое прерывание происходит через  определенные
       интервалы времени.   Периодичность приближенно равна одной милли-
       секунде.   Прерывание тревоги происходит когда значение трех  ре-
       гистров тревоги совпадает со значениями соответствующих временных
       регистров.  Прерывание конца модификации происходит после каждого
       обновления значений регистров микросхемы.
          Прерывание 1AH расширено в BIOS AT, чтобы оно позволяло читать
       и устанавливать часы реального времени.  Поскольку показания  ни-
       когда не состоят более чем их  двух  десятичных цифр, то значения
       времени  выдаются в двоично-кодированной десятичной форме  (BCD),
       когда байт делится на  две  половины  и  каждая  десятичная цифра
       представляется  четырьмя  битами.  Такой формат  позволяет  легко
       переводить числа в форму ASCII.  Программе  нужно только сдвинуть
       половину байта в младший конец регистра и добавить 48 для получе-
       ния кода ASCII,  соответствующего  данному числу. Для всех IBM PC
       функции  0 и 1 прерывания 1AH читают и устанавливают счетчик вре-
       мени суток BIOS.  Для  часов  реального  времени AT имеется шесть
       


                                     -41-
       новых функций:

          Функция 2:  Чтение времени из часов реального времени
                      При возврате: CH = часы в BCD
                                    CL = минуты в BCD
                                    DH = секунды в BCD
          Функция 3:  Установка времени часов реального времени
                      При входе: CH = часы в BCD
                                 CL = минуты в BCD
                                 DH = секунды в BCD
                                 DL = if daylight savings, else 1

          Функция 4:  Чтение даты из часов реального времени
                      При возврате: CH = век в BCD (19 или 20)
                                    CL = год в BCD (с 1980)
                                    DH = месяц в BCD
                                    DL = день месяца в BCD
          Функция 5:  Установка даты часов реального времени
                      При входе:    CH = век в BCD (19 или 20)
                                    CL = год в BCD (с 1980)
                                    DH = месяц в BCD
                                    DL = день месяца в BCD
          Функция 6:  Установка тревоги для часов реального времени
                      При входе: CH = часы в BCD
                                 CL = минуты в BCD
                                 DH = секунды в BCD
          Функция 7:  Сброс тревоги (нет входных регистров)

       Тревога устанавливается как  смещение,  относительно текущего мо-
       мента времени. Максимальный период равен 23:59:59.  Как уже гово-
       рилось выше, вектор прерывания  4AH должен указывать на процедуру
       обработки тревоги.  Отметим, что если часы не работают  (наиболее
       вероятно, из-за отсутствия питания), то выполнение функций 2, 4 и
       6 устанавливает флаг переноса.

          2.1.5 Задержка программных операций.

          Если Вы осуществляете задержку в программе посредством пустого
       цикла, то Вам может  потребоваться  много времени для того, чтобы
       добиться  нужного времени задержки.  Даже если Вы определите тре-
       буемую длительность, то нельзя быть уверенным, что Ваша программа
       будет  давать нужное время задержки при всех условиях.   Длитель-
       ность цикла может меняться в  зависимости от используемого компи-
       лятора  (или,  для Бейсика, от того, компилируется программа  или
       нет). А в наше время, когда имеется большой набор машин совмести-
       мых  с  IBM PC - имеющих широкий диапазон скорости  процессора  -
       даже цикл на языке ассемблера может  приводить к различным време-
       нам  задержки.  Поэтому разумно определять время программной  за-
       держки непосредственно  по  часам.   Частота  отсчета 18.2 раза в
       секунду,  используемая  для модификации счетчика  времени  суток,
       должна вполне удовлетворять  большинство потребностей (как увели-
       чить частоту отсчетов см. [2.1.1]).
          Чтобы  обеспечить задержку данной продолжительности, программа
       должна подсчитать  требуемое  число  импульсов  счетчика  времени
       суток.   Это значение добавляется к считанному текущему  значению
       счетчика. Затем программа постоянно считывает значение счетчика и
       сравнивает  его  с запомненным.  Когда достигается равенство,  то
       требуемая задержка прошла и можно  продолжать выполнение програм-
       мы.   Четыре байта, в которых хранится значение счетчика  времени
       суток хранятся, начиная с адреса 0040:006C (как обычно, начиная с
       младшего байта). Для задержек меньших 14 секунд можно пользовать-
       ся только младшим байтом. Два младших байта позволяют задержки до
       одного часа (точнее, на пол-секунды меньше, чем час).
       


                                     -42-
          Высокий уровень.

          В  Бейсике можно использовать оператор SOUND [2.2.2] со значе-
       нием частоты, равным 32767. В этом случае звук не будет генериро-
       ваться вообще.  Это отсутствие звука будет длиться столько отсче-
       тов времени суток, сколько Вы укажете.   Для 5-секундной задержки
       нужен 91 отсчет (5 * 18.2). Поэтому

       100 SOUND 32767,91  'останавливает программу на 5 секунд
       Для прямого чтения счетчика времени суток нужно:

       100 DEF SEG = 0            'установка сегмента на начало памяти
       110 LOWBYTE = PEEK(&H46C)  'получение младшего байта
       120 NEXTBYTE = PEEK(&H46D) 'получение следующего байта
       130 LOWCOUNT = NEXTBYTE*256 + LOWBYTE  'значение двух байтов

          Средний уровень.

          Прочитайте  значение  счетчика времени суток  BIOS,  используя
       функцию 0 прерывания 1AH  и  добавьте  к  нему  необходимое число
       импульсов по 1/18 секунды.  После этого считывайте текущие значе-
       ния счетчика времени суток, постоянно сравнивая с требуемой вели-
       чиной. При достижении равенства надо кончать задержку. Прерывание
       1AH возвращает два младших байта в DX (большинство задержек укла-
       дываются  в этих пределах), поэтому два старших байта, возвращае-
       мые  в  CX,  могут  игнорироваться,  что  позволит  Вам  избежать
       32-байтных операций.  В данном примере установлена задержка на  5
       секунд, что соответствует 91 отсчету.

       ;---получение значения счетчика и установка задержки
                   MOV   AH,0   ;номер функции для "чтения"
                   INT   1AH    ;получаем значение счетчика
                   ADD   DX,91  ;добавляем 5 сек. к младшему слову
                   MOV   BX,DX  ;запоминаем требуемое значение в BX
       ;---постоянная проверка значения счетчика времени суток BIOS
       REPEAT:     INT   1AH    ;получаем значение счетчика
                   CMP   DX,BX  ;сравниваем с искомым
                   JNE   REPEAT ;если неравен, то повторяем снова
                                ;иначе, задержка окончена

       AT имеет добавочную  функцию  прерывания  15H,  которая позволяет
       осуществить  задержку на указанное время.  Поместите 86H в AH,  а
       число микросекунд задержки в CX:DX.  После этого выполните преры-
       вание.

          2.1.6 Операции запрограммированные во времени.

          Программа определяет время для выполнения определенной  опера-
       ции в точности так же, как и человек: берется начальное показание
       счетчика  времени суток и затем сравнивается с последующими пока-
       заниями.  Можно получать  значения в формате часы-минуты-секунды,
       но  слишком хлопотно вычислять разницу между такими  показаниями,
       поскольку система счета не десятичная. Лучше прямо читать счетчик
       времени  суток BIOS, измерять продолжительность в 1/18 секунды, а
       затем уже переводить ее в обычный формат чч:мм:сс.

       100 GOSUB 500              'получаем значение счетчика
       110 START = TOTAL          'сохраняем начальное значение в START
             .
        (далее идет процесс, длительность которого измеряется)
             .
       300 GOSUB 500              'получаем финальное значение
       310 TOTAL = TOTAL - START  'подсчитываем число импульсов
       


                                     -43-
       320 HOURS = FIX(TOTAL/65520)  'вычисляем число часов
       330 TOTAL = TOTAL - HOURS*65520  'вычитаем часы из TOTAL
       340 MINUTES = FIX(TOTAL/1092)    'вычисляем число минут
       350 TOTAL = TOTAL - MINUTES*1092 'вычитаем минуты из TOTAL
       360 SECONDS = FIX(TOTAL/18.2)    'вычисляем число секунд
       370 PRINT HOURS,MINUTES,SECONDS  'печатаем результат
       380 END
             .
             .
       500 DEF SEG = 0            'подпрограмма чтения времени суток
       510 A = PEEK(&H46C)        'получаем младший байт
       520 A = PEEK(&H46D)        'получаем следующий байт
       530 A = PEEK(&H46E)        'и еще один
       540 TOTAL = A + B*256 + C*65535  'подсчитываем результат в TOTAL
       550 RETURN                 'все сделано

          Функция TIMER в Бейсике  возвращает  число секунд, прошедших с
       момента, когда счетчик времени суток был последний раз установлен
       в 0. Обычно это число  секунд,  прошедших  со  времени последнего
       включения  компьютера.   Если при старте системы  правильно  было
       установлено системное время,  то  TIMER  возвращает число секунд,
       прошедших с полуночи. Просто напишите N = TIMER.

          Средний уровень.

          Прерывание  1AH  имеет  две функции для установки (AH =  1)  и
       получения (AH = 0) счетчика  времени  суток.  Для чтения счетчика
       надо просто выполнить прерывание с AH = 0.  При возврате значение
       счетчика содержится в CX:DX, причем младшее слово в CX. AL содер-
       жит 0, если счетчик не переходил через границу 24 часов с момента
       последней установки. Для установки счетчика поместите два слова в
       те  же  регистры, а в AH - 1.  В приведенном  примере  измеряются
       промежутки времени в  пределах  часа.  При  этом нужны только два
       младших  байта счетчика.  Но в этом случае необходимо  проверять,
       что не было перехода через границу, когда начальное значение было
       больше, чем следующее.

       ;---в сегменте данных
       OLDCOUNT  DW   0     ;храним начальное значение счетчика
       ;---получаем начальное значение счетчика
                 MOV  AH,0        ;номер функции
                 INT  1AH         ;получаем значение счетчика
                 MOV  OLDCOUNT,DX ;сохраняем начальное значение
                  .
          (здесь идет процесс, длительность которого измеряется)
                  .
       ;---позднее вычисляем длительность процесса
                 MOV  AH,0        ;номер функции
                 INT  1AH         ;получаем значение счетчика
                 MOV  BX,OLDCOUNT ;считываем старое значение
                 CMP  BX,DX       ;проверяем на переполнение
                 JG   ADJUST      ;обработка переполнения
                 SUB  DX,BX       ;иначе берем разность
                 JMP  SHORT FIGURE_TIME  ;и переводим ее в обычный вид
       ;---обработка переполнения
       ADJUST:   MOV  CX,0FFFFH   ;помещаем в CX максимальное число
                 SUB  CX,BX       ;вычитаем первое значение
                 ADD  CX,DX       ;добавляем второе значение
                 MOV  DX,CX       ;результат храним в DX
       ;---процедура перевода времени в обычный формат
       FIGURE_TIME:               ;делим на 18.2 секунды и т.д.

          2.1.7 Управление работой в реальном времени.
       


                                     -44-
          При  операциях в реальном времени программа выполняет инструк-
       ции в указанный  момент  времени,  а  не  при первой возможности.
       Такого  рода  операции обычно  ассоциируются с  роботехникой,  но
       имеется множество  других  приложений.   Имеется  выбор подхода к
       операциям  в реальном времени.  Для программ, которые  не  должны
       ничего делать в промежутке между инструкциями, требующими времен-
       ной привязки, можно просто периодически проверять счетчик времени
       суток, ожидая наступления нужного  момента.  Такой подход практи-
       чески сводится к набору пустых циклов, описанных в [2.1.5].
          Второй  подход более сложен.  Он используется, когда программа
       постоянно занята какой-либо работой, но она должна в определенные
       моменты времени прерывать свои операции для выполнения определен-
       ной задачи.  В этом случае  расширяют прерывание таймера, которое
       выполняется 18.2 раза в секунду. Когда это прерывание происходит,
       дополнительный  код  проверяет  новое  значение  счетчика времени
       суток  и  если  наступил определенный момент  времени,  запускает
       нужную процедуру.  Этот процесс показан на рис.  2-3. Приведенные
       здесь  простые примеры показывают, как создать в своей  программе
       будильник, который устанавливается  пользователем и подает звуко-
       вой  сигнал, когда подошло время.  (Более сложный пример  низкого
       уровня в [2.2.6]  исполняет  музыку,  в  то время когда процессор
       занят другими делами.)

          Высокий уровень.

          Бейсик  обеспечивает  примитивный контроль  над  операциями  в
       реальном времени посредством оператора  ON TIMER(n) GOSUB.  Когда
       программа встречает этот оператор, то она начинает отсчитывать  n
       секунд.  Тем временем выполнение программы продолжается.  Когда n
       секунд  прошло, то программа переходит на подпрограмму,  начинаю-
       щуюся с указанного номера  строки,  выполняет ее и возвращает уп-
       равление  на  то место, откуда была вызвана подпрограмма.   После
       этого отсчет снова начинается с нуля и подпрограмма будет вызвана
       снова еще через n секунд.
          ON  TIMER не будет функционировать, до тех пор пока он не раз-
       решен оператором TIMER ON. Оператор TIMER OFF запрещает его рабо-
       ту.   В тех случаях, когда отсчет времени должен продолжаться, но
       переход на подпрограмму должен  быть  задержан, надо использовать
       оператор TIMER STOP. В этом случае отмечается, что n секунд прош-
       ло, но переход на подпрограмму будет выполенен только после того,
       как встретится оператор TIMER ON.
          Поскольку  он повторяется, оператор ON TIMER особенно  полезен
       для вывода на экран текущего времени:

       100 ON TIMER(60) GOSUB 500   'меняем показания часов каждые 60
       110 TIMER ON                 'секунд и разрешаем работу таймера
        .
        .
       500 LOCATE 1,35:PRINT "TIME: ";LEFT$(TIME$,5)  'позиционируем
       510 RETURN                   'курсор и печатаем время

          Низкий уровень.

          BIOS содержит  специальное  пустое  прерывание  (1CH), которое
       ничего  не  делает, пока Вы не напишите для него процедуру.   При
       старте  вектор  этого  прерывания  указывает  на  инструкцию IRET
       (возврат  из прерывания); при его вызове происходит  моментальный
       возврат. Но  прерывание  1CH  интересно  тем,  что оно вызывается
       прерыванием  таймера BIOS после того, как это прерывание обновило
       значение счетчика времени суток.  Можно сказать, что это аппарат-
       ное  прерывание, происходящее автоматически 18.2 раза в  секунду.
       Вы можете изменить вектор этого прерывания так, чтобы он указывал
       на процедуру в Вашей программе.  После этого Ваша процедура будет
       


                                     -45-
       вызываться 18.2 раза в секунду.   О том как написать и установить
       свою процедуру обработки прерывания см. в [1.2.3].
          Написанная  Вами процедура должна прочитать только что модифи-
       цированное значение счетчика  времени  суток, сравнить его с ожи-
       даемым  временем,  и выполнить то что требуется, когда  ожидаемое
       время наконец наступит.   Естественно, что когда время еще не по-
       дошло,  то процедура просто возвращает управление, ничего не  де-
       лая. Таким образом, процессор не выполняет лишней работы.
          В приведенном примере процедура (не показанная здесь) запраши-
       вает у пользователя число минут (до 60), которое должно пройти до
       того, как раздастся звонок  будильника.   Это число, запасенное в
       MINUTES,  умножается  на 1092 для перевода в эквивалентное  число
       импульсов счетчика времени  суток.  Для периода в пределах одного
       часа  достаточно  16  бит - более длинные периоды  требуют  более
       сложных 32-битовых операций.   Это  число импульсов добавляется к
       младшему слову текущего значения счетчика времени суток и запоми-
       нается в ALARMCOUNT.
          Затем вектор прерывания 1CH изменяется таким образом, чтобы он
       указывал на процедуру ALARM. Помните, что как только вектор будет
       изменен, ALARM будет  автоматически вызываться 18.2 раза в секун-
       ду.   При вызове эта процедура читает текущее  значение  счетчика
       времени суток через прерывание 1AH и сравнивает с ALARMCOUNT. При
       совпадении этих величин вызывается процедура BEEP (также не пока-
       занная здесь - см.  [2.2.4]),  которая выдает звуковой сигнал.  В
       противном  случае  происходит возврат.  Обычный код  возврата  из
       аппаратных прерываний (MOV AH,20H / OUT 20H,AL) включать в проце-
       дуру  не  нужно, так как он будет в прерывании  таймера.   Будьте
       внимательны и не забудьте сохранить изменяемые регистры.

       ;---в сегменте данных
          MINUTES     DW    0     ;хранит число минут до звонка
          ALARMCOUNT  DW    0     ;хранит счетчик времени для звонка

       ;---установка ожидаемого значения счетчика времени суток
          CALL  REQUEST_MINUTES   ;запрос числа минут до звонка
          MOV   AX,MINUTES        ;пересылка в AX
          MOV   BX,1092           ;число импульсов счетчика в минуте
          MUL   BX                ;умножаем - результат в AX
          ;получаем текущее значение счетчика
          MOV   AH,0              ;номер функции чтения счетчика
          INT   1AH               ;читаем значение, младший байт в DX
          ;складываем оба значения
          ADD   AX,DX             ;
          MOV   ALARMCOUNT,AX     ;получаем нужное значение счетчика
       ;---заменяем вектор пустого прерывания
          PUSH  DS                ;сохраняем сегмент данных
          MOV   AX,SEG ALARM      ;берем сегмент процедуры ALARM
          MOV   DS,AX             ;помещаем его в DS
          MOV   DX,OFFSET ALARM   ;берем смещение процедуры
          MOV   AL,1CH            ;номер изменяемого вектора
          MOV   AH,25H            ;функция изменения вектора
          INT   21H               ;меняем вектор
          POP   DS                ;восстанавливаем сегмент данных
       ;
       ;---дальше продолжается программа
       ;
       ;---в конце программы возвращаем вектор прерывания
          MOV   DX,0FF53H         ;оригинальные значения для
          MOV   AX,0F000H         ;прерывания 1CH
          MOV   DS,AX             ;помещаем сегмент в DS
          MOV   AL,1CH            ;номер изменяемого вектора
          MOV   AH,25H            ;номер функции
          INT   21H               ;восстанавливаем вектор
       


                                     -46-
       ;---процедура выдачи звукового сигнала
       ALARM    PROC FAR          ;создаем длинную процедуру
                PUSH AX           ;сохраняем изменяемые регистры
                PUSH CX           ;
                PUSH DX           ;
       ;---читаем счетчик времени суток
                MOV  AH,0         ;номер функции чтения счетчика
                INT  1AH          ;читаем значение счетчика
       ;---сравниваем с требуемым значением
                MOV  CX,ALARMCOUNT   ;берем требуемое значение
                CMP  DX,CX        ;сравниваем с текущим
                JNE  NOT_YET      ;если неравны, то на выход
       ;---выдаем звуковой сигнал, если значения совпали
                CALL BEEP         ;эта процедура не показана
       ;---иначе возвращаемся из прерывания
       NOT_YET: POP  DX           ;восстанавливаем регистры
                POP  CX           ;
                POP  AX           ;
                IRET              ;возврат из прерывания
       ALARM    ENDP              ;конец процедуры

          2.1.8 Генерация случайных чисел с помощью микросхемы таймера.

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

       100 RANDOMIZE TIMER      'сброс генератора случайных чисел
       110 PRINT RND,RND,RND    'печать трех случайных чисел

       в результате получаем:  .7122483  .4695052  .9132487

          Низкий уровень.

          Поскольку регистр  счетчика   канала  таймера  перезагружается
       снова  и  снова данным числом (а в промежутках идет счет вниз  до
       0), выберите в качестве  загружаемого  в  счетчик значения число,
       равное требуемому диапазону случайных чисел.  Например, для полу-
       чения случайного значения часа дня загружайте в счетчик 23.
          Лучше всего использовать режим 3 канала 2 (порт 42H) микросхе-
       мы  таймера  [2.1.1].  Сначала установите для  счетчика  желаемый
       диапазон случайных чисел (в примере  используется 10000, что при-
       водит к выдаче случайного числа в диапазоне от 0 до 9999). Затем,
       чтобы получить из  канала  случайное  число,  надо подать команду
       командному  регистру микросхемы таймера через порт 43H  перенести
       текущее значение счетчика  в  регистр  "задвижки",  для чего надо
       сбросить  биты 4 и 5.  Этот перенос в регистр задвижки не  мешает
       продолжающемуся счету. Затем установите оба бита 4 и 5 командного
       регистра, чтобы процессор мог читать из регистра задвижки.  После
       этого две инструкции IN дадут  сначала  младший,  а затем старший
       байт в регистре AL. Наконец, восстановите первоначальное значение
       регистра задвижки, чтобы счет  продолжался  в пределах указанного
       диапазона времени.

       ;---установка адресов портов
       COMMAND_REG  EQU   43H     ;адрес командного регистра
       CHANNEL_2    EQU   42H     ;адрес канала 2
       


                                     -47-
                    CALL  SET_COUNT  ;установка диапазона
                     .
       ;---здесь программа работает, а затем требует случайное число
                     .
                    CALL  GET_NUMBER ;получение случайного числа
                     .
                     .
       ;---начинаем отсчет канала 2
       SET_COUNT    PROC
                    MOV   AL,10110110B   ;канал 2, режим 2, оба байта
                    OUT   COMMAND_REG,AL ;посылаем в командный регистр
                    MOV   AX,10000       ;значение счетчика
                    OUT   CHANNEL_2,AL   ;посылаем младший байт
                    MOV   AL,AH          ;передвигаем старший байт в AL
                    OUT   CHANNEL_2,AL   ;посылаем старший байт
                    RET
       SET_COUNT    ENDP
       ;---получение случайного числа
       READ_NUMBER  PROC
       ;---пересылаем значение счетчика в регистр задвижки
                    MOV   AL,10000110B   ;требуемая команда
                    OUT   COMMAND_REG,AL ;посылаем в командный регистр
       ;---читаем значение счетчика
                    MOV   AL,10110110B   ;запрос на чтение/запись
                    OUT   COMMAND_REG,AL ;посылаем запрос
                    IN    AL,CHANNEL_2   ;получаем младший байт
                    MOV   AH,AL          ;временно храним его в AH
                    IN    AL,CHANNEL_2   ;получаем старший байт
                    CALL  SET_COUNT      ;восстанавливаем задвижку
                    SWAP  AH,AL          ;ставим байты на место
                    RET                  ;теперь случайное число в AX
       READ_NUMBER  ENDP

          Раздел 2. Создание звука.

          Бейсик оснащен достаточно изощренными средствами для генерации
       звука, однако операционная система позволяет только просто подать
       звуковой сигнал.  Если Вы хотите получить какие-либо сложные зву-
       ки, то Вы должны прямо  программировать  микросхему таймера 8253.
       Канал  2  этой  микросхемы прямо связан с  динамиком  компьютера.
       Когда этот канал программируется в  режиме 3, то он посылает пря-
       моугольные волны данной частоты. Из-за простоты динамика он сгла-
       живает края прямоугольной волны, получая более приятную для слуха
       синусоидальную  волну.  К сожалению, микросхема 8253 не может ме-
       нять амплитуду волны, поэтому мы не можем менять громкость звука,
       издаваемого динамиком.
          Динамик  имеет  не один, а два входа для генерации звука.   На
       рис. 2-2 в [2.1.1] показано, что кроме микросхемы таймера, сигнал
       посылает  также микросхема интерфейса с периферией 8255  [1.1.1].
       Частота импульсов каждой микросхемы  может быть изменена, поэтому
       комбинируя  воздействия  этих двух источников мы  можем  получать
       специальные звуковые эффекты.
          Только PCjr имеет специальную микросхему,  управляющую генера-
       тором звука. Он может одновременно выдавать три разных тона, плюс
       шум для звуковых  эффектов.   Громкость  каждого  из трех каналов
       может устанавливаться независимо.  Другой уникальной возможностью
       PCjr является то, что он может  управлять внешним источником зву-
       ка, таким как кассетный магнитофон.

          2.2.1 Программирование генератора звука 76496 (только PCjr).

          PCjr  снабжен  4-канальным  генератором звука, в  котором  три
       канала генерируют тона, а четвертый служит для генерации шума для
       


                                     -48-
       звуковых эффектов.  Все четыре канала программируются независимо,
       причем каждый из  них  имеет  свой  регулятор  громкости, а затем
       выход со всех них объединяется в единый звуковой сигнал.  Исполь-
       зуется микросхема комплексного генератора  звука TI SN76496N. Она
       имеет  8 регистров - 2 для каждого канала - и все они  адресуются
       через один порт с адресом 0C0H. Этот порт служит только для запи-
       си; если подать инструкцию IN, то вся система будет заморожена.
          PCjr  имеет  также разъем для внешнего источника  звука.   При
       старте системы звуковой канал получает выходной сигнал от микрос-
       хемы таймера 8253. Но этот канал может быть переключен на микрос-
       хему генератора звука или любой  из двух внешних звуковых входов.
       Это  достигается изменением битов 5 и 6 порта B микросхемы интер-
       фейса с периферией 8255 (адрес порта 61H - см. [1.1.1]). Значение
       битов следующее:

               Биты 6 и 5          Выбранная функция

                  00               микросхема таймера 8253
                  01               вход с кассетного магнитофона
                  10               вход канала ввода/вывода
                  11               генератор звука 76496

       Для  выбора  источника звука в BIOS PCjr  добавлена  функция  80H
       прерывания 1AH. Поместите в AL номер кода от 0 до 3, в соответст-
       вии с вышеприведенной таблицей, и вызовите функцию.  Возвращаемых
       регистров нет.  Генератор  звука  76496  должен использовать этот
       звуковой  канал, поскольку он не может управлять внутренним дина-
       миком PCjr.
          В общем случае, когда байт данных посылается генератору звука,
       то  биты  4-6 содержат код идентификации,  сообщающий  какому  из
       восьми регистров предназначены данные. Эти коды такие:

               Биты 6-4            Адресуемый регистр

                000                Частота первого тона
                001                Громкость первого тона
                010                Частота второго тона
                011                Громкость второго тона
                100                Частота третьего тона
                101                Громкость третьего тона
                110                Частота четвертого тона
                111                Громкость четвертого тона

          В случае регистров частоты тонов требуются два байта. Значение
       битов при этом следующее:

          байт 1: биты 0-3   младшие 4 бита частоты
                       4-6   код идентификации регистра
                         7   всегда равен 1
          байт 2: биты 0-5   старшие 6 битов частоты
                         6   не используется
                         7   всегда равен 0

       Для установки частоты тона в регистр  посылается 10-битное значе-
       ние, которое после деления  на  111  843  дает желаемую частоту в
       герцах. Таким образом, доступны частоты, начиная с 110 герц вверх
       (111 843/2^10).  Как только регистр инициализирован (и соответст-
       венно  установлен порт B микросхемы 8255), немедленно  начинается
       звуковой сигнал и продолжается до тех пор, пока он не будет прек-
       ращен.   Не обязательно для изменения частоты посылать новые  два
       байта.  Если послан только второй байт (старшие 6 битов частоты),
       то  он автоматически заменяет соответствующие данные в канале,  к
       которому была  последняя  адресация.   Эта  возможность позволяет
       


                                     -49-
       плавно варьировать частоту.
          Генератору  шума для программирования нужен только один  байт.
       Значение битов для него следующее:

          биты 0-1     плотность шума
                 2     качество шума
                 3     не используется
               4-6     код идентификации регистра
                 7     всегда установлен в 1

       Качество шума устанавливается на  белый шум (постоянное шипение),
       когда  бит 2 равен 1 и на периодический шум (волны звука),  когда
       бит 2 равен 0. Плотность звука увеличивается при увеличении битов
       0-1  от 00B до 10B; когда они установлены в 11B, то звук меняется
       в зависимости от выходного тона канала 3.
          Громкость каждого из четырех  каналов  изменяется  ослаблением
       основного сигнала. Для этой установки требуется только один байт.
       Значение его битов следующее:

          биты 0-3     ослабление сигнала
               4-6     код идентификации регистра
                 7     всегда установлен в 1

       Когда все 4 бита данных равны 0, то  громкость максимальна. Когда
       все  они  равны 1, то звук полностью подавляется.  Для  получения
       звука промежуточной громкости может  быть использована любая ком-
       бинация битов.  Бит 0 ослабляет звук на 2 Дб (децибелла), бит 1 -
       на 4 Дб, бит 2 - на 8 Дб и бит 3 - на 16 Дб.  Максимальное ослаб-
       ление равно 28 Дб.

          2.2.2 Генерация тона.

          Этот подраздел объясняет как производить звук, когда компьютер
       не занят ничем другим; в [2.2.3]  показано как это сделать, когда
       производятся  другие действия.  Забавно, но для программистов  на
       ассемблере последнее проще.  Для  этого достаточно запрограммиро-
       вать  микросхему  таймера  8253, которая работает  независимо  от
       процессора.  В приведенном здесь методе процессор непосредственно
       управляет динамиком, поэтому программе приходится выполнять рабо-
       ту, которую может выполнять микросхема таймера.  Хотя этот способ
       более  труден, но он допускает существенно больший  контроль  над
       динамиком и  создание  большинства  специальных звуковых эффектов
       [2.2.8] основывается на нем.

          Высокий уровень.

          Оператор Бейсика SOUND используется для генерации тона в широ-
       ком диапазоне частот и длительностей. Частота дается в герцах (от
       37  до 32767), а длительность в импульсах счетчика времени  суток
       BIOS (от 0 до 65535), причем в  секунду происходит 18.2 импульса.
       SOUND  440,91  воспроизводит ноту A в течение 5 секунд  (5*18.2).
       Частоты первой октавы, начиная с ноты C(до) таковы:

                        C(до)              523.3
                        D(ре)              587.3
                        E(ми)              659.3
                        F(фа)              698.5
                        G(соль)            784.0
                        A(ля)              880.0
                        B(си)              987.7

       Частоты  на октаву выше можно получить, удваивая эти значения, на
       две октавы выше - еще раз удваивая  частоты.  И наоборот, частоты
       


                                     -50-
       на октаву ниже равны приблизительно половине этих значений (хоро-
       шо настроенное пианино точно не  следует  арифметическим интерва-
       лам).
          Благодаря своему генератору звука [2.2.1] PCjr может использо-
       вать  оператор  SOUND для трех независимых каналов звука,  причем
       может управляться громкость каждого  из них. В этом случае формат
       оператора:  SOUND частота, длительность, громкость, канал.  Гром-
       кость может меняться от 0  до  15,  по  умолчанию 8. Номер канала
       может  меняться от 0 до 2, по умолчанию 0.  Поскольку PCjr  может
       использовать возможности многоголосия и контроля звука только для
       внешнего  динамика, то надо сначала разрешить этот динамик.   Это
       делается с помощью оператора SOUND  ON.  SOUND OFF передает конт-
       роль  внутреннему динамику.  Чтобы сыграть аккорд D-минор (ре-ми-
       нор) (D-F-A) с малой громкостью, напишите:

       100 SOUND ON             'разрешение внешнего динамика
       110 SOUND 587,50,3,0     'нота ре
       120 SOUND 699,50,3,1     'нота фа
       130 SOUND 880,50,3,1     'нота ля

          Низкий уровень.

          Генерация звука с  помощью  адаптера  интерфейса  с периферией
       8255  состоит во включении и выключении с желаемой частотой  бита
       порта B, который связан с динамиком  (бит 1).  Порт B имеет адрес
       61H (хотя AT не имеет микросхемы интерфейса с периферией 8255 как
       таковой, он использует для этой  цели тот же адрес порта и тот же
       бит).   Если  программа переключает значение  бита с  максимально
       возможной частотой, то частота слишком высокая, чтобы быть полез-
       ной.   Поэтому  между двумя переключениями надо вставлять  пустой
       цикл.  Помните, что бит 0  порта  B  управляет  воротами канала 2
       микросхемы  таймера, который в свою очередь  связан с  динамиком.
       Поэтому этот бит  должен  быть  сброшен,  отсоединяясь  от канала
       таймера. На рис. 2-4 показано как этот метод устанавливает часто-
       ту звука.
          В следующем примере введены две переменные. Одна, обозначенная
       "FREQUENCY",  используется  в  качестве  счетчика  в пустом цикле
       между действиями включения и выключения.  Чем меньше ее значение,
       тем быстрее происходит изменение бита и тем больше частота. Пере-
       менная  же "NUMBER_CYCLES" устанавливает продолжительность  тона.
       Она говорит сколько раз должен быть  повторен процесс включения и
       выключения. Чем больше это число, тем дольше звучит данный звук.
          Отметим,  что для этой процедуры аппаратные прерывания  должны
       быть  запрещены.   Причина этого в том,  что  прерывание  таймера
       происходит с такой частотой и  регулярностью  (18.2 раза в секун-
       ду),  что оно будет существенно влиять на частоту.  Имейте ввиду,
       что пока прерывания  запрещены,  счетчик  времени  суток  BIOS не
       будет  работать.  Если затем прочитать его значение, то оно будет
       отличаться на некоторую  величину  от реального, до тех пор, пока
       не будет сделано соответствующее изменение.

       NUMBER_CYCLES  EQU   1000
       FREQUENCY      EQU   300
       PORT_B         EQU   61H
                      CLI                 ;запрет прерываний
                      MOV   DX,NUMBER_CYCLES  ;длительность тона в DX
                      IN    AL,PORT_B     ;получаем значение из порта B
                      AND   AL,11111110B  ;отключаем динамик от таймера
       NEXT_CYCLE:    OR    AL,00000010B  ;включаем динамик
                      OUT   PORT_B,AL     ;посылаем команду в порт B
                      MOV   CX,FREQUENCY  ;задержка на пол-цикла в CX
       FIRST_HALF:    LOOP  FIRST_HALF    ;делаем задержку
                      AND   AL,11111101B  ;выключаем динамик
       


                                     -51-
                      OUT   PORT_B,AL     ;посылаем команду в порт B
                      MOV   CX,FREQUENCY  ;задержка на пол-цикла в CX
       SECOND_HALF:   LOOP  SECOND_HALF   ;делаем задержку
                      DEC   DX            ;вычитаем единицу из счетчика
                      JNZ   NEXT_CYCLE    ;если 0, то надо кончать
                      STI                 ;разрешаем прерывания

          2.2.3 Генерация звука одновременно с другими действиями.

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

          Высокий уровень.

          Оператор SOUND в Бейсике не позволяет генерировать звук однов-
       ременно  с другими действиями, но оператор PLAY - позволяет  если
       ему это задать. За оператором PLAY должна следовать строка, кото-
       рая сообщает какие ноты долны быть сыграны, какой длительности, а
       также другие характеристики.  Детали командной строки PLAY обсуж-
       даются в [2.2.5]. Если строка содержит буквы MB (фоновая музыка),
       то строка помещается в  специальный  буфер и выполняется одновре-
       менно с другими программными действиями.  Напротив, MF (музыка на
       переднем плане)  останавливает  все  программные  операции до тех
       пор,  пока вся строка не будет исполнена.  Вот как исполнить одну
       ноту A (ля) в фоновом режиме:

       100 PLAY "MB A"    'исполняется нота ля...
       110 ......         'и следующие операторы программы

          Отметим, что в фоновом режиме, оператор X = PLAY(0) возвращает
       число  нот  (до 32), которое осталось сыграть.  В  многоканальном
       режиме на PCjr  возвращается  число  нот  в буфере данного канала
       (0-2), номер которого указан в скобках.

          Низкий уровень.

          Просто  пошлите  счетчик в канал 2, как  объяснено в  [2.1.1].
       Микросхема  должна  быть  предварительно  разрешена  через порт B
       микросхемы  интерфейса с периферией 8255 (адрес 61H).   Вычислите
       требуемое значение счетчика для задвижки, разделив 1.19 миллионов
       на  требуемую  частоту в герцах.  Звук будет продолжаться до  тех
       пор, пока не будут  закрыты  ворота  канала  2. Поэтому Вы должны
       сбросить  бит 1 порта B в 0, иначе звук будет продолжаться беско-
       нечно и может быть прекращен только перезагрузкой компьютера. Для
       точного регулирования длительности звука можно использовать счет-
       чик времени суток BIOS, как  указано  в [2.1.6]. В данном примере
       генерируется  частота 440 герц.  Звук прекращается после  нажатия
       любой клавиши на клавиатуре.

       ;---рарешение канала 2 установкой порта B микросхемы 8255
       PORT_B     EQU  61H           ;установка адреса порта B
                  IN   AL,PORT_B     ;чтение его значения
                  OR   AL,3          ;установка двух младших битов
                  OUT  PORT_B,AL     ;посылаем байт в порт B
       ;---установка регистров ввода/вывода
       COMMAND_REG  EQU  43H         ;адрес командного регистра
       CHANNEL_2    EQU  42H         ;адрес канала 2
       


                                     -52-
                    MOV  AL,10110110B    ;цепочка битов для канала 2
                    OUT  COMMAND_REG,AL  ;засылка в командный регистр
       ;---засылка счетчика в задвижку
                  MOV  AX,2705       ;счетчик = 1190000/440
                  OUT  CHANNEL_2,AL  ;посылаем младший байт
                  MOV  AL,AH         ;сдвигаем младший байт в AL
                  OUT  CHANNEL_2,AL  ;посылаем старший байт
       ;---ждем нажатия клавиши
                  MOV  AH,1          ;номер функции прерывания 21H
                  INT  21H           ;вызываем прерывание
       ;---выключение звука
                  IN   AL,PORT_B     ;получаем байт из порта B
                  AND  AL,11111100B  ;сбрасываем два младших бита
                  OUT  PORT_B,AL     ;посылаем байт обратно

          2.2.4 Гудок динамика.

          Некоторым  программам требуется набор предостерегающих гудков.
       Их легко создавать на Бейсике, но операционная система не обеспе-
       чивает  функцию гудка, как таковую, и только  косвенно  позволяет
       получать доступ к гудку,  который  Вы слышите при старте системы.
       Для  изменения  тона вся процедура генерации  звука  должна  быть
       запрограммирована на низком  уровне.   Для того чтобы гудок соот-
       ветствовал  подаваемому им сигналу необходимо проявить  воображе-
       ние. Для предсказания близкой  опасности  создайте набор понижаю-
       щихся  тонов [2.2.7] или, если принтер включен,  чередуйте  гудки
       динамика компьютера и принтера (вывод кода ASCII 7 на принтер).

          Высокий уровень.

          В Бейсике просто  напишите  BEEP.   Вот  кусочек кода, который
       реагирует на вероятную ошибку гудком и запросом:

       100 INPUT "Enter your age",AGE             'запрос возраста
       110 IF AGE > 100 THEN BEEP:PRINT"Are you really over 100?"

          Для  гудков  другой  частоты и  продолжительности  используйте
       оператор SOUND.  Его  форма:  SOUND  частота,  длительность , где
       частота  дается в герцах (3000 - середина диапазона), а  длитель-
       ность дается в восемнадцатых  долях  секунды.  SOUND 3000,18 дает
       гудок длительностью около одной секунды. В нижеприведенном приме-
       ре динамик быстро переходит от высокого тона к низкому и обратно,
       распугивая все живое в ближайшей окрестности.

       100 FOR N = 1 TO 200   'установка числа повторений
       110 SOUND 500,1        'звук низкой частоты на 1 секунду
       120 SOUND 5000,1       'звук высокой частоты на 1 секунду
       130 NEXT               'повтор

          Средний уровень.

          Операционная система не предоставляет  специальной функции для
       генерации звука. Но Вы можете вызвать знакомый гудок просто пода-
       вая код ASCII 7 на стандартное устройство вывода (т.е. терминал),
       используя  одну из функций DOS или BIOS.  Код ASCII 7 интерпрети-
       руется как управляющий символ "звонок"  и он не рисуется на экра-
       не. Проще всего использовать функцию 2 прерывания 21H:

          MOV  AH,2     ;функция вывода символа на экран
          MOV  DL,7     ;посылаем код ASCII 7
          INT  21H      ;динамик гудит

          Низкий уровень.
       


                                     -53-
          Для  простого гудка лучше всего подходит метод, основанный  на
       использовании микросхемы  интерфейса  с  периферией 8255 [1.1.1].
       Ниже приведен пример, который практически повторяет гудок,  кото-
       рый Вы слышите при старте системы.

       ;---гудок динамика
                   MOV  DX,800          ;счетчик числа циклов
                   IN   AL,61H          ;читаем порт B 8255
                   AND  AL,0FEH         ;выключаем бит таймера 8253
       NEXTCYCLE:  OR   AL,2            ;включаем бит динамика
                   OUT  61H,AL          ;посылаем байт в порт B
                   MOV  CX,150          ;длительность первой половины
       CYCLEUP:    LOOP CYCLEUP         ;задержка пока сигнал высокий
                   AND  AL,0FDH         ;выключаем бит динамика
                   OUT  61H,AL          ;посылаем байт в порт B
       CYCLEDOWN:  LOOP CYCLEDOWN       ;задержка пока сигнал низкий
                   DEC  DX              ;уменьшаем счетчик циклов
                   JNZ  NEXTCYCLE       ;повторяем цикл пока DX не 0

          2.2.5 Генерация набора тонов.

          В  этом  подразделе показано как генерировать цепочку  звуков,
       когда компьютер ничем другим не занят; в следующем будет показано
       как выполнить ту же задачу, когда компьютер занят другой работой.
       Когда компьютер ничем другим не занят,  то можно выводить мелодию
       или производить специальные звуковые эффекты; когда же  компьютер
       занят другой работой, то нельзя производить звуковые эффекты.
          Создание звуковых строк является одной из мощнейших возможнос-
       тей, предоставляемых Бейсиком.  Построение же строк звуков в  ас-
       семблере требует большой работы.  Может быть использован любой из
       двух  методов генерации звука, предложенных в [2.2.2] и  [2.2.3].
       Для обоих методов надо  просто  генерировать  один  тон в течении
       заданного времени, затем следующий и т.д.  Каждая звуковая строка
       формируется из двух строк данных, одна из которых содержит часто-
       ты  последовательных тонов, а другая хранит их длительности  (при
       условии, что требуются разные  длительности).   Продолжительность
       звучания  определяется  с использованием счетчика  времени  суток
       BIOS [2.1.6].

          Высокий уровень.

          Опреатор Бейсика PLAY предоставляет большие возможности.  Опе-
       ратор  сопровождается строкой нот, перемешанных с  информацией  о
       том, как эти ноты должны быть исполнены. Ноты записываются буква-
       ми A - G и последующими знаками для диезов и бемолей. Диезы обоз-
       начаются знаками # или +, а  бемоли  минусом (-).  Операторы PLAY
       "CC#D" и PLAY "CD-D" эквивалентны, но нельзя использовать диезы и
       бемоли для обозначения белых  клавиш.   Второй способ задания нот
       состоит  в вычислении кодового номера от 0 до 84, причем 0  соот-
       ветствует отсутствию звучания,  а  числа от 1 до 84 соответствуют
       84 возможным нотам семи октав, начиная снизу. Номеру должна пред-
       шествовать буква N: PLAY "N3N72N44".
          Допустимый диапазон -  семь  октав,  внутри  каждой могут быть
       ноты от C(до) до B(си).  Октавы пронумерованы от 0 до 6 и нота до
       первой октавы соответствует  октаве  3. Текущая октава может быть
       изменена  в  любой момент, за счет вставки в строку буквы  O,  за
       которой следует номер октавы.  Если  не было начальной установки,
       то  используется октава 4.  Оператор PLAY "O3CO4CO5CO6C"  выводит
       ноты до последовательных октав  вверх.   Другой  способ изменения
       октавы  состоит во включении в строку символов > или  <,  которые
       переключают тон вверх и вниз на октаву, соответственно.  Оператор
       PLAY "O3C>C>C>C" приводит к тому же результату, что и предыдущий.
          Длительность исполнения нот также может быть изменена за  счет
       


                                     -54-
       вставки кодового номера, которому предшествует буква L.  Все пос-
       ледующие ноты будут исполняться с этой длительностью до тех  пор,
       пока не встретится другой код длины.  Код - это число от 1 до 64,
       причем  1 соответствует целой ноте, а 64 - 1/64.  Запись L4 соот-
       ветствует четверти.  Темп с которым исполняются ноты регулируется
       кодом темпа, который состоит из буквы T, за которой следует число
       от 32 до 255, дающее число четвертей, исполняемых в минуту.  Если
       эти параметры не указаны, то по умолчанию берется длительность L4
       и темп 120.  Для  изменения  длительности  только одной ноты надо
       поместить значение длины после ноты и без буквы L.  Оператор PLAY
       "L4CDE16FG" исполнит E как шестнадцатую, а все остальные ноты как
       четверти.  Длительность пауз берется такой же, как и длительность
       нот.  Поместите номер от 1 до 64 после буквы P для паузы.  P1 де-
       лает  паузу интервалом в целую, а P64 - в 1/64.  Помещение  точки
       после ноты имеет тот же эффект,  какой  он  имеет в обычной музы-
       кальной  нотации:  длительность  ноты  увеличивается  наполовину.
       Вторая точка продолжит длительность еще наполовину.
          По умолчанию ноты играются 7/8 указанной  длительности.  Чтобы
       они исполнялись полную длительность (легато), поместите в  строку
       ML. Чтобы они исполнялись 3/4  длительности (стаккато), поместите
       в строку MS. Чтобы вернуться к нормальному стилю надо указать MN.
          Обычно,  вся прочая деятельность программы прекращается до тех
       пор, пока не будет  сыграна  строка.  Для  того чтобы выполнялись
       операторы,  следующие за оператором PLAY, а строка исполнялась  в
       фоновом режиме, поместите в  строку  MB.  Для восстановления нор-
       мальной ситуации напишите MF.
          Наконец,  оператор  PLAY позволяет исполнять подстроки  внутри
       длинной строки.  Имеется  в  виду,  что  часть исполняемой строки
       может быть введена как обычная строковая переменная, а затем  эта
       переменная может быть вызвана из строки сформированной в операто-
       ре  PLAY.   Например,  если S$ =  "EEEEE",  то в  операторе  PLAY
       "CDXS$;FG" нота E будет повторена 5 раз. Отметим, что имени пере-
       менной должна предшествовать буква X, а за именем следовать точка
       с запятой (;).  (Для  компилируемых  программ  применяется другой
       метод, использующий переменную VARPTR$ - детали см. в руководстве
       по Бейсику).
          В приведенном  примере  исполняется  знакомый  бой  дедушкиных
       часов.  В строке сначала устанавливается стиль исполнения легато,
       затем темп и начальная октава,  и, наконец, четыре ноты, пауза, и
       те же самые четыре ноты, но в обратном порядке.  Пробелы в строке
       включены исключительно для удобства программиста - Бейсик игнори-
       рует их.

          100 PLAY "ML T40 O3 ECD<G P32 G>DEC"

          Благодаря наличию генератора звука PCjr добавляет к  оператору
       PLAY две возможности. Во-первых, допускается параметр V, устанав-
       ливающий  громкость.  Выражение V5 устанавливает  (или  изменяет)
       громкость на уровень 5. Допустимый диапазон от 0 до 15, причем по
       умолчанию  берется 8.  0 полностью подавляет звук.  Во-вторых,  с
       помощью оператора PLAY можно  одновременно исполнять три звуковых
       строки.  Поместите все три строки в одну программную строку, раз-
       деляя их запятыми.  Для того чтобы иметь возможность использовать
       эти  специальные  свойства,  Вы должны  предварительно  разрешить
       внешний динамик с помощью оператора SOUND ON.

          100 SOUND ON
          110 PLAY "...........","..........","............"

          Низкий уровень.

          В примере для генерации звука используется  микросхема таймера
       8253.   Здесь просто исполняются 8 нот, но небольшая  модификация
       


                                     -55-
       может сильно расширить  возможности  этой процедуры.  Имеется три
       строки данных. Первая устанавливает длительность каждой ноты, как
       кратное произвольного периода  задержки  (изменяя этот период за-
       держки,  можно  изменять темп).  Вторая строка  содержит  частоты
       каждой из 8 нот;  эти  значения  должны  быть  помещены в регистр
       задвижки  канала 2 микросхемы 8253 для исполнения желаемых тонов.
       Третья строка содержит мелодию в виде  кодовых номеров от 1 до 8,
       которые  соответствуют  восьми частотам.  Эта строка  завершается
       кодом 0FFH, который  служит  признаком  конца  мелодии. Процедура
       просто  читает  очередную ноту мелодии,  находит  соответствующую
       частоту и помещает ее в канал 2. Затем длительность для этой ноты
       помещается  в счетчик цикла задержки, который использует  счетчик
       времени суток, а когда задержка  кончается,  то переходим к обра-
       ботке следующей ноты. На рис. 2-5 показана работа этой процедуры.

       ;---в сегменте данных
       BEAT        DB   10,9,8,7,6,5,4,3,2    ;длительность нот
       FREQUENCY   DW   2280,2031,1809,1709   ;таблица частот
                   DW   1521,1353,1207,1139   ;
       MELODY      DB   1,2,3,4,5,6,7,8,0FFH  ;номер частоты ноты

       ;---инициализация
       PORT_B      EQU  61H
       COMMAND_REG EQU  43H
       LATCH2      EQU  42H
                   IN   AL,PORT_B      ;получаем текущий статус
                   OR   AL,00000011B   ;разрешаем динамик и таймер
                   OUT  PORT_B,AL      ;заменяем байт
                   MOV  SI,0           ;инициализируем указатель
                   MOV  AL,0B6H        ;установка для канала 2
                   OUT  COMMAND_REG,AL ;посылаем в командный регистр
       ;---смотрим ноту, получаем ее частоту и помещаем в канал 2
       NEXT_NOTE:  LEA  BX,MELODY      ;берем смещение для мелодии
                   MOV  AL,[BX][SI]    ;берем код n-ной ноты строки
                   CMP  AL,0FFH        ;проверка на конец строки
                   JE   NO_MORE        ;если конец, то на выход
                   CBW                 ;переводим в слово
          ;получение частоты
                   MOV  BX,OFFSET FREQUENCY  ;смещение таблицы частот
                   DEC  AX             ;начинаем отсчет с 0
                   SHL  AX,1           ;умножаем на 2, т.к. слова
                   MOV  DI,AX          ;адресуем через DI
                   MOV  DX,[BX][DI]    ;получаем частоту из таблицы
          ;начинаем исполнение ноты
                   MOV  AL,DL          ;готовим младший байт частоты
                   OUT  LATCH2,AL      ;посылаем его
                   MOV  AL,DH          ;готовим старший байт частоты
                   OUT  LATCH2,AL      ;посылаем его
       ;---создание цмкла задержки
                   MOV  AH,0           ;номер функции чтения счетчика
                   INT  1AH            ;получаем значение счетчика
                   MOV  BX,OFFSET BEAT ;смещение таблицы длин
                   MOV  CL,[BX][SI]    ;берем длину очередной ноты
                   MOV  CH,0           ;
                   MOV  BX,DX          ;берем младшее слово счетчика
                   ADD  BX,CX          ;определяем момент окончания
       STILL_SOUND: INT 1AH            ;берем значение счетчика
                   CMP  DX,BX          ;сравниваем с окончанием
                   JNE  STILL_SOUND    ;неравны - продолжаем звук
                   INC  SI             ;переходим к следующей ноте
                   JMP  NEXT_NOTE      ;
       ;---завершение
       NO_MORE:    IN   AL,PORT_B      ;получаем статус порта B
       


                                     -56-
                   AND  AL,0FCH        ;выключаем динамик
                   OUT  61H,AL         ;заменяем байт

          2.2.6 Генерация строки тонов, одновременно с другими операция-
       ми.

          Хотя в Бейсике это  делается  очень  просто, на самом деле это
       нетривиальный трюк программирования в реальном времени. Для реше-
       ния этой задачи нужно  использовать генерацию звука через микрос-
       хему  8253  [2.2.3], так как метод, использующий микросхему  8255
       [2.2.2], занимает процессор. Соответственно, только строки чистых
       музыкальных  тонов  могут производиться таким  методом -  всякого
       рода звуковые эффекты при этом недоступны. Основная техника прог-
       раммирования  в реальном времени показана в [2.1.7].   Программы,
       работающие в реальном времени,  модифицируют  прерывание таймера,
       которое  останавливает процессор 18.2 раз в секунду, чтобы  изме-
       нить показание счетчика времени суток.  Расширение процедуры пре-
       рывания сравнивает новое значение счетчика времени суток со  зна-
       чением, показывающим время завершения генерации тона, и когда это
       значение  достигнуто, прерывает звук, начинает генерацию  другого
       тона и устанавливает время его окончания.

          Высокий уровень.

          Генерация  строки  звуков  одновременно  с  другими операциями
       является  одной  из  возможностей очень мощного  оператора  PLAY,
       который детально обсуждался  в  [2.2.5].  Надо  просто добавить в
       начало управляющей строки MB.  Это сокращение от Music Background
       (фоновая музыка); для того  чтобы  заставить  PLAY прекратить все
       другие операции, пока генерация звуковой строки не будет заверше-
       на, вставьте MF.  В  нижеприведенном примере во время рисования и
       заполнения  рамки  исполняется  гамма (для его  работы  требуется
       наличие графических возможностей).

       100 PLAY "MB T100 O3 L4;CDEFG>ABC"  'исполняем набор нот
       110 LINE (10,10)-(80,80),1,BF       'одновременно рисуем рамку

          Низкий уровень.

          Приведенная процедура является развитием процедуры, показанной
       в предыдущем разделе, на случай  реального  времени.  Она требует
       понимания, как перепрограммировать прерывание таймера, что обсуж-
       далось в [2.1.7]. На эту процедуру должен указывать вектор преры-
       вания  и  тогда она будет выполняться 18.2 раза в  секунду, в  те
       моменты, когда будет обновляться  значение счетчика времени суток
       BIOS. Обычно, будут выполняться только несколько строчек, которых
       достаточно, чтобы  определить,  что  время изменения звука еще не
       наступило,  - и процедура освождает процессор для решения  других
       задач.
          Счетчик времени суток BIOS используется для измерения длитель-
       ности каждой ноты.  При переходе от одной ноты к другой, длитель-
       ность новой ноты вычисляется  как  число импульсов счетчика и это
       значение  добавляется  к текущему его значению.  Каждый  раз  при
       вызове процедуры проверяется  текущее  значение  счетчика времени
       суток, и когда ожидаемое время наконец наступает, то  выполняется
       набор операций по поиску новой  ноты, программированию ее частоты
       в  канале  2 микросхемы 8253 и установлению нового счетчика  дли-
       тельности.  Добавочный  код  требуется  для обработки специальных
       случаев первой и последней нот в строке.

       ;---в сегменте данных
       BEAT        DB   10,9,8,7,6,5,4,3,2   ;длительность нот
       FREQUENCY   DW   2280,2031,1809,1709  ;таблица частот
       


                                     -57-
                   DW   1521,1355,1207,1139  ;
       MELODY      DB   1,2,3,4,5,6,7,8,0FFH ;номер частоты в таблице
       HOLDIP      DW   0                    ;запоминаем оригинальный
       HOLDCS      DW   0                    ;вектор прерывания
       SOUND_NOW?  DB   1                    ;звук включен?
       FIRST_NOTE? DB   1                    ;первая нота?
       END_NOTE    DW   0                    ;счетчик конца ноты
       WHICH_NOTE  DW   0                    ;указатель на текущую ноту

       ;---инициализация вектора прерывания
          ;изменение вектора
          PUSH  DS                      ;сохраняем регистр
          MOV   AX,SEG MELODY2          ;сегмент процедуры
          MOV   DS,AX                   ;помещаем в DS
          MOV   DX,OFFSET MELODY2       ;смещение процедуры
          MOV   AL,1CH                  ;номер вектора прерывания
          MOV   AH,25H                  ;функция установки вектора
          INT   21H                     ;изменение вектора
          POP   DS                      ;восстановление регистра
       ;
       ;---программа работает дальше, постоянно вызывая процедуру
       ;
       ;---в конце программы восстанавливаем вектор прерывания
          MOV   DX,0FF53H        ;восстанавливаем оригинальные
          MOV   AX,0F000H        ;значения для вектора 1CH
          MOV   DS,AX            ;
          MOV   AL,1CH           ;номер прерывания
          MOV   AH,25H           ;функция установки вектора
          INT   21H              ;восстанавливаем вектор
          RET                    ;

       ;---это само прерывание
       MELODY2    PROC FAR
                  PUSH AX        ;сохраняем изменяемые регистры
                  PUSH BX        ;
                  PUSH CX        ;
                  PUSH DX        ;
                  PUSH DI        ;
                  PUSH SI        ;
                  PUSH DS        ;
                  MOV  AX,SS:[114]   ;берем начальный DS со стека
                  MOV  DS,AX         ;восстанавливаем его
                  CMP  SOUND_NOW?,1  ;нужен ли звук?
                  JE   PLAY_IT       ;если нет, то выход из прерывания
                  JMP  NOT_NOW       ;
       PLAY_IT:   CMP  FIRST_NOTE?,0 ;это первая нота?
                  JE   TIME_CHECK    ;если нет, то на установку времени
       ;---инициализация
       PORT_B        EQU  61H           ;определяем имена портов
       COMMAND_REG   EQU  43H           ;
       LATCH2        EQU  42H           ;
                     IN   AL,PORT_B     ;берем статус порта B
                     OR   AL,00000011B  ;разрешаем динамик и таймер
                     OUT  PORT_B,AL     ;посылаем байт обратно
                     MOV  SI,0          ;указатель на строки
                     MOV  AL,0B6H       ;инициализация канала 2 таймера
                     OUT  COMMAND_REG,AL   ;посылаем в командный регистр
                     MOV  FIRST_NOTE?,0    ;сбрасываем флаг первой ноты
       ;---ищем ноту, получаем ее частоту, посылаем в канал 2
       NEXT_NOTE:    LEA  BX,MELODY     ;берем смещение строки мелодии
                     MOV  SI,WHICH_NOTE ;указатель на текущую ноту
                     MOV  AL,[BX][SI]   ;код текущей ноты строки
                     CMP  AL,0FFH       ;проверяем признак конца
       


                                     -58-
                     JE   NO_MORE       ;если да, то на конец
                     CBW                ;иначе в словный формат
          ;получаем частоту
                     MOV  BX,OFFSET FREQUENCY  ;смещение таблицы частот
                     DEC  AX            ;начинаем отсчет с нуля
                     SHL  AX,1          ;умножаем на 2, т.к. словная
                     MOV  DI,AX         ;адресуемся через DI
                     MOV  DX,[BX][DI]   ;получаем частоту из таблицы

          ;начинаем исполнение ноты
                     MOV  AL,DL         ;готовим младший байт частоты
                     OUT  LATCH2,AL     ;посылаем в регистр задвижки
                     MOV  AL,DH         ;готовим старший байт
                     OUT  LATCH2,AL     ;посылаем его
       ;---пустой цикл, определяющий длительность нот
       TIME_IT:      MOV  AH,0          ;фнукция чтения счетчика
                     INT  1AH           ;получаем значение счетчика
                     MOV  BX,OFFSET BEAT  ;смещение строки длин нот
                     MOV  CL,[BX][SI]   ;длительность текущей ноты
                     MOV  CH,0          ;
                     MOV  BX,DX         ;младшее слово значения счетчика
                     ADD  BX,CX         ;добавляем длину в импульсах
                     MOV  END_NOTE,BX   ;запоминаем время окончания
       TIME_CHECK:   MOV  AH,0          ;функция чтения счетчика
                     INT  1AH           ;читаем счетчик
                     CMP  DX,END_NOTE   ;сравниваем с нужным
                     JNE  NOT_NOW       ;если неравно, то выходим
                     MOV  SI,WHICH_NOTE ;иначе, берем следующую ноту
                     INC  SI            ;увеличиваем номер ноты
                     MOV  WHICH_NOTE,SI ;запоминаем его
                     JMP  NEXT_NOTE     ;начинаем следующую ноту
       ;---завершение процедуры
       NO_MORE:      IN   AL,PORT_B     ;берем статус порта B
                     AND  AL,0FCH       ;выключаем динамик
                     OUT  61H,AL        ;возвращаем байт
                     MOV  SOUND_NOW?,0  ;восстанавливаем переменные
                     MOV  FIRST_NOTE?,1 ;
       NOT_NOW:      POP  DS            ;восстанавливаем регистры
                     POP  SI            ;
                     POP  DI            ;
                     POP  DX            ;
                     POP  CX            ;
                     POP  BX            ;
                     POP  AX            ;
                     IRET               ;возврат из прерывания
       MELODY2       ENDP

          2.2.7 Создание плавного перехода тонов.

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

          Высокий уровень.

          В Бейсике надо просто поместить оператор SOUND [2.2.2] в цикл,
       используя очень малые длины тонов. При каждом новом проходе цикла
       надо увеличивать частоту.  Смотрите  [2.2.8], где приведен пример
       использования оператора PLAY для более быстрых переходов.

       


                                     -59-
       100 FOR N = 1 TO 500 STEP 15
       110 SOUND 400 + N,1
       120 NEXT

          Низкий уровень.

          Проще  всего использовать метод генерации  звука,  управляемый
       микросхемой интерфейса с периферией 8255. Просто меняйте значение
       бита  1 порта B между 0 и 1, используя для отсчета времени пустой
       цикл, как показано в [2.2.2].   При начале каждого нового пустого
       цикла,  засчет засылки значения в CX, слегка изменяйте это значе-
       ние. Здесь тон повышается:

       ;---запрет микросхемы таймера
       PB       EQU  61H        ;адрес порта B микросхемы 8255
                IN   AL,PB      ;получаем из него байт
                OR   AL,1       ;сбрасываем бит 0
                OUT  PB,AL      ;возвращаем байт в порт
       ;---установка частоты и длительности звука
                MOV  BX,9000    ;начальное значение счетчика
                MOV  DX,3000    ;длительность звука 3000 циклов
       REPEAT:                  ;сюда возвращаемся после цикла
       ;---установка бита динамика
                OR   AL,00000010B   ;устанавливаем бит 1
                OUT  PB,AL          ;посылаем байт в порт B
                MOV  CX,BX          ;установка счетчика для 1/2 цикла
       CYCLE1:  LOOP CYCLE1         ;пустой цикл на 1000 повторов
       ;---сброс бита динамика
                AND  AL,11111101B   ;сбрасываем бит 1
                OUT  PB,AL          ;посылаем байт в порт
                MOV  CX,BX          ;установка счетчика
       CYCLE2:  LOOP CYCLE2         ;пустой цикл
       ;---переход к следующему циклу
                DEC  BX             ;увеличиваем частоту, уменьшая
                DEC  BX             ;счетчик
                DEC  DX             ;уменьшаем оставшуюся длительность
                JNZ  REPEAT         ;если DX не 0, то новый цикл

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

          2.2.8 Создание звуковых эффектов.

          Звуковые  эффекты  обычно достигаются  непрерывным  изменением
       частоты тона. Только PCjr  достаточно  хорошо оборудован для этой
       цели (см. обсуждение в [2.2.1]).  На других машинах нельзя произ-
       водить звуковые эффекты одновременно с другими операциями.

          Высокий уровень.

          Благодаря мощности своих операторов SOUND и PLAY Бейсик позво-
       ляет достаточно легко создавать сложные звуковые эффекты.  Но все
       должно быть сконструировано из  чистых  музыкальных  тонов, а это
       значит,  что  эффект дисторции звука должен достигаться  за  счет
       такого быстрого  изменения  тона,  что  ухо не успевает разделить
       тона.  Например, душераздирающее "чириканье" может быть  получено
       при быстром переключении между одним и тем же тоном, отстоящим на
       несколько октав:

       100 FOR N = 1 TO 100     'установка длительности
       


                                     -60-
       110 PLAY "L64 T255"      'самый быстрый темп
       120 PLAY "O1A"           'выдаем низкое A
       130 PLAY "O5A"           'выдаем высокое A
       140 NEXT                 'повтор

       При изменении частоты всего на несколько герц получаем вибрацию:

       100 FOR N = 1 TO 100     'установка длительности
       110 SOUND 440,1          'выдаем ноту A
       120 SOUND 445,1          'немного меняем частоту
       130 NEXT                 'повтор

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

       100 FOR I = 1 TO 10   'число повторений
       110 FOR J = 1 TO 6    'число разных октав
       120 PLAY "MBL64T255O=J;BA#AG#GF#FED#DC#CC#DD#EFF#GG#AA#B"
       130 NEXT              'повтор в более высокой октаве
       140 NEXT              'повтор всей последовательности

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

          NOISE источник, громкость, длительность

       Источник  -  это число от 0 до 7, значение которого  приведено  в
       таблице:

          0       периодический шум в высоком диапазоне
          1       периодический шум в среднем диапазоне
          2       периодический шум в низком диапазоне
          3       периодический шум, диапазон меняется с каналом 3
          4       белый шум в высоком диапазоне
          5       белый шум в среднем диапазоне
          6       белый шум в низком диапазоне
          7       белый шум, диапазон меняется с каналом 3
       Громкость задается числом от  0  до  15,  где 0 соответствует от-
       сутствию звука. Длительность указывается числом импульсов счетчи-
       ка времени суток, которые отсчитываются 18.2 раза в секунду.

          Низкий уровень.

          Любой из способов, показанных на Бейсике может быть реализован
       на  ассемблере, хотя, как видно из предыдущих разделов, это  тре-
       бует затрат на программирование.  Кроме того, ассемблер позволяет
       генерировать  нечистые  тона, когда интервал, в течение  которого
       динамик включен, не равен интервалу, в течение которого он выклю-
       чен.  Такое нарушение симметрии может приводить к жужжащим и бря-
       кающим звукам. Когда отношение этих интервалов составляет, скажем
       50  к 1, то получаем жужжание.  Если увеличить отношение еще в 10
       - 20 раз, то жужжание переходит  в  отдельные  брякающие звуки. В
       любом  случае звук генерируется микросхемой интерфейса с  перифе-
       рией 8255, с помощью  техники  показанной  в  [2.2.2]. Вот пример
       жужжания:

       NUMBER_CYCLES  EQU  300     ;число переключений динамика
       FREQUENCY1     EQU  50      ;время, когда динамик включен
       FREQUENCY2     EQU  3200    ;время, когда динамик выключен
       PORT_B         EQU  61H     ;адрес порта B микросхемы 8255
       


                                     -61-
                   CLI                  ;запрет прерываний
                   MOV  DX,NUMBER_CYCLES;DX считает длину тона
                   IN   AL,PORT_B       ;получаем статус порта
                   AND  AL,11111110B    ;отключаем динамик от таймера
       NEXT_CYCLE: OR   AL,00000010B    ;включаем динамик
                   OUT  PORT_B,AL       ;посылаем команду
                   MOV  CX,FREQUENCY1   ;задержка для первой части
       FIRST_HALF: LOOP FIRST_HALF      ;
                   AND  AL,11111101B    ;выключаем динамик
                   OUT  PORT_B,AL       ;посылаем команду
                   MOV  CX,FREQUENCY2   ;задержка для второй части
       SECND_HALF: LOOP SECND_HALF      ;
                   DEC  DX              ;уменьшаем число циклов
                   JNZ  NEXT_CYCLE      ;если 0, то пора кончать
                   STI                  ;разрешаем прерывания

       Для  создания брякающих звуков можно использовать этот же код, но
       надо заменить значение FREQUENCY2 на величину около 40000.

          2.2.9 Одновременная генерация разных звуков.

          Только микросхема генератора  звука,  имеющаяся в PCjr, позво-
       ляет  одновременно генерировать разные звуки (см.   обсуждение  в
       [2.2.1]). Однако ассемблер позволяет объединить два способа гене-
       рации  звука,  что создает имитацию одновременной генерации  двух
       разных звуков.  Интерференция этих двух сигналов приводит к слож-
       ной  форме звуковой волны.  Каждый из двух звуков  имеет  меньшую
       громкость, поэтому в результате  получается  скорее жужжание, чем
       два разных голоса. Этот прием реально полезен только для создания
       звуковых эффектов.

          Низкий уровень.

          Надо просто объединить два метода  генерации звука, показанные
       в [2.2.2] и [2.2.3]. Начните звук через канал 2 микросхемы тайме-
       ра.  Затем модулируйте  выход  динамика,  за  счет бита 1 порта B
       микросхемы  интерфейса с периферией.  Второе действие  определяет
       продолжительность звука. Не забудьте выключить микросхему таймера
       при завершении.

       ;---начинаем генерацию звука через канал 2 таймера
             IN   AL,61H          ;получаем байт из порта B
             OR   AL,3            ;устанавливаем младшие два байта
             OUT  61H,AL          ;посылаем байт обратно
             MOV  AL,10110110B    ;цепочка для командного регистра 8253
             OUT  43H,AL          ;посылаем в регистр
             MOV  AX,600H         ;счетчик для канала 2
             OUT  42H,AL          ;посылаем младший байт
             MOV  AL,AH           ;готовим старший байт
             OUT  42H,AL          ;посылаем старший байт

       ;---генерируем вторую частоту микросхемой 8255
       NUMBER_CYCLES  EQU  9000           ;число переключений
       FREQUENCY      EQU  150            ;задержка для половины цикла
                      CLI                 ;запрет прерываний
                      MOV  DX,NUMBER_CYCLES  ;DX считает длину тона
                      IN   AL,61H         ;получаем статус порта
                      AND  AL,11111111B   ;отключаем динамик от таймера
       NEXT_CYCLE:    OR   AL,00000010B   ;включаем динамик
                      OUT  61H,AL         ;посылаем назад в порт
                      MOV  CX,FREQUENCY   ;задержка на 1/2 цикла
       FIRST_HALF:    LOOP FIRST_HALF     ;
                      AND  AL,11111101B   ;выключаем динамик
       


                                     -62-
                      OUT  61H,AL         ;посылаем команду в порт
                      MOV  CX,FREQUENCY   ;задержка на 1/2 цикла
       SECOND_HALF:   LOOP SECOND_HALF    ;
                      DEC  DX             ;меняем счетчик циклов
                      JNZ  NEXT_CYCLE     ;если 0, то пора кончать
                      STI                 ;разрешаем прерывания
       ;---выключение канала 2 микросхемы таймера
                      IN   AL,61H         ;получаем статус порта
                      AND  AL,11111100B   ;сбрасываем 2 младших бита
                      OUT  61H,AL         ;посылаем байт обратно

                               Глава 3. Клавиатура.

          Раздел 1. Управление клавиатурой.

          Клавиатура  содержит интелевский микропроцессор, который восп-
       ринимает каждое нажатие  на  клавишу  и  выдает скан-код в порт A
       микросхемы  интерфейса  с  периферией [1.1.1],  расположенной  на
       системной плате.  Скан-код это однобайтное число, младшие 7 битов
       которого представляют идентификационный номер, присвоенный каждой
       клавише. Таблица скан-кодов приведена в [3.3.2]. На всех машинах,
       кроме  AT, старший бит кода говорит о том, была ли клавиша нажата
       (бит = 1, код нажатия) или  освобождена  (бит = 0, код освобожде-
       ния).   Например, 7-битный скан-код клавиши B - 48, или 110000  в
       двоичной системе. Когда эта клавиша нажимается, то в порт A посы-
       лается  код 10110000, а когда ее отпустили - код 00110000.  Таким
       образом, каждое нажатие на  клавишу  дважды регистрируется в мик-
       росхеме 8255.  И каждый раз микросхема 8255 выдает  подтверждение
       микропроцессору клавиатуры. AT работает немного по-другому, посы-
       лая  в  обоих  случаях один и тот же скан-код, но  предваряя  его
       кодом F0H, когда клавиша отпускается.
          Когда скан-код выдается  в  порт  A,  то вызывается прерывание
       клавиатуры (INT 9).  Процессор моментально прекращает свою работу
       и выполняет процедуру,  анализирующую  скан-код.  Когда поступает
       код  от  клавиши сдвига или переключателя, то  изменение  статуса
       записывается в память.  Во всех остальных случаях скан-код транс-
       формируется в код символа, при условии, что он подается при нажа-
       тии клавиши (в противном случае, скан-код отбрасывается).  Конеч-
       но, процедура сначала определяет установку клавиш сдвига и перек-
       лючателей, чтобы  правильно  получить  вводимый  код (это "a" или
       "A"?).  После этого введенный код помещается в буфер  клавиатуры,
       который является областью  памяти, способной запомнить до 15 вво-
       димых  символов, пока программа слишком занята, чтобы  обработать
       их. На рис. 3-1 показан путь, который проходит нажатие на клавишу
       перед тем, как покасть в Вашу программу.
          Имеется  два  типа  кодов символов, коды  ASCII и  расширенные
       коды.  Коды ASCII - это байтные числа, которые соответствуют рас-
       ширенному  набору  кодов  ASCII для IBM PC,  который  приведен  в
       [3.3.3]. Для IBM PC этот набор  включает  обычные символы пишущей
       машинки,  а также ряд специальных букв и символов  псевдографики.
       ASCII коды включают также  32  управляющих  кода,  которые обычно
       используются  для передачи команд периферийным устройствам, а  не
       выводятся как символы на экране; однако каждый из них имеет соот-
       ветствующий  символ,  который  может быть выведен на  дисплей,  с
       использованием прямой адресации дисплейной памяти [4.3.1]. (Стро-
       го говоря, только первые 128 символов являются настоящими  симво-
       лами ASCII, так как  ASCII  -  это  аббревиатура  от Американский
       стандартный  код для обмена информацией.  Но программисты  обычно
       говорят о кодах ASCII, чтобы отличить  их от других чисел. Напри-
       мер,  "ASCII 8" относится к клавише "Backspace", в то  время  как
       "8" - это цифра, которой соответствует ASCII 56).
          Второй набор  кодов,  расширенные  коды, присвоен клавишам или
       комбинациям  клавиш, которые не имеют представляющего их  символа
       


                                     -63-
       ASCII, таким как функциональные клавиши или комбинации с клавишей
       Alt.   Расширенные коды имеют длину 2 байта, причем  первый  байт
       всегда ASCII 0. Второй байт  -  номер  расширенного  кода, список
       которых  приведен  в  [3.3.5].  Например, код  0:30  представляет
       Alt-A.  Начальный ноль позволяет  программе принадлежит ли данный
       код набору ASCII или расширенному набору.
          Имеется  несколько  комбинаций клавиш, которые выполняют  спе-
       циальные функции и не генерируют скан-коды.  Эти комбинации вклю-
       чают <Ctrl-Break>, <Ctrl-Alt-Del> и <PrtSc>, плюс <SysReq> для AT
       и <Ctrl-Alt-стрелка влево, -стрелка  вправо, -CapsLock, -Ins> для
       PCjr.  Эти исключения приводят к заранее предопределенным резуль-
       татам [3.3.2]. Все остальные  нажатия клавиш должны интерпретиро-
       ваться  Вашей программой и если они имеют специальное назначение,
       скажем сдвинуть курсор влево, то Ваша  программа должна содержать
       код, обеспечивающий достижение этого эффекта.
          К счастью операционная система предоставляет различные  проце-
       дуры для чтения кодов из буфера  клавиатуры, включая средства для
       получения сразу целой строки.  Поскольку эти процедуры  позволяют
       делать практически все, что  Вы  можете  пожелать, то практически
       бессмысленно писать свои процедуры обработки ввода с клавиатуры и
       поэтому в данной главе имеется очень мало примеров программирова-
       ния на низком уровне. Однако содержится обсуждение вопроса о том,
       как перепрограммировать прерывание клавиатуры.

          3.1.1 Очистка буфера клавиатуры.

          Программа должна  очистить  буфер  клавиатуры,  перед тем, как
       выдать  запрос  на ввод, исключая тем самым  посторонние  нажатия
       клавиш, которые могут к тому  времени  накопиться в буфере. Буфер
       может  накапливать до 15 нажатий на клавишу, независимо от  того,
       являются ли они однобайтными кодами ASCII или двухбайтными расши-
       ренными  кодами.  Таким образом, буфер должен отвести  два  байта
       памяти для каждого нажатия  на  клавишу.   Для  однобайтных кодов
       первый байт содержит код ASCII, а второй - скан-код клавиши.  Для
       расширенных кодов первый байт  содержит  ASCII  0, а второй номер
       расширенного кода. Этот код обычно совпадает со скан-кодом клави-
       ши, но не всегда, поскольку  некоторые  клавиши  могут комбиниро-
       ваться с клавишами сдвига для генерации различных кодов.
          Буфер устроен как циклическая очередь, которую называют  также
       буфером FIFO (первый вошел - первый  ушел).  Как и любой буфер он
       занимает  непрерывную область адресов памяти.  Однако не  имеется
       определенной  ячейки  памяти,  которая  хранит  "начало строки" в
       буфере. Вместо этого два указателя хранят позиции головы и хвоста
       строки  символов, находящейся в буфере в текущий  момент.   Новые
       нажатия клавиш запасаются  в  позициях,  следующих  за хвостом (в
       более старших адресах памяти) и соответственно обновляется указа-
       тель хвоста буфера.  После того,  как  израсходовано все буферное
       пространство,  новые  символы продолжают вставляться,  начиная  с
       самого начала буферной области; поэтому  возможны ситуации, когда
       голова строки в буфере имеет больший адрес, чем хвост. После того
       как буфер заполнен, новые вводимые символы игнорируются, при этом
       прерывание  клавиатуры выдает гудок через динамик.  На рис.   3-2
       показаны некоторые возможные конфигурации данных в буфере.
          В то время как указатель на  голову  установлен на первый вве-
       денный  символ, указатель на хвост установлен на позицию за  пос-
       ледним введенным  символом.  Когда  оба указателя равны, то буфер
       пуст.   Чтобы  разрешить ввод 15 символов требуется  16-я  пустая
       позиция, 2 байта  которой  всегда  содержат  код возврата каретки
       (ASCII  13)  и скан-код клавиши <Enter>, равный 28.   Эта  пустая
       позиция непосредственно  предшествует  голове строки символов. 32
       байта буфера начинаются с адреса 0040:001E. Указатели на голову и
       хвост расположены по адресам 0040:001A и 0040:001C, соответствен-
       но.   Хотя  под указатели отведено 2 байта,  используется  только
       


                                     -64-
       младший байт. Значения указателей меняются от 30 до 60, что соот-
       ветствует позициям в области данных BIOS. Для очистки буфера надо
       просто установить значение ячейки 0040:001A равным значению ячей-
       ки 0040:001C.
          Отметим,  что программа имеет возможность вставлять символы  в
       буфер, завершая строку символом возврата каретки и соответственно
       меняя значения указателей.  Если это проделать правильным образом
       перед  завершением программы, то при возврате управления в MS DOS
       эти символы будут считаны и  может  быть  автоматически загружена
       другая программа.

          Низкий уровень.

          В Бейсике для получения и изменения значений указателей буфера
       используются операторы PEEK и POKE:

       100 DEF SEG = &H40        'устанавливаем значение сегмента
       110 POKE &H1C, PEEK(&H1A) 'выравниваем указатели

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

       100 IF INKEY$<>"" THEN 100   'берем следующее если не нуль

          Средний уровень.

          Функция  0C прерывания 21H выполняет любую из функций ввода  с
       клавиатуры 1, 6, 7, 8 и A (описанных в этой главе), но перед этим
       чистит  буфер  клавиатуры.  Надо просто поместить  номер  функции
       ввода в AL (в этом примере - 1):

       ;---очистка буфера перед ожиданием нажатия клавиши
          MOV  AH,0CH    ;выбираем функцию DOS 0CH
          MOV  AL,1      ;выбираем функцию ввода символа
          INT  21H       ;чистим буфер, ждем ввода

          Низкий уровень.

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

       ;---выравниваем значения указателей на голову и хвост
          CLI                   ;запрещаем прерывания
          SUB  AX,AX            ;обнуляем регистр
          MOV  ES,AX            ;добавочный сегмент - с начала памяти
          MOV  AL,ES:[41AH]     ;берем указатель на голову буфера
          MOV  ES:[41CH],AL     ;посылаем его в указатель хвоста
          STI                   ;разрешаем прерывания

          3.1.2 Проверка символов в буфере.

          Вы можете проверить был ли ввод с клавиатуры, не удаляя символ
       из буфера клавиатуры.  Буфер  использует  два  указателя, которые
       отмечают голову и хвост очереди символов, находящихся в буфере  в
       текущий момент. Когда  значения  этих  указателей равны, то буфер
       пуст.   Надо просто сравнить содержимое ячеек памяти 0040:001A  и
       


                                     -65-
       0040:001C.  (Нельзя просто проверить символ, находящийся в голове
       очереди, поскольку буфер организован в виде циклической очереди и
       позиция ее головы постоянно меняется [3.1.1].)

          Высокий уровень.

          Надо просто использовать оператор PEEK для получения значений,
       а затем сравнить их:

       100 DEF SEG = &H40    'устанавливаем сегмент на начало памяти
       110 IF PEEK(&H1A)<>PEEK(&H1C) THEN ... '...то буфер не пуст

          Средний уровень.

          Функция 0BH прерывания 21H возвращает значение 0FFH в регистре
       AL, когда буфер  клавиатуры  содержит  один  или более символов и
       значение 0, когда буфер пуст:

       ;---проверка наличия символа в буфере
          MOV  AH,0BH           ;номер функции
          INT  21H              ;вызываем прерывание 21H
          CMP  AL,0FFH          ;сравниваем с 0FFH
          JE   GET_KEYSTROKE    ;переход если буфер не пуст

          Функция 1 прерывания BIOS 16H предоставляет ту же возможность,
       но, кроме того, показывает какой символ  в буфере. Флаг нуля (ZF)
       сбрасывается,  если буфер пуст, и устанавливается, если в  буфере
       имеется символ.  В последнем случае копия символа, находящегося в
       голове буфера, помещается в AX, но символ из буфера не удаляется.
       В AL возвращается код  символа  для  однобайтных  символов ASCII,
       иначе ASCII 0 для расширенных кодов, и тогда номер кода - в AH.

       ;---проверяем наличие символа в буфере
          MOV  AH,1             ;номер функции
          INT  16H              ;проверка наличия символа
          JZ   NO_CHARACTER     ;переход если ZF = 1
       ;---имеется символ - смотрим какой
          CMP  AL,0             ;это расширенный код?
          JE   EXTENDED_CODE    ;если да, то на другую ветку

          Низкий уровень.

          Как и в примере высокого уровня просто сравниваем указатели:

       ;---сравниваем указатели на голову и хвост
          MOV  AX,0           ;устанавливаем добавочный сегмент
          MOV  ES,AX          ;на начало памяти
          MOV  AL,ES:[41AH]   ;берем один указатель
          MOV  AH,ES:[41CH]   ;берем другой указатель
          CMP  AH,AL          ;сравниваем их
          JNE  GET_KEYSTROKE  ;если неравны, то к процедуре ввода

          3.1.3 Ожидать ввод символа и не выводить его на экран.

          Обычно  вводимые символы выводятся на экран, чтобы было видно,
       что напечатано.  Но иногда  автоматическое  эхо на экране нежела-
       тельно.  Например, выбор пункта меню по нажатию клавиши.   Иногда
       надо сначала проверить вводимые  символы  на ошибку перед выводом
       на экран.  В частности, любая программа, обрабатывающая расширен-
       ные коды, должна избегать  автоматического  эха, так как при этом
       первый  байт  этих  кодов (ASCII 0) будет  выводиться  на  экран,
       вставляя пробелы между символами.

       


                                     -66-
          Высокий уровень.

          Функция Бейсика INKEY$ не дает эхо на терминал. Она возвращает
       строку  длиной  1 байт для символов ASCII и  длиной 2  байта  для
       расширенных кодов. INKEY$ не ожидает нажатия клавиши, до тех пор,
       пока она не помещена в цикл, в котором ожидается нажатие клавиши.
       Цикл работает, обращаясь к  INKEY$, а затем присваивая возвращае-
       мую  им строку переменной, в данном случае C$.  Если  клавиша  не
       была нажата, то INKEY$  возвращает  нулевую  строку, т.е.  строку
       длиной ноль символов, которая обозначается двумя знаками кавычек,
       между которыми ничего нет (""). До тех пор пока INKEY$ возвращает
       "" - цикл повторяется: 100 C$=INKEY$:IF C$="" THEN 100.

          В нижеприведенном примере предполагается, что вводимые символы
       выбирают одну из  возможностей  меню  и  каждый  выбор приводит к
       выполнению  определенной  процедуры программы.  Выбор может  быть
       сделан за счет нажатия клавиш  A,  B, C ... (давая 1-байтные коды
       ASCII) или Alt-A, Alt-B, Alt-C ...  (давая 2-байтные  расширенные
       коды). Для их  распознавания  используется  функция  LEN, которая
       определяет была ли строка длиной в 1 или 2 байта.  В случае кодов
       ASCII набор операторов  IF...THEN  сразу начинает проверять какая
       клавиша была нажата, отсылая программу на соответствующую  проце-
       дуру. В случае 2-байтных  кодов  управление  передается отдельной
       процедуре.  В этой процедуре функция RIGHT$ убирает левый символ,
       который просто равен нулю  и  только  отмечает  расширенный  код.
       Затем используется функция ASC для преобразования строки из  сим-
       вольной формы в числовую.  И,  наконец,  вторая  серия операторов
       IF...THEN проверяет получившееся число на соответствующие  Alt-A,
       Alt-B и т.д.

       100 C$ = INKEY$:IF C$="" THEN 100   'ожидаем нажатия клавиши
       110 IF LEN(C$)=2 THEN 500           'если расш. код - на 500
       120 IF C$="a" OR C$="A" THEN GOSUB 1100 'это A?
       130 IF C$="b" OR C$="B" THEN GOSUB 1200 'это B?
       140 IF C$="c" OR C$="C" THEN GOSUB 1300 'это C?
        .
        .
       500 C$=RIGHT$(C$,1)        'получаем второй байт расш. кода
       510 C=ASC(C$)              'преобразуем его в число
       520 IF C=30 THEN GOSUB 2100  'это Alt-A?
       530 IF C=48 THEN GOSUB 2200  'Alt-B?
       540 IF C=46 THEN GOSUB 2300  'Alt-C?

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

       120 IF C=97 OR C=65 THEN GOSUB 1100

       Конечно надо сначала  преобразовать  C$ в форму целого числа, как
       это сделано в строке 510. В программах, в которых требуется длин-
       ная цепочка таких операторов,  можно  сэкономить место, изменяя C
       таким  образом,  чтобы она всегда соответствовала либо  верхнему,
       либо нижнему регистру.  Сначала  нужно  только проверить, что код
       ASCII  C$  находится в правильном диапазоне.   Затем  установить,
       меньше ли этот код 91, тогда  мы  имеем  дело с символом верхнего
       регистра.   Если это так, то надо для перевода в  нижний  регистр
       добавить 32.  В противном  случае,  оставить все как есть.  После
       этого  будет достаточно более короткого оператора, такого как  IF
       C=97 THEN ... Вот код этой процедуры:

       500 C=ASC(C$)         'получаем ASCII код символа
       510 IF NOT ((C>64 AND C<91)OR(C>96 AND C<123)) THEN ...
       520 IF C<91 THEN C=C+32  'приводим все к нижнему регистру
       


                                     -67-
       530 IF C=97 THEN ...     '... начинаем проверку значений

          Средний уровень.

          Функции 7 и 8 прерывания 21H ожидают ввода символа, если буфер
       клавиатуры пуст, а когда он появляется, то не выводится на экран.
       При этом функция 8 определяет Ctrl-Break  (и инициирует процедуру
       обработки Ctrl-Break[3.2.8]), а функция 7 не реагирует на него. В
       обоих случаях символ  возвращается  в AL. Когда AL содержит ASCII
       0, то получен расширенный код.  Повторите прерывание и в AL  поя-
       вится второй байт расширенного кода.

       ;---получаем введенный символ
             MOV  AH,7           ;номер функции
             INT  21H            ;ожидаем ввод символа
             CMP  AL,0           ;проверка на расширенный код
             JE   EXTENDED_CODE  ;если да, то на особую процедуру
              .                  ;иначе, код символа в AL

       ;---процедура обработки расширенных кодов
       EXTENDED_CODE:  INT  21H        ;берем второй байт кода
                       CMP  AL,75      ;проверяем на "стрелку-влево"
                       JNE  C_R        ;если нет, то след. проверка
                       JMP  CURSOR_LEFT;если да, то на процедуру
       C_R:            CMP  AL,77      ;сравниваем дальше и т.д.

          BIOS обеспечивает процедуру,  которая предоставляет те же воз-
       можности, что и функции MS DOS.  Поместите 0 в AH и вызовите пре-
       рывание 16H. Функция ожидает ввода символа и возвращает его в AL.
       В этом случае и расширенные коды обрабатываются за одно  прерыва-
       ние. Если в AL  содержится  0,  то  в  AH будет содержаться номер
       расширенного кода. При это не обрабатывается Ctrl-Break.

       ;---ждем нажатия клавиши
          MOV  AH,0       ;номер функции ожидания ввода
          INT  16H        ;получаем введенный код
          CMP  AL,0       ;проверка на расширенный код
          JE   EXTENDED_CODE   ;если да, то на спец. процедуру
           .                   ;иначе символ в AL

       ;---процедура обработки расширенного кода
       EXTENDED_CODE:  CMP  AH,75   ;берем расширенный код из AH
                                    ;и т.д.

          3.1.4 Ожидание нажатия клавиши и эхо на экран.

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

          Высокий уровень.

          В Бейсике надо перехватить введенный символ с помощью операто-
       ра INKEY$, как показано  в  [3.1.3].   Затем  его надо вывести на
       экран, прежде чем получать таким же способом следующий. Для выво-
       да можно использовать либо  оператор  PRINT, либо оператором POKE
       


                                     -68-
       прямо  поместить  символ в видеобуфер,  используя  отображение  в
       память, как показано в [4.3.1] (буфер начинается с сегмента памя-
       ти  &HB000  для монохромного адаптера и с &HB800 -  для  цветного
       адаптера). При использовании  PRINT не забудьте поставить в конце
       двоеточие, иначе будет автоматически добавлен код возврата карет-
       ки. Ниже приведены примеры использования обоих методов.  При этом
       не  проводится никакого анализа на управляющие символы.  Вводимые
       символы формируются в виде строки данных в переменной KEYSTROKE$.

       100 ' метод использующий PRINT
       110 LOCATE 10,40           'установка курсора в позицию 10,40
       120 KEYSTROKE$=""          'очистка переменной
       130 C$=INKEY$:IF C$="" THEN 130  'ожидание ввода символа
       140 KEYSTROKE$=KEYSTROKE$ + C$   'запись его в переменную
       150 PRINT C$;              'печать символа
       160 GOTO 130               'прием следующего символа

       100 ' метод использующий POKE
       110 DEF SEG = &HB000       'установка сегмента на видеобуфер
       120 POINTER = 1678         'указатель на позицию 10,40
       130 KEYSTROKE$=""          'очистка переменной
       140 C$=INKEY$:IF C$="" THEN 140  'ожидание ввода символа
       150 KEYSTROKE$=KEYSTROKE$ + C$   'запись его в переменную
       160 POKE POINTER,ASC(C$)   'помещение символа в видеобуфер
       170 POINTER=POINTER + 2    'сдвиг указателя на следующий символ
       180 GOTO 140               'прием следующего символа

          Средний уровень.

          Функция  1  прерывания 21H ожидает ввода символа,  если  буфер
       клавиатуры пуст, а затем  выводит  его на экран в текущую позицию
       курсора.   Обрабатывается  Ctrl-Break, поэтому может  выполняться
       процедура обработки Ctrl-Break [3.2.8].  Введенный символ возвра-
       щается в AL. При вводе расширенного кода AL содержит ASCII 0. Для
       получения в AL второго  байта  расширенного  кода  надо повторить
       прерывание.

       ;---получение введенного символа
          MOV  AH,1           ;номер функции
          INT  21H            ;ожидаем нажатия клавиши
          CMP  AL,0           ;расширенный код?
          JE   EXTENDED_CODE  ;если да, то на спец. процедуру
           .                  ;иначе символ находится в AL

       ;---процедура обработки расширенных кодов
             INT  21H            ;получаем в AL номер кода
             CMP  AL,77          ;проверка на "курсор-вправо"
             JNE  C_R            ;если нет, проверка следующего
             JMP  CURSOR_RIGHT   ;если да, то на процедуру
       C_R:  CMP  AL,75          ;... и т.д.

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

          3.1.5 Прием символа без ожидания.

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


                                     -69-
       клавиатуры  остановило бы все действия на экране в игровой  прог-
       рамме. Напомним, что легко проверить пуст или нет буфер клавиату-
       ры, используя методы, описанные в [3.1.2].

          Высокий уровень.

          Надо просто использовать INKEY$, не помещая его в цикл:

       100 C$=INKEY$          'получение символа
       110 IF C$ <> "" THEN...'если символ введен, то ...
       120 ...                'иначе нет символа в буфере
       


                                     -70-
          Средний уровень.

          Функция  6 прерывания 21H - это единственный  способ  получить
       введенный символ без ожидания. Эта функция не дает эха на экран и
       не  распознает Ctrl-Break.  Перед вызовом прерывания в DL  должно
       быть помещено 0FFH. В противном случае функция 6 служит совершен-
       но  противоположной  цели - печатает в  текущей  позиции  курсора
       символ, находящийся в DL.  Флаг  нуля  устанавливается  в 1, если
       буфер клавиатуры пуст. Если символ принят, то он помещается в AL.
       Код ASCII 0  индицирует  расширенный  код  и для получения номера
       кода прерывание должно быть повторено.

                  MOV  AH,6           ;номер функции DOS
                  MOV  DL,0FFH        ;запрос ввода с клавиатуры
                  INT  21H            ;получение символа
                  JZ   NO_CHAR        ;переход если нет символа
                  CMP  AL,0           ;проверка на расширенный код
                  JE   EXTENDED_CODE  ;если да, то на спец. процедуру
                  ...                 ;иначе в AL код ASCII

       EXTENDED_CODE:   INT 21H       ;получаем номер расширенного кода
                        ...           ;номер кода в AL

          3.1.6 Получение строки символов.

          И  Бейсик  и MS DOS предоставляют процедуры для приема  строки
       символов.  Они  автоматически  повторяют  процедуры  ввода одного
       символа,  описанные в предыдущих разделах, ожидая ввода  возврата
       каретки, сигнализирующего  окончание  строки. Конечно должна быть
       отведена  память, достаточная для приема всех символов строки,  и
       должна записываться длина каждой  строки для того, чтобы отделить
       одну строку от другой.  Это делается с помощью дескрипторов стро-
       ки, которые состоят из одного или  более байтов, содержащих адрес
       и/или длину строки. В Бейсике первые два байта дескриптора строки
       содержат адрес строки, а  сами  дескрипторы  хранятся  в  массиве
       отдельно от строк.  Длина строки хранится в третьем байте 3-байт-
       ного дескриптора. С другой стороны, DOS хранит длину строки прямо
       в  начале самой строки и для программы достаточно знать положение
       строки в памяти.

          Высокий уровень.

          Бейсик может принимать с и без автоматического  эха на экране.
       Более  просто  делается  ввод  с эхом,  так  как  он  выполняется
       встроенной функцией ввода строки INPUT. INPUT автоматически соби-
       рает вводимые символы, выводя их на экран по мере получения.  При
       нажатии клавиши <Enter> ввод  завершается и значение строки прис-
       ваивается  указанной переменной (посылаемый клавишей <Enter>  код
       ASCII 13 не добавляется к  строке).   INPUT допускает возможность
       редактирования  строки,  предоставляемую  DOS,  поэтому  опечатки
       могут быть исправлены перед вводом  строки. INPUT принимает числа
       в  ввиде строки и автоматически преобразует их в числовую  форму,
       если для ввода будет указано  имя  числовой  переменной. Наконец,
       INPUT  может выдавать на экран строку, запрашивающую пользователя
       о требуемой информации.   Такая  строка  может быть длиной до 254
       символов.  Если ее длина больше, то лишние символы  игнорируются.
       Основная форма этого  оператора  INPUT  "запрос", имя_переменной.
       Полное описание этого опертора см. в руководстве по Бейсику.

       110 INPUT "Enter your name: ",NAME$  'принимает имя как строку
       120 INPUT "Enter your age: ",AGE%    'принимает возраст как число

          Оператор  INPUT  неадекватен,  когда в вводимой  строке  могут
       


                                     -71-
       встречаться расширенные коды, такие  как коды управления курсором
       в  полноэкранном  текстовом редакторе.  В этом  случае  требуется
       использовать функцию  INKEY$  для  приема  каждого символа, затем
       проверять  ввод на расширенные коды, выделять символы  управления
       курсором, такие как возврат  каретки,  и только после этого выво-
       дить  на  экран те символы, которые следует выводить.   При  этом
       управляющие символы также включаются  по одному в конец строковой
       переменной.  Текстовые файлы представляют собой набор таких стро-
       ковых переменных. В пункте  [3.1.8]  Вы найдете процедуру ввода с
       клавиатуры, в которой функция INKEY$ испоьзуется указанным  обра-
       зом.

          Средний уровень.

          Функция 0AH прерывания 21H  позволяет вводить строку длиной до
       254 символов, выдавая эхо на терминал.  Эта процедура  продолжает
       ввод поступающих  символов  до  тех  пор,  пока не нажата клавиша
       возврат  каретки.  DS:DX указывает на адрес памяти,  куда  должна
       быть помещена строка. При входе первый байт в этой позиции должен
       содержать число байтов, отводимых для этой строки. После того как
       строка введена, второй байт  даст  число реально введенных симво-
       лов. Сама строка начинается с третьего байта.
          Надо  отвести  достаточно памяти для строки нужной длины  плюс
       два байта для дескриптора строки и один добавочный байт для возв-
       рата каретки. Когда Вы устанавливаете максимальную длину строки в
       первом байте, то не забудьте добавить 1 для возврата каретки. Код
       возврата каретки - ASCII 13 - вводится как последний символ стро-
       ки, но он не учитывается в  результате,  который функция помещает
       во второй байт дескриптора строки.  Таким образом, для  получения
       50-символьной строки надо  отвести  53 байта памяти и поместить в
       первый  байт ASCII 51.  После ввода 50 символов второй байт будет
       содержать ASCII 50, а 53-й байт отведенной памяти - ASCII 13.

       ;---в сегменте данных
       STRING   DB   53 DUP(?)     ;область для строки 50 символов

       ;---получение строки с клавиатуры
                LEA  DX,STRING     ;DS:DX указывают на адрес строки
                MOV  BX,DX         ;пусть BX тоже указывает на строку
                MOV  AL,51         ;установка длины строки (+1 для CR)
                MOV  [BX],AL       ;посылаем в 1-й байт дескриптора
                MOV  AH,0AH        ;номер функции
                INT  21H           ;получаем строку
       ;---проверка длины строки
                MOV  AH,[BX]+1     ;теперь длина в AH

          В этой процедуре можно использовать возможности редактирования
       строки MS DOS.  Нажатие клавиши забой или "стрелка-влево" удаляет
       символ с экрана, а также не помещает его в память.  Работает кла-
       виша  табуляции,  расширенные коды  игнорируются,  пустые  строки
       допускаются (имеется ввиду возврат каретки, которому не предшест-
       вует другого символа).  На терминале при достижении правого  края
       строка переносится на следующую  строку, а при достижении правого
       нижнего  угла экран сдвигается на строку вверх.   Когда  вводится
       больше символов, чем отведено места для строки, то лишние символы
       игнорируются и включается гудок динамика.
          MS  DOS  обеспечивает  и другой способ получения  строки,  при
       котором не выводится эхо на терминал.  Функция 3FH прерывания 21H
       - это функция ввода общего назначения, которая чаще всего исполь-
       зуется  при  дисковых операциях.  Она  требует  предопределенного
       дескриптора файла (file handle), который является кодовым числом,
       используемым  операционной  системой для  обозначения  устройства
       ввода/вывода. Для клавитуры используется дескриптор 0 и он должен
       


                                     -72-
       быть  помещен в BX.  Поместите в DS:DX адрес, по которому  должна
       находиться строка, а в CX -  максимальную длину строки и вызовите
       функцию:

       ;---чтение строки без эха
          MOV  AH,3FH            ;номер функции
          MOV  BX,0              ;номер дескриптора файла
          LEA  DX,STRING_BUFFER  ;указатель на буфер ввода строки
          MOV  CX,100            ;максимальная длина строки
          INT  21H               ;ждем ввода

       Ввод строки завершается  нажатием  клавиши  возврат каретки и DOS
       добавляет  в конец строки два символа: возврат каретки и  перевод
       строки (ASCII 13 и ASCII 10). Из-за этих добавочных символов, при
       указании длины строки 100 символов она может занимать до 102 байт
       памяти.  Длина введенной строки  возвращается в AX и это значение
       включает два символа-ограничителя.

          3.1.7 Проверка/установка статуса клавиш-переключателей.

          Два   байта,  расположенные  в  ячейках  памяти  0040:0017   и
       0040:0018 содержат биты, отражающие  статус клавиши сдвига и дру-
       гих клавиш-переключателей следующим образом:

                   Бит    Клавиша      Значение, когда бит = 1
       0040:0017    7     Insert       режим вставки включен
                    6     CapsLock     режим CapsLock включен
                    5     NumLock      режим NumLock включен
                    4     ScrollLock   режим ScrollLock включен
                    3     Alt          клавиша нажата
                    2     Ctrl         клавиша нажата
                    1     левый Shift  клавиша нажата
                    0     правый Shift клавиша нажата

       0040:0018    7     Insert       клавиша нажата
                    6     CapsLock     клавиша нажата
                    5     NumLock      клавиша нажата
                    4     ScrollLock   клавиша нажата
                    3     Ctrl-NumLock режим Ctrl-NumLock включен
       остальные биты не используются

          Прерывание  клавиатуры немедленно обновляет эти биты  статуса,
       как только будет нажата одна из клавиш-переключателей,  даже если
       не было считано ни одного символа из буфера клавиатуры. Это верно
       и для клавиши Ins, которая единственная из этих 8 клавиш помещает
       код  в буфер (установка статуса Ins меняется даже  если в  буфере
       нет места для  символа).  Отметим,  что бит 3 по адресу 0040:0018
       устанавливается в 1, когда действует режим задержки Ctrl-NumLock;
       поскольку в этом состоянии  программа приостановлена, то этот бит
       несущественен.
          Прерывание  клавиатуры  проверяет  состояние  статусных  битов
       перед тем, как интерпретировать  нажатые  клавиши,  поэтому когда
       программа меняет один из этих битов, то эффект такой же, как  при
       физическом нажатии  соответствующей  клавиши.  Вы можете захотеть
       установить  состояние клавиш NumLock и CapsLock, чтобы быть  уве-
       ренным, что ввод будет требуемого вида.  Наоборот, Ваша программа
       может нуждаться в чтении статуса этих клавиш, например для  того,
       чтобы вывести текущий статус на экран. Отметим, что клавиатура AT
       правильно  устанавливает  световые индикаторы  состояния  клавиш,
       даже если переключены программно.

          Высокий уровень.

       


                                     -73-
          В  данном  примере клавиша NumLock переводится в режим,  когда
       клавиши дополнительной  клавиатуры  используются  для перемещения
       курсора, за счет сбрасывания бита 5 по адресу 0040:0017 в 0.  Это
       достигается за счет операции  логического  "И" значения, располо-
       женного  по этому адресу с числом 223 (цепочка битов 11011111B  -
       описание логики битовых  операций  см. в Приложении Б). Результат
       помещается  в  байт статуса.  В примере  затем  восстанавливается
       значение этого бита  в  1,  за   счет   логического  "ИЛИ"  с  32
       (00100000B).

       100 DEF SEG = &H40             'устанавливаем сегмент на область
       110 STATUSBYTE=PEEK(&H17)      'BIOS и берем байт статуса
       120 NEWBYTE=STATUSBYTE AND 223 'обнуляем бит 5
       130 POKE(&H17,NEWBYTE)         'посылаем новое значение статуса

       Чтобы, наоборот, включить этот бит:

       120 NEWBYTE=STATUSBYTE OR 32   'устанавливаем бит 5
       130 POKE(&H17,NEWBYTE)         'посылаем новое значение статуса

       Строки 110-130 могут быть уплотнены к виду:

       110 POKE(&H417,PEEK(&H417)AND 223)
          или
       110 POKE(&H417,PEEK(&H417)OR 223)

          Средний уровень.

          Функция  2 прерывания 16H предоставляет доступ к  одному -  но
       только одному - из байтов статуса.  Это байт по адресу 0040:0017,
       который содержит больше полезной информации.  Байт возвращается в
       AL.

       ;---проверка статуса клавиши вставки
          MOV  AH,2           ;номер функции
          INT  16H            ;получаем байт статуса
          TEST AL,10000000B   ;проверяем бит 7
          JZ   INSERT_OFF     ;если 0, то INSERT выключен

          Низкий уровень.

          В данном примере  устанавливается режим вставки, за счет уста-
       новки бита 7 байта статуса по адресу 0040:0017 (который адресует-
       ся как 0000:0417).

          SUB  AX,AX            ;устанавливаем добавочный сегмент на
          MOV  ES,AX            ;начало памяти
          MOV  AL,10000000B     ;готовим бит 7 к установке
          OR   ES:[417H],AL     ;меняем байт статуса

          3.1.8 Написание процедуры  ввода  с клавиатуры общего назначе-
       ния.

          Система  кодов, используемых клавиатурой, не поддается простой
       интрепретации. Коды могут иметь длину 1 или 2 байта и нет просто-
       го соответствия между длиной кода и тем, служит ли он для обозна-
       чения символа или для управления оборудованием. Не все комбинации
       клавиш даже выдают уникальный код, поэтому необходимы  добавочные
       усилия, чтобы различить их. Ни коды ASCII, ни расширенные коды не
       упорядочены таким образом, который бы позволил их простую группи-
       ровку и проверку ошибок. Другими  словами, процедура ввода с кла-
       виатуры общего назначения требует хлопотливого программирования.
          Здесь приведены примеры на Бейсике и с использованием прерыва-
       


                                     -74-
       ния 16H. В них показано как свести вместе большинство информации,
       приведенной в данной главе. Общий алгоритм показан на рис. 3-3.

          Высокий уровень.

          Процедура обработки ввода с клавиатуры, написанная на Бейсике,
       может делать все  что  делает  ассемблерная  процедура,  за одним
       исключением.  Функция INKEY$ не предоставляет доступа к  скан-ко-
       дам. Это означает, что  Вы  не  можете  сказать  получены ли коды
       ASCII 8, 9, 13 и 27 от нажатия клавиш <BackSpace>, <Tab>, <Enter>
       и <Escape> или через Ctrl-H,  -I,  -M  и -[.  Различие может быть
       установлено  проверкой  бита  статуса  клавиши  Ctrl,  по  адресу
       0040:0017, в момент нажатия клавиши. Но этот метод не будет рабо-
       тать,  если  введенный символ был запасен в буфере  клавиатуры  в
       течение некоторого времени.

       100 C$=INKEY$:IF C$="" THEN 100   'получение символа
       110 IF LEN(C$)=2 THEN 700         'если расширенный, то на 700
       120 C=ASC(C$)                     'иначе берем номер кода ASCII
       130 IF C<32 THEN 300              'если управляющий, то на 300
       140 IF C<65 OR C>123 THEN 100     'принимаем только символы
       150 '''пишущей машинки и делаем с ними, что хотим, например:
       160 S$=S$+C$                      'добавляем символ к строке
       170 PRINT C$;                     'выводим его на экран
       180 '''... и т.д.
       190 GOTO 100                      'на ввод следующего символа
        .
        .
       300 '''процедура обработки управляющих кодов ASCII
       310 DEF SEG = 0                   'указываем на начало памяти
       320 REGISTER=PEEK(&H417)          'берем регистр статуса
       330 X=REGISTER AND 4              'X=4, когда нажат Ctrl
       340 IF X=0 THEN 500               'если не нажат, то на 500
       350 '''если это комбинация Ctrl-буква, то делаем что хотим
       360 IF C=8 THEN GOSUB 12000       'например, переходим на проце-
       370 '''дуру вывода экрана помощи и т.д.
       380 GOTO 100                      'на ввод следующего символа
        .
        .
       500 '''процедура обработки 4-х клавиш: декодирует коды ASCII 8,
       510 '''9, 13 и 27, когда клавиша Ctrl не нажата
       520 IF C=8 THEN GOSUB 5000        'обработка <BackSpace>
       530 IF C=9 THEN GOSUB 6000        'обработка <Tab>
       540 IF C=13 THEN GOSUB 7000       'обработка <CR>
       550 IF C=27 THEN GOSUB 8000       'обработка <Esc>
       560 GOTO 100                      'на ввод следующего символа
        .

        .
       700 '''процедура обработки расширенных кодов
       710 C$=RIGHT$(C$,1)               'берем только 2-й байт C$
       720 C=ASC(C$)                     'переводим в числовую форму
       730 '''в C - расширенный код - делаем с ним, что хотим, например
       740 IF C<71 OR C>81 THEN 100   'берем только управление курсором
       750 IF C=72 THEN GOSUB 3500    'обработка "курсор-вверх"
       760 '''... и т.д.
       770 GOTO 100                      'на ввод следующего символа

          Средний уровень.

          Этот  пример  отличается от предыдущего методом  распознавания
       четырех частных случаев Ctrl-H, -I,  -M и -[. Здесь, когда встает
       вопрос о том, возник ли указанный код при нажатии одной  клавиши,
       


                                     -75-
       или в комбинации с  клавишей  Ctrl,  проверяется  скан-код.  Этот
       метод более правилен, чем проверка бита статуса, так как скан-код
       запоминается в буфере  клавиатуры, а установка бита статуса может
       быть изменена.

       ;---получение кода нажатой клавиши и определение его типа
       NEXT:  MOV  AH,0           ;функция ввода с клавиатуры BIOS
              INT  16H            ;получаем введенный код
              CMP  AL,0           ;проверка на расширенный код
              JE   EXTENDED_CODE  ;если да, то на спец. процедуру
              CMP  AL,32          ;проверка на управляющий символ
              JL   CONTROL_CODE   ;если да, то на спец. процедуру
              CMP  AL,65          ;если символ не входит в набор пишу-
              JL   NEXT           ;щей машинки, то берем следующий
              CMP  AL,123         ;
              JL   NEXT           ;
       ;---теперь обрабатываем символ в AL
              STOSB               ;запоминаем символ по адресу ES:DI
              MOV  AH,2           ;функция вывода символа на экран
              MOV  DL,AL          ;помещаем символ в DL перед выводом
              INT  21H            ;выводим его на экран
               .
               .
              JMP  NEXT           ;переходим к следующему символу
       ;---анализируем управляющие коды
       CONTROL_CODE:  CMP  AL,13       ;код ASCII 13?
                      JNE  TAB         ;если нет, то след. проверка
                      CMP  AH,28       ;иначе проверяем скан-код <CR>
                      JNE  C_M         ;если нет, то было Ctrl-M
                      CALL CARRIAGE_RET;обработка возврата каретки
                      JMP  NEXT        ;переход к следующему символу
       C_M:           CALL CTRL_M      ;обработка Ctrl-M
                      JMP  NEXT        ;переход к следующему символу
       TAB:           CMP  AL,9        ;проверка на табуляцию...
                       .
                       .
                      CMP  AL,10       ;затем проверка других
                       .
                       .
       REJECT:        JMP  NEXT        ;переход к следующему символу
       ;---анализ расширенных кодов (2-й байт кода в AH):
       EXTENDED_CODE: CMP  AH,71       ;проверка нижней границы
                      JL   REJECT      ;если меньше, то след. символ
                      CMP  AH,81       ;проверка верхней границы
                      JL   REJECT      ;если больше, то след. символ

       ;---AH содержит символ управления курсором, анализируем его:
                      CMP  AH,72       ;"курсор-вверх"?
                      JE   C_U         ;если да, то на процедуру
                      CMP  AH,80       ;"курсор-вниз"?
                      JE   C_D         ;если да, то на процедуру
                       .
                       .
       C_U:           CALL CURSOR_UP   ;вызов соответствующей процедуры
                      JMP  NEXT        ;переход к следующему символу
       C_D:           CALL CURSOR_DOWN ;вызов соответствующей процедуры
                      JMP  NEXT        ;переход к следующему символу

          3.1.9 Перепрограммирование прерывания клавиатуры.

          Когда  микропроцессор  клавиатуры помещает скан-код в  порт  A
       микросхемы 8255 (адрес  порта  60H  -  см.  [1.1.1]), то при этом
       вызывается прерывание 9.  Задача этого прерывания - преобразовать
       


                                     -76-
       скан-код символа, основываясь на состоянии клавиш-переключателей,
       и поместить его в буфер клавиатуры.  (Если скан-код соответствует
       клавише-переключателю, то в  буфер  клавиатуры не пишется ничего,
       за  исключением случая клавиши <Ins>, а вместо  этого  прерывание
       изменяет  байты  статуса,  расположенные  в  области  данных BIOS
       [3.1.7]).   Прерывания  "ввода с клавиатуры" DOS и BIOS на  самом
       деле всего лишь прерывания "ввода из буфера клавиатуры". На самом
       деле они не распознают нажатия клавиш. Точнее, они читают интерп-
       ретацию введенных клавиш, которую  обеспечило прерывание 9. Заме-
       тим,  что  PCjr использует специальную процедуру  (INT  48H)  для
       преобразования ввода от его 62  клавиш к 83-клавишному протоколу,
       используемому другими IBM PC. Результат этой процедуры передается
       прерыванию 9, которое выполняет свою работу как обычно.  Прерыва-
       нием  49H  PCjr обеспечивает специальные  неклавишные  скан-коды,
       которые потенциально  могут  устанавливаться  периферийными  уст-
       ройствами,  использующими  инфракрасную (беспроволочную) связь  с
       клавиатурой.
          Требуется весьма необычное  применение,  чтобы имело смысл пе-
       репрограммировать  это прерывание, особенно учитывая, что MS  DOS
       позволяет  Вам   перепрограммировать   любую   клавишу клавиатуры
       [3.2.6].  Если все же Вам придется перепрограммировать прерывание
       9, то эта глава даст Вам основы для  старта.  Сначала надо прочи-
       тать  [1.2.3], чтобы понимать как программируются прерывания.   В
       прерывании клавиатуры можно выделить три основных шага:

          1.  Прочитать скан-код и послать клавиатуре подтвердающий сиг-
       нал.
          2. Преобразовать скан-код в номер кода или в установку оегист-
       ра статуса клавиш-переключателей.
          3. Поместить код клавиши в буфер клавиатуры.

          В  момент вызова прерывания скан-код будет находиться в  порте
       A.  Поэтому сначала надо этот код прочитать и сохранить на стеке.
       Затем используется порт B (адрес 61H),  чтобы быстро послать сиг-
       нал подтверждения микропроцессору клавиатуры. Надо просто устано-
       вить бит 7 в 1, а затем сразу  изменить  его  назад в 0. Заметим,
       что бит 6 порта B управляет сигналом часов клавиатуры.  Он всегда
       должен быть установлен в 1, иначе клавиатура будет выключена. Эти
       адреса  портов  применимы и к AT, хотя он и не  имеет  микросхемы
       интерфейса с периферией 8255.
          Сначала скан-код анализируется на предмет того, была ли клави-
       ша нажата (код нажатия) или отпущена (код освобождения).  На всех
       машинах, кроме AT, код освобождения  индицируется установкой бита
       7  скан-кода  в 1.  Для AT, у которого бит 7 всегда равен 0,  код
       освобождения состоит  из  двух  байтов:  сначала  0F0H,  а  затем
       скан-код.  Все коды освобождения отбрасываются, кроме случая кла-
       виш-переключателей, для которых  делаются соответствующие измене-
       ния в байтах их статуса. С другой стороны, все коды нажатия обра-
       батываются. При этом  опять  могут  изменяться байты статуса кла-
       виш-переключателей.  В случае же символьных кодов, надо проверять
       байты статуса, чтобы определить, например,  что скан-код 30 соот-
       ветствует нижнему или верхнему регистру буквы A.
          После  того  как введенный символ  идентифицирован,  процедура
       ввода с клавиатуры должна найти соответствующий ему код ASCII или
       расширенный код.  Приведенный пример слишком короток, чтобы  рас-
       смотреть  все  случаи.  В общем случае  скан-коды  сопоставляются
       элементам таблицы данных, которая анализируется инструкцией XLAT.
       XLAT принимает в AL число от 0 до 255, а возвращает в AL  1-байт-
       ное значение из 256-байтной  таблицы, на которую указывает DS:BX.
       Таблица может находиться в сегменте данных.  Если в AL  находился
       скан-код 30, то туда будет помещен из таблицы байт номер 30 (31-й
       байт,  так  как отсчет начинается с нуля).  Этот  байт в  таблице
       должен быть установлен равным 97, давая код ASCII для "a". Конеч-
       


                                     -77-
       но  для  получения заглавной A нужна  другая  таблица, к  которой
       обращение будет происходить, если  статус сдвига установлен.  Или
       заглавные буквы могут храниться в другой части той же таблицы, но
       в этом случае к скан-коду надо будет  добавлять смещение, опреде-
       ляемое статусом клавиш-переключателей.
          Наконец, номера кодов должны быть помещены в буфер клавиатуры.
       Процедура должна сначала проверить, имеется ли в буфере место для
       следующего  символа.  В [3.1.1] показано, что этот буфер  устроен
       как циклическая очередь. Ячейка  памяти 0040:001A содержит указа-
       тель  на  голову буфера, а 0040:001C - указатель на  хвост.   Эти
       словные указатели дают смещение  в  области  данных BIOS (которая
       начинается  в сегменте 40H) и находятся в диапазоне от 30 до  60.
       Новые символы вставляются в ячейки  буфера с более старшими адре-
       сами,  а  когда достигнута верхняя граница, то  следующий  символ
       переносится в нижний конец  буфера.  Когда буфер полон, то указа-
       тель хвоста на 2 меньше указателя на голову - кроме случая, когда
       указатель на голову равен  30  (начало  области буфера), а в этом
       случае буфер полон, когда указатель хвоста равен 60.
          Для  вставки символа в буфер, надо поместить его в позицию, на
       которую указывает хвост буфера и затем увеличить указатель хвоста
       на  2; если указатель хвоста был равен 60, то надо  изменить  его
       значение на 30. Вот и все.  Схема  прерывания клавиатуры показана
       на рис. 3-4.

          Низкий уровень.

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

       ;---в сегменте данных
       TABLE   DB   16 DUP(0)            ;пропускаем 1-е 16 байт
               DB   'qwertyuiop',0,0,0,0 ;верхний ряд клавиатуры
               DB   'asdfghjkl',0,0,0,0,0 ;средний ряд клавиатуры
               DB   'zxcvbnm'            ;нижний ряд клавиатуры
               DB   16 DUP(0)            ;пропуск до верхнего регистра
               DB   'QWERTYUIOP',0,0,0,0 ;те же символы на верхнем
               DB   'ASDFGHJKL',0,0,0,0,0 ;регистре
               DB   'ZXCVBNM'            ;

       ;---в начале программы устанавливаем прерывание
               CLI                       ;запрет прерываний
               PUSH DS                   ;сохраняем регистр
               MOV  AX,SEG NEW_KEYBOARD  ;DS:DX должны указывать на
               MOV  DS,AX                ;процедуру обработки
               MOV  DX,OFFSET NEW_KEYBOARD ;прерывания
               MOV  AL,9                 ;номер вектора прерывания
               MOV  AH,25H               ;номер функции DOS
               INT  21H                  ;меняем вектор прерывания
               POP  DS                   ;восстанавливаем регистр
               STI                       ;разрешаем прерывания

       Программа продолжается, затем оставаясь резидентной [1.3.4].

       ;---это само прерывание клавиатуры
       NEW_KEYBOARD  PROC FAR         ;сохраняем все изменяемые
                     PUSH AX          ;регистры
                     PUSH BX          ;
                     PUSH CX          ;
                     PUSH DI          ;
       


                                     -78-
                     PUSH ES          ;
       ;---получаем скан-код и посылаем сигнал подтверждения
          IN   AL,60H         ;получаем скан-код из порта A
          MOV  AH,AL          ;помещаем копию в AH
          PUSH AX             ;сохраняем скан-код
          IN   AL,61H         ;читаем состояние порта B
          OR   AL,10000000B   ;устанавливаем бит 7
          OUT  61H,AL         ;посылаем измененный байт в порт
          AND  AL,01111111B   ;сбрасываем бит 7
          OUT  61H,AL         ;возвращаем состояние порта B
       ;---ES должен указывать на область данных BIOS
          MOV  AX,40H         ;устанавливаем сегмент
          MOV  ES,AX          ;
          POP  AX             ;возвращаем скан-код из стека
       ;---проверка клавиши сдвига
                CMP  AL,42          ;нажат левый сдвиг?
                JNE  KEY_UP         ;нет - смотрим следующее
                MOV  BL,1           ;да - изменяем бит статуса
                OR   ES:[17H],BL    ;меняем прямо регистр статуса
                JMP  QUIT           ;выход из процедуры
       KEY_UP:  CMP  AL,170         ;левый сдвиг отпущен?
                JNE  NEXTKEY        ;нет - смотрим следующее
                MOV  BL,11111110B   ;да - меняем бит статуса
                AND  ES:[17H],BL    ;меняем прямо регистр статуса
                JMP  QUIT           ;выход из процедуры
       NEXTKEY:                     ;просмотр других переключателей
       ;---это символьная клавиша - интерпретируем скан-код
                    TEST AL,10000000B  ;код освобождения клавиши?
                    JNZ  QUIT          ;да - выходим из процедуры
                    MOV  BL,ES:[17H]   ;иначе берем байт статуса
                    TEST BL,00000011B  ;клавиша сдвига нажата?
                    JZ   CONVERT_CODE  ;нет - уходим дальше
                    ADD  AL,100        ;да - значит заглавная буква
       CONVERT_CODE:  MOV  BX,OFFSET TABLE  ;готовим таблицу
                    XLAT TABLE         ;преобразуем скан-код в ASCII
                    CMP  AL,0          ;возвращен 0?
                    JE   QUIT          ;если да, то на выход

       ;---код клавиши готов, проверяем не полон ли буфер клавиатуры
                    MOV  BX,1AH        ;смещение указателя на голову
                    MOV  CX,ES:[BX]    ;получаем его значение
                    MOV  DI,ES:[BX]+2  ;получаем указатель хвоста
                    CMP  CX,60         ;голова на вершине буфера?
                    JE   HIGH_END      ;да - переходим к спец. случаю
                    INC  CX            ;увеличиваем указатель головы
                    INC  CX            ;на 2
                    CMP  CX,DI         ;сравниваем с указателем хвоста
                    JE   QUIT          ;если равны, то буфер полон
                    JMP  GO_AHEAD      ;иначе вставляем символ
       HIGH_END:    CMP  DI,30         ;проверка спец. случая
                    JE   QUIT          ;если буфер полон, то выход
       ;---буфер не полон - вставляем в него символ
       GO_AHEAD:    MOV  ES:[DI],AL    ;помещаем символ в позицию хвоста
                    CMP  DI,60         ;хвост в конце буфера?
                    JNE  NO_WRAP       ;если нет, то добавляем 2
                    MOV  DI,28         ;иначе указатель хвоста = 28+2
       NO_WRAP:     ADD  DI,2          ;получаем новое значение хвоста
                    MOV  ES:[BX]+2,DI  ;посылаем его в область данных
       ;---завершение прерывания
       QUIT:        POP  ES            ;восстанавливаем изменяемые
                    POP  DI            ;регистры
                    POP  CX            ;
                    POP  BX            ;
       


                                     -79-
                    POP  AX            ;
                    MOV  AL,20H        ;выдаем сигнал об окончании
                    OUT  20H,AL        ;аппаратного прерывания
                    IRET               ;возврат из прерывания
       NEW_KEYBOARD ENDP

                      Раздел 2. Доступ к отдельным клавишам.

          Процедура  обработки  нажатия клавиши должна  проверять  массу
       различных типов  клавиш  и  условий,  поскольку  как одно-, так и
       двухбайтные коды могут появляться в комбинации с клавишами-перек-
       лючателями. Не все клавиши логически сгруппированы, по типу кода,
       который им соответствует.  Например, клавиша <Backspace>  генери-
       рует однобайтный код  ASCII,  а  клавиша  <Delete>  - двухбайтный
       расширенный код.  Клавиша Ctlr генерирует однобайтный код,  когда
       она используется в сочетании с  алфавитными клавишами и двухбайт-
       ный код в остальных случаях.  Эти нерегулярности вознмкают  из-за
       ограниченности набора ASCII: прерывание клавиатуры следует согла-
       шениям ASCII, когда возможно, но когда это невозможно выдает свои
       (расширенные) коды.
          В данном разделе перечислены  различные группы клавиш, даны их
       коды и указаны встречающиеся аномалии.  В большинстве случаев эта
       информация доступна в менее удобном  виде из таблиц кодов ASCII и
       расширенных  кодов,  приведенных в разделе 3 этой  главы.   Здесь
       обсуждаются также специальные  свойства,  приписываемые отдельным
       клавишам Бейсиком, а также специальная обработка, для интерпрета-
       ции отдельных клавиш (таких как забой), применяемая в прерываниях
       DOS.

          3.2.1 Использование клавиш  <BackSpace>,  <Enter>,  <Escape> и
       <Tab>.

          Клавиши <BackSpace>, <Enter>, <Escape>  и <Tab> - единственные
       четыре  несимвольные клавиши, которые генерируют однобайтные  ко-
       ды ASCII. Эти коды содержатся в наборе управляющих кодов [7.1.9],
       которые  занимают первые 32 кода в наборе ASCII.  Эти четыре кода
       могут быть получены также комбинацией буквенных клавиш с клавишей
       Ctrl:

          ASCII   8    BackSpace          Ctrl + H
          ASCII   9    Tab                Ctrl + I
          ASCII  13    Enter              Ctrl + M
          ASCII  27    Escape             Ctrl + [

       В [3.2.2] показано как различать нажатие одной клавиши и комбина-
       цию с клавишей Ctrl.  Отметим, что обратная табуляция, производи-
       мая  нажатием комбинации <Shift> + <Tab>, выдает расширенный  код
       0;15.
          Некоторые из прерываний обработки ввода с клавиатуры автомати-
       чески интерпретируют эти четыре специальных кода. В Бейсике функ-
       ция INPUT реагирует  на  <Backspace>,  <Tab>  и <Enter>.  Функция
       INKEY$  не интерпретирует ни один из управляющих кодов, поскольку
       у нее нет автоматического эха на экран.  Всю работу должна выпол-
       нять Ваша программа.  Напомним, что для управления движением кур-
       сора Бейсик предоставляет функцию TAB.  Из прерываний BIOS и DOS,
       те  которые выдают эхо на терминал интерпретируют  также  клавиши
       <BackSpace> и <Tab>.  После  того  как  эти коды интерпретируются
       соответствующим  образом, коды ASCII все равно  появляются в  AL,
       после чего они могут быть  включены в строку символов или игнори-
       рованы, в зависимости от того, что требуется.

          3.2.2  Использование клавиш-переключателей: <Shift>, <Ctrl>  и
       <Alt>.
       


                                     -80-
          Три типа клавиш-переключателей заставляют только другие клави-
       ши  клавиатуры генерировать различные коды.  Как  правило,  такие
       комбинации генерируют расширенные коды.Но в двух случаях они дают
       коды ASCII:  (1) когда используется клавиша  <Shift>  с клавишами
       алфавитно-цифровых символов и  (2)  нажатие  комбинации клавиш от
       Ctrl-A до Ctrl-Z дает ASCII коды от 1 до 26. Все остальные комби-
       нации дают расширенные коды, перечисленные  в [3.3.5]. PCjr имеет
       несколько исключений, которые обсуждаются ниже.
          Недопустимые комбинации клавиш не производят кода, вообще.  За
       исключением случая специальных комбинаций с Ctrl-Alt, одновремен-
       ное нажатие нескольких переключателей приводит к тому, что только
       один из них становится эффективным, причем приоритет у Alt, затем
       Ctrl, и затем Shift. В [3.1.7] показано как проверить нажата ли в
       данный  момент  клавиша-переключатель.   В  [3.2.3] показано, как
       использовать клавишу ScrollLock, в качестве переключателя с любой
       другой клавишей клавиатуры.  Другие комбинации с клавишами-перек-
       лючателями  можно сделать допустимыми только полностью  переписав
       прерывание  клавиатуры,   которое   заменило   бы прерывание BIOS
       [3.1.9].
          Имеется проблема, связанная с некоторыми комбинациями с клави-
       шей Ctrl, такими как Ctrl + H, I, M и [, поскольку они генерируют
       коды ASCII, идентичные тем, которые генерируют клавиши  <BackSpa-
       ce>, <Tab>, <Enter> и <Escape>.  В [3.1.8] показано как программа
       на ассемблере может может, проверив скан-коды, определить была ли
       нажата управляющая клавиша или  комбинация буквы с Ctrl (скан-код
       находится  в AH, когда мы получаем код нажатой клавиши через пре-
       рывание 16H).  К  сожалению,  программы  на  Бейсике лишены такой
       возможности.  В таком случае программа может попытаться различить
       эти две возможности, анализируя состояние регистра статуса.  Если
       бит 2 байта статуса по адресу  0040:0017  установлен,  то клавиша
       Ctrl  -  нажата.  Этот метод работает только в тот момент,  когда
       происходит нажатие клавиши, но  не  тогда, когда Вы берете символ
       из буфера клавиатуры через некоторое время.
          Клавиатура PCjr имеет только 63 клавиши, по сравнению с 83 для
       IBM PC или XT и 84 для AT. Некоторые комбинации клавиш-переключа-
       телей  служат для имитации некоторых недостающих клавиш (комбина-
       ции с использованием функциональных клавиш приведены в [3.2.5]):

          Комбинация клавиш PCjr      PC/XT/AT эквиваленты

          Alt + Fn + 0-9              0-9 (скан-коды дополнительной циф-
                                      ровой клавиатуры
          Alt + /                     \
          Alt + '                     `
          Alt + [                     |
          Alt + ]                     ~
          Alt + .                     * (скан-код, как от клавиши PrtSc
          Shift + Del                 . (скан-код, как от доп. кл-ры)

          Клавиатура PCjr допускает также  следующие уникальные комбина-
       ции с участием клавиш-переключателей:

          Fn + Shift + Esc            переключает цифровые клавиши в
                                      функциональные
          Ctrl + Alt + CapsLock       переключает звуковое подтверждение
                                      нажатия клавиши
          Ctrl + Alt + Ins            запускает диагностику
          Ctrl + Alt + CursorLeft     сдвигает экран влево
          Ctrl + Alt + CursorRight    сдвигает экран вправо

          3.2.3 Использование клавиш-переключателей: NumLock,  CapsLock,
       Ins и ScrollLock.

       


                                     -81-
          За исключением клавиши Ins, все остальные клавиши-переключате-
       ли  не производят кода, который помещался бы в буфер  клавиатуры.
       Вместо этого, они изменяют состояние двух байтов статуса, которые
       расположены   в  области  данных  BIOS  по  адресам  0040:0017  и
       0040:0018.  Прерывание клавиатуры проверяет установку этих байтов
       перед  тем как присвоить код введенному символу.  Ваши  программы
       имеют доступ к регистрам статуса и могут изменить установку любой
       из клавиш-переключателей как объяснено в [3.1.7].
          Другие  биты регистра статуса показывают нажата ли данная кла-
       виша-переключатель в текущий момент. Это свойство позволяет прог-
       рамме использовать клавиши-переключатели в качестве клавиш  сдви-
       га.  Возможны  потенциальные  применения  этого,  пока не создано
       новых кодов клавиш.  Например, <ScrollLock> может быть итспользо-
       ван для того, чтобы добавить добавочный  набор комбинаций сдвиг +
       функциональная клавиатура.  Программа, которая будет получать код
       обычной  функциональной   клавиши,   проверять  нажата ли клавиша
       <ScrollLock>  и соответственно интерпретировать нажатие  клавиши.
       Отметим, что любая из клавиш  <Shift>  обращает текущую установку
       клавиши <NumLock>.
          Клавиша  <Ins> помещает в буфер клавиатуры код  0;82,  который
       Ваша программа может прочитать в  любой момент.  Однако установка
       для  <Ins>  в байтах регистра статуса меняется немедленно.   Даже
       если в буфере нет места для кода <Ins>, то в регистре статуса при
       нажатии  клавиши  вносятся изменения.  Как <Ins>, так и  <Scroll-
       Lock>, не влияют на другие клавиши клавиатуры (в отличие от <Num-
       Lock>  и  <CapsLock>).  Вы можете приписать им любую роль,  какую
       захотите.   Техническое  руководство  IBM утверждает, что клавиша
       <ScrollLock> должна использоваться для переключения между состоя-
       ниями, когда  нажатие  клавиши  перемещения  курсора  приводит  к
       сдвижке экрана, а не к передвижению курсора.
          Конечно,  Вы можете создать все требуемые Вашей программе кла-
       виши-переключатели просто  назначив  клавиши  для этой цели. Хотя
       для этой цели Вы не имеете готовых регистров статуса, но Вы може-
       те создать переменную, значение которой -1 соответствует включен-
       ному состоянию Вашего переключателя, а значение 0 - выключенному.
       Например, используем клавишу F10 для включения и выключения пере-
       менной Clock:

       100 '''переключение статуса переменной
       110 CLOCK = -1            'начинаем с включенным состоянием
       120 IF X<=100 THEN NOT CLOCK 'переключаем переменную

          3.2.4 Использование цифровой дополнительной клавиатуры и  кла-
       виш перемещения курсора.

          Для IBM PC и  XT  дополнительная  цифровая клавиатура включает
       цифровые  клавиши, клавиши <Ins> и <Del>, а также клавиши + и  -.
       На AT добавляется клавиша  "System Request" (Sys Rec), в то время
       как  PCjr  имеет только 4 клавиши перемещения курсора  (остальные
       могут быть  эмулированы  специальными  комбинациями  с  клавишами
       <Shift>  и <Fn>, описанными в [3.2.2] и [3.2.5]).  Клавиша  <Num-
       Lock> переключает между цифрами  и клавишами управления курсором.
       Клавиши  <Ins> и <Del> работают только если режим <NumLock> вклю-
       чен, т.е.  дополнительная клавиатура выдает цифры.  Клавиши + и -
       выдают  одни  и те же коды независимо от установки  режима  <Num-
       Lock>.
          Цифровые клавиши  дополнительной  клавиатуры выдают в точности
       те  же однобайтные коды, которые выдают цифровые клавиши верхнего
       ряда основной клавиатуры - т.е.   коды ASCII от 48 до 57 для цифр
       от 0 до 9.  Это верно и для клавиш + и -. Программисты на ассемб-
       лере могут определить какая из  двух  клавиш  нажата по скан-коду
       клавиши,  который находится в AH при возврате как  из  прерывания
       16H, так и из процедур ввода одной клавиши прерывания 21H.  Отме-
       


                                     -82-
       тим, что любая из клавиш <Shift> переводит клавиши дополнительной
       клавиатуры в режим противоположный  тому, который установлен кла-
       вишей <NumLock>.  Установка клавиши <CapsLock> не имеет значения.
       Клавиша "5" в  центре  активна  только  как  цифровая клавиша и в
       режиме перемещения курсора ввобще не выдает кода.
          Кроме четырех общепринятых стрелок клавиши управления курсором
       включают также  <Home>,  <End>,  <PgUp>  и  <PgDn>, которые часто
       используются  для  перемещения курсора сразу на целую строку  или
       страницу.  Все они генерируют двухбайтные  расширенные коды.  Эти
       клавиши не обеспечивают прямого контроля над курсором. Они просто
       выдают коды, как и все другие клавиши,  и это уже задача програм-
       миста преобразовать эти коды в перемещения курсора на экране.
          Допустимы некоторые комбинации клавиш дополнительной клавиату-
       ры с клавишей Ctrl.  <NumLock>  должен соответствовать режиму уп-
       равления курсором, чтобы эти комбинации работали. В [3.1.7] пока-
       зано как Ваша программа может  автоматически  устанавливать режим
       NumLock. Вот перечень кодов клавиш дополнительной клавиатуры:

          Коды ASCII:          43                   +
                               45                   -
                               46                   .
                               48-57                0-9
          Расширенные коды:

          72,75,77,80          CursorUp,Left,Right&Down
          71,73,79,81          Home,PgUp,End,PgDn
          82,83                Ins,Del
          115,116              Ctlr-cursor left, -cursor right
          117,118,119,132      Ctlr-end, -PgDn, -Home, -PgUp

          AT  имеет  84-ю клавишу, Sys Req, которая уникальна  по  своей
       функции. Клавиша предназначена  для многопользовательских систем,
       как способ входа в главное меню системы.  Когда клавиша нажимает-
       ся, в AX появляется код 8500H и  выполняется  прерывание 15H. При
       отпускании  клавиши в AX появляется код 8501H, и опять же  выпол-
       няется прерывание 15H. BIOS AT  не обрабатывает функции 84H и 85H
       прерывания  15H,  а просто делает возврат.  Но  можно  программно
       заменить вектор прерывания для 15H, чтобы он указывал на процеду-
       ру  обработки  клавиши Sys Req.  Такая процедура  должна  сначала
       прочитать AL, чтобы узнать  была  ли  клавиша нажата (AL = 0) или
       отпущена (AL = 1).  Заметим, что прерывание 15H предоставляет ряд
       процедур, некоторые из  которых   могут  потребоваться  программе
       обработки  Sys  Req.  В этом случае процедура обработки  Sys  Req
       должна восстанавливать замененный  ей вектор прерывания, и если в
       AH указаны функции отличные от 84H и 85H, то надо передать управ-
       ление оригинальному прерыванию 15H [1.2.4].

          3.2.5 Использование функциональных клавиш.

          10 функциональных клавиш генерируют различные коды в сочетании
       с  Shift,  Ctrl и Alt, что обеспечивает 40 разных вариантов.   Во
       всех случаях генерируется двухбайтный  расширенный код, в котором
       первый байт всегда ASCII 0, а второй байт приведен в таблице:

          Коды                  Клавиши

          59-68                 F1-F10
          84-93                 Shift + F1-F10
          94-103                Ctrl + F1-F10
          104-113               Alt + F1-F10

       Слишком  много комбинаций с использованием функциональных  клавиш
       могут смущать пользователя,  но  если  Вам  потребовалось  еще 10
       


                                     -83-
       комбинаций, то можно использовать сочетание <ScrollLock> +  <Fn>,
       как объяснено в [3.2.3].
          Клавиатура PCjr имеет только 62 клавиши, по сравнению с 83 для
       IBM PC и XT, и 84 для AT.  Некоторые комбинации с участием  функ-
       циональных клавиш  эмулируют  часть  недостающих клавиш, согласно
       следующей таблице:

            PCjr комбинации        PC/XT/AT эквиваленты

            Fn + 1-0               F1-F10
            Fn + B                 Break
            Fn + E                 Ctrl + PrtSc
            Fn + P                 Shift + PrtSc
            Fn + Q                 Ctrl + NumLock
            Fn + S                 ScrollLock
            Fn + CursorLeft        PgUp
            Fn + CursorRight       PgDn
            Fn + CursorUp          Home
            Fn + CursorDown        End
            Fn + -                 (скан-код серого минуса)
            Fn + =                 (скан-код серого плюса)

          Комбинации с участием клавиш-переключателей описаны в [3.2.2].

          3.2.6 Перепрограммирование отдельных клавиш.

          Под  перепрограммированием клавиши понимается способ заставить
       ее выдавать другой код.  Но к тому времени, когда программа полу-
       чает  код нажатой клавиши, прерывание клавиатуры уже проинтерпре-
       тировало входящий скан-код и преобразовало  его в некоторый зара-
       нее  предопределенный код ASCII или расширенный код.  К  счастью,
       начиная с MS DOS версии 2.0,  система содержит средства перепрог-
       раммирования  клавиш.   Это средство действует только  если  ввод
       воспринимается через  функции  DOS  ввода  с клавиатуры - функции
       прерывания  BIOS  16H продолжают интерпретировать нажатия  клавиш
       нормальным образом.
          Перепрограммирование доступно за счет Esc-последовательностей.
       Короткая  строка,  которая начинается с символа Esc  (ASCII  27),
       предназначается для вывода на  "стандартное  устройство  вывода",
       т.е.  на терминал.  Но благодаря наличию кода Esc символы даже не
       достигают монитора.  Вместо  этого такая строка заставляет MS DOS
       по  другому  интерпретировать клавишу, указанную в  этой  строке.
       Каждое изменение  клавиши  требует  собственной  строки, при этом
       один  и тот же код может присваиваться какому  угодно  количеству
       клавиш.
          Общий вид такой строки такой: она начинается с кода Esc (ASCII
       27),  за которым идет [, затем номер кода переопределяемой клави-
       ши, затем точка с запятой (;), затем новый номер кода, присваива-
       емый   клавише  и,  наконец,  символ p.   Таким  образом,  строка
       27,'[65;97p' меняет A  (ASCII  65)  на  a (ASCII 97). Расширенные
       коды  записываются  с  указанием обоих байтов, причем  за  первым
       нулевым   байтом   должны   стоять   точка   с   запятой.  Строка
       27,'[0;68;0;83p' присваивает клавише F10 (0;68) тот же код, что и
       клавише Delete (0;83). Вы  можете  присваивать только расширенные
       коды, приведенные в таблице расширенных кодов [3.3.5].
          Имеется  несколько вариантов допустимого вида строки.  Во-пер-
       вых, символьные клавиши могут обозначаться самим символом, заклю-
       ченным  в  кавычки.  Таким образом, строка  27,'["A";"a"p'  также
       меняет A на a. Во-вторых клавише может быть присвоена целая стро-
       ка  символов,  путем указания символов или их кодовых  номеров  в
       выражении. Строка  27,'["A";"A  is  for Apple"p' приведет к тому,
       что при нажатии на клавишу A в верхнем регистре, будет печататься
       вся строчка A is for Apple. На  самом деле эти Esc-последователь-
       


                                     -84-
       ности - ничего более, чем строки, в которых первый код или символ
       указывает какую клавишу нужно  переопределить, а оставшаяся часть
       строки  указывает какое значение Вы хотите ей придать.   Помните,
       что номера кодов должны быть всегда разделены точкой с запятой, а
       символы заключены в кавычки. Коды и символы могут быть перемешаны
       в любых сочетаниях.  Для того  чтобы такие переопределения клавиш
       были возможны, необходимо чтобы драйвер ANSI.SYS был загружен при
       загрузке операционной системы. В  противном случае Esc-последова-
       тельности будут игнорироваться.  В приложении Д показано как  это
       делается.
          Некоторые аспекты  функционирования клавиатуры программируются
       на PCjr и AT. Процедуры доступные для AT интересны в основном для
       системных  программистов;  поскольку  они нужны весьма немногим и
       достаточно  сложны, то мы не будем рассматривать их  здесь.   При
       необходимости Вам придется  обратиться к Техническому руководству
       по AT. В случае PCjr прерывание BIOS 16H имеет две дополнительные
       функции (AH = 3 и AH = 4), первая из которых устанавливает часто-
       ту автоповтора. "Частота автоповтора" - это та частота, с которой
       клавиша посылает свой код, когда  она постоянно держится нажатой.
       Вторая  функция включает и выключает звуковое подтверждение нажа-
       тия клавиши. Для функции 3 надо поместить в AL 0, чтобы вернуться
       к  частоте автоповтора, устанавливаемой по умолчанию,  1 -  чтобы
       увеличить  начальную  задержку  перед  тем,  как начинается режим
       автоповтора,  2 - чтобы уменьшить частоту автоповтора вдвое, 3  -
       чтобы установить свойства 1 и 2 вместе и 4 - выключить автоповтор
       вообще.  Для функции 4, поместив в AL 1, Вы будете иметь звуковое
       подтверждение нажатия клавиши, а 0 - выключите его.

          Высокий уровень.

          К несчастью, операторы  Бейсика  PRINT  и  WRITE не работают с
       Esc-последовательностями.   Программы на Бейсике должны  включать
       простые ассемблерные подпрограммы, использующие прерывания вывода
       MS DOS, обсуждаемые ниже в части "Средний уровень".  В приложении
       Г показано, как включить  ассемблерные  процедуры  в программы на
       Бейсике.   В  приведенном примере предполагается,  что  процедура
       находится в памяти, начиная с  адреса  2000:0000.  Операторы DATA
       содержат ассемблерный код. В конце строки переопределения клавиши
       должен быть добавлен код $.

       100 DATA &H55, &H8B, &HEC, &H8B, &H5E, &H06, &H8B, &H57
       110 DATA &H01, &HB4, &H09, &HCD, &H21, &H5D, &HCA, &H02, &H00
       120 'помещаем процедуру в память по адресу 2000:0000
       130 DEF SEG = &H2000         'определяем сегмент
       140 FOR N=0 TO 16            'процедура длиной 17 байт
       150 READ Q                   'читаем байт
       160 POKE N,Q                 'помещаем его в память
       170 NEXT                     '
       180 ''' меняем A на a
       190 Q$ = CHR$(27)+"[65;97p$" 'строка переопределения
       200 ROUTINE = 0              'указываем на строку
       210 CALL ROUTINE(Q$)         'вызываем процедуру

          Средний уровень.

          Используйте функцию  9  прерывания  21H  для посылки строки на
       стандартное  устройство вывода.  DS:DX должны указывать на первый
       символ строки в памяти  и  строка  должна  завершаться символом $
       (24H).  Здесь F2 (0;60) переопределяется таким образом, чтобы она
       действовала как Del (0;83).

       ;---в сегменте данных
       CHANGE_KEY   DB   27,'[0;60;0;83p$'
       


                                     -85-
       ;---для изменения определения клавиши
          LEA  DX,CHANGE_KEY       ;DS:DX должны указывать на строку
          MOV  AH,9                ;номер функции
          INT  21H                 ;переопределение клавиши

          3.2.7 Создание макроопределений для отдельных клавиш.

          Макроопределение - это строка  символов,  которая  будет выво-
       диться  при  нажатии одной клавиши.  Макроопределения могут  быть
       запрограммированы в  интерпретаторе  Бейсика или на уровне опера-
       ционной системы для уменьшения печатания.  Поскольку строка может
       содержать управляющие  коды,  такие  как  символ возврата каретки
       (ASCII  13), то одно макроопределение может выполнять целый набор
       команд.  Для ускорения разработки программ, например, можно напи-
       сать  макроопределение, содержащее все необходимые команды, чтобы
       оттранслировать и скомпоновать определенную программу.
          Макроопределения,   обеспечиваемые   Бейсиком,  работают как в
       Бейсиковских программах, так и на командном уровне Бейсика.  Нап-
       ример, если Вы  запрограммировали  клавишу,  чтобы при ее нажатии
       выводилось слово "Орангутан", то при нажатии этой клавиши функция
       INPUT получит всю эту строку, а цикл, включающий INKEY$, последо-
       вательно получит девять символов.  С другой стороны, макроопреде-
       ления, созданные на уровне  операционной системы, всегда работают
       на  командном  уровне DOS, но внутри программ они будут  работать
       только если программа для ввода  с  клавиатуры использует функции
       DOS.   Поскольку большинство коммерческих  программных  продуктов
       используют прерывание BIOS 16H, то для этих программ макроопреде-
       ления не будут работать.  Конечно, средства для создания макрооп-
       ределений могут быть вставлены  в  процедуры  ввода с клавиатуры.
       Например, чтобы позволить пользователю программы создать макрооп-
       ределение для F1, запросив  строку  и поместив ее в MACRO1$, надо
       на Бейсике написать что-то вроде:

       1000 '''процедура ввода расширенного кода, в C - 2-й байт кода
       1010 IF C=59 THEN LOCATE X,Y: PRINT MACRO1$

          Высокий уровень.

          Бейсик имеет встроенный механизм создания макроопределений, но
       он  позволяет программировать только 10 функциональных клавиш,  а
       строки должны быть не длиннее 15  символов.  Бейсик рассматривает
       функциональные клавиши, как программируемые клавиши. Оператор KEY
       присваивает макроопределение  данной  клавише. Строка KEY 5,"END"
       приводит  к  тому, что функциональная клавиша #5  будет  посылать
       слово END в текущую позицию курсора на экране.
          Символы  составляющие строку могут вводиться как строки симво-
       лов, как ASCII коды (используя  CHR$)  или  как комбинация того и
       другого.   Команды KEY 5,"A" и KEY 5,CHR$(65) эквивалентны.   Для
       того, чтобы строка сразу исполнялась надо добавить в конце строки
       символ  возврата  каретки (ASCII 13).  Команда  FILES,  выводящая
       каталог диска, исполняется после  того, как Вы присвоите это зна-
       чение F1 командой KEY 1,"FILES"+CHR$(13).
          Бейсик  присваивает десяти функциональным клавишам распростра-
       ненные операторы Бейсика. Вы можете отменить макроопределение для
       данной  клавиши, присвоив ей пустую строку,например, команда  KEY
       1,"" приведет к тому, что  при  нажатии  F1  ничего  вводиться не
       будет. Первые шесть символов каждой строки автоматически выводят-
       ся в нижней части экрана  интерпретатором  Бейсика. Вы можете уп-
       равлять наличием этого вывода используя команды KEY ON и KEY OFF.
       Для того чтобы вывести на экран полные определения клавиш, введи-
       те команду KEY LIST. Вот несколько примеров:

       KEY 1,"ERASE"           ; теперь F1 выводит "ERASE"
       


                                     -86-
       KEY 10,"LIST"+CHR$(13)  ; теперь F10 выдает листинг
       KEY 7,""                ; теперь F7 ничего не выдает
       KEY OFF                 ; подавляет вывод внизу экрана
       KEY ON                  ; включает вывод внизу экрана
       KEY LIST                ; выдает список значений 10 клавиш

       Для  создания макроопределений других клавиш в Бейсике, Вы должны
       использовать средства MS DOS, описанные в [3.2.6].
          Средний уровень.

          В MS DOS макроопределения создаются с помощью метода перепрог-
       раммирования клавиш, описанного в [3.2.6]. Единственное отличие в
       том, что  клавише  сопоставляется  целая  строка символов. Строка
       может  быть введена в виде символов, заключенных в кавычки, или в
       виде кодов или комбинации того и другого. Вот несколько примеров:

       27,'["A";"SET"p'          ; присваивает SET заглавной A
       27,'["ASET"p'             ; эквивалентно предыдущему
       27,'[27;"dir";13p'        ; присваивает dir<CR> клавише Esc
       27,'[0;59;"copy *.* b:";13p'  ;присваивает F1 команду
       27,'[0;68;0;72;0;72;0;72p'    ;заставляет F10 сдвинуть курсор на
                                     ;три строки вверх

          3.2.8 Создание процедуры обработки Ctrl-Break.

          Когда вводится комбинация Ctrl-Break, то прерывание клавиатуры
       устанавливает флаг, указывающий что должна быть выполнена  проце-
       дура обработки Ctrl-Break.  Управление  передается этой процедуре
       только в тот  момент,  когда  программа  использует  функцию DOS,
       способную распознавать этот флаг. Обычно только стандартные функ-
       ции ввода/вывода MS DOS могут  распознавать этот флаг (функции от
       1 до C прерывания 21H, за исключением функций 6 и 7). Но поместив
       строку BREAK=ON либо  в  файл  AUTOEXEC.BAT,  либо  в CONFIG.SYS,
       используемые  MS  DOS при старте системы, Вы  получите  ситуацию,
       когда обращение к любой функции  DOS  приведет к вызову процедуры
       обработки Ctrl-Break. При этом выполнение программы будет немного
       замедлено.
          Процедура  обработки  Ctrl-Break  дает  возможность  завершить
       программу  в любой момент времени.  Когда функция DOS  распознает
       статус Ctrl-Break, то управление передается процедуре, на которую
       указывает вектор прерывания 23H. DOS использует эту процедуру для
       завершения работающей программы.  Но процедура может быть перепи-
       сана Вами, с тем чтобы она удовлетворяла любым Вашим требованиям.
       Эта процедура  должна  быть  программируемой,  с  тем чтобы перед
       завершением программы могли быть выполнены все критические опера-
       ции. Может требоваться  выравнивание стека, с тем чтобы SP указы-
       вал  на  второе слово от вершины (первое слово для программ  COM)
       перед выполнением завершающей инструкции RET. Вектора прерывания,
       измененные  программой должны быть восстановлены, а все  открытые
       устройства ввода/вывода -  закрыты.  Если были запрещены прерыва-
       ния, то надо разрешить их.  Все это должно обеспечить машине воз-
       можность нормально работать со следующей  программой после завер-
       шения  программы  по Ctrl-Break.  Другая  альтернатива -  сделать
       процедуру  обработки  Ctrl-Break,  состоящей  из одной инструкции
       IRET, что запрещает завершение программы таким способом.

          Средний уровень.

          В  данном примере выход из программы происходит после выравни-
       вания стека.   Процедура  кончается  инструкцией  RET, а не IRET,
       поскольку в данном случае она действует в точности так же, как  и
       инструкция RET при нормальном  завершении  программы.   В момент,
       когда она используется, указатель стека (SP) должен указывать  на
       


                                     -87-
       второе слово стека. Это  предполагает, что программа в форме EXE.
       Помните,  что  стек помещаает свое первое слово в  самую  старшую
       ячейку памяти, второе - в ячейку  ниже, и т.д.  Если размер стека
       400  байт,  то надо установить SP на 396.  Для программ COM  надо
       устанавливать указатель стека  на  первое  слово стека или просто
       завершать процедуру обработки Ctrl-Break прерыванием 21H.

       ;---это новая процедура обработки Ctrl-Break
       C_B     PROC FAR
               MOV  AX,396           ;значение для второго слова стека
               MOV  SP,AX            ;выравниваем указатель стека
               RET                   ;возврат в DOS
       C_B     ENDP                  ;
       ;---изменение вектора прерывания
               PUSH DS               ;сохраняем регистр
               MOV  AX,SEG C_B       ;готовим адрес процедуры
               MOV  DS,AX            ;
               MOV  DX,OFFSET C_B    ;
               MOV  AH,25H           ;номер функции
               MOV  AL,23H           ;номер вектора
               INT  21H              ;изменяем вектор
               POP  DS               ;восстанавливаем регистр

          Программа  может в любое время проверить был ли сделан  запрос
       на выполнение процедуры обработки Ctrl-Break. Надо поместить в AL
       0  и  вызвать функцию 33 прерывания 21H.  При возврате  DL  будет
       содержать 1, если был установлен флаг прерывания по Ctrl-Break, и
       0  -  в противном случае.  Если при вызове поместить в AL  1,  то
       статус будет установлен.  В этом  случае,  перед вызовом функции,
       поместите в DL 0 или 1, чтобы флаг был установлен или очищен.

          3.2.9 Перепрограммирование клавиши PrtSc.

          Клавиша  PrtSc  выдает звездочку (ASCII 42),  если  нажать  ее
       одну, она выдает  расширенный  код  114,  если нажать ее вместе с
       клавишей  Ctrl.  Но комбинация <Shift> + <PrtSc> имеет совершенно
       отдельный статус. Нажатие на другие клавиши заставляют прерывание
       клавиатуры  помещать  их коды в буфер клавиатуры (или,  для  кла-
       виш-переключателей,  записывать  их  состояние  [3.1.7]). Нажатие
       клавиши не влияет на выполняемую программу, до тех пор пока прог-
       рамма не станет считывать символ клавиши из буфера клавиатуры. Но
       комбинация  <Shift>  + <PrtSc> заставляет  прерывание  клавиатуры
       немедленно передать  управление  процедуре,  на которую указывает
       вектор прерывания 5.  В некотьором смысле она работает как  аппа-
       ратное прерывание.
          Прерывание  5  запрограммировано  таким  образом, чтобы выдать
       содержимое  экрана на принтер.  Но вектор прерывания может указы-
       вать на процедуру,  предназначенную  для  совершенно другой цели.
       Например,  изощренная программа имитации, которой требуются  часы
       для завершения своей работы, может  прервана в любое время комби-
       нацией Shift + PrtSc, чтобы она выдала рапорт о текущем состоянии
       расчетов. Вам может также захотеться, чтобы на принтер можно было
       посылать копию графического экрана. Другая возможность, использо-
       вать PrtSc как  способ  доступа  к  программе,  которая находится
       резидентно в памяти во время загрузки MS DOS [1.3.4]. Такая стра-
       тегия позволит Вам  написать  утилиту,  которая может работать из
       другого программного обеспечения.

          Низкий уровень.

          Здесь приведена основная форма перепрограммирования процедуры.
       Не   забудьте   восстановить   оригинальный   вектор   прерывания
       (F000:FF54)  при завершении программы.  Если Вы забудете  сделать
       


                                     -88-
       это, то все будет идти нормально, до тех пор пока не будет нажата
       комбинация Shift + PrtSc, а тогда произойдет крах системы  (более
       полный пример программирования прерывания см. в [1.2.3]).

       ;---изменить вектор прерывания для PrtSc
          CLI                       ;запрет прерываний
          MOV  AX,SEG NEW_ROUTINE   ;получаем адрес процедуры
          MOV  DS,AX                ;
          MOV  DX,OFFSET NEW_ROUTINE   ;
          MOV  AL,5                 ;номер изменяемого вектора
          MOV  AH,25H               ;номер функции
          INT  21H                  ;изменяем вектор
          STI                       ;разрешаем прерывания
           .
           .
       ;---описание процедуры PrtSc
       NEW_ROUTINE  PROC FAR
                    STI             ;разрешаем прерывания
                    PUSH AX         ;сохраняем регистры
                     .
                     .
                    MOV  CX,100     ;Ваша процедура
                     .
                     .
                    POP  AX         ;восстанавливаем регистры
                    IRET            ;возврат из прерывания
       NEW_ROUTINE  ENDP            ;

                   Раздел 3: Сводка кодов клавиш и применений.

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

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

          -  имеется  4  кода ASCII, которые могут быть  получены  двумя
       способами. ASCII 8 - нажатием клавиши BackSpace и Ctrl-H, ASCII 9
       -  клавиши  Tab и Ctrl-I, ASCII 13 - клавиши  Enter и  Ctrl-M,  а
       ASCII 27 - клавиши Esc и Ctrl-[.

          - символы, соответствующие 32 управляющим кодам ASCII не выво-
       дятся  на  экран, при использовании функций  ввода с  клавиатуры,
       обеспечивающих автоматическое эхо. Они могут быть выведены либо с
       помощью  функции 10H прерывания 10H, либо прямым выводом в память
       дисплея (оба способа обсуждаются в [4.3.1]).

          - комбинации клавиши Ctrl с буквами  алфавита генерируют одно-
       байтные  коды  ASCII.  Все остальные комбинации  Ctrl  генерируют
       двухбайтные (расширенные) коды.

          - клавиша <5>  дополнительной  клавиатуры  не  действует, если
       установлен режим управления курсором клавишей NumLock.

          -  комбинации Shift-PrtSc и Ctrl-Alt (а также SysReq  для  AT)
       это единственные случаи, когда  комбинация  клавиш приводит к не-
       медленному  вызову некоторой процедуры.  Из них только первая пе-
       репрограммируема.  Прерывание  обработки Ctrl-Break (также переп-
       рограммируемое)  вызывается только тогда, когда статус Ctrl-Break
       будет обнаружен процедурой MS DOS.

       


                                     -89-
          - любой код ASCII, кроме 0,  может  быть  введен путем нажатия
       клавиши  Alt, набора кода ASCII на дополнительной  клавиатуре  и,
       затем, отпускания клавиши Alt.  Поскольку код 0 исключен, то рас-
       ширенные коды не могут быть введены таким способом.

       Отметим, что Вы практически ничего не можете сделать, чтобы прео-
       долеть  ограничения,  накладываемые   на  недопустимые комбинации
       клавиш.  Например, Вы не можете определить комбинацию Ctrl + Cur-
       sorUp, принимая код CursorUp,  а  затем  проверяя регистр статуса
       перключателей  для определения того, была ли нажата клавиша Ctrl.
       Если Ctrl была нажата, то клавиша CursorUp вообще не выдает ника-
       кого кода.

          3.3.1 Предопределенное использование клавиш.

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

          ScrollLock        Переключает режим вывода на терминал, при
                            котором перемещение курсора сдвигает экран,
                            а не сам курсор
          CTRL 4/6          Сдвигает курсор на слово влево/вправо.
                            Другая возможность: горизонтальный сдвиг
                            экрана на позицию табуляции влево/вправо.
          Pg Up             Возврат на 25 строк назад.
          Pg Dn             Сдвиг на 25 строк вперед.
          CTRL END          Удаление текста от позиции курсора до конца
                            строки.
          CTRL PgDn         Удаление текста от позиции курсора до конца
                            экрана.
          HOME              В тексте перемещает курсор к началу строки
                            или документа. В меню - возвращает в главное
                            меню.
          CTRL HOME         Чистит экран и помещает курсор в левый
                            верхний угол.
          END               Перемещает курсор к концу строки или к
                            концу документа.
          BACKSPACE/DELETE  DELETE уничтожает символ, на который указы-
                            вает курсор, и сдвигает остаток строки на
                            одну позицию влево. BACKSPACE удаляет символ
                            слева от курсора и делает то же самое.
          INS               Переключает режим вставки/замены.
          TAB/BACKTAB       Перемещает курсор в следующую позицию табу-
                            ляции, вправо - если была нажата одна и
                            влево - если вместе с клавишей Shift.
          ESC               Выход из программы или процедуры.

          3.3.2 Сводная таблица скан-кодов.

          Каждая клавиша генерирует два типа скан-кодов, "код нажатия" -
       когда клавиша нажимается,  и  "код  освобождения" - когда клавиша
       отпускается.   Для всех машин, кроме AT, код освобождения на  128
       больше кода нажатия (бит 7 = 1).  Таким образом клавиша T создает
       код 20 при нажатии и код 148 при отпускании. AT использует одну и
       ту же цепочку битов для  кодов  нажатия  и  освобождения, но коды
       освобождения  состоят  из двух байтов, первый из  которых  всегда
       равен 0F0H. PCjr имеет специальный скан-код мнимой клавиши, номер
       55.  Этот код порождается, когда были одновременно нажаты три или
       более клавиш, что помогает избежать ошибок при вводе.  Прерывание
       


                                     -90-
       клавиатуры  отбрасывает этот код и он не связывается  ни с  каким
       кодом ASCII или расширенным кодом.

                             Клавиши пишущей машинки

          Клавиша Код нажатия  Клавиша Код нажатия  Клавиша Код нажатия

            "1"       2          "T"        20        "L"       38
            "2"       3          "Y"        21        ";"       39
            "3"       4          "U"        22        "'"       40
            "4"       5          "I"        23        "`"       41
            "5"       6          "O"        24        "\"       43
            "6"       7          "P"        25        "Z"       44
            "7"       8          "["        26        "X"       45
            "8"       9          "]"        27        "C"       46
            "9"       10         "A"        30        "V"       47
            "0"       11         "S"        31        "B"       48
            "-"       12         "D"        32        "N"       49
            "="       13         "F"        33        "M"       50
            "Q"       16         "G"        34        ","       51
            "W"       17         "H"        35        "."       52
            "E"       18         "J"        36        "/"       53
            "R"       19         "K"        37      пробел      57

                               Управляющие клавиши

          Esc - 1               Ctrl - 29           Alt - 56
          BackSpace - 14        left shift - 42     CapsLock - 58
          Tab - 15              right shift - 42    NumLock - 58
          Enter - 28            PrtSc - 55          ScrollLock - 70

                              Функциональные клавиши

          F1 - 59               F5 - 63             F9 - 67
          F2 - 60               F6 - 64             F10 - 68
          F3 - 61               F7 - 65
          F4 - 62               F8 - 66

                        Клавиши дополнительной клавиатуры

          "7" - 71           "5" - 76            "3" - 81
          "8" - 72           "6" - 77            "0" - 82
          "9" - 73           "+" - 78            "." - 83
          "-" - 74           "1" - 79      Sys Req - 132 (только AT)
          "4" - 75           "2" - 80       мнимая - 55 (только PCjr)

          3.3.3 Сводная таблица кодов ASCII

          Номера  кодов  от 0 до 31, управляющих кодов, объяснены  более
       детально в [7.1.9].  Напоминаем,  что любой код ASCII от 1 до 255
       может  быть введен с клавиатуры, если держать нажатой клавишу Alt
       при наборе номера кода на дополнительной клавиатуре (с соответст-
       венно  установленным  режимом NumLock).  Когда клавиша Alt  затем
       освобождается, то код вводится.

       Символ  10-ный  16-ричный  двоичный  Символ  10-ный  16-ричный  двоичный

       (null)     0        00     00000000     0      48       30      00110000
                  1        01     00000001     1      49       31      00110001
                  2        02     00000010     2      50       32      00110010
                  3        03     00000011     3      51       33      00110011
                  4        04     00000100     4      52       34      00110100
                  5        05     00000101     5      53       35      00110101
       


                                     -91-
                  6        06     00000110     6      54       36      00110110
                  7        07     00000111     7      55       37      00110111
                  8        08     00001000     8      56       38      00111000
                  9        09     00001001     9      57       39      00111001
                 10        0A     00001010     :      58       3A      00111010
                 11        0B     00001011     ;      59       3B      00111011
                 12        0C     00001100     <      60       3C      00111100
                 13        0D     00001101     =      61       3D      00111101
                 14        0E     00001110     >      62       3E      00111110
                 15        0F     00001111     ?      63       3F      00111111
                 16        10     00010000     @      64       40      01000000
                 17        11     00010001     A      65       41      01000001
                 18        12     00010010     B      66       42      01000010
                 19        13     00010011     C      67       43      01000011
                 20        14     00010100     D      68       44      01000100
                 21        15     00010101     E      69       45      01000101
                 22        16     00010110     F      70       46      01000110
                 23        17     00010111     G      71       47      01000111
                 24        18     00011000     H      72       48      01001000
                 25        19     00011001     I      73       49      01001001
                 26        1A     00011010     J      74       4A      01001010
                 27        1B     00011011     K      75       4B      01001011
                 28        1C     00011100     L      76       4C      01001100

       Символ  10-ный  16-ричный  двоичный  Символ  10-ный  16-ричный  двоичный

                 29        1D     00011101     M      77       4D      01001101
                 30        1E     00011110     N      78       4E      01001110
                 31        1F     00011111     O      79       4F      01001111
       пробел    32        20     00100000     P      80       50      01010000
         !       33        21     00100001     Q      81       51      01010001
         "       34        22     00100010     R      82       52      01010010
         #       35        23     00100011     S      83       53      01010011
         $       36        24     00100100     T      84       54      01010100
         %       37        25     00100101     U      85       55      01010101
         &       38        26     00100110     V      86       56      01010110
         '       39        27     00100111     W      87       57      01010111
         (       40        28     00101000     X      88       58      01011000
         )       41        29     00101001     Y      89       59      01011001
         *       42        2A     00101010     Z      90       5A      01011010
         +       43        2B     00101011     [      91       5B      01011011
         ,       44        2C     00101100     \      92       5C      01011100
         -       45        2D     00101101     ]      93       5D      01011101
         .       46        2E     00101110     ^      94       5E      01011110
         /       47        2F     00101111     _      95       5F      01011111
         `       96        60     01100000     Щ     153       99      10011001
         a       97        61     01100001     Ъ     154       9A      10011010
         b       98        62     01100010     Ы     155       9B      10011011
         c       99        63     01100011     Ь     156       9C      10011100
         d      100        64     01100100     Э     157       9D      10011101
         e      101        65     01100101     Ю     158       9E      10011110
         f      102        66     01100110     Я     159       9F      10011111
         g      103        67     01100111     а     160       A0      10100000
         h      104        68     01101000     б     161       A1      10100001
         i      105        69     01101001     в     162       A2      10100010
         j      106        6A     01101010     г     163       A3      10100011
         k      107        6B     01101011     д     164       A4      10100100
         l      108        6C     01101100     е     165       A5      10100101
         m      109        6D     01101101     ж     166       A6      10100110
         n      110        6E     01101110     з     167       A7      10100111
         o      111        6F     01101111     и     168       A8      10101000
         p      112        70     01110000     й     169       A9      10101001
         q      113        71     01110001     к     170       AA      10101010
       


                                     -92-
         r      114        72     01110010     л     171       AB      10101011
         s      115        73     01110011     м     172       AC      10101100
         t      116        74     01110100     н     173       AD      10101101
         u      117        75     01110101     о     174       AE      10101110
         v      118        76     01110110     п     175       AF      10101111
         w      119        77     01110111     ░     176       B0      10110000
         x      120        78     01111000     ▒     177       B1      10110001
         y      121        79     01111001     ▓     178       B2      10110010
         z      122        7A     01111010     │     179       B3      10110011
         {      123        7B     01111011     ┤     180       B4      10110100
         |      124        7C     01111100     ╡     181       B5      10110101
         }      125        7D     01111101     ╢     182       B6      10110110
         ~      126        7E     01111110     ╖     183       B7      10110111
                127        7F     01111111     ╕     184       B8      10111000
         А      128        80     10000000     ╣     185       B9      10111001
         Б      129        81     10000001     ║     186       BA      10111010
         В      130        82     10000010     ╗     187       BB      10111011
         Г      131        83     10000011     ╝     188       BC      10111100
         Д      132        84     10000100     ╜     189       BD      10111101
         Е      133        85     10000101     ╛     190       BE      10111110
         Ж      134        86     10000110     ┐     191       BF      10111111
         З      135        87     10000111     └     192       C0      11000000
         И      136        88     10001000     ┴     193       C1      11000001
         Й      137        89     10001001     ┬     194       C2      11000010
         К      138        8A     10001010     ├     195       C3      11000011

       Символ  10-ный  16-ричный  двоичный  Символ  10-ный  16-ричный  двоичный

         Л      139        8B     10001011     ─     196       C4      11000100
         М      140        8C     10001100     ┼     197       C5      11000101
         Н      141        8D     10001101     ╞     198       C6      11000110
         О      142        8E     10001110     ╟     199       C7      11000111
         П      143        8F     10001111     ╚     200       C8      11001000
         Р      144        90     10010000     ╔     201       C9      11001001
         С      145        91     10010001     ╩     202       CA      11001010
         Т      146        92     10010010     ╦     203       CB      11001011
         У      147        93     10010011     ╠     204       CC      11001100
         Ф      148        94     10010100     ═     205       CD      11001101
         Х      149        95     10010101     ╬     206       CE      11001110
         Ц      150        96     10010110     ╧     207       CF      11001111
         Ч      151        97     10010111     ╨     208       D0      11010000
         Ш      152        98     10011000     ╤     209       D1      11010001
         ╥      210        D2     11010010     щ     233       E9      11101001
         ╙      211        D3     11010011     ъ     234       EA      11101010
         ╘      212        D4     11010100     ы     235       EB      11101011
         ╒      213        D5     11010101     ь     236       EC      11101100
         ╓      214        D6     11010110     э     237       ED      11101101
         ╫      215        D7     11010111     ю     238       EE      11101110
         ╪      216        D8     11011000     я     239       EF      11101111
         ┘      217        D9     11011001     Ё     240       F0      11110000
         ┌      218        DA     11011010     ё     241       F1      11110001
         █      219        DB     11011011     Є     242       F2      11110010
         ▄      220        DC     11011100     є     243       F3      11110011
         ▌      221        DD     11011101     Ї     244       F4      11110100
         ▐      222        DE     11011110     ї     245       F5      11110101
         ▀      223        DF     11011111     Ў     246       F6      11110110
         р      224        E0     11100000     ў     247       F7      11110111
         с      225        E1     11100001     °     248       F8      11111000
         т      226        E2     11100010     ∙     249       F9      11111001
         у      227        E3     11100011     ·     250       FA      11111010
         ф      228        E4     11100100     √     251       FB      11111011
         х      229        E5     11100101     №     252       FC      11111100
         ц      230        E6     11100110     ¤     253       FD      11111101
       


                                     -93-
         ч      231        E7     11100111     ■     254       FE      11111110
         ш      232        E8     11101000           255       FF      11111111

          3.3.4 Сводка кодов псевдографики для построения рамок.

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

         218       194      191           213      209      184
          ┌         ┬        ┐             ╒        ╤        ╕

           195       197      180           198      216      181
          ├         ┼        ┤      │      ╞        ╪        ╡

                                   179
          └         ┴        ┘             ╘        ╧        ╛
         192       193      217           212      207      190

                    ─  196                          ═  205

         214       210      183           201      203      187
          ╓         ╥        ╖             ╔        ╦        ╗

           199       215      182           204      206      185
          ╟         ╫        ╢      ║      ╠        ╬        ╣

                                   186
          ╙         ╨        ╜             ╚        ╩        ╝
         211       208      189           200      202      188

          3.3.5 Сводная таблица расширенных кодов.

       Значение 2-го байта     Соответствующие клавиши

          15                   Shift + Tab ("back-tab")
          16-25                Alt-Q - Alt-P (верхний ряд букв)
          30-38                Alt-A - Alt-L (средний ряд букв)
          44-50                Alt-Z - Alt-M (нижний ряд букв)
          59-68                Функциональные клавиши F1 - F10
          71                   Home
          72                   Cursor-up (стрелка вверх)
          73                   PgUp
          75                   Cursor-left (стрелка влево)
          77                   Cursor-right (стрелка вправо)
          79                   End
          80                   Cursor-down (стрелка вниз)
          81                   PgDn
          82                   Ins
          83                   Del
          84-93                F1-F10 + Shift
          94-103               F1-F10 + Ctrl
          104-113              F1-F10 + Alt
          114                  Ctrl + PrtSc
          115                  Ctrl + Cursor-left
          116                  Ctrl + Cursor-right
          117                  Ctrl + End
          118                  Ctrl + PgDn
          119                  Ctrl + Home
          120-131              Alt + 1 - Alt + = (верхний ряд)
          132                  Ctrl + PgUp

                           Глава 4. Вывод на терминал.

       


                                     -94-
          Раздел 1. Управление выводом на терминал.

          В  этой главе рассмотрены монохромный адаптор, цветной  графи-
       ческий адаптор, видеосистема  PCjr и улучшенный графический адап-
       тер (EGA).  Все 4 системы базируются на микросхеме Motorola  6845
       CRTC (cathode ray  tube  controller);  хотя EGA на самом деле ис-
       пользует заказную микросхему, основанную на принципах 6845.   Эта
       микросхема выполняет массу  технических  задач, которые обычно не
       интересуют  программиста.  Однако, она также устанавливает  режим
       экрана, управляет курсором и (для цветного графического адаптора)
       управляет цветом. Микросхема легко программируется напрямую, хотя
       процедуры операционной системы  позволяют  управлять большинством
       ее  действий.  PCjr имеет вспомогательную микросхему для дисплея,
       "video gate array" (массив ворот дисплея),  которая обсуждается в
       этом  разделе вместе с 6845.  EGA имеет архитектуру, отличающуюся
       от всех остальных, поэтому он обсуждается  отдельно. Среди не-EGA
       систем имеется совместимость по использованию адресов портов,  но
       есть и некоторые  важные  отличия.   Некоторые  адреса портов EGA
       такие же, как и у других систем.
          Все  видеосистемы  используют буфера, в  которые  отображаются
       данные для изображения на экране.  Экран периодически обновляется
       сканированием  этих  данных.  Размер и расположение этих  буферов
       меняется с системой, режимом экрана,  а также количеством заранее
       отведенной  памяти.   Когда в буфере хранится  несколько  образов
       экрана, то каждый отдельный  образ называют дисплейной страницей.
       Ниже приведена короткая сводка:

       Монохромный адаптор

          Монохромный  адаптор имеет 4K байт памяти на плате, начиная  с
       адреса B0000H (т.е.  B000:0000).  Этой  памяти хватает только для
       хранения одной 80-символьной страницы текста.

       Цветной графический адаптор.

          Цветной  графический  адаптор имеет 16K байт памяти на  плате,
       начиная с адреса памяти B8000H.  Этого достаточно для отображения
       одного графического экрана, без страниц, или от четырех до восьми
       экранов текста, в зависимости от числа символов в строке - 40 или
       80.

       PCjr.

          PCjr  имеет видеосистему, которая на самом деле является улуч-
       шенной версией цветного графического адаптора. Она уникальна тем,
       что использует для видеобуфера обычную оперативную память  систе-
       мы. Когда BIOS инициализирует систему, то верхние 16K установлен-
       ной  памяти отводятся под буфер терминала.  Таким  образом  адрес
       буфера зависит от того  сколько  памяти  имеется  в системе.  Для
       добавочных  дисплейных страниц могут быть отведены блоки памяти в
       других местах, а также начальный объем  может быть уменьшен до 4K
       и была поддержка только одного экрана текста.

       EGA.

          EGA  может быть снабжен 64K, 128K или 256K памяти.  Кроме  ис-
       пользования в качестве видеобуфера эта память может также хранить
       битовые  описания  вплоть  до  1024  символов  (как  объяснено  в
       [4.3.4]). Стартовый адрес  буфера  дисплея программируем, поэтому
       буфер начинается с адреса A000H для улучшенных графических  режи-
       мов, и с B000H и B800H для  совместимости  со стандартными монох-
       ромным и цветным графическим режимами.  В большинстве случаев EGA
       занимает два сегмента  с  адресами  от A000H до BFFFH, даже когда
       


                                     -95-
       имеется 256K памяти.  Это возможно, поскольку в некоторых режимах
       два или более байтов памяти дисплея считываются из одних и тех же
       адресов.   Доступное число страниц зависит как от режима  экрана,
       так и от количества имеющейся памяти.  Вследствие своей сложности
       EGA имеет ПЗУ на 16K байт, которое заменяет и расширяет процедуры
       работы с терминалом BIOS. Начало области ПЗУ - адрес C000:0000.
          В текстовых режимах  буфера  начинаются  с  данных для верхней
       строки экрана, начиная с левого угла.  Дальнейшие данные  перено-
       сятся с правого конца одной  строки на левый конец следующей, как
       будто  экран  представляется  одной большой строкой -  и с  точки
       зрения видеобуфера так оно и  есть.  Однако в графических режимах
       буфер может быть разделен на 2 или 4 части.  У цветного графичес-
       кого адаптора и PCjr различные части  буфера содержат информацию,
       относящуюся  к каждой второй или каждой четвертой линии точек  на
       экране.  У EGA каждая часть  буфера содержит один бит из двух или
       четырех, которые определяют цвет данной точки экрана.
          При  выводе текста различные видеосистемы работают  одинаково.
       Для экрана отводится 4000 байтов, так что на каждую из 2000 пози-
       ций  экрана приходится 2 байта (25 строк * 80 символов).   Первый
       байт содержит код  ASCII.   Аппаратура  дисплея преобразует номер
       кода  ASCII  в  связанный с ним символ и посылает его  на  экран.
       Второй байт (байт атрибутов) содержит  информацию о том, как дол-
       жен  быть выведен данный символ.  Для монохромного дисплея он ус-
       танавливает будет ли данный  символ  подчеркнут, выделен яркостью
       или негативом, или использует комбинацию этих атрибутов. В цвето-
       вых системах  байт  атрибутов  устанавливает  основной  и фоновый
       цвета символа.  В любом случае Ваша программа может писать данные
       прямо в буфер терминала, что значительно повышает скорость вывода
       на экран.
          Все  системы, кроме монохромной, предоставляют  набор  цветных
       графических режимов,  которые  отличаются  как разрешением, так и
       числом одновременно выводимых цветов. И PCjr и EGA могут одновре-
       менно выводить 16  цветов,  причем  EGA  может выбирать эти 16 из
       набора 64 цветов. При использовании 16 цветов каждая точка экрана
       требует четырех бит памяти, поскольку  4 бита могут хранить числа
       от 0 до 15.  По аналогии, четырехцветная графика требует только 2
       бита на точку. Двухцветная  графика может упаковать представление
       восьми точек в один байт видеобуфера.  Количество памяти, требуе-
       мое для данного режима экрана  может  быть  легко вычислено, если
       известно  количество выводимых в этом режиме  точек и  количество
       бит, необходимое для описания одной  точки.  Текст легко комбини-
       руется  с графикой (BIOS рисует символы на графическом экране)  и
       Вы можете создавать свои специальные символы.

          4.1.1 Программирование контроллера дисплея 6845.

          Все видеосистемы строятся вокруг микросхемы контроллера видео-
       терминала  Motorola  6845 (EGA  использует  заказную  микросхему,
       основанную на 6845). Микросхема используется во многом аналогично
       в  монохромном  адапторе, в цветном адапторе и в PCjr; но EGA  не
       настолько совместим и по этой причине мы рекомендуем Вам избегать
       прямого  программирования микросхемы, когда BIOS может  выполнить
       работу за Вас.  Говоря общими словами, микросхема 6845 устанавли-
       вает  видеодисплей  в  один из нескольких  алфавитноцифровых  или
       графических режимов. Она выполняет основную работу по интерпрета-
       ции номеров кодов ASCII и поиску данных для вывода  соответствую-
       щих символов в микросхеме  ПЗУ  (а  иногда в оперативной памяти).
       Она декодирует значения атрибутов цвета и соответственно устанав-
       ливает экран. Она также создает курсор и управляет им. В архитек-
       туре  EGA часть этих функций распределена между другими микросхе-
       мами.
          Микросхема  6845 имеет 18 управляющих регистров, пронумерован-
       ных от 0 до 17.  Первые 10  регистров  фиксируют горизонтальные и
       


                                     -96-
       вертикальные параметры дисплея.  Эти регистры, как правило, неин-
       тересны для программистов, поскольку они автоматически устанавли-
       ваются BIOS при изменении режима экрана.  Не советуем эксперимен-
       тировать с этими регистрами, поскольку имеется возможность испор-
       тить терминал.  Регистры имеют размер 8 бит, но некоторые связаны
       в пары, чтобы хранить 16-битные  величины.   Пары #10-11 и #14-15
       устанавливают  форму  [4.2.4] и местоположение  [4.2.1]  курсора.
       Пара #12-13 управляет  страницами  дисплея  [4.5.3].  Пара #16-17
       сообщает  позицию светового пера [7.3.2].  Большинство  регистров
       доступно только для записи; только регистр адреса курсора можно и
       читать и писать, а регистр светового пера предназначен только для
       чтения. EGA имеет 6 добавочных  регистров, которые связаны с тех-
       ническими деталями.  Регистр 20 наиболее интересен; он определяет
       какая линия сканирования в  строке  символа используется для под-
       черкивания.
          Доступ ко всем 18 регистрам осуществляется через один и тот же
       порт, адрес которого для монохромного  адаптора равен 3B5H.  Этот
       адрес  равен 3D5H для цветного адаптора и PCjr (заметим, что  все
       адреса портов для монохромного адаптора такие же, как и для цвет-
       ного,  за исключением того, что средней цифрой является  B, а  не
       D). EGA использует один из  этих  двух  адресов, в зависимости от
       того, присоединен ли к нему цветной или монохромный монитор.  Для
       записи в регистр  монохромного  адаптора  надо  сначала в регистр
       адреса,  расположенный в порте 3B4H (3D4H для цветного),  послать
       номер требуемого регистра. Тогда следующий байт, посланный в порт
       с адресом 3B5H будет записан в этот регистр.  Поскольку регистры,
       интересные для программиста, используются попарно, то надо снача-
       ла  записать  в адресный регистр, потом в  первый  регистр  пары,
       потом снова в  адресный  регистр  и,  наконец,  во второй регистр
       пары.  Поскольку адреса портов смежные, то легче всего адресовать
       их, используя инструкции INC и DEC, как в следующем примере:

       ;---запись в регистры 11 и 12 микросхемы 6845 (данные в BX)
          ;---выбираем регистр младшего байта
             MOV  DX,3B4H        ;порт адресного регистра
             MOV  AL,11          ;номер регистра для младшего байта
             OUT  DX,AL          ;посылаем номер регистра
          ;---посылаем байт
             INC  DX             ;увеличиваем адрес порта
             MOV  AL,BL          ;берем младший байт
             OUT  DX,AL          ;посылаем его в регистр 11
          ;---выбираем регистр старшего байта
             DEC  DX             ;восстанавливаем адрес порта
             MOV  AL,12          ;номер регистра для старшего байта
             OUT  DX,AL          ;посылаем номер регистра

          ;---посылаем байт
             INC  DX             ;увеличиваем адрес порта
             MOV  AL,BH          ;берем старший байт
             OUT  DX,AL          ;посылаем его в регистр 12

          У  монохромного  и цветного адапторов имеются еще  три  порта,
       которые важны для  программистов.   Они имеют адреса 3B8H, 3B9H и
       3BAH  для монохромного и 3D8H, 3D9H и 3DAH - для цветного адапто-
       ра. Первый устанавливает режим экрана, второй - связан в основном
       с установкой цветов экрана, а третий сообщает полезную информацию
       о статусе дисплея.
          PCjr использует не все эти адреса  аналогичным образом. Вместо
       этого,  он держит часть информации, относящейся к этим портам,  в
       микросхеме массива ворот  дисплея,  основное назначение которой -
       обеспечить  дополнительное управление цветами экрана.   Доступ  к
       массиву ворот дисплея осуществляется через порт с адресом 3DAH. У
       цветного  адаптора этот порт возвращает байт статуса; у PCjr этот
       


                                     -97-
       порт также возвращает байт  статуса  при использовании инструкции
       IN, но он предоставляет доступ к массиву ворот, когда использует-
       ся инструкция OUT. Массив ворот дисплея имеет следующие регистры:

                Номер             Назначение

                  0               режим управления 1
                  1               маска набора цветов (палетты)
                  2               цвет границы
                  3               режим управления 2
                  4               сброс
                  10H-1FH         назначение цветов палетты

          Доступ ко всем регистрам осуществляется через порт 3DAH.  Сна-
       чала надо послать в этот порт номер требуемого регистра, а  затем
       значение этого регистра. Порт  автоматически  переключается между
       этими  функциями  работы с адресами и с данными.  Чтобы он  начал
       ожидать ввод адреса, надо  прочитать  его. Отдельные регистры об-
       суждаются в различных местах этой главы.
          Особый интерес представляют 16 регистров палетты с номерами от
       10H до 1FH. Каждый регистр имеет размер всего 4 бита, что как раз
       достаточно,  чтобы  хранить 16 кодовых номеров для  16  возможных
       цветов. Для каждой позиции символа или точки на экране видеобуфер
       содержит  данные, указывающие каким цветом должен выводиться этот
       объект.  Эту информацию называют данными атрибутов.  В отличие от
       цветного графического адаптора PCjr не использует данные  атрибу-
       тов для непосредственного  определения цвета, который будет выво-
       диться.   Вместо этого данные атрибутов являются  указателями  на
       один из 16 регистров палетты,  а  число,  содержащееся в этом ре-
       гистре,  определяет каким цветом будет выводиться данный  символ.
       При таком методе, программе  нужно  изменить только установку ре-
       гистра палетты, и все символы или точки с соответствующим атрибу-
       том изменят свой цвет. Регистры палетты работают во всех режимах,
       как текстовых, так и графических.
          EGA  распределяет  эти функции между  микросхемой  контроллера
       атрибутов (адрес порта 3C0H)  и  двумя  микросхемами  контроллера
       графики (адреса портов 3CCH-3CFH).  Контроллер атрибутов содержит
       16 регистров палетты EGA,  пронумерованных  от 00 до 0FH. Эти ре-
       гистры  могут содержать 6-битные коды цветов, когда EGA связан  с
       улучшенным  цветным  дисплеем,  поэтому  могут  быть использованы
       любые 16 цветов из набора 64-х. В [4.4.1] показано как программи-
       ровать регистры палетты для PCjr и EGA.

          4.1.2 Установка/проверка режима дисплея.

          Монохромный адаптор поддерживает один режим терминала, цветной
       графический - семь, PCjr -  десять,  а EGA - двенадцать.  Система
       PCjr более гибкая, чем монохромный или цветной адапторы, посколь-
       ку она предоставляет  широкий  выбор  цветов  в режимах с двумя и
       четырьмя  цветами, а также серые тени в черно-белом режиме.   EGA
       еще более сложен,  поддерживая  палетту  из 64 цветов, графику на
       монохромном дисплее и вывод в 43 строки.  Ниже приведен  перечень
       различных режимов:

          Номер         Режим                                       Адапторы

           0        40*25 (320*200) B&W алфавитноцифровой         цветной, PCjr, EGA
           1        40*25 (320*200) цветной алфавитноцифровой     цветной, PCjr, EGA
           2        80*25 (640*200) B&W алфавитноцифровой         цветной, PCjr, EGA
           3        80*25 (640*200) цветной алфавитноцифровой     цветной, PCjr, EGA
           4        320*200 4-цветная графика                     цветной, PCjr, EGA
           5        320*200 B&W графика (4 тени на PCjr)          цветной, PCjr, EGA
           6        640*200 B&W графика                           цветной, PCjr, EGA
       


                                     -98-
           7        80*25 (720*350) B&W алфавитноцифровой         монохромный, EGA
           8        160*200 16-цветный графика                    PCjr
           9        320*200 16-цветный графика                    PCjr
           A        640*200 4-цветный графика                     PCjr
           B        зарезервирован для EGA
           C        зарезервирован для EGA
           D        320*200 16-цветный графика                    EGA
           E        640*200 16-цветный графика                    EGA
           F        640*350 4-цветная графика на монохромном      EGA
          10        640*350 4- или 16-цветная графика             EGA

          EGA  разрешает иметь 8 страниц в режиме 7 - стандартном монох-
       ромном текстовом режиме. Режимы 0-6 полностью совместимы, исполь-
       зуя память одинаковым образом.  При условии, что переключатели на
       EGA установлены для работы  с  улучшенным  цветным дисплеем фирмы
       IBM,  традиционные текстовые режимы выводятся с высоким  разреше-
       нием, используя рисунок  символов,  состоящий из 8*14 точек, а не
       обычные 8*8.
          BIOS  хранит  однобайтную переменную по  адресу  0040:0049,  в
       которой  содержится  номер  текущего   режима.   Байт  по  адресу
       0040:004A дает число символов в строке в текстовом режиме.

          Высокий уровень.

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

          0   текстовый режим
          1   графический режим среднего разрешения
          2   графический режим высокого разрешения

       SCREEN 1  устанавливает  графический  режим  среднего разрешения.
       Второй параметр включает и выключает цвет. Этот параметр не имеет
       смысла для режима высокого  разрешения  на цветном адапторе, пос-
       кольку разрешен только черно-белый режим. Для текстовых режимов 0
       в качестве  второго  параметра  выключает  цвет,  а 1 - включает.
       Оператор  SCREEN  0,0 устанавливает текстовый черно-белый  режим.
       Для графического режима ситуация обратная: 0 - включает цвет, а 1
       - выключает.  Поэтому оператор SCREEN 1,1 устанавливает черно-бе-
       лый графический режим среднего разрешения.

          Все режимы первоначально показываются  черно-белыми.  Оператор
       COLOR  (см.   [4.1.3]) должен быть использован,  чтобы  закрасить
       экран фоновым цветом. В графическом режиме одного оператора COLOR
       достаточно,  чтобы изменить весь фон на указанный цвет.   Но  для
       текстового режима Вы  должны  после  оператора COLOR использовать
       оператор CLS.
          В  текстовых  режимах в строке может быть 40 или 80  символов.
       Для установки требуемого числа  символов  в строке надо использо-
       вать оператор WIDTH.  WIDTH 40 дает 40 символов в строке, а WIDTH
       80 - 80. Другие значения недопустимы. Если оператор WIDTH исполь-
       зуется в графическом режиме (SCREEN 1 или SCREEN 2), то WIDTH  40
       переводит экран в режим среднего разрешения, а WIDTH 80 - в режим
       высокого разрешения. Вот несколько примеров:

       100 SCREEN 0,1: WIDTH 40  'цветной текстовый режим с 40 символами

       100 SCREEN 0,1: WIDTH 40  'цветной дисплей как монохромный

       


                                     -99-
       100 SCREEN 0,1: WIDTH 40  'цветная графика среднего разрешения
        .
        .
       500 WIDTH 80              'переводим в режим высокого разрешения

          Монохромный монитор может быть переведен в режим 40 символов в
       строке операторами SCREEN 0: WIDTH 40.  Для восстановления режима
       с  80  символами введите WIDTH 80.  В режиме с 40  символами  они
       сохраняют свою обычную ширину, поэтому будет использоваться толь-
       ко левая часть экрана.  Строка переносится после 40-го столбца  и
       невозможно поместить курсор  в  правую  половину экрана с помощью
       оператора  LOCATE.  CLS чистит только левую часть экрана.  Трудно
       представить программу, которая  использовала  бы это свойство, но
       оно  действительно  позволяет программе принимать  ввод  (скажем,
       через оператор INPUT), в  то  время  как  пользователь продолжает
       печатать в левой половине экрана, оставляя правую половину экрана
       для возможной корректировки  вводимой  информации. При этом любой
       вывод  в правую половину экрана возможен только прямого обращения
       к памяти дисплея, как объяснено в [4.3.1].
          PCjr использует в Бейсике 7 номеров режимов:

          Номер                   Режим

            0       текстовый режим, ширина может быть 40 или 80
            1       4-цветная графика среднего разрешения
            2       2-цветная графика высокого разрешения
            3       16-цветная графика низкого разрешения
            4       4-цветный режим среднего разрешения
            5       16-цветный режим среднего разрешения
            6       4-цветная режим высокого разрешения

          Последние  четыре режима требуют дискетты с Бейсиком.   Размер
       страницы определяет количество памяти, требуемое для одного экра-
       на (дисплейные страницы обсуждаются в [4.5.3]).  Программа должна
       отвести соответствующее  количество памяти перед установкой режи-
       ма.   Это делается оператором CLEAR.  За оператором CLEAR  должны
       следовать три  числа,  определяющие  отводимую  память, третье из
       этих чисел устанавливает размер видеобуфера (первые два параметра
       обсуждаются в  [1.3.1]).   Например,  размер для видеобуфера 16K,
       устанавливаемый  по умолчанию, выделяется командой CLEAR ,,16384.
       К сожалению, размер видеобуфера указывается  в байтах, поэтому он
       не  равен  круглому числу типа 4000 или 32000, а равен  4096  или
       32768.  Помните, что 2K =  2^11,  4K  = 2^12, 16K = 2^14, а 32K =
       2^15.  Для выделения трех страниц по 16K, введите CLEAR ,,3*2^14.
       Этот оператор должен помещаться  в  самом  начале программы, пос-
       кольку при использовании оператора CLEAR все переменные  очищают-
       ся. Отметим также, что при  создании нескольких страниц, страница
       0 начинается с младших адресов памяти.

          К  моменту  выхода этой книги Бейсик не поддерживает  дополни-
       тельные режимы терминала EGA. В [4.3.3] приведена подпрограмма на
       машинном языке, которая позволит Вам установить эти режимы.

          Средний уровень.

          Функция  0  прерывания 10H устанавливает режим дисплея.  В  AL
       должен находиться номер режима от  0 до A. Чтобы установить цвет-
       ной графический режим среднего разрешения надо:

          MOV  AH,0       ;номер функции
          MOV  AL,4       ;номер требуемого режима
          INT  10H        ;устанавливаем режим

       


                                     -100-
       Для  определения текущего графического режима  надо  использовать
       функцию F прерывания  10H.  Прерывание  возвращает номер режима в
       AL.   Оно также дает номер текущей страницы дисплея в BH и  число
       символов в строке в AH.

          MOV  AH,0FH          ;номер функции
          INT  10H             ;получение информации о режиме дисплея
          MOV  MODE_NUMBER,AL  ;номер режима в AL
          MOV  NUMBER_COLS,AH  ;число символов в строке в AH
          MOV  CURRENT_PAGE,BH ;номер текущей страницы в BH

          MS DOS обеспечивает также Esc-последовательности для установки
       и сброса режимов дисплея.  Для этого необходимо, чтобы Вы предва-
       рительно загрузили драйвер  ANSI.SYS,  как объяснено в приложении
       Д.   Управляющая строка имеет вид ESC [=#h, где # - номер режима,
       указанный как код ASCII, а  ESC  обозначает  один  символ с кодом
       ASCII 27. Например:

       ;---в сегменте данных
       MED_RES_COLOR  DB   27, '[=4h$'
       MED_RES_B&W    DB   27, '[=5h$'
       ;---установка цветного графического режима среднего разрешения
          MOV  AH,9             ;номер функции вывода строки
          LEA  DX,MED_RES_COLOR ;DS:DX должны указывать на строку
          INT  21H              ;изменение режима

          Низкий уровень.

          В  данном  пункте цветной адаптор, монохромный адаптор и  PCjr
       рассматриваются отдельно,  поскольку  они существенно отличаются.
       Цветной  графический адаптор имеет регистр, который устанавливает
       режим дисплея. Он расположен в порте с адресом 3D8H. Биты 0, 1, 2
       и  4 хранят установку.  Бит 0 устанавливает 40 символов в строке,
       когда он равен 0 и 80 - когда равен  1. Бит 1 устанавливает дисп-
       лей в текстовый режим, когда равен 0 и в графический, когда равен
       1.  Бит 2 устанавливает цветной режим,  когда равен 0 и черно-бе-
       лый, когда равен 1. И, наконец, бит 4 устанавливает для графичес-
       кого режима среднее разрешение, когда  равен 0 и высокое разреше-
       ние,  когда равен 1 (бит 2 должен быть равен 1).  Ниже  приведены
       возможные комбинации:

          Режим                      биты:  5  4  3  2  1  0

       0. 40*25, черно-белый, текст         1  0  1  1  0  0
       1. 40*25, цветной, текст             1  0  1  0  0  0
       2. 80*25, черно-белый, текст         1  0  1  1  0  1
       3. 80*25, цветной, текст             1  0  1  0  0  1
       4. 320*200, черно-белый, графика     0  0  1  1  1  0
       5. 320*200, цветной, графика         0  0  1  0  1  0
       6. 640*200, черно-белый, графика     0  1  1  1  1  0
                                            │  │  │  │  │  текст 80*25
                                            │  │  │  │  графика 320*200
                                            │  │  │  черно-белый
                                            │  │  разрешение вывода
                                            │  графика 640*200
                                            мигание

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


                                     -101-
       вечают за установку режима.   Когда бит 5 сброшен в 0, то он зап-
       рещает  атрибут мигания символов; в этом случае, если старший бит
       байта атрибутов установлен,  то  это  приводит  к выводу фонового
       цвета высокой интенсивностью (см.  пример в [4.1.3]). Бит 3 этого
       регистра управляет разрешением вывода.  Когда он равен 0, то весь
       экран  закрашивается  в цвет рамки, но видеобуфер  не  очищается.
       Вывод мгновенно возвращается, когда  значение этого бита меняется
       на 1. Это свойство полезно использовать для избежания интерферен-
       ции экрана при сдвигах [4.5.1].  Некоторые утилиты используют это
       свойство  для того, чтобы зря не утомлять фосфорное покрытие тру-
       бки терминала, когда компьютер включен, но не используется. Отме-
       тим также, что два старших бита регистра не используются.
          Монохромный  адаптор имеет соответствующий адрес  порта  3B8H.
       Имеют значение только три бита.  Бит 0 устанавливает высокое раз-
       решение,  которое  является единственным допустимым  режимом  для
       монохромного дисплея. Если этот  бит равен 0, то компьютер перес-
       тает работать. Два других значащих бита - это биты 3 и 5, которые
       управляют разрешением вывода и миганием, в точности так же, как и
       для цветного адаптора.
          PCjr  распределяет информацию, содержащуюся в одном порте  для
       монохромног и цветного адаптора.   Массив ворот дисплея имеет два
       регистра режима, номера 0 и 3.  Для доступа к этим регистрам надо
       послать номер регистра в порт  с  адресом  3DAH, а затем записать
       данные  по  тому же адресу (чтение этого порта обеспечивает,  что
       первая запись в него будет  воспринята,  как указание номера тре-
       буемого регистра). Вот значение битов этих регистров:

       Регистр 0:
          бит 0   1 = текст, 80*25 и режимы 5 и 6, иначе 0
              0   1 = графический режим, 0 = текстовый
              0   1 = запрет цветов, 0 = разрешение цветов
              0   1 = разрешение вывода, 0 = запрет вывода
              0   1 = 16-цветный режим, 0 = все остальные режимы

       Регистр 3:
          бит 0   всегда 0
              1   1 = разрешение мигания, 0 = 16 фоновых цветов
              2   всегда 0
              3   1 = 2-цветная графика, 0 = все остальные режимы

       Как  и в двух предыдущих случаях, не стоит устанавливать эти  ре-
       гистры прямо из программы,  так  как  нужно  еще много работы для
       программирования  микросхемы  6845.  Но каждый из этих  регистров
       содержит бит, который иногда  приходится  программно  модифициро-
       вать, а поскольку эти регистры только для записи, то Вам  необхо-
       димо понимать значение всех их битов.   Эти биты - бит разрешения
       вывода  в  регистре 0 и бит разрешения мигания в регистре 3.   Их
       действие было описано ранее и  возможное  их использование еще не
       раз будет обсуждаться в этой главе (в [4.5.1] и [4.1.3]).
          EGA  имеет  два регистра, управляющих режимом  дисплея.   Один
       имеет адрес порта 3D5H.  Этот регистр не содержит ни одного бита,
       связанного  с  чем-либо другим, поэтому нет никаких причин  обра-
       щаться к нему.  Второй регистр  имеет адрес порта 3C0H и содержит
       бит, который выбирает будет ли бит 7 байта атрибутов соответство-
       вать миганию или высокой интенсивности. Этот вопрос обсуждается в
       [4.1.3].

          4.1.3 Установка атрибутов/цветов символов.

          Когда  дисплей  установлен в текстовый режим в любой из  видео
       систем, то каждой позиции  символа  на экране отводится два байта
       памяти.   Первый байт содержит номер кода ASCII кода  символа,  а
       второй - атрибуты символа.  Цветной адаптор и PCjr могут выводить
       


                                     -102-
       в  цвете,  как сам символ, так и всю область, отведенную  данному
       символу (фоновый  цвет).   Монохромный  адаптор  ограничен только
       черным  и  белым цветом, но он  может  генерировать  подчеркнутые
       символы, чего не могут делать  цветной  адаптор  и PCjr.  Все три
       системы могут выдавать мигающие символы и негативное изображение.
       Все три системы могут также создавать символы с высокой интенсив-
       ностью,  хотя для цветного адаптора и PCjr  повышенная  интенсив-
       ность символа на  самом  деле  приводит  к  другому цвету (восемь
       основных  цветов  имеют версии с повышенной  интенсивностью,  что
       дает набор 16 цветов).  EGA  умеет  делать все, что могут все ос-
       тальные системы и многое другое. В частности, на улучшенном дисп-
       лее он может  выводить  подчеркнутые  цветные  символы, поскольку
       матрица изображения символов 8*14 дает такую возможность.

          Атрибуты цвета:
          Для  указания цветов экрана одни и те же номера кодов  исполь-
       зуются в Бейсике и прерываниями операционной системы. Они такие:

                 0 - черный                  8 - серый
                 1 - синий                   9 - голубой
                 2 - зеленый                10 - светлозеленый
                 3 - циан                   11 - светлый циан
                 4 - красный                12 - светлокрасный
                 5 - магента                13 - светлая магента
                 6 - коричневый             14 - желтый
                 7 - белый                  15 - яркобелый

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

       когда бит 0 = 1, синий включается в основной цвет
                 1 = 1, зеленый включается в основной цвет
                 2 = 1, красный включается в основной цвет
                 3 = 1, символ выводится с высокой интенсивностью
                 4 = 1, синий включается в фоновый цвет
                 5 = 1, зеленый включается в фоновый цвет
                 6 = 1, красный включается в фоновый цвет
                 7 = 1, символы мигают

          Биты  0-2  и 4-6 содержат одни и те же компоненты  цветов  для
       самих символов и фона.  Эти трехбитные группы позволяют 8 возмож-
       ных  комбинаций.  Когда включается бит высокой интенсивности,  то
       добавляются еще 8 цветов. Шестнадцать возможных цветов получаются
       из этих установок битов следующим образом:

          Красный  Зеленый  Синий  Низкая интенсивность  Высокая

             0        0       0        черный            серый
             0        0       1        синий             светлосиний
             0        1       0        зеленый           светлозеленый
             0        1       1        циан              светлый циан
             1        0       0        красный           светлокрасный
             1        0       1        магента           светлая магента
             1        1       0        коричневый        желтый
             1        1       1        белый             яркобелый

       Можно  иметь 16 цветов и для фонового цвета.  В этом случае бит 7
       должен служить указателем  высокой  интенсивности  для фона, а не
       указателем мигания символов.  Для цветного адаптора надо изменить
       бит 5 порта с адресом 3D8H в 0, как показано ниже. Поскольку этот
       порт  доступен  только для записи, то все остальные  биты  должны
       


                                     -103-
       быть  переустановлены.  Эта  возможность  доступна  только в двух
       случаях:  текстовых режимов с 40 и с 80 символами в строке.   Для
       режима с 80 символами надо послать в порт число 9, а для режима с
       40  символами  - число 8.  Чтобы вернуть мигание надо добавить  к
       обоим этим значениям 32. Для PCjr надо сбросить в 0 бит 1 регист-
       ра 3 массива ворот дисплея.  Все остальные биты должны быть равны
       нулю, кроме номера 3, который  должен  быть установлен для режима
       двухцветной графики. Кроме этого режима, для установки бита мига-
       ния надо сначала прочитать порт с адресом 3DAH, чтобы подготовить
       массив  ворот дисплея, затем послать в него 3, чтобы указать  ре-
       гистр, и затем послать 0, чтобы  установить  бит мигания. При за-
       вершении  программы всегда надо восстанавливать мигание, так  как
       следующая программа может полагаться на это.
          EGA также может  разрешать/запрещать мигание, хотя в этом слу-
       чае  адрес  порта 3C0H.  Сначал надо прочитать порт  3DAH,  чтобы
       получить доступ к адресному регистру в 3C0H. затем надо послать в
       3C0H  10H, чтобы указать соответствующий регистр.  Наконец,  надо
       послать данные по тому же адресу.   Поскольку этот регистр только
       для записи, то все биты должны быть правильно установлены.  Мига-
       ние включается установкой  бита  3,  а  выключается сбросом этого
       бита.   Все остальные биты в цветном текстовом режиме должны быть
       равны 0.
          Для цветного адаптора, когда  символы  выводятся  на дисплей в
       цветном графическом режиме, то они изображаются в текущем фоновом
       цвете.  Операторы, которые выводят на экран, как в Бейсике, так и
       в  MS DOS (прерывание 21H) ограничены выводом символов в  третьем
       цвете используемой палетты  (имеются две палетты из трех цветов -
       см.  [4.4.1]). В палетте 0 символы желтые/коричневые, а в палетте
       1 они белые.  Процедуры  вывода  символов  BIOS (прерывание 10H),
       однако,  могут  указать любой из трех цветов палетты.   С  другой
       стороны, для PCjr, цвет назначенный  определенной позиции палетты
       может быть изменен, поэтому для вывода символов могут использова-
       ны любые цвета.
          Для  PCjr  цвета соответствующие данным кодовым номерам  могут
       быть изменены.  Каждый кодовый номер связан с регистром палетты в
       массиве ворот дисплея [4.1.1].  Эти регистры пронумерованы от 10H
       до 1FH, что соответствует кодам  от  0 до 15. Каждый 4-битный ре-
       гистр  содержит  число  в диапазоне  0-15,  которое  представляет
       реальный цвет, выводимый когда  оператор программы встречает один
       из  кодовых номеров.  Например, если в каком-то  месте  программы
       указано, что символ  должен  выводиться  с  кодовым номером 0, то
       цвет  выводимого символа определяется кодом цвета,  хранящемся  в
       регистре палетты 0. Начальное  значение этого регистра 0000, поэ-
       тому будет выводиться черный цвет.  Но содержимое этого  регистра
       может быть изменено,  скажем,  на  0001,  а в этом случае кодовый
       номер  0 приведен к выводу синим цветом.  Кодовые номера, исполь-
       зуемые в регистрах палетты такие же,  как и в операторах програм-
       мы.  На рис.  4-1 показана начальная установка регистров  палетты
       для всех регистров,  кроме  регистра  для зеленого цвета, который
       изменен так, чтобы выводился цвет магента.
          Чтобы  запрограммировать  регистр палетты PCjr  нужно  сначала
       послать его номер (от 10H до  1FH)  в массив ворот дисплея, адрес
       порта которого 3DAH.  Затем нужно послать данные по тому же адре-
       су. Чтобы быть уверенным, что  массив готов принять номер регист-
       ра,  а не данные, надо сначала прочитать из порта 3DAH,  отбросив
       прочитанное.
          EGA также использует 16 регистров  палетты.  Они расположены в
       порте  с  номером 3C0H, а номера их меняются от 00 до 0FH.   Надо
       сначала прочитать из порта  3DAH,  чтобы  переключить порт на его
       адресный регистр, затем послать номер регистра палетты в 3C0H,  а
       затем послать данные. Когда  переключатели  на EGA установлены на
       улучшенный  режим (для улучшенного цветного дисплея IBM), то  па-
       летта может быть выбрана из 64  цветов.  В  этом случае установка
       


                                     -104-
       регистра  палетты имеет длину 6 битов в формате  R'G'B'RGB.  Биты
       RGB дают темные цветы, а биты R'G'B'  - цвета повышенной яркости.
       Когда  установлены  и R' и R, например, то это  приводит к  очень
       яркому красному цвету.  Биты могут смешиваться давая новые оттен-
       ки. Если регистры палетты, предназначенные для 64 цветов, исполь-
       зуются не в улучшенном режиме, то 4-й и 5-й биты регистра игнори-
       руются  и  содержимое регистров рассматривается по обычной  схеме
       RGB.  Поскольку PCjr и EGA  используют регистры палетты, то выбор
       фонового цвета не ограничен использованием бита 7 байта атрибутов
       в качестве бита мигания.

       Монохромные символы:

          Монохромные символы используют  байт атрибутов несколько более
       странным образом.  Как и с атрибутами цвета, биты 0-2  устанавли-
       вают основной цвет, а биты 4-6  -  фоновый.  Эти цвета могут быть
       только белым и черным, со следующим соответствием битам:

          Бит      Бит     Бит     Основной атрибут        Фоновый
        6 или 2  5 или 1 4 или 0

           0        0       0      черный                  черный
           0        0       1      подчеркнутый белый      белый
           0        1       0      белый                   белый
           0        1       1      белый                   белый
           1        0       0      белый                   белый
           1        0       1      белый                   белый
           1        1       0      белый                   белый
           1        1       1      белый                   белый

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

          Атрибут                 Цепочка битов       Гекс    10-ное

          нормальный                00000111            7         7
          интенсивный               00001111            F        15
          нормальный подчеркнутый   00000001            1         1
          интенсивный подчеркнутый  00001001            9         9
          негативный                01110000           70       112
          нормальный мигающий       10000111           87       135
          интенсивный мигающий      10001111           8F       143
          нормальный мигающий подч. 10000001           81       129
          яркий мигающий подчерк.   10001001           89       137
          яркий негативный          11110000           F0       240

          Высокий уровень.

          Бейсик  устанавливает  цвета  и атрибуты  символов  оператором
       COLOR.  Все операторы PRINT  и  WRITE,  которые следуют за данным
       оператором  COLOR,  выполняются с атрибутами,  указанными в  этом
       операторе. Цвет фона меняется только для выодимых символов, но не
       для всего экрана.  Новый оператор COLOR не влияет на то, что было
       выведено ранее.
          Кроме случая монохромного  адаптора,  COLOR  3,4 устанавливает
       


                                     -105-
       основной цвет символа циан (#3), а фоновый - красный (#4). Диапа-
       зон кодов основных цветов 0-31,  причем  числа 0-15 соответствуют
       цветам,  перечисленным  в вышеприведенной таблице, а числа  16-31
       получаются прибавлением к любому из этих кодов числа 16, что дает
       тот же самый цвет, но с миганием символов.  (При мигании основной
       цвет периодически меняется  на  фоновый,  в  то время как фоновый
       цвет остается неизменным.)
          Операторы PRINT и WRITE могут также выводить символы на графи-
       ческий экран.  При этом  цвет  символов  - это всегда третий цвет
       текущей палетты, т.е.  желтый/коричневый для палетты 0 и белый  -
       для палетты 1.
          Отметим, что когда Вы  начинаете  работать в цветном текстовом
       режиме,  то весь экран черно-белый.  Чтобы закрасить весь экран в
       фоновый цвет, необходимо  указать  оператором COLOR ,2, например,
       зеленый цвет и затем очистить экран командой CLS. Когда Вы чисти-
       те экран по ходу выполнения  программы, то необходимо, чтобы пос-
       ледний  оператор  COLOR  установил фоновый цвет таким,  каким  Вы
       хотите закрасить весь экран.
          Для монохромного дисплея атрибуты  устанавливаются аналогичным
       образом.   0  соответствует черному цвету, а любое из  чисел  1-7
       соответствует белому. Таким  образом COLOR 0,7 устанавливает чер-
       ное изображение на белом фоне (негатив), в то время как COLOR 7,0
       дает вывод белых символов  на  черном  фоне  (обычная установка).
       Имеется одно исключение: если в качестве основного цвета  исполь-
       зовать код 1, то будут выводиться подчеркнутые символы.  Прибавив
       8  к любому из кодов основного цвета, получим яркое  изображение.
       Прибавив 16 к любому из  кодов  0-15,  получим  мигающие символы.
       Таким  образом  7+8+16=31 дает яркое мигающее белое  изображение.
       Для фонового цвета допустимы только значения от 0 до 7.
          Если Вы используете прямое  отображение  в  память [4.3.1], то
       оператор COLOR не влияет на вывод. Вместо этого Вы должны выбрать
       требуемую установку атрибутов из таблиц  и прямо присвоить значе-
       ние  соответствующего байта атрибутов оператором POKE.   Помните,
       что байты атрибутов всегда занимают нечетные позиции в видеобуфе-
       ре.  Отображение в память позволяет Вам иметь 16 фоновых цветов в
       Бейсике (при условии, что  Вам  не  нужны  мигающие символы). Для
       графического  адаптора  введите  OUT &H3D8,8, чтобы  старший  бит
       каждого атрибута действовал как бит яркости для фоновых цветов. В
       следующем  примере в центре экрана печатается яркокрасный "!"  на
       светлокрасном фоне.

       100 DEF SEG = &HB800   'указываем на буфер цветного дисплея
       110 OUT &H3D8,8        'используем 16 фоновых цветов
       120 POKE 1000,33       'печатаем ! в центре экрана
       130 POKE 1001,196      'красный на светлокрасном (11000100)

       Как уже говорилось выше PCjr  хранит  бит мигания в массиве ворот
       дисплея. Вот та же программа для PCjr (но она не будет работать в
       режиме двухцветной графики):

       100 DEF SEG = &HB800   'указываем на видеобуфер
       110 X = INP(&H3AH)     'читаем из массива ворот дисплея
       120 OUT &H3AH,3        'требуем доступ к регистру 3
       130 OUT &H3AH,0        'сбрасываем все биты этого регистра
       140 POKE 1000,33       'печатаем ! в центре экрана
       150 POKE 1001,196      'красный на светлокрасном (11000100)

       Приведем еще пример изменения  назначения цвета регистра палетты.
       Код  цвета, который обычно выводится синим (0001) сделаем,  чтобы
       он выводил цвет  магента  (0101).   Номер  регистра массива ворот
       дисплея, соответствующий коду цвета 1 равен 11H.

       100 X = INP (&H3AH)    'читаем из массива ворот дисплея
       


                                     -106-
       110 OUT &H3AH,&H11     'требуем доступ к регистру 11H
       120 OUT &H3AH,5        'помещаем туда код магенты (0101 = 5)

          Средний уровень.

          Прерывания  DOS и BIOS предоставляют очень бедные  возможности
       для работы с цветным  текстом.  Только  функция  9 прерывания 10H
       принимает байт атрибутов при выводе символа. Функция A прерывания
       10H выводит символ без указания  цвета  или  атрибута; она просто
       помещает  символ  в видеобуфер, не трогая  байт  атрибута,  таким
       образом атрибуты сохраняют свое старое значение. Функция D преры-
       вания  10H  также оставляет нетронутым байт атрибутов.   Все  эти
       функции обсуждаются в [4.3.1].
          Функции вывода на экран  DOS  прерывания  21H  всегда  выводят
       белое на черном.  Даже если для всего экрана установлен некоторый
       фоновый цвет, то функции DOS  устанавливают  атрибут в нормальный
       черный при выводе каждого символа.  Однако имеется способ преодо-
       леть это ограничение. MS  DOS  предоставляет  драйвер  устройства
       ANSI.SYS, который может интерпретировать специальные Esc-последо-
       вательности. В приложении Д объясняются основы его использования.
       Esc-последовательности выводятся через функцию 9 прерывания  21H,
       которые обычно выводят строку  символов  на экран.  В этом случае
       строка состоит из символа Esc, за которым следует [, а далее одно
       или более кодовых чисел из нижеприведенного списка. Строка должна
       кончаться символом m и обычным ограничителем $. Вот кодовые номе-
       ра:

          0   все атрибуты выключены (черный на белом)
          1   включена повышенная интенсивность
          4   включено подчеркивание
          5   включено мигание
          7   включено негативное изображение
          8   все включено (при этом символы невидимы)

          30 черный основной цвет         40 черный фон
          31 красный основной цвет        41 красный фон
          32 зеленый основной цвет        42 зеленый фон
          33 желтый основной цвет         43 желтый фон
          34 синий основной цвет          44 синий фон
          35 основной цвет магента        45 фон магента
          36 основной цвет циан           46 фон циан
          37 белый основной цвет          40 белый фон

       Отметим, что когда функции MS  DOS  выводят символы в графическом
       режиме, то они обычно используют код 3 текущей палетты. С помощью
       Esc-последовательностей можно установить  цвет символа соответст-
       вующим  любому  из цветов палетты.  Надо указывать 30 или 31  для
       фонового цвета, 32 или 33 - для кода  1, 34 или 35 - для кода 2 и
       36  или 37 - для кода 3.  В этом случае не надо указывать фоновый
       цвет.
          В следующем примере на экран  выводятся  две  строки с помощью
       функции  9 прерывания 21H.  Первая выводится синим на красном,  а
       вторая - мигающим цианом на красном. Не надо переопределять крас-
       ный в качестве фонового цвета для второй строки, поскольку назна-
       чения цветов действуют на все последующие команды вывода (включая
       функции  BIOS прерывания 10H), до тех пор, пока не будут  сделаны
       другие назначения. Отметим, как  просто  перемешивать команды уп-
       равления цветом с выводом самих строк.

       ;---в сегменте данных
       STRING_1     DB   'The rain in Spain',0AH,0DH,'$'
       STRING_2     DB   'Falls mainly on the plain$'
       BLUE_RED     DB   27,'[34;41m$'
       


                                     -107-
       BLINK_CYAN   DB   27,'[5;36m$'
       ;---вывод строк
          MOV  AH,9          ;функция вывода строки
          LEA  DX,BLUE_RED   ;адрес управляющей строки в DX
          INT  21H           ;все будет выдаваться синим на красном
          LEA  DX,STRING_1   ;указываем на первую строку
          INT  21H           ;печатаем строку
          LEA  DX,BLINK_CYAN ;адрес второй управляющей строки
          INT  21H           ;меняем цвет на мигающий циан
          LEA  DX,STRING_2   ;указываем на вторую строку
          INT  21H           ;печатаем строку

       Вы  всегда  должны позаботиться о том,  чтобы  сбросить  атрибуты
       цвета в нормальное  состояние  перед  завершением программы, пос-
       кольку в противном случае они будут действовать и на вывод после-
       дующих программ. В конце следует  вывести Esc-последовательность,
       использующую код номер 0, как указано выше.
          PCjr и EGA имеют специальную функцию BIOS для установки содер-
       жимого регистров палетты. Это подфункция 0 функции 10H прерывания
       10H.  Надо поместить номер регистра палетты (от 0 до 15) в BL,  а
       значение кода цвета (также от  0  до  15) в BH, а затем выполнить
       прерывание.  Подфункция 2 функции 10H устанавливает все  регистры
       палетты, а также цвет  границы,  используя  17-байтный массив, на
       который должны указывать ES:DX.  Байты 0-15 массива помещаются  в
       регистры палетты 0-15, а байт  16  устанавливает цвет границы.  О
       том, как отдельно установить цвет границы см. [4.1.4].

          Низкий уровень.

          Как  уже объяснялось в разделе "Высокий уровень", надо  просто
       поместить требуемое значение байта атрибутов в видеобуфер, за тем
       символом,  к которому эти атрибуты должны  относиться.   Приведен
       пример для цветного адаптора или PCjr.  В примере устанавливается
       текстовый  экран 80*25 с 16 фоновыми цветами, а затем экран  ини-
       циализируется в красный цвет светлосинем фоне:

       ;---установка 16 фоновых цветов в текстовом режиме 80*25
               MOV  AL,00001001B   ;установка в 0 бита мигания
               MOV  DX,3D8H        ;адрес регистра
               OUT  DX,AL          ;посылаем в регистр
       ;---инициализируем весь экран в красный на светлосинем фоне
               MOV  AX,0B800H      ;указываем на видеобуфер
               MOV  ES,AX          ;
               MOV  CX,2000        ;записываем атрибут в 2000 ячеек
               MOV  BX,1           ;BX указывает на байт атрибутов
               MOV  AL,10010100B   ;значение байта атрибутов
       NEXT_CHAR:   MOV  ES:[BX],AL   ;посылаем атрибуты в буфер
               INC  BX             ;увеличиваем указатель на атрибуты
               INC  BX             ;
               LOOP NEXT_CHAR      ;пишем в следующую позицию

          4.1.4 Установка цвета границы экрана.

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

       


                                     -108-
          Высокий уровень.

          Третий  параметр  оператора Бейсика COLOR  устанавливает  цвет
       границы.  Используются те же  самые кодовые номера цветов, приве-
       денные в [4.1.3].  Например, для установки границы в  светлосиний
       цвет, надо написать COLOR  ,,8.  PCjr  кроме  того может изменять
       цвет, за счет изменения установки регистра палетты, соответствую-
       щего коду цвета, указанного для цвета  границы. Полное объяснение
       см. в [4.1.3].

          Средний уровень.

          Для всех видеосистем фоновый цвет может быть установлен  функ-
       цией BH, прерывания 10H. Эта функция устанавливает также основные
       цвета. Чтобы указать, что надо изменить фоновый цвет, надо помес-
       тить 0 в BH, а код цвета в BL и выполнить прерывание. Кроме того,
       PCjr  и EGA имеют специальную функцию для установки фонового цве-
       та.  Это подфункция 1 функции 10H прерывания 10H.  Надо поместить
       10H  в AH, 1 в AL и код цвета в BH.  Никаких значений не  возвра-
       щается.

          Низкий уровень.

          Для цветного  графического  адаптора  биты 0-3 порта 3D9H (Ре-
       гистр  выбора цвета) устанавливают цвет границы, когда экран  на-
       ходмтся в текстовом режиме. Как обычно, назначение битов в восхо-
       дящем  порядке - синий (B), зеленый (G), красный (R) и  интенсив-
       ность. Поскольку этот адрес  предназначен  только для записи, все
       остальные биты этого регистра должны быть правильно  установлены.
       Это бит 4, который, если его установить в 1, приводит к тому, что
       все фоновые цвета будут выводиться с высокой интенсивностью.

       ;---установка светлосинего цвета границы
          MOV  AL,00001001B   ;атрибут светлосинего цвета
          MOV  DX,3D9H        ;адрес регистра выбора цвета
          OUT  DX,AL          ;устанавливаем цвет границы

          Для  PCjr массив ворот дисплея [4.1.1] имеет регистр,  который
       устанавливает цвет границы. Это 4-битный регистр, причем биты 0-3
       соответствуют синему, зеленому, красному и высокой интенсивности,
       когда установлены в  1.  Для  установки  светлосинего  цвета надо
       послать  в регистр 1001.  Регистр цвета границы - это  регистр  2
       массива ворот дисплея.  Чтобы  получить  доступ  к этому регистру
       надо сначала послать 2 в порт по адресу 3DAH.  Затем надо послать
       данные по тому же адресу.  Чтобы  быть  уверенным, что микросхема
       готова  принять номер регистра, а не данные, надо сначала  прочи-
       тать из порта 3DAH. Следующий  пример  устанавливает красный цвет
       границы (бит 2 установлен).

          MOV  DX,3DAH     ;адрес порта массива ворот дисплея
          IN   AL,DX       ;чтение для подготовки микросхемы
          MOV  AL,2        ;номер требуемого регистра
          OUT  DX,AL       ;посылаем в порт
          MOV  AL,4        ;устанавливаемс только бит 2
          OUT  DX,AL       ;устанавливаем цвет границы

          Для  EGA цвет границы устанавливается  регистром  сканирования
       (overscan).  Это регистр  номер  11H  порта с адресом 3C0H.  Надо
       сначала  прочитать  этот порт, чтобы переключить его на  адресный
       регистр, затем послать туда номер 11H в качестве индекса, а затем
       послать данные. Имеют значение только младшие 4 бита данных, если
       только EGA не связан с улучшенным  цветным дисплеем IBM, а в этом
       случае имеют значение младшие 6 битов, которые устанавливают цвет
       


                                     -109-
       границы.

          4.1.5 Очистка части/всего экрана.

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

          Высокий уровень.

          Бейсик  для очистки экрана использует оператор CLS.  При  этом
       25-я строка внизу экрана  становится пустой только если был убран
       список  значений функциональных клавиш с помощью команды KEY OFF.
       Байты атрибутов устанавливаются  равными  ASCII 7. В [4.5.1] дана
       процедура  прокрутки,  которая может быть использована в  Бейсике
       для очистки окон на экране.

          Средний уровень.

          Операционная система предоставляет  несколько способов очистки
       экрана.  Какой из них Вы выберете зависит от того, какие средства
       требуются программе для достижения  других целей.  Первый метод -
       это  просто сброс режима дисплея, используя функцию 0  прерывания
       10H [4.1.2].  Для символьного  экрана  каждая позиция заполняется
       пробелом  (ASCII 32), а все атрибуты устанавливаются  нормальными
       (ASCII 7).  Обычно этот  метод  хорош  только в начале программы,
       когда  все  равно надо устанавливать режим работы  дисплея.   Для
       цветного графического  адаптора  и  PCjr  реинициализация  режима
       дисплея приводит к катавасии на экране. Этот эффект отсутствует у
       монохромного адаптора и EGA.

       ;---очистка экрана путем установки нового режима
          MOV  AH,0      ;номер функции установки режима дисплея
          MOV  AL,2      ;код режима 80*25 черно-белого
          INT  10H       ;очистка экрана

          Второй метод состоит в использовании  функций 6 и 7 прерывания
       10H,  которые сдвигают экран.  Число строк, на которое надо сдви-
       нуть экран помещается в AL  и  когда  это  число равно нулю экран
       очищается.   Прерывание позволяет сдвигать только  часть  экрана,
       поэтому таким образом  можно  очистить  отдельное окно на экране.
       Надо поместить координаты левого верхнего угла окна в CX, а коор-
       динаты правого нижнего угла в  DX  (номер строки в CH/DH, а номер
       столбца  в CL/DL).  Поместите атрибут, с которым должен чиститься
       экран в BH. Координаты отсчитываются от 0.

       ;---очистка окна между 3,4 и 13,15
          MOV  AH,6     ;используем процедуру сдвига
          MOV  AL,0     ;число строк сдвига делаем равным нулю
          MOV  BH,7     ;байт атрибутов для заполнения
          MOV  CH,3     ;строка для верхнего левого угла
          MOV  CL,4     ;столбец для левого верхнего угла
          MOV  DH,13    ;строка для нижнего левого угла
          MOV  DL,15    ;столбец для нижнего левого угла
          INT  10H      ;чистим окно

          Третий метод заключается в использовании  фукнции 9 прерывания
       10H; которая выводит символ и атрибуты столько раз, сколько  ука-
       зано в CX. Значение 2000 чистит весь экран, если курсор был уста-
       новлен  в 0,0, используя метод показанный в [4.2.1].   AH  должен
       содержать символ пробела, AL - байт атрибутов, а BH - номер стра-
       


                                     -110-
       ницы дисплея.

       ;---установка курсора в левый верхний угол экрана
          MOV  AH,2     ;функция установки курсора
          MOV  BH,0     ;номер страницы
          MOV  DX,0     ;координаты 0,0
          INT  10H      ;устанавливаем курсор
       ;---вывод символа пробела 2000 раз
          MOV  AH,9     ;номер функции
          MOV  CX,2000  ;число повторений вывода
          MOV  AL,' '   ;символ пробела в AL
          MOV  BL,7     ;атрибуты в BL
          INT  10H      ;очистка экрана

          Наконец, DOS обеспечивает очистку экрана с помощью специальных
       Esc-последовательностей,  которые  работают с драйвером ANSI.SYS.
       Основные сведения о нем приведены в приложении Д.  Эти последова-
       тельности - это строки,  начинающиеся  с символа Esc, а завершаю-
       щиеся ограничителем $. Такие строки выводятся функцией 9 прерыва-
       ния 21H, при этом DS:DX должны указывать на первый символ строки.
       DOS интерпретирует строку не выводя ее на дисплей.  Чтобы стереть
       весь экран строка должна быть  [2J.   Чтобы стереть конец строки,
       начиная от позиции курсора (включая эту позицию), строка [K.

       ;---в сегменте данных
       CLEAR_LINE   DB   27,'[K$'

       ;---очистка конца строки, начиная от позиции курсора
          MOV  AH,9          ;функция вывода строки
          LEA  DX,CLEAR_LINE ;DX должен указывать на начало строки
          INT  21H           ;стираем конец строки

          Низкий уровень.

          На  низком уровне надо просто поместить символы пробела и тре-
       буемый байт атрибутов в  память   дисплея,  используя  инструкцию
       STOSW. Вот пример для монохромного дисплея:

             MOV  AX,0B000H   ;указываем на память дисплея
             MOV  ES,AX       ;
             MOV  DI,0        ;DI указывает на начало буфера
             MOV  AL,32       ;символ пробела
             MOV  AH,7        ;нормальные атрибуты
             MOV  CX,2000     ;число повторений
       REP   STOSW            ;посылаем AX в ES:DI 2000 раз

          4.1.6 Переключение между видеоадапторами.

          Машина  может быть оснащена и монохромным и цветным адаптором,
       или одним из этих адапторов  и  EGA.  Программа  может  выбирать,
       какой из мониторов должен быть активным, изменяя значения битов 4
       и 5 в ячейке памяти  0000:0410.   Установив  оба этих бита в 1 мы
       выбираем монохромный адаптор.  Изменив установку битов 5-4 на  10
       устанавливаем графический  адаптор в режиме 80 символов в строке,
       а на 01 - 40 символов в строке.  И, наконец, изменив биты на  00,
       выбираем EGA. Во всех случаях Вы должны немедленно подать команду
       установки режима, поскольку BIOS имеет еще очень много регистров,
       которые надо изменить, прежде чем дисплей будет работать нормаль-
       но.
          Отметим,  что  хотя  операционная система не  может  управлять
       одновременно двумя мониторами, программы могут осуществлять вывод
       на оба дисплея, используя прямое отображение в память [4.3.1] для
       адресов буфера неактивного монитора.
       


                                     -111-
          Высокий уровень.

          В Бейсике надо просто использовать следующий код:

       100 'Переключение на монохромный дисплей
       110 KEY OFF: CLS
       120 WIDTH 40
       130 DEF SEG = 0
       140 M = PEEK(&H410)
       150 POKE &H410,M OR &H30
       160 WIDTH 80
       170 LOCATE,,1,12,13
       180 KEY ON

       100 'Переключение на цветной графический дисплей (80 символов)
       110 KEY OFF: CLS
       120 WIDTH 80
       130 DEF SEG = 0
       140 M = PEEK(&H410)
       150 POKE &H410,(M AND &HCF) OR &H20
       160 WIDTH 80
       170 SCREEN 0
       180 LOCATE,,1,6,7
       190 KEY ON

       100 'Переключение на EGA (80 символов)
       110 KEY OFF: CLS
       120 WIDTH 80
       130 DEF SEG = 0
       140 M = PEEK(&H410)
       150 POKE &H410,M AND &HCF
       160 WIDTH 80
       170 SCREEN 0
       180 LOCATE,,1,6,7
       190 KEY ON

       Измените команды WIDTH и  SCREEN,  чтобы  переключиться на другие
       начальные режимы дисплея.

          Низкий уровень.

          В ассемблере, как и в Бейсике, надо прямо изменить биты 4 и  5
       по адресу 0000:0410. Надо  сбросить  режим дисплея сразу вслед за
       изменением.

       ;---переключение на монохромный монитор
          SUB  AX,AX           ;обнуляем AX
          MOV  ES,AX           ;устанавливаем ES на начало памяти
          MOV  DL,ES:[410H]    ;получаем байт по адресу 0000:0410
          OR   DL,00110000B    ;устанавливаем биты 4 и 5
          MOV  ES:[410H],DL    ;возвращаем байт
          MOV  AH,0            ;фукция установки режима дисплея
          MOV  AL,0            ;монохромный режим 80*25
          INT  10H             ;устанавливаем режим

       ;---переключение на цветной монитор (40 символов)
          SUB  AX,AX           ;устанавливаем ES на начало памяти
          MOV  ES,AX           ;
          MOV  DL,ES:[410H]    ;берем байт по адресу 0000:0410
          AND  DL,11001111B    ;сбрасываем биты 4 и 5
          OR   DL,00010000B    ;устанавливаем бит 4
          MOV  ES:[410H],DL    ;возвращаем байт
          MOV  AH,0            ;функция установки режима дисплея
       


                                     -112-
          MOV  AL,1            ;цветной режим 40*25
          INT  10H             ;устанавливаем режим

       ;---переключение на EGA
          SUB  AX,AX           ;устанавливаем ES на начало памяти
          MOV  ES,AX           ;
          MOV  DL,ES:[410H]    ;берем байт по адресу 0000:0410
          AND  DL,11001111B    ;сбрасываем биты 4 и 5
          MOV  ES:[410H],DL    ;возвращаем байт
          MOV  AH,0            ;функция установки режима дисплея
          MOV  AL,1            ;цветной режим 40*25
          INT  10H             ;устанавливаем режим

                          Раздел 2. Управление курсором.

          Курсор  служит  двум целям.  Во-первых, он  служит  указателем
       места на экране, в  которое  операторы  программы  посылают  свой
       вывод. Во-вторых, он обеспечивает видимую точку отсчета на экране
       для пользователя программы.  Только для второго применения курсор
       должен быть видимым.  Когда курсор невидим (выключен), то он  все
       равно указывает на позицию  экрана.   Это  важно, поскольку любой
       вывод на экран, поддерживаемый операционной системой,  начинается
       с текущей позиции курсора.
          Курсор  генерируется  микросхемой   контроллера  дисплея 6845,
       описанной в [4.1.1]. Эта микросхема имеет регистры, устанавливаю-
       щие размер и  положение  курсора.  Микросхема  6845 делает только
       мерцающий  курсор, хотя имеются программные способы создания  не-
       мерцающего курсора  [4.2.6].  Частота  мерцания  курсора не может
       быть  изменена.  В графических режимах курсор не выводится,  хотя
       символы  позиционируются  на  экране  теми  же самыми процедурами
       установки курсора, что и в текстовых режимах.
          Когда  видеосистема  работает в режиме, допускающем  несколько
       дисплейных страниц, то  каждая  страница  имеет  свой собственный
       курсор  и  при  переключении между  страницами  восстанавливается
       позиция курсора, которую он занимал, когда было последнее обраще-
       ние к восстанавливаемой странице. Некоторые режимы дисплея позво-
       ляют иметь до 8 дисплейных  страниц  и соответствующие им позиции
       курсора  хранятся в наборе восьми 2-байтных переменных в  области
       данных BIOS, начиная с адреса  0040:0050H.   В  каждой переменной
       младший  байт содержит номер столбца, отсчитывая от 0, а  старший
       байт содержит номер строки, также  отсчитывая от 0. Когда исполь-
       зуется меньше чем 8 страниц, то используются переменные, располо-
       женные в более младших адресах памяти.

          4.2.1 Установка курсора в абсолютную позицию.

          Для курсора могут быть  установлены  абсолютные координаты или
       координаты  относительно его текущей позиции [4.2.2].  Абсолютные
       координаты могут меняться в  пределах  25  строк и 80 (иногда 40)
       столбцов.   Языки высокого уровня обычно  отсчитывают  координаты
       экрана, начиная с 1, и таким образом позиция левого верхнего угла
       1,1.   Язык  ассемблера всегда начинает отсчет с  нуля и  позиция
       левого верхнего угла 0,0.

          Высокий уровень.

          Бейсик нумерует строки от 1 до 25, а столбцы от 1 до 80.  Фор-
       мат  оператора LOCATE, который устанавливает позицию курсора  та-
       кой: LOCATE строка,столбец.   Если установки курсора не делается,
       то  он  переходит  в первую позицию строки после  ввода  возврата
       каретки, а сдвиг экрана начинается после того, как будет заполне-
       на  24-я строка.  Чтобы вывести в 25-ю строку Вы должны использо-
       вать LOCATE  (предварительно  очистив  эту  строку  с помощью KEY
       


                                     -113-
       OFF).  Для отмены автоматического сдвига экрана в строках 24 и 25
       надо  завершать  оператор PRINT точкой с запятой (чтобы  отменить
       сдвиг в позициях 24,80 и 25,80 надо использовать прямое отображе-
       ние в память [4.3.1]).  Ниже приведен пример рисования вертикаль-
       ной черты с помощью одного  из  символов  псевдографики  в центре
       экрана.

       100 FOR N = 1 TO 25     'повтор для каждой строки
       110 LOCATE N,40         'установка курсора в середину строки
       120 PRINT CHR$(186);    'печатаем вертикальную черту
       130 NEXT                'переход к следующей строке

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

          Средний уровень.

          Операционная  система предоставляет два способа позиционирова-
       ния курсора в абсолютную позицию на  экране. Функция 2 прерывания
       10H устанавливает курсор, относящийся к указанной странице  памя-
       ти. Страницы нумеруются начиная с нуля и для монохромного дисплея
       номер  страницы  (находящийся в BH) должен всегда быть равным  0.
       DH:DL содержат строку  и  столбец,  которые  тоже нумеруются с 0.
       Курсор  меняет  свое  положение на экране только  если  установка
       курсора относится к текущей активной странице.

       ;---установка курсора в строку 13, столбец 39
          MOV  AH,2        ;номер функции
          MOV  BH,0        ;номер страницы
          MOV  DH,13       ;строка
          MOV  DL,39       ;столбец
          INT  10H         ;позиционируем курсор

          Второй метод позиционирования курсора  состоит в использовании
       специального  драйвера устройства ANSI.SYS, который  должен  быть
       загружен при старте  системы.  В  приложении  Д  даны необходимые
       сведения.   Для вывода строки, содержащей информацию о  строке  и
       столбце используется функция 9 прерывания 21H.  Строка начинается
       с символа Esc (ASCII 27), а завершается символом ограничителем $.
       Формат строки  Esc[строка,столбецH$,  где  строка и столбец нуме-
       руются  от нуля, а Esc обозначает код ASCII 27.  Например, строка
       27,'10;60H$' устанавливает курсор в строку 10, столбец 60.
          Хотя такой метод кажется  излишне  сложным,  но он оказывается
       очень удобным при выводе ряда строк на экран, так как  Esc-после-
       довательность обрабатывается как  одна из строк набора.  В данном
       примере три строки сообщения разбросаны по всему экрану.

       ;---в сегменте данных
       POSITION_1   DB   27,'[10;30H$'
       STRING_1     DB   'There are two options:$'
       POSITION_2   DB   27,'[13;32H$'
       STRING_2     DB   '(1) Review part 1$'
       POSITION_3   DB   27,'[15;32H$'
       STRING_3     DB   '(2) Move on to part 2$'
       ;---печать строк
          MOV  AH,9           ;номер функции вывода строки
          LEA  DX,POSITION_1  ;1-я строка позиционирования курсора
       


                                     -114-
          INT  21H            ;позиционируем курсор
          LEA  DX,STRING_1    ;1-я текстовая строка
          INT  21H            ;вывод строки
          LEA  DX,POSITION_2  ;и т.д.
          INT  21H            ;
          LEA  DX,STRING_2    ;
          INT  21H            ;
          LEA  DX,POSITION_3  ;
          INT  21H            ;
          LEA  DX,STRING_3    ;
          INT  21H            ;

          Низкий уровень.

          Регистры 14 и 15 микросхемы 6845 хранят положение курсора.  Вы
       можете изменить их значение и курсор передвинется в соответствую-
       щую  позицию  экрана,  но прерывания вывода на экран  DOS и  BIOS
       будут игнорировать Вашу установку  и вернут курсор в старое поло-
       жение.   Это  происходит потому, что каждый раз при  вызове  этих
       прерываний,  они   восстанавливают   регистры  курсора, используя
       2-байтное  значение,  хранящееся в области данных BIOS.   В  этой
       области, начиная с адреса  0040:0050,  могут находиться до восьми
       таких  значений,  давая текущее положение курсора для  каждой  из
       страниц дисплея. Процедура низкого уровня должна модифицировать и
       эти значения, чтобы изменить состояние курсора полностью.
          Позиция курсора хранится в регистрах 14 и 15 как число от 0 до
       1999, что соответствует 2000 (25*80) позициям экрана. Не спутайте
       эту  систему нумерации с позициями видеобуфера от 0 до 3999,  где
       каждый символ сопровождается еще  байтом атрибутов (для получения
       эквивалентного  указателя на позицию курсора надо сдвинуть указа-
       тель видеобуфера на 1 бит вправо).  Обращаем также Ваше внимание,
       на  то,  что не надо менять местами старший и  младший  байты:  в
       регистре 14 - старший, а 15 - младший.

       ;---в программе
          MOV  BL,24         ;строка в BL (0-24)
          MOV  BH,79         ;столбец в BH (0-79)
          CALL SET_CURSOR    ;вызов процедуры

       ;---процедура установки курсора
       SET_CURSOR  PROC
          ;получаем доступ к регистру младшего байта
              MOV  DX,3B4H   ;порт адресного регистра 6845
              MOV  AL,15     ;выбираем регистр 15
              OUT  DX,AL     ;посылаем запрос
          ;вычисление позиции курсора
              MOV  AL,80     ;умножаем номер строки на 80
              MUL  BL        ;в AX - номер строки, умноженный на 80
              MOV  BL,BH     ;переносим номер столбца в BL
              SUB  BH,BH     ;распространяем BL на BX
              ADD  AX,BX     ;вычисляем позицию курсора
          ;посылаем младший байт результата
              INC  DX        ;адресуем управляющий регистр
              OUT  DX,AL     ;посылаем младший байт
          ;получаем доступ к регистру старшего байта
              MOV  AL,14     ;номер требуемого регистра
              DEC  DX        ;восстанавливаем порт адресного регистра
              OUT  DX,AL     ;посылаем запрос
          ;посылаем старший байт результата
              INC  DX        ;адресуем управляющий регистр
              MOV  AL,AH     ;помещаем старший байт в AL
              OUT  DX,AL     ;посылаем старший байт
              RET
       


                                     -115-
       SET_CURSOR    ENDP

          4.2.2 Относительное позиционирование курсора

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

          Средний уровень.

          Функции   относительного   перемещения   курсора   выполняются
       Esc-последовательностями.  Это строки, которые выводятся на экран
       с помощью функции 9 прерывания 21H. В приложении Д даны основы их
       использования.   Такие последовательности интерпретируются MS DOS
       как команды  перемещения  курсора,  а  не  вывод символов строки.
       Строка начинается с символа Esc (ASCII 27), затем идет символ  [,
       а символ $ отмечает конец  строки.   Сама строка состоит из числа
       позиций,  на которое надо сдвинуться, и кода направления.   Чтобы
       сдвинуться на 3 позиции:

                вверх           3A
                вниз            3B
                вправо          3C
                влево           3D

       Числа записываются как коды ASCII. Не преобразуйте, например, 33C
       (33 пробела вправо) в 33,'C'; должно быть '33C'.  В нижеприведен-
       ном примере цифры  1-8  помещаются  через  определенные интервалы
       поперек экрана, как метки столбцов данных.  Промежутки между циф-
       рами  генерируются  Esc-последовательностями,   которые  сдвигают
       курсор вправо после вывода каждой цифры.

       ;---в сегменте данных
       CURSOR_RIGHT   DB   27,'[9C$'

       ;---установка начальной позиции курсора
          MOV  BH,0             ;ноиер страницы
          MOV  DH,1             ;строка
          MOV  DL,5             ;столбец
          MOV  AH,2             ;функция установки курсора
          INT  10H              ;установка курсора
       ;---вывод цифр
          LEA  BX,CURSOR_RIGHT  ;BX будет обмениваться с DX
          MOV  CX,8             ;число цифр для вывода
          MOV  DL,'0'           ;начинаем с 0
       NEXT_NUMBER:   MOV  AH,2 ;функция DOS для вывода символа
          INT  21H              ;выводим символ
          INC  DL               ;переходим к следующему коду ASCII
          XCHG DX,BX            ;помещаем указатель на строку в DX
          MOV  AH,9             ;функция вывода строки
          INT  21H              ;сдвигаем курсор на 9 позиций вправо
          XCHG DX,BX            ;возвращаем в DX код ASCII
          LOOP NEXT_NUMBER      ;переходим к следующей цифре

          Имеется также пара Esc-последовательностей, которые  управляют
       переносом курсора на  следующую  строку  при  достижении им конца
       текущей  строки.   Когда устанавливается отсутствие переноса,  то
       лишние символы при  выводе   отбрасываются.  Строка,  запрещающая
       перенос  - Esc [=7h (или как данные, 27,'[=7h').  Для возврата  к
       режиму автоматического переноса на  следующую строку используется
       строка Esc [=7l (27,'[=7l').
       


                                     -116-
          4.2.3 Включение и выключение курсора.

          Курсор генерируется микросхемой 6845.  Он функционирует совер-
       шенно независимо от видеопамяти. Это значит, что при прямой адре-
       сации  в  память дисплея [4.3.1] программное  обеспечение  должно
       координировать перемещения  курсора  с  вставкой нового символа в
       буфер.  Отметим, что микросхема 6845 не может ни создавать немер-
       цающий курсор, ни изменить частоту его мерцания.  В [4.2.6] пока-
       зано как сконструировать другие "искусственные" типы курсора.

          Высокий уровень.

          Интерпретатор  Бейсика автоматически выключает курсор при  за-
       пуске программы.  Курсор  появляется, когда используется оператор
       INPUT,  но не в других случаях.  Если Вашей  программе  необходим
       курсор, скажем для  процедуры  INKEY$,  то он должен быть включен
       установкой  третьего параметра оператора LOCATE в 1 (0 снова вык-
       лючит его). Напоминаем, что первые два параметра оператора LOCATE
       устанавливают строку и столбец, в которых должен выводиться  кур-
       сор.

          100 LOCATE 15,40,1  ;включить курсор, его позиция 15,40
       или
          100 LOCATE ,,1      ;включить курсор в текущей позиции
       и
          100 LOCATE ,,0      ;снова выключить курсор

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

          Средний уровень.

          Ассемблерные  программы  оставляют курсор включенным,  до  тех
       пор, пока им не указано обратное. Операционная система не предос-
       тавляет  специальных  средств  выключения курсора, но  это  легко
       сделать.  Надо просто позиционировать курсор за пределы экрана, с
       помощью  функции 2 прерывания 10H установить его в первую позицию
       26-й строки.  Помните, что  координаты отсчитываются от нуля, так
       что этой позиции соответствуют координаты 25,0.

          MOV  BH,0    ;номер страницы (всегда 0 для монохромного)
          MOV  DH,25   ;строка
          MOV  DL,0    ;столбец
          MOV  AH,2    ;номер функции
          INT  10H     ;устанавливаем курсор за пределы экрана

          Низкий уровень.

          Бит  6  регистра 10 микросхемы 6845 [4.1.1] выключает  курсор,
       когда он установлен в 1, и включает  его, когда сброшен в 0. Этот
       регистр  содержит также значение "начальной строки" для  курсора,
       которое вместе со значением "конечной  строки" определяет толщину
       курсора  [4.2.4].  Поскольку тип курсора не имеет значения, когда
       курсор выключен, то надо просто  поместить  в регистр 10 значение
       32, чтобы установить бит 6.  Чтобы восстановить курсор Вы  должны
       также вернуть значение "начальной  строки" курсора.  Для нормаль-
       ного  курсора это значение равно 11.  Значение "конечной  строки"
       при этих процедурах не меняется,  поскольку оно хранится в другом
       регистре.

       ;---выключение курсора
       


                                     -117-
          MOV  DX,3B4H     ;номер порта адресного регистра 6845
          MOV  AL,10       ;выбор регистра 10
          OUT  DX,AL       ;посылаем запрос
          INC  DX          ;доступ к регистру через следующий порт
          MOV  AL,32       ;устанавливаем бит 6 для выключения курсора
          OUT  DX,AL       ;выключаем курсор
       ;---обратное включение курсора
          MOV  AL,11       ;значение "начальной строки"
          OUT  DX,AL       ;включаем курсор

          4.2.4 Изменение формы курсора.

          Курсор может меняться по толщине от тонкой линии до максималь-
       ного размера, отводимого  под  символ.   Он  строится из коротких
       горизонтальных отрезков, верхний из которых называется "начальной
       строкой" курсора, а нижний - "конечной строкой". Для монохромного
       дисплея  под каждый символ отводится 14 строк, пронумерованных от
       0 до 13, начиная сверху. Промежутки между символами обеспечивают-
       ся двумя верхними строками и тремя нижними.  Большинство символов
       распологаются в строках  2-10,  хотя  хвостики некоторых символов
       достигают  линий  12 и 13, в то время как подчеркивание  занимает
       одну двенадцатую строку.
          На 200-строчном цветном дисплее для каждого символа  отводится
       только 8 строк, а символ  рисуется  в верхних семи строках. Эти 8
       строк  пронумерованы от 0 до 7, начиная сверху, и нормальный кур-
       сор формируется одной строкой 7. (Отметим, что на цветном дисплее
       нет  подчеркивания,  поскольку  использование  для  подчеркивания
       строки 7 привело бы к тому, что  символы  сливались бы с располо-
       женными под ними.) Цветной дисплей высокого разрешения использует
       14-строчный монохромный вариант, когда он работает в режиме высо-
       кого разрешения, а когда он работает в одном из цветных графичес-
       ких режимов, то он использует 8-строчный режим.
          Курсор может быть сформирован  любой  комбинацией  прилегающих
       отрезков. Для монохромного дисплея он занимает все отведенное под
       символ место, когда "начальная строка" равна 0, а "конечная стро-
       ка" равна 13 (для графического дисплея надо использовать значение
       "конечной строки" равное 7).  Если значения "начальной" и "конеч-
       ной"  строки совпадают, то возникает однострочный  курсор.   Если
       номер "конечной строки" меньше  чем "начальной" то возникает кур-
       сор, состоящий из двух частей, так как происходит перенос в верх-
       ние строки. Например, если "начальная строка" равна 12, а "конеч-
       ная"  - 1, то сначала заполняется строка 12, затем 13, затем 0 и,
       наконец, 1.  Курсор при  этом  принимает  форму двух параллельных
       линий,  указывающих  верхнюю  и нижнюю границы ряда,  который  он
       занимает.
          BIOS хранит 2-байтную переменную  по адресу 0040:0060, которая
       содержит текущие значения "начальной" и "конечной" строк.  Первый
       байт содержит значение "конечной строки", а второй - "начальной".

          Высокий уровень.

          В Бейсике  оператор  LOCATE  может  не  только позиционировать
       курсор  и включать или выключать его, но и управлять его  формой.
       Парметры, устанавливающие  "начальную"  и "конечную" строки - это
       4-е  и  5-е число, следующие за словом LOCATE.  Другие  параметры
       могут быть опущены,  если  присутствуют  разделяющие  их запятые.
       Таким образом, чтобы создать толстый курсор, занимающий строки со
       2 по 12, надо записать LOCATE ,,,2,12. Отметим, что Бейсик обычно
       выключает курсор, когда начинает выполнение программы.  Как вклю-
       чить его обратно см. в [4.2.3].

          Средний уровень.

       


                                     -118-
          Функция 1 прерывания  BIOS  10H  устанавливает  "начальную"  и
       "конечную" строки курсора.  В CH должна быть указана "начальная",
       а в CL - "конечная" строка.

       ;---установка "начальной" и "конечной" строк курсора
          MOV  AH,1      ;номер функции
          MOV  CH,0      ;начать курсор в верхней строке
          MOV  CL,7      ;окончить курсор в восьмой строке
          INT  10H       ;

          Низкий уровень.

          Регистры 10 и 11  контроллера  дисплея  6845 содержат значения
       "начальной" и "конечной" строки, соответственно.  Доступ к  обоим
       регистрам осуществляется через порт 3B5H для монохромного адапто-
       ра  и 3D5H - для цветного алаптора и PCjr.   Предварительно  надо
       послать номер  требуемого  регистра  в  адресный регистр, имеющий
       адрес порта 3B4H (см.  [4.1.1]).  Значения занимают младший конец
       каждого регистра.  Однако регистр "начальной" строки (#10) битами
       5  и 6 индицирует также должен ли выводиться  курсор.   Поскольку
       курсор выводится, когда  оба  этих  бита  сброшены в 0, то просто
       поместив в регистр номер "начальной" строки мы установим эти биты
       в 0. Остальные биты этого регистра не используются.

       ;---установка "начальной" строки
          MOV  DX,3B4H     ;доступ к адресному регистру 6845
          MOV  AL,10       ;выбор регистра 6845
          OUT  DX,AL       ;посылка запроса
          MOV  AL,0        ;номер "начальной строки" 0
          INC  DX          ;переходим к управляющему регистру
          OUT  DX,AL       ;посылаем номер "начальной строки"
       ;---установка "конечной строки"
          MOV  AL,11       ;выбираем регистр 11
          DEC  DX          ;возвращаемся к адресному регистру
          OUT  DX,AL       ;посылаем запрос
          MOV  AL,7        ;номер "конечной строки" 7
          INC  DX          ;переходим к управляющему регистру
          OUT  DX,AL       ;посылаем номер "конечной строки"

          4.2.5 Чтение/сохранение/восстановление позиции курсора.

          Программы иногда читают и сохраняют текущее положение курсора,
       с тем чтобы  можно  было  временно  перевести  курсор в командную
       строку, а затем вернуть его в исходную позицию.  Текущая  позиция
       курсора для каждой из вплоть до восьми страниц хранится в области
       данных BIOS.  Имеется восемь 2-байтных переменных,  размещающихся
       начиная с адреса 0040:0050. Первая позиция соответствует странице
       0,  вторая  - странице 1 и т.д.  Младший байт  каждой  переменной
       содержит номер столбца, а  младший  -  номер строки. Как столбцы,
       так и строки нумеруются, начиная с нуля.

          Высокий уровень.

          В Бейсике оператор CRSLIN возвращает строку, а POS -  столбец.
       Оператор POS должен быть  снабжен  фиктивным  аргументом, т.е. он
       всегда должен записываться в виде POS(0). В данном примере курсор
       переводится в нижнюю строку экрана,  а затем возвращается на мес-
       то.   Отметим, что курсор возвращается на место после  выполнения
       оператора INPUT [4.2.3].

       100 ROW = CRSLIN        'получаем строку курсора
       110 COL = POS(0)        'получаем столбец курсора
       120 LOCATE 25,1         'переводим курсор в командную строку
       


                                     -119-
       130 INPUT "Enter file name", F$  'запрос на ввод
       140 LOCATE ROW,COL,1    'восстанавливаем позицию курсора

          Средний уровень.

          Функция  3  прерывания 10H возвращает строку курсора в  DH,  а
       столбец - в DL. На  входе  надо  поместить  в  BH  номер страницы
       (всегда 0 для монохромного дисплея).

       ;---определение позиции курсора
          MOV  AH,3     ;номер функции
          MOV  BH,0     ;страница 0
          INT  10H      ;строка:столбец в DH:DL

          MS DOS предоставляет две Esc-последовательности для сохранения
       и восстановления позиции курсора. Это специальные строки, которые
       если их "вывести" на терминал управляют монитором. Основы исполь-
       зования этих последовательностей описаны в приложении Д. Последо-
       вательность для запоминания позиции курсора - Esc[s, а для  восс-
       тановления - Esc[u. Нет нужды запоминать координаты в переменной.

       ;---в сегменте данных
       SAVE_CURSOR     DB   27,'[s$'
       RESTORE_CURSOR  DB   27,'[u$'

       ;---сохранение курсора
          LEA  DX,SAVE_CURSOR   ;адрес начала строки в DX
          MOV  AH,9             ;номер функции вывода строки
          INT  21H              ;сохраняем позицию курсора
       ;---восстановление курсора
          LEA  DX,RESTORE_CURSOR   ;адрес начала строки в DX
          MOV  AH,9             ;номер функции вывода строки
          INT  21H              ;восстанавливаем позицию курсора

          Низкий уровень.

          Регистры 14 и 15 микросхемы 6845 хранят текущую позицию курсо-
       ра, как объяснялось в [4.1.1].  Старший байт хранится в  регистре
       14. Два байта хранят числа от  0  до  1999 в режиме 80 символов в
       строке и от 0 до 999 в режиме 40 символов.  Вам необходимо  пере-
       вести получаемое число в координаты строки и столбца.  Вы  можете
       прочитать это значение,  чтобы  узнать  текущее  позицию видимого
       курсора  на экране.  Но запоминание этого значения и  последующее
       восстановление его в регистрах не обязательно приведет к возврату
       курсора  в  предыдущую позицию, особенно если Ваша программа  ис-
       пользует любую из обычных функций работы с экраном, предоставляе-
       мых операционной системой. Это происходит потому, что BIOS хранит
       положение курсора в своих  переменных,  для того чтобы иметь воз-
       можность управлять страницами дисплея [4.5.3].  После того как Вы
       восстановите регистры 14 и 15 курсор переместится в соответствую-
       щую  позицию, но при следующем вызове прерывания вывода на  экран
       курсор вернется назад к той  позиции,  в  которой он должен нахо-
       диться согласно значениям переменных BIOS.

          4.2.6 Создание альтернативных типов курсора.

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


                                     -120-
       ра.  Вместо этого  создается  "фальшивый"  курсор с помощью байта
       атрибутов.
          Наиболее эффективным методом является установка атрибута выво-
       да в негативе для символа, на который указывает курсор.  Для чер-
       но-белого  экрана  для этого атрибута  следует  использовать  код
       ASCII 112. Другой способ - заставить символ, на который указывает
       курсор мигать.  В этом случае надо просто добавить 128 к текущему
       значению атрибута,  чтобы  символ  начал  мигать,  и вычесть 128,
       чтобы прекратить мигание.  Третий способ - установить для символа
       режим подчеркивания (используя код ASCII 1).  И, наконец, в прог-
       раммах  использующих командную строку можно  рассмотреть  возмож-
       ность  использования  специального  графического символа, который
       следует за последним символом командной строки, такого как стрел-
       ки выводимые кодами ASCII 17 или 27. Отметим, что когда программа
       получает ввод в нескольких режимах, то Вы можете помочь идентифи-
       цировать текущий режим за счет особого типа курсора.

          Высокий уровень.

          В данном примере курсор  формируется  за счет вывода символа в
       позиции  курсора  в негативе.  Переменная  CURSORPOSITION  хранит
       смещение символа, на который указывает курсор в видеобуфере.  Это
       четное  число в интервале от 0 до 3998.  Прибавление к этой пере-
       менной 1 дает позицию байта атрибутов  для этого символа и помес-
       тив  туда 112 мы обеспечим вывод этого символа в негативе.  Пере-
       менная FORMERATTRIBUTE  хранит  обычные  атрибуты  символа, с тем
       чтобы можно было восстановить их после того как курсор сдвинется.

       500 '''процедура анализа поступающих расширенных кодов
        .
       560 IF EXTENDEDCODE = 77 THEN GOSUB 5000  'курсор вправо

       5000 '''процедура сдвигающая курсор вправо на одну позицию
       5010 POKE CURSORPOSITION+1,FORMERATTRIBUTE  'восст. атрибут
       5020 CURSORPOSITION = CURSORPOSITION+2      'новая позиция
       5030 FORMERATTRIBUTE = PEEK(CURSORPOSITION+1)  'сохр. атрибут
       5040 POKE CURSORPOSITION+1,112              'включаем негатив
       5050 RETURN                                 'все сделано

          Низкий уровень.

          Здесь тот же самый пример реализован на ассемблере:

       ;---процедура перемещения курсора на одну позицию вправо
       CURSOR_RIGHT:  MOV  BX,CURSORPOSITION  ;получение позиции
          INC  BX                  ;указываем на атрибут символа
          MOV  AL,FORMERATTRIBUTE  ;берем сохраненный атрибут
          MOV  ES:[BX],AL          ;восстанавливаем его
          INC  BX                  ;указываем на следующий символ
          MOV  CURSORPOSITION,BX   ;сохраняем его смещение
          MOV  AL,ES:[BX]+1        ;получаем атрибут нового символа
          MOV  FORMERATTRIBUTE,AL  ;сохраняем его
          MOV  AL,112              ;помещаем атрибут вывода в негативе
          MOV  ES:[BX]+1,AL        ;засылаем его для следующего символа

                        Раздел 3. Вывод символов на экран.

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


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

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

          Все процедуры для  вывода  символа  на  экран  в BIOS и DOS (а
       также  в  Бейсике) помещают символ в текущую  позицию  курсора  и
       автоматически передвигают курсор на одну позицию вправо.  Все они
       переносят вывод на следующую строку при достижении конца  строки,
       если не сделано специальных  указаний  отбрасывать все символы за
       80-м столбцом [4.2.2]. Важное отличие между отдельными процедура-
       ми состоит в том, что некоторые  вместе  с символом пишут также и
       его атрибуты, а некоторые этого не делают.
          Как  в языках высокого, так и в языках низкого уровня, символы
       могут выводиться на  экран  без  использования  обычных  операций
       печати.   Вместо этого используется прямое отображение в  память,
       при котором коды символов и их атрибуты прямо засылаются в ячейки
       памяти видеобуфера, соответствующие определенной позиции  курсора
       на экране. Буфер начинается с  адреса  B000:0000 для монохромного
       адаптора и с адреса B800:0000 - для цветного графического адапто-
       ра и PCjr.  EGA использует те же самые адреса в аналогичных режи-
       мах экрана.  Позиции с четными номерами (начиная с нуля) содержат
       коды ASCII символов, а позиции с нечетными номерами - байты атри-
       бутов.  На рис. 4-2 показан участок памяти видеобуфера.  При этих
       операциях позиция курсора не  меняется  и  он может быть выключен
       при  желании  [4.2.3].  Вместо курсора надо  хранить  переменные,
       служащие указателями на текущую позицию.

          Высокий уровень.

          Бейсик выводит как  отдельные  символы,  так и целые строки, с
       помощью  одних и тех же операторов PRINT и WRITE.   Как  правило,
       используется  PRINT; WRITE - это один из вариантов со специальны-
       ми, редко используемыми форматами вывода. PRINT работает с данны-
       ми трех видов.  Он выводит содержимое как строковых, так и число-
       вых переменных, например, PRINT S$ или PRINT X.  Он выводит также
       символы, вставленные (в кавычках) внутрь самого оператора  PRINT,
       например, PRINT "This words are printed". Он выводит также симво-
       лы,  соответствующие  кодам ASCII, включенным в оператор PRINT  в
       виде операторов CHR$, например,  PRINT  CHR$(65),  что приводит к
       выводу на экран символа A (код ASCII #65).
          В  одном  операторе PRINT могут выводиться много  данных,  при
       этом все три формы данных могут быть перемешаны. Отдельные данные
       отделяются запятой или точкой с запятой. Запятая приводит к тому,
       что следующие данные будут выводиться  со следующей позиции табу-
       ляции данной строки.  Точка с запятой приводит к тому, что данные
       печатаются на экране подряд, не  разделенные  пробелами (отметим,
       что PRINT вставляет пробел перед выводом любой числовой  перемен-
       ной, а WRITE не делает этого). Обычно оператор PRINT автоматичес-
       ки  делает перевод на новую строку при завершении, таким  образом
       следующий  такой  оператор  начнет  вывод  с новой строки экрана.
       Чтобы перенос на новую строку не происходил надо в конце операто-
       


                                     -122-
       ра PRINT поставить точку с запятой, например, PRINT S$;.
          Для установки позиции курсора  перед  выводом используется оп-
       ератор LOCATE. Без оператора LOCATE PRINT всегда начинает вывод с
       первой позиции строки, в которой находится курсор. Последователь-
       ные  операторы  PRINT заполняют экран до тех пор, пока  не  будет
       записана 24-я строка, после  чего  экран  сдвигается вверх, с тем
       чтобы  следующий оператор PRINT снова выводил 24-ю строку.  PRINT
       может выводить в 25-й  строке  только  при  помощи  LOCATE; и это
       также приводит к автоматическому сдвигу экрана вверх.  Чтобы зап-
       ретить сдвиг надо окончить оператор PRINT точкой с запятой. Одна-
       ко  этот  метод не сработает в последних позициях строк 24 и  25.
       Для заполнения этих позиций без сдвига экрана Вы должны использо-
       вать отображение в память, как показано ниже.
          Вы можете включать управляющие символы [7.1.9] внутрь операто-
       ра PRINT для того чтобы  реализовать  перемещения  курсора внутри
       строки.  Например, если Вы поместите в строку CHR$(13), то в этой
       точке будет сделан возврат каретки.   Если Вы выведете оператором
       PRINT строку "One"+CHR$(13)+"Two"+CHR$(13)+"Three", то в  резуль-
       тате каждое слово будет  выводиться  с  новой строки.  Коды ASCII
       28-31  сдвигают  курсор на одну  позицию  соответственно  вправо,
       влево, вверх и вниз. Оператор PRINT не содержащий данных приводит
       к  выводу  возврата каретки и, таким образом, следующий  оператор
       PRINT будет выводить на строке через одну.
          Прямое отображение в память  существенно  увеличивает скорость
       вывода на экран в Бейсике. Оно особенно полезно при конструирова-
       нии табличного вывода, когда формы могут достигать правого нижне-
       го  угла экрана.  Сначала надо установить указатель  сегмента  на
       &HB000, а затем  использовать  оператор  POKE  для засылки байтов
       памяти.  Прилегающие по горизонтали символы отстоят друг от друга
       на два байта, разделяемые  байтом  атрибутов.   Для 80-символьных
       экранов прилегающие по вертикали символы отстоят на 160 байт друг
       от друга (2 байта для каждого  символа  и атрибутов). В следующих
       двух  примерах  вдоль  границы экрана рисуется  рамка,  используя
       символы псевдографики.  В первом примере чаще используется опера-
       тор  PRINT, а во втором используется исключительно прямое отобра-
       жение в память. Отметим, что и в первом случае приходится исполь-
       зовать прямое отображение в память в последних столбцах строк  24
       и 25, чтобы избежать сдвига экрана.

          Использование PRINT:

        10 CLS: KEY OFF              'очистка экрана
        20 DEF SEG = &HB000          'указываем на видеобуфер
        30 LOCATE 1,1: PRINT CHR$(201)   'левый верхний угол
        40 LOCATE 1,80: PRINT CHR$(187)  'правый верхний угол
        50 LOCATE 1,24: PRINT CHR$(186)  '
        60 LOCATE 1,25: PRINT CHR$(200)  '
        70 POKE 3838,186                 'позиция 80 строки 24
        80 POKE 3998,188                 'позиция 80 строки 25
        90 FOR N=2 TO 79                 'горизонтальные линии
       100 LOCATE 1,N: PRINT CHR$(205);: LOCATE 25,N: PRINT CHR$(205)
       110 NEXT                          '
       120 FOR N=2 TO 23                 'вертикальные линии
       130 LOCATE N,1: PRINT CHR$(186): LOCATE N,80: PRINT CHR$(186)
       140 NEXT

          Использование прямого отображения в память:

        10 CLS: KEY OFF               'очистка экрана
        20 DEF SEG = &HB000           'буфер монохромного дисплея
        30 POKE 0,201                 'левый верхний угол
        40 POKE 158,187               'правый верхний угол
        50 POKE 3840,200              'левый нижний угол
       


                                     -123-
        60 POKE 3998,188              'правый нижний угол
        70 FOR N=2 TO 156 STEP 2      'горизонтальные прямые
        80 POKE N,205: POKE N+3840,205  'как верхняя, так и нижняя
        90 NEXT
       100 FOR N=160 TO 3680 STEP 160 'вертикальные прямые
       110 POKE N,186: POKE N+158,186 'правая и левая
       120 NEXT

          Средний уровень.

          Операционная  система  предоставляет шесть процедур вывода  на
       экран - три в BIOS и три в DOS.   Они  отличаются главным образом
       тем, передвигается курсор или нет, после вывода символа, вызывают
       ли они сдвиг экрана, позволяют  ли  они  устанавливать атрибуты и
       цвета символов, а также какие управляющие коды они интерпретируют
       (некоторые  рассматривают  символ  BackSpace,  просто как обычный
       символ, а некоторые действительно сдвигают курсор на одну позицию
       назад). Эти шесть процедур следующие:

          Прерывание 10H:

          функция   9     вывод символа с атрибутами
                    A     вывод символа без атрибутов
                    E     "телетайпная" процедура (как на принтер)

          Прерывание 21H:

          функция   2     вывод символа без атрибутов
                    6     вывод символа без атрибутов
                    9     вывод строки символов

          Функции 9 и A прерывания 10H  вообще  не интерпретируют управ-
       ляющие  символы.   Функции DOS интерпретируют  управляющие  коды,
       приведенные в следующей таблице. Функция E прерывания 10H интерп-
       ретирует все коды таблицы, кроме ASCII 9.

          ASCII   7     звонок
          ASCII   8     возврат на шаг (BackSpace)
          ASCII   9     табуляция
          ASCII  10     перевод строки
          ASCII  13     возврат каретки

          Первые  две функции прерывания 10H не передвигают курсор после
       вывода символа.  Функция  9  этого  прерывания выводит на экран с
       указанием  атрибутов, а функция A - без указания, при этом сохра-
       няется текущее значение  байта  атрибутов  для  этого символа. AL
       должен содержать выводимый символ, а BL - атрибуты. Номер страни-
       цы дисплея содержится в BH. Он должен указываться даже для монох-
       ромного  дисплея, который имеет только одну страницу памяти дисп-
       лея. В этом случае должна быть установлена первая страница, кото-
       рой соответствует номер 0. Особое свойство этих двух функций BIOS
       состоит в том, что символ выводится такое число раз, какое указа-
       но в CX. Обычно указывают CX равным 1, но эти функции могут легко
       выводить целые  строки  символов,  если  указать большее значение
       счетчика  - полезное свойство при создании рамок.   Отметим,  что
       даже если выводится много  символов,  то позиция курсора не изме-
       няется.   Когда  строка выводимых символов займет  все  свободное
       пространство экрана справа-вниз  от курсора, то вывод будет пере-
       несен в первые позиции экрана.

       ;---вывод символа в негативе
          MOV  AH,9             ;функция записи с атрибутами
          MOV  AL,THE_CHARACTER    ;символ в AL
       


                                     -124-
          MOV  BL,112           ;атрибуты в BL
          MOV  BH,0             ;страница 1
          MOV  CX,1             ;вывести один раз
          INT  10H

       Вместо  того, чтобы постоянно восстанавливать значение счетчика в
       CX прерывание  BIOS  предоставляет  также  телетайпную процедуру,
       которая  больше подходит для вывода строки символов.  Она  выпол-
       няется функцией E.  Она  готовится так же, как и функция A, но не
       надо  засылать  значение в CX.  Строка выводится просто  за  счет
       изменения символа в AL и  повторного  вызова  прерывания. При ис-
       пользовании в графическом режиме в BL устанавливается цвет палет-
       ты, в противном случае сохраняется старый атрибут.

       ;---вывод строки с помощью телетайпной процедуры
                   MOV  AH,0EH     ;номер функции
                   MOV  BH,0       ;номер страницы
                   LEA  BX,STRING  ;BX указывает на строку
       NEXT_CHAR:  MOV  AL,[BX]    ;берем символ в AL
                   CMP  AL,'$'     ;проверка на конец строки
                   JE   ALL_DONE   ;если да, то выход
                   INT  10H        ;вывод строки
                   INC  BX         ;переходим к следующему символу
                   JMP  SHORT NEXT_CHAR   ;повторяем процедуру
       ALL_DONE:

          Прерывание DOS 21H как  правило  предоставляет  более полезные
       процедуры,  поскольку они перемещают курсор и  приводят к  сдвигу
       экрана при достижении нижней строки, а также интерпретируют неко-
       торые из обычных управляющих кодов.  Функции DOS выводят на стра-
       ницу, которая должна быть  установлена  функцией 5 прерывания 10H
       [4.5.3].  Предоставляются две функции для вывода символа, с номе-
       рами 2 и 6. Первая из них распознает Ctrl-Break [3.2.8], а вторая
       -  нет.   (Когда с клавиатуры вводится Ctrl-Break,  то  процедура
       обработки Ctrl-Break не  выполняется  до тех пор, пока не исполь-
       зуется функция, которая распознает его наличие).
          Обе функции выводят белые символы на черном фоне, до тех  пор,
       пока не сделана  специальная  установка  цвета с помощью драйвера
       устройства ANSI.SYS [4.1.3].  В общем необходимо только поместить
       символ в DL, номер функции в AH и  вызвать прерывание 21H. Однако
       функция 6 особенная в том смысле, что она имеет второе назначение
       в качестве функции ввода с клавиатуры.  Она выступает в этой роли
       только  если в DL помещен код FF [3.1.5].  Во всех остальных слу-
       чаях она выводит на  экран  содержимое  DL.  В  следующем примере
       функция  6 поочередно принимает и печатает символ (в [3.1.4]  об-
       суждается процедура, которая комбинирует оба этих свойства).

              MOV  AH,6       ;номер функции
       NEXT:  MOV  DL,0FFH    ;при этом значении принимаем ввод
              INT  21H        ;выполняем прерывание
              JZ   NEXT       ;если не было ввода, то обратно
              CMP  AL,13      ;это был возврат каретки?
              JE   END_INPUT  ;если да, то на конец
              MOV  DL,AL      ;иначе посылаем символ в DL
              INT  21H        ;и выводим его на экран
              JMP  SHORT NEXT ;повторяем процедуру

          Низкий уровень.

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


                                     -125-
       компьютеров устроенным одинаково и расположенным в одних и тех же
       адресах памяти.  Поскольку буфер устроен таким образом, что байты
       атрибутов  перемежаются с байтами символов, то символьные  данные
       не могут просто пересылаться из памяти в буфер инструкцией MOVSB,
       поскольку  указатель  в буфере должен увеличиваться на два  после
       каждого переноса байта.  Однако,  использование  этой техники су-
       щественно  ускоряет  вывод на экран.  Отметим, что отображение  в
       память не работает при выводе  символов  в  графическом режиме. В
       этом  случае размер видеобуфера 16K или 32K и BIOS рисует  каждый
       символ поточечно.  Отметим также, что при отображении в память не
       используется  курсор для указания на символ.  При  желании  можно
       перемещать курсор по мере ввода  [4.2.1] или выключить его и соз-
       дать свой псевдокурсор [4.2.6].

       ;---в сегменте данных
       SAMPLE_STRING  DB   'PRINT THIS STRING$'

       ;---вывод строки
              MOV  AX,0B000H            ;монохромный дисплей
              MOV  ES,AX                ;указываем на видеобуфер
              LEA  BX,SAMPLE_STRING     ;BX указывает на строку
              MOV  DI,CURSOR_START      ;начальная позиция в буфере
       NEXT:  MOV  AL,[BX]              ;берем символ
              CMP  AL,'$'               ;проверка на конец строки
              JE   ALL_DONE             ;если да, то выход
              MOV  ES:[DI],AL           ;иначе помещаем символ в буфер
              INC  DI                   ;увеличиваем указатель на 2
              INC  DI                   ;
              INC  BX                   ;переходим к обработке следу-
              JMP  SHORT NEXT           ;щего символа
       ALL_DONE:

          У  цветного графического адаптора и PCjr (но не у EGA) имеется
       проблема, связанная с  отображением  в память. Когда запись в бу-
       ферную  память происходит одновременно с чтением ее для вывода на
       экран, то на экране возникает интерференция. Эта проблема решает-
       ся  ожиданием  сигнала "все чисто" (all clear)  перед  записью  в
       видеобуфер. Надо непрерывно читать значение из порта 3DAH.  Когда
       бит 0 равен 1, то можно спокойно писать.  (3DAH - это порт, через
       который PCjr посылает данные массиву ворот дисплея; когда из него
       читаем,  то он возвращает регистр статуса, как и у цветного адап-
       тора.)

       ;---ожидаем пока все чисто
               MOV  DX,3DAH          ;порт регистра статуса
       CHECK_AGAIN:   IN   AL,DX     ;получаем значение
               TEST AL,1             ;проверка первого бита
               JNE  CHECK_AGAIN      ;если он 0, то обратно
       ;---теперь выводим сообщение
               LEA  BX,MESSAGE       ;сообщение в сегменте данных
               MOV  DI,2000          ;начинаем вывод с центра экрана
               MOV  AH,01000001B     ;атрибут синий на красном
       NEXT_CHAR:   MOV  AL,[BX]     ;берем символ
               CMP  AL,'$'           ;проверяем на конец строки
               JE   ALL_DONE         ;если конец, то на выход
               MOV  ES:[DI],AX       ;иначе выводим символ
               INC  BX               ;увеличиваем указатель строки
               INC  DI               ;увеличиваем указатель буфера
               INC  DI               ;
               JMP  SHORT NEXT_CHAR  ;обрабатываем следующий символ
       ALL_DONE:

          Вы можете поэкспериментировать  сколько  символов за один цикл
       


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

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

          Процедуры, которые выводят целые  строки символов очень полез-
       ны, но они могут накладывать ограничения на содержимое  выводимой
       строки. Надо  обращать  внимание  на  то,  какие управляющие коды
       (табуляция,  пробел  и т.п.) интерпретируются, а какие  нет.   До
       появления AT BIOS не  имел  функции  вывода  строки,  хотя MS DOS
       всегда  имела такую фукнцию.  Функция BIOS предоставляет  больший
       контроль над атрибутами символов. Естественно, что ее использова-
       ние создает проблему совместимости с предыдущими машинами.  Напо-
       минаем, что EGA имеет ПЗУ, расширяющее  ROM-BIOS и функция вывода
       строки символов является одним из таких расширений. В этом случае
       любой IBM PC и XT имеет возможность использовать эту процедуру.

          Высокий уровень.

          Бейсик выводит строку точно  так  же, как и отдельные символы.
       Надо  просто  написать PRINT S$, где S$ может быть любой  строкой
       длиной до 255 символов, которую  сконструировала  программа.  Ин-
       терпретируются 10 управляющих кодов, а именно:

          ASCII   7          звонок
          ASCII   9          табуляция
          ASCII  10          перевод строки
          ASCII  11          курсор в первую позицию экрана (Home)
          ASCII  12          перевод формата (стирает экран + Home)
          ASCII  13          возврат каретки
          ASCII  28          курсор вправо
          ASCII  29          курсор влево
          ASCII  30          курсор вверх
          ASCII  31          курсор вниз

       Все остальные коды выводятся на экран как символы.

          Средний уровень.

          Функция  9 прерывания 21H выводит строку.  DS:DX должны указы-
       вать на первый символ строки.  Строка должна завершаться символом
       $,  что  означает,  что сам символ $ не может  входить в  строку.
       Строка может быть любой длины. Функция не переводит автоматически
       курсор на начало следующей строки после завершения вывода;  чтобы
       это выполнялось надо добавить в конец строки символы 0AH (перевод
       строки) и 0DH (возврат каретки).

       ;---в сегменте данных
       FIRST_STRING    DB   'This is the first string',0AH,0DH,'$'
       SECOND_STRING   DB   'And this is the second string$'

       ;---вывод строки
          MOV  AH,9             ;номер функции вывода строки
          LEA  DX,FIRST_STRING  ;загружаем адрес первой строки
          INT  21H              ;печатаем строку с позиции курсора
          LEA  DX,SECOND_STRING ;загружаем адрес второй строки
       


                                     -127-
          INT  21H              ;печатаем строку с начала новой строки

       Интрепретируются следующие управляющие коды:

          ASCII   7           звонок
          ASCII   8           возврат на шаг (BackSpace)
          ASCII   9           табуляция
          ASCII  10           перевод строки
          ASCII  13           возврат каретки

          Функция  DOS 40H прерывания 21H также полезна при выводе строк
       на экран.  Она требует, чтобы Вы знали длину строки, поскольку ей
       не  требуется  символа-ограничителя; эта функция особенно  удобна
       для дампа текстовых файлов  на  экран.  Исходно  эта функция была
       предназначена для вывода в файл. Она требует дескриптора, который
       является  идентификационным  номером  для  данного файла или уст-
       ройства.   Дисплей  имеет заранее предназначенный дескриптор  #1.
       Надо поместить дескриптор в BX, а число байтов строки в CX. DS:DX
       должны  указывать на строку.  Функция выводит текст с нормальными
       (белый на черном) атрибутами. Отметим, что не надо предварительно
       "открывать"  дисплей,  как это Вы делает с  другими  файлами  при
       использовании этой функции. Вот пример:

       ;---вывод 1000 байтов текста
          MOV  AH,40H          ;номер функции
          MOV  BX,1            ;дескриптор дисплея
          LEA  DX,STRING       ;загржаем адрес строки
          MOV  CX,1000         ;число выводимых байтов
          INT  21H             ;

          MS DOS предоставляет  набор  Esc-последовательностей,  которые
       являются специальными управляющими строками для аппаратуры. Когда
       они выводятся с помощью  функции  9  прерывания 21H, то они могут
       управлять курсором, режимом дисплея, цветом символов и некоторыми
       аспектами клавиатуры. В приложении Д обсуждается как их использо-
       вать. Когда программа выводит на экран много строк, то Esc-после-
       довательности часто являются  самым удобным способом позициониро-
       вания  курсора и установки цвета строки.  Это происходит  потому,
       что они сами рассматриваются просто  как очередные строки в серии
       выводимых строк.
          У AT и машин, снабженных EGA, функция 13H прерывания 10H выво-
       дит строку.  ES:BP  должны  указывать  на  строку, а длина строки
       должна быть в CX.  DX указывает позицию курсора, с которой должна
       начинаться строка (вычисляемую  как  смещение от начала страницы,
       на  которую идет вывод без учета байтов атрибутов).  В BX  должен
       быть указан номер страницы.  Наконец номер кода от 0 до 3, содер-
       жащийся в AL указывает как должна выводиться строка.

          AL = 0    строка состоит только из символов, курсор неподвижен
          AL = 1    строка состоит только из символов, курсор движется
          AL = 2    в строке чередуются символы и атрибуты,
                    курсор неподвижен
          AL = 3    в строке чередуются символы и атрибуты
                    курсор движется

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

          Низкий уровень.

          Ограничение на использование символа $ делает функцию 9 беспо-
       


                                     -128-
       лезной  для  многих приложений.  Однако на многих машинах это  е-
       динственное прерывание,  доступное  для вывода строки неизвестной
       длины. Попробуйте написать свое собственное прерывание (в [1.2.3]
       показано как), использующее технику отображения в память [4.3.1].
       Используйте  в  качестве  ограничителя  какой-нибудь  специальный
       символ, например, ASCII 0, вместо $. Сделайте чтобы эта процедура
       обрабатывала только те управляющие коды, которые нужны Вам. Такой
       метод будет работать намного быстрее, чем при использовании функ-
       ции MS DOS.

          4.3.3 Чтение символа и его атрибутов в данной позиции.

          Обычно  программа получает данные из своих переменных и  поме-
       щает их в видеобуфер  для  вывода  на  экран.  В некотором смысле
       программа "знает" что на экране. Но встречаются ситуации, в кото-
       рых сам видеобуфер используется  как рабочая область (например, в
       графиченских  программах вырезки и вставки) и текущее  содержимое
       экрана не записано в  памяти  программы.   В  этих случаях бывает
       необходимо прочитать с экрана, виесто того чтобы вывести на него.
       Функция BIOS позволяет прочитать  символ и его атрибуты в опреде-
       ленной  позиции  экрана; другой метод состоит в обращении  метода
       прямого отображения в  память  дисплея  [4.3.1].  Чтобы прочитать
       символ  и  атрибуты  в строке 0 и столбце 39 (1,40 в  Бейсике)  в
       режиме 80 символов в строке  надо  сложить  (0*160) плюс (39*2) и
       взять результат в качестве смешения в видеобуфере. В случае когда
       нужны смещения для различных  страниц  см. [4.5.3]. Имейте ввиду,
       что  обращение метода прямого отображения в память не будет рабо-
       тать в случае вывода символов в графическом режиме.

          Высокий уровень.

          Бейсик использует  функцию  SCREEN  для  получения символа или
       атрибутов (эта функция не имеет ничего общего с оператором SCREEN
       устанавливающим режим  дисплея).  SCREEN  5,10 получает код ASCII
       символа,  расположенного в строке 5, столбце 10 (строки и столбцы
       нумеруются от 1).  Чтобы  получить атрибуты символа надо добавить
       третий параметр 1, например, SCREEN 5,10,1.  При использовании  в
       графическом режиме  данная  функция  возвращает 0, если требуемая
       позиция экрана не содержит (немодифицированного) символа.
          Атрибуты также возвращаются в виде кода от 0 до 255. Поскольку
       Бейсик  не  позволяет использования двоичных чисел, то  требуются
       некоторые манипуляции, чтобы  определить  атрибуты. Основной цвет
       равен ATTRIBUTE MOD 16. После того как Вы выделили основной цвет,
       цвет фона определяется по  формуле  (((ATTIBUTE - FOREGROUND)/16)
       MOD  128).   Если байт атрибутов больше 127, то включено  мигание
       (или, при соответствующей  установке,  включены интенсивные цвета
       фона  [4.1.3]).   В приложении Б обсуждаются битовые  операции  в
       Бейсике.

          Средний уровень.

          Функция 8 прерывания 10H возвращает  символ и его атрибуты для
       текущей  позиции курсора.  В BH должен содержаться номер  текущей
       страницы дисплея (отсчитываемый от 0 и всегда равный 0 для монох-
       ромного дисплея). Код символа возвращается в AL, а байт атрибутов
       в  AH.   Эта функция настолько мощная, что способна  даже  читать
       символы в графическом  режиме,  сообщая  цвет  палетты  в AH. Она
       работает даже для символов определяемых пользователем [4.3.4].  В
       примере определяется символ и атрибуты в позиции 0,39 для страни-
       цы 2 графического адаптора:

       ;---установка позиции курсора
          MOV  AH,2        ;функция установки курсора
       


                                     -129-
          MOV  DH,0        ;номер строки
          MOV  DL,39       ;номер столбца
          MOV  BH,0        ;номер страницы
          INT  10H         ;позиционируем курсор
       ;---чтение символа и атрибутов
          MOV  AH,8        ;функция чтения символа/атрибутов
          MOV  BH,2        ;номер страницы
          INT  10H         ;в AH:AL теперь атрибуты и символ

          Низкий уровень.

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

       ;---чтение символа и атрибутов позиции 7,39 страницы 2
          MOV  AX,0B800H       ;адрес видеобуфера
          MOV  ES,AX           ;ES указывает на первый байт буфера
          MOV  DI,1000H        ;смещение до начала страницы
          MOV  AL,80           ;умножаем номер строки на 160
          MOV  BL,7            ;номер строки
          MUL  BL              ;теперь в AX (строка-1)*160
          MOV  AX,39           ;номер столбца
          ADD  BX,AX           ;номер позиции в видеобуфере
          SHL  BX,1            ;умножаем его на два
          MOV  AX,ES:[BX][DI]  ;теперь AH:AL содержат атрибуты/символ

          4.3.4 Создание специальных символов.

          Только  монохромный  адаптор не может выводить  символы  вида,
       заданного  самим  программистом.  Цветной  адаптор  позволяет 128
       символов, определяемых пользователем, PCjr - 256, а EGA - 1024 из
       которых одновременно доступно 512. Для цветного адаптора ROM-BIOS
       содержит  данные для разрисовки только первых 128 символов набора
       ASCII (с номерами от 0 до 127). Следующие 128 символов недоступны
       для Вас, пока Вы не создатите их, используя описанную здесь  тех-
       нику. Отметим, что MS DOS  3.00  предоставляет  команду GRAFTABL,
       которая  предоставляет требуемые данные для второй порции из  128
       символов. PCjr имеет данные для второй порции из 128 символов уже
       готовые. EGA имеет полные наборы символов для режимов с 200 стро-
       ками и с 350 строками.
          Символы для графического адаптора и PCjr описываются с помощью
       матрицы 8*8 точек. Данные для каждого символа содержатся в восьми
       байтах. Каждый байт  содержит  установку  для  точек одного ряда,
       начиная с верхнего ряда, причем старший бит (номер 7)  соответст-
       вует самой левой точке в ряду. Когда соответствующий бит равен 1,
       то точка высвечивается. Для описания символа Вы должны определить
       правильные последовательности битов для восьми байтов и поместить
       их в последовательные ячейки памяти.  На рис.  4-3 показано как 8
       байтов описывают бубновую масть.
          Все 128  символов  вместе  требуют  1024  байта, хотя вовсе не
       требуется,  чтобы были описаны все символы.   Специальный  вектор
       прерывания   (постоянный   указатель   в   младших адресах памяти
       [1.2.0])  указывает на адрес первого байта первого символа расши-
       ренного набора, т.е. на символ номер 128. Когда в позицию символа
       в видеобуфере посылается код 128, то просматриваются и  выводятся
       первые восемь байт. Если номер  символа 129, то выводятся байты с
       девятого по шестнадцатый, и т.д.
          Номер  этого вектора прерывания 1FH и он расположен по  адресу
       0000:007C. Поместите значение  смещения  в младшее слово (сначала
       младший байт), а адрес сегмента - в старшее слово (снова, сначала
       младший байт).  Отметим,  что  можно  символы с большими номерами
       


                                     -130-
       кодов,  не  отводя памяти для символов с меньшими номерами;  надо
       просто чтобы вектор указывал на некоторый  адрес, который меньше,
       чем адрес начала блока, содержащего данные для описания символов.
       Восьмибайтные  последовательности,   описывающие  символы ASCII с
       кодами  128-255 приведены в [4.3.5].  У PCjr вектор 1FH указывает
       на вторые 128 символов ASCII, а вектор  44H - на первые. Оба этих
       вектора могут быть изменены, допуская полный набор 256  символов,
       определяемых пользователем.
          Для EGA картина намного сложнее, но и намного гибче.  При ини-
       циализации  текстового режима один из двух наборов символов  (8*8
       или 8*14) копируется из ПЗУ EGA в  карту битов 2 видеобуфера. Эта
       часть буфера рассматривается как разбитая на блоки, причем  стан-
       дартный набор символов помещается в блок 0.  При условии, что EGA
       оснащен  достаточной памятью могут быть определены еще три  блока
       для описания символов.   Размер  блока  определяется числом строк
       матрицы, используемой для описания символа.  Символы, описываемые
       матрицей 8*8 требуют 8*256 или  2048  байт. Когда разрешены более
       одного  блока  символов, то бит 3 байта атрибутов  определяет  из
       какого блока будут браться данные для описания символа.
          Какой из  блоков  будет  использоваться  зависит  от установки
       битов  0-3  регистра выбора карты символов, адрес порта  которого
       3C5H.  Предварительно надо  послать  3 в порт 3C4H, чтобы указать
       требуемый  регистр.  Биты 1-0 дают номер блока символов,  который
       берется когда бит 3 байта  атрибутов равен 0, а биты 3-2 - делают
       то же самое, когда бит 3 равен 1. Когда установка обоих пар битов
       совпадает, то  возможность  использования  двух  наборов символов
       отсутствует  и бит 3 байта атрибутов переключается  на  установку
       интенсивности символа. В этом  случае используется только блок 0.
       Однако никто не может помешать Вам поместить свои символы в любую
       нужную Вам позицию в этом  блоке.   Если  Вы изменили стандартный
       набор  символов, то Вы можете в любой момент восстановить его  из
       ПЗУ.

          Высокий уровень.

          В Бейсике Вы должны позаботиться о том, чтобы данные описываю-
       щие символы находились за пределами памяти, используемой програм-
       мой. Если имеется много памяти, то можно поместить данные в стар-
       шие адреса; если имеется опасность конфликта, то следует  исполь-
       зовать команду CLEAR для  ограничения  количества памяти, которую
       может использовать Бейсик.  Затем следует поместить адрес первого
       байта данных в вектор прерывания. В следующем примере описывается
       символ 128 как квадратная рамка.  Операторы DATA содержат  значе-
       ния, описывающие символ. Они  равны  либо 255, либо 129; в первом
       случае все биты равны 1, а во втором равны 1 только крайние биты.
       О вычислении десятичных значений, соответствующих данным цепочкам
       битов см. приложение Б.

       100 '''помещаем данные, начиная с адреса &H3000
       110 DATA 255, 129, 129, 129, 129, 129, 129, 255
       120 DEF SEG = &H3000    'указываем начало сегмента
       130 FOR N = 0 TO 7      'определяем 8 байт
       140 READ Q              'читаем 1 байт
       150 POKE N,Q            'помещаем его в память
       160 NEXT                'и т.д.
       170 '''установка вектора прерывания
       180 DEF SEG = 0         'указываем на начало памяти
       190 POKE 124,0          'указываем смещение
       200 POKE 125,0          '
       210 POKE 126,0          'указываем сегмент
       220 POKE 127,&H30       '
       230 '''печатаем символ
       240 LOCATE 12,12: PRINT CHR$(128)  'теперь есть символ 128
       


                                     -131-
          Средний уровень.

          Для цветного адаптора и PCjr используйте функцию 25H  прерыва-
       ния 21H для изменения вектора  прерывания  1FH.   При входе DS:DX
       должны  указывать  на первый байт блока данных.  Более  подробное
       описание см. в [1.2.3]. В примере создаются два символа с номера-
       ми 128 и 129.  Они являются зеркальными отображениями друг друга,
       а выведенные подряд образуют небольшой прямоугольник.

       ;---в сегменте данных
       CHARACTER_DATA   DB  11111111B, 10000000B, 10000000B, 10000000B
                        DB  10000000B, 10000000B, 10000000B, 11111111B
                        DB  11111111B, 00000001B, 00000001B, 00000001B
                        DB  00000001B, 00000001B, 00000001B, 11111111B

       ;---установка вектора прерывания
          PUSH DS                ;сохраняем DS
          LEA  DX,CHAR_DATA      ;смещение для данных в DX
          MOV  AX,SEG CHAR_DATA  ;сегмент для данных в DS
          MOV  DS,AX             ;
          MOV  AH,25H            ;функция установки вектора
          MOV  AL,1FH            ;номер изменяемого вектора
          INT  21H               ;установка вектора
          POP  DS                ;восстанавливаем DS

       ;---печать символов
          MOV  AH,2              ;номер функции
          MOV  DL,128            ;первый символ
          INT  21H               ;вывод его
          MOV  DL,129            ;второй символ
          INT  21H               ;вывод его

          Для EGA функция 11H прерывания 10H манипулирует набором симво-
       лов. Эта функция может быть очень сложной, когда она используется
       для создания специальных режимов  экрана, но ее основное примене-
       ние достаточно простое. Имеется четыре подфункции. Когда AL равен
       0, то данные, определяемые  пользователем переносятся из памяти в
       специальный  блок  символов.  Когда AL равен 1 или 2,  то  наборы
       данных для символов 8*14 и 8*8 соответственно копируются из ПЗУ в
       блок символов.  Когда AL равен 3, то функция устанавливает назна-
       чение блока в регистре выбора карты символов, как описано выше. В
       последнем случае надо просто поместить соотвествующие данные в BL
       и вызвать функцию.  Для  загрузки  данных  из ПЗУ поместите номер
       блока  в BL и выполните функцию.  Для загрузки своих данных  надо
       чтобы ES:BP указывали на них,  число передаваемых символов должно
       быть  в  CX, смещение (номер символа) в блоке должно  быть в  DX,
       число байтов на символ - в BH, а номер блока - в BL.  После этого
       вызывайте прерывание 10H. Вот пример:

       ;---устанавливаем 128 пользовательских символов в блоке 0
          MOV  AX,SEG CHARACTER_DATA   ;ES:BP должны указывать на данные
          MOV  ES,AX                   ;
          MOV  BP,OFFSET CHARACTER_DATA   ;
          MOV  CX,128                  ;число символов
          MOV  DX,128                  ;начальное смещение
          MOV  BL,0                    ;номер блока
          MOV  BH,8                    ;матрица 8*8
          MOV  AL,1                    ;номер подфункции
          MOV  AH,11H                  ;номер функции
          INT  10H                     ;переносим данные

          4.3.5 Сводка данных для описания символов.

       


                                     -132-
          Ниже  приведены 8-байтные последовательности, необходимые  для
       описания символов для цветного графического  адаптора. Их исполь-
       зование объяснено в [4.3.4].

          Код ASCII      Символ        Последовательность (16-ная)

             128           А           78 CC C0 CC 78 18 0C 78
             129           Б           00 CC 00 CC CC CC 7E 00
             130           В           1C 00 78 CC FC C0 78 00
             131           Г           7E C3 3C 06 3E 66 3F 00
             132           Д           CC 00 78 0C 7C CC 7E 00
             133           Е           E0 00 78 0C 7C CC 7E 00
             134           Ж           30 30 78 0C 7C CC 7E 00
             135           З           00 00 78 0C 7C CC 7E 00

             136           И           7E C3 3C 66 7E 60 3C 00
             137           Й           CC 00 78 CC FC C0 78 00
             138           К           E0 00 78 CC FC C0 78 00
             139           Л           CC 00 70 30 30 30 78 00
             140           М           7C C6 38 18 18 18 3C 00
             141           Н           E0 00 70 30 30 30 78 00
             142           О           C6 38 6C C6 FE C6 C6 00
             143           П           30 30 00 78 CC FC CC 00

             144           Р           1C 00 FC 60 78 60 FC 00
             145           С           00 00 7F 0C 7F CC 7F 00
             146           Т           3E 6C CC FE CC CC CE 00
             147           У           78 CC 00 78 CC CC 78 00
             148           Ф           00 CC 00 78 CC CC 78 00
             149           Х           00 E0 00 78 CC CC 78 00
             150           Ц           78 CC 00 CC CC CC 7E 00
             151           Ч           00 E0 00 CC CC CC 7E 00

             152           Ш           00 CC 00 CC CC 7C 0C F8
             153           Щ           C3 18 3C 66 66 3C 18 00
             154           Ъ           CC 00 CC CC CC CC 78 00
             155           Ы           18 18 7E C0 C0 7E 18 18
             156           Ь           38 6C 64 F0 60 E6 FC 00
             157           Э           CC CC 78 FC 30 FC 30 30
             158           Ю           F8 CC CC FA C6 CF C6 C7
             159           Я           0E 1B 18 3C 18 18 D8 70

             160           а           1C 00 78 00 7C CC 7E 00
             161           б           38 00 70 30 30 30 78 00
             162           в           00 1C 00 78 CC CC 78 00
             163           г           00 1C 00 CC CC CC 7E 00
             164           д           00 F8 00 F8 CC CC CC 00
             165           е           FC 00 CC EC FC DC CC 00
             166           ж           3C 6C 6C 3E 00 7E 00 00
             167           з           38 6C 6C 38 00 7C 00 00

             168           и           30 00 30 60 C0 CC 78 00
             169           й           00 00 00 FC C0 C0 00 00
             170           к           00 00 00 FC 0C 0C 00 00
             171           л           C3 C6 CC DE 33 66 CC 0F
             172           м           C3 C6 CC DB 37 6F CF 03
             173           н           18 18 00 18 18 18 18 00
             174           о           00 33 66 CC 66 33 00 00
             175           п           00 CC 66 33 66 CC 00 00

             176           ░           22 88 22 88 22 88 22 88
             177           ▒           55 AA 55 AA 55 AA 55 AA
             178           ▓           DB 77 DB EE DB 77 DB EE
       


                                     -133-
             179           │           18 18 18 18 18 18 18 18
             180           ┤           18 18 18 18 F8 18 18 18
             181           ╡           18 18 F8 18 F8 18 18 18
             182           ╢           36 36 36 36 F6 36 36 36
             183           ╖           00 00 00 00 FE 36 36 36

             184           ╕           00 00 F8 18 F8 18 18 18
             185           ╣           36 36 F6 06 F6 36 36 36
             186           ║           36 36 36 36 36 36 36 36
             187           ╗           00 00 FE 06 F6 36 36 36
             188           ╝           36 36 F6 06 FE 00 00 00
             189           ╜           36 36 36 36 FE 00 00 00
             190           ╛           18 18 F8 18 F8 00 00 00
             191           ┐           00 00 00 00 F7 18 18 18

             192           └           18 18 18 18 1F 00 00 00
             193           ┴           18 18 18 18 FF 00 00 00
             194           ┬           00 00 00 00 FF 18 18 18
             195           ├           18 18 18 18 1F 18 18 18
             196           ─           00 00 00 00 FF 00 00 00
             197           ┼           18 18 18 18 FF 18 18 18
             198           ╞           18 18 1F 18 1F 18 18 18
             199           ╟           36 36 36 36 37 36 36 36

             200           ╚           36 36 37 30 3F 00 00 00
             201           ╔           00 00 3F 30 37 36 36 36
             202           ╩           36 36 F7 00 FF 00 00 00
             203           ╦           00 00 FF 00 F7 36 36 36
             204           ╠           36 36 37 30 37 36 36 36
             205           ═           00 00 FF 00 FF 00 00 00
             206           ╬           36 36 F7 00 F7 36 36 36
             207           ╧           18 18 FF 00 FF 00 00 00

             208           ╨           36 36 36 36 FF 00 00 00
             209           ╤           00 00 FF 00 FF 18 18 18
             210           ╥           00 00 00 00 FF 36 36 36
             211           ╙           36 36 36 36 3F 00 00 00
             212           ╘           18 18 1F 18 1F 00 00 00
             213           ╒           00 00 1F 18 1F 18 18 18
             214           ╓           00 00 00 00 3F 36 36 36
             215           ╫           36 36 36 36 FF 36 36 36

             216           ╪           18 18 FF 18 FF 18 18 18
             217           ┘           18 18 18 18 F8 00 00 00
             218           ┌           00 00 00 00 1F 18 18 18
             219           █           FF FF FF FF FF FF FF FF
             220           ▄           00 00 00 00 FF FF FF FF
             221           ▌           F0 F0 F0 F0 F0 F0 F0 F0
             222           ▐           0F 0F 0F 0F 0F 0F 0F 0F
             223           ▀           FF FF FF FF 00 00 00 00

             224           р           00 00 76 DC CB DC 76 00
             225           с           00 78 CC F8 CC F8 C0 C0
             226           т           00 CC C0 C0 C0 C0 00 00
             227           у           00 FE 6C 6C 6C 6C 6C 00
             228           ф           FC CC 60 30 60 CC FC 00
             229           х           00 00 7E D8 D8 D8 70 00
             230           ц           00 66 66 66 66 7C 60 C0
             231           ч           00 76 DC 18 18 18 18 00

             232           ш           FC 30 78 CC CC 78 30 FC
             233           щ           38 6C C6 FE C6 6C 38 00
             234           ъ           38 6C C6 C6 6C 6C EE 00
       


                                     -134-
             235           ы           1C 30 18 7C CC CC 78 00
             236           ь           00 00 7E DB DB 7E 00 00
             237           э           06 0C 7E DB DB 7E 60 C0
             238           ю           38 60 C0 F8 C0 60 38 00
             239           я           78 CC CC CC CC CC CC 00

             240           Ё           00 FC 00 FC 00 FC 00 00
             241           ё           30 30 FC 30 30 00 FC 00
             242           Є           60 30 18 30 60 00 FC 00
             243           є           18 30 60 30 18 00 FC 00
             244           Ї           0E 1B 1B 18 18 18 18 18
             245           ї           18 18 18 18 18 D8 D8 70
             246           Ў           30 30 00 FC 00 30 30 00
             247           ў           00 76 DC 00 76 DC 00 00

             248           °           38 6C 6C 38 00 00 00 00
             249           ∙           00 00 00 18 18 00 00 00
             250           ·           00 00 00 00 18 00 00 00
             251           √           0F 0C 0C 0C EC 6C 3C 1C
             252           №           78 6C 6C 6C 6C 00 00 00
             253           ¤           70 18 30 60 78 00 00 00
             254           ■           00 00 3C 3C 3C 3C 00 00
             255                       00 00 00 00 00 00 00 00

                      Раздел 4. Вывод точечной графики.

          Цветной графический адаптор имеет три графических режима, PCjr
       - шесть, а EGA - семь.  Как  устанавливать  эти режимы показано в
       [4.1.2].   Требования к размеру памяти существенно отличаются для
       различных режимов,  в  зависимости  от  разрешения экрана и числа
       используемых цветов.  В своих улучшенных графических режимах  EGA
       использует память дисплея совсем по-другому, чем остальные видео-
       системы, но он точно эмулирует их использование памяти при работе
       в трех общих режимах.
          Сначала рассмотрим цветной адаптор  и систему PCjr.  Два цвета
       (черный и белый) требуют только один бит памяти для каждой  точки
       на экране. Четыре цвета занимают 2 бита, а 16 цветов - 4 (8-цвет-
       ные  режимы не используются, поскольку три бита, требующиеся  для
       их представления нельзя  удобно  разместить  в  8 бит байта). Для
       всех  режимов по вертикали имеется 200 точек.  Низкое  разрешение
       (используемое только на PCjr) использует  160 точек по горизонта-
       ли, среднее разрешение - вдвое больше (320 точек) и высокое  раз-
       решение - еще вдвое больше (640  точек).   Число килобайт памяти,
       требуемое для каждого режтима приведено в [4.5.3].
          В двух- и четырехцветном режимах PCjr имеет выбор любого из 16
       доступных цветов. Цветной адаптор более ограничен.  В двухцветном
       режиме  он  всегда ограничен белым и черным,  а в  четырехцветном
       режиме только цвет фона может выбираться из 16 цветов, в то время
       как основной цвет должен браться только из двух  предопределенных
       палетт. Палетта 0 содержит коричневый, зеленый и красный цвета, а
       палетта 1 - циан, магента и белый.
          В  отличие от текстовых данных в режимах 4-6 и 8-A графические
       данные разбиты на видеостранице  на  части. В большинстве режимов
       данные  разбиваются на две части, при этом первая половина буфера
       содержит данные для  четных  строк  экрана,  а  вторая половина -
       данные  для  нечетных строк (строки нумеруются,  начиная с  верха
       экрана вниз). Однако в 16-цветных режимах PCjr буфер размером 32K
       делится на четыре части, каждая  из  которых  содержит данные для
       каждой четвертой строки.
          В  4-цветных режимах первый байт буфера содержит информацию  о
       самых левых точках строки 0, причем старший бит относится к самой
       левой точке.  Следующий байт содержит информацию о следующем сег-
       менте строки и т.д. Для всей строки требуется 80 байт.  81-й байт
       


                                     -135-
       содержит информацию о левом конце строки 2.  В 16-цветных режимах
       картина приблизительно такая  же,  но для каждой строки требуется
       160  байт и каждая часть буфера содержит данные только для  вдвое
       меньшего числа строк. Для  цветного  графического адаптора четные
       строки занимают память со смещениями от 0000 до 1F3FH, а нечетные
       - от 2000H до 3F3FH. Промежуток между 1F3FH и 2000H игнорируется.
       Для PCjr соответствующие ячейки могут существенно различаться,  в
       зависимости от режима и числа  используемых  страниц.   PCjr спе-
       циально  устроен таким образом, что вывод в 16K,  начинающихся  с
       сегмента B800H перенаправляется  в ту область памяти, где реально
       расположен видеобуфер.  Это свойство позволяет писать  программы,
       которые будут одинаково работать на цветном дисплее и PCjr.
          Для режимов экрана EGA от DH до 10H память организована совсем
       по-другому. Она разделяется на одну, две или четыре битовые плос-
       кости, каждая из которых организована так же, как для черно-бело-
       го режима высокого разрешения, описанного выше: когда байт данных
       посылается в определенный адрес  видеобуфера, то каждый бит соот-
       ветствует  точке  на экране, причем они описывают  горизонтальный
       сегмент строки и бит 7  соответствует  самой левой точке. Записы-
       ваются  четыре  таких битовых плоскости, соответствующих одним  и
       тем же адресам в видеобуфере.   Это  отводит каждой точке 4 бита,
       что позволяет описывать 16 цветов. На рис. 4-4 показаны различные
       схемы распределения памяти.
          В графическом режиме могут  выводиться  и  символы. Однако они
       создаются не обчыным способом, вместо этого BIOS вырисовывает  их
       поточечно, не изменяя фонового цвета.  По этой причине такие вещи
       как негативное изображение и мигание символов недоступны в графи-
       ческом режиме. Не выводится и курсор. BIOS может читать и опреде-
       лять установку точек в позиции курсора, чтобы узнать какой символ
       там содержится.  Символы  располагаются в одной из позиций, соот-
       ветствующих  обычным  строкам и столбцам, что означает,  что  они
       всегда начинаются на границе кратной восьми точкам.

          4.4.1 Установка цветов для точечной графики.

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

          Номер кода            Цепочка битов         Цвет

             0                     0000             черный
             1                     0001             синий
             2                     0010             зеленый
             3                     0011             циан
             4                     0100             красный
             5                     0101             магента
             6                     0110             коричневый
             7                     0111             белый
             8                     1000             серый
             9                     1001             яркосиний
             10                    1010             яркозеленый
             11                    1011             яркий циан
             12                    1100             розовый
             13                    1101             яркая магента
             14                    1110             желтый
             15                    1111             яркобелый

       


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

          Номер кода    Цепочка битов    Палетта 0    Палетта 1

              0             00           цвет фона    цвет фона
              1             01           зеленый      циан
              2             10           красный      магента
              3             11      желтый/коричневый белый

       Если Вы в какой-то момент  переключились  между палеттами, то все
       выведенные на экран цвета будут соответственно изменены.  Единст-
       венный способ использовать цвет, не  входящий в эти палетты, сос-
       тоит в том, чтобы искуственно рассматривать один из цветов палет-
       ты как фоновый цвет,  что  предполагает  заполнение  этим  цветом
       всего экрана, когда экран чистится (используйте для этого  прямое
       отображение в память).  После  этого  истинный фоновый цвет может
       показываться  "сквозь  него" в качестве основного  цвета.   Такая
       техника приводит к созданию границы  экрана, аналогичной той, что
       изображается  в  текстовых режимах.  В противном  случае  граница
       экрана не может быть выделена  цветом,  так как весь экран закра-
       шивается фоновым цветом, хотя точки относящиеся к области границы
       нельзя адресовать.  Отметим, что BIOS хранит в своей области дан-
       ных однобайтную переменную, которая содержит текущий номер палет-
       ты. Ее адрес равен  0040:0066H.  Изменение  этого числа не меняет
       текущую установку палетты; наоборот, если Вы измените цвет палет-
       ты другими средствами,  помимо  функций  операционной системы, то
       значение этой переменной будет модифицировано.
          Символы могут перемешиваться с точечной графикой.  Цвет, кото-
       рым будут выводиться символы,  зависит  от того, какую фукнцию Вы
       будете использовать для их вывода.  Простейшая функция по умолча-
       нию использует третий цвет  текущей  палетты.  Однако имеется ряд
       способов  использовать любой из цветов палетты, а также  выводить
       символы различными цветами. Смотрите обсуждение в [4.1.3].
          EGA и PCjr обеспечивают  добавочную  гибкость  в использовании
       атрибутов цвета, независимо от того, в каком режиме они работают.
       При 16-цветной графике  четыре  бита,  находящиеся  в  памяти для
       каждой  точки экрана дают цепочку битов, которая  не  переводится
       прямо в соответствующие цвета приведенной  таблицы.  Вместо этого
       каждый  номер относится к одному из 16 регистров палетты.  Каждый
       из этих регистров содержит цепочку  битов, соответствующую цвету,
       который  будет  выводиться на самом деле.  Если все 16  регистров
       будут содержать 0100, то независимо  от того, какой атрибут будет
       приписан точке в памяти, она будет выведена красным цветом.  Зна-
       чение в регистре 0  используется  в  качестве фонового цвета.  На
       рис. 4-1 в [4.1.3] показан этот механизм.  В двух- и четырехцвет-
       ном режимах используются  только  первые  два или четыре регистра
       палетты.
          Регистры  палетты позволяют программе изменить все выводимое в
       одном цвете на другой, не делая никаких  изменений в видеобуфере.
       Более  того отдельные объекты могут появляться и исчезать как  по
       волшебству.  Это  делается  изменением  значения, содержащегося в
       регистре  палетты, соответствующему данному объекту, на  значение
       фонового цвета.  Например,  предположим,  что фоновый цвет черный
       (0000) и что объект выведен с атрибутом 1110, так что он выводит-
       ся в том цвете, который указан в  регистре палетты 15 (по умолча-
       нию значение для этого регистра желтый).  Если изменить  значение
       регистра 15 на 0000 (черный фоновый цвет), то объект исчезнет. Но
       


                                     -137-
       на  самом  деле  объект хранится в памяти, так как он  записан  с
       атрибутом 1110, а не с атрибутом 0000, как все точки фона. Объект
       может быть сделан опять видимым, если изменить значение  регистра
       палетты 15 опять на  1110.  Не  обязательно,  чтобы  исчезали все
       желтые  объекты, поскольку некоторые могут быть выведены с другим
       атрибутом, который также соответствует регистру палетты, содержа-
       щему также желтый цвет.
          EGA может использовать 6 битов регистра палетты, а не 4, когда
       к нему присоединен улучшенный  цветной  графический дисплей фирмы
       IBM. При этом становятся доступными 64 цвета, кодировка для кото-
       рых R'G'B'RGB. R, G и B  соответствуют  темным цветам, а R', G' и
       B' - светлым. Различные комбинации создают 64 оттенка.  Как всег-
       да, 111111 соответствует белому цвету, а 000000 - черному.  Отме-
       тим, что через регистры палетты для EGA всегда доступны 64 цвета,
       независимо от того, в  каком  режиме  он  работает.  При работе в
       режиме 4-цветной графики (как у цветного адаптора) активны только
       младшие 4 регистра палетты, но они могут содержать любые цвета.

          Высокий уровень.

          Когда цветной дисплей работает в графическом режиме, то Бейсик
       обрабатывает  оператор COLOR по другому, чем в текстовом  режиме.
       Сначала идет фоновый цвет, в виде  числа от 0 до 15, а затем идет
       номер палетты 0 или 1.  Например, COLOR 2,1 устанавливает зеленый
       фоновый цвет (#2)  для  всего  экрана  и  активизирует палетту 1.
       После  этого три возможных основных цвета указываются их номерами
       в палетте: 1 - циан, 2 - магента и 3 - белый (сравните с операто-
       ром  PAINT).  Чтобы выключить цвет в режиме умеренного разрешения
       напишите SCREEN ,1.  Отметим,  что использование только черного и
       белого  цветов в режиме умеренного разрешения  не приводит к эко-
       номии памяти. PCjr использует оператор COLOR таким образом только
       в  режиме  SCREEN 1.  Для режимов от SCREEN 3 до SCREEN 6  формат
       этого оператора COLOR основной,фоновый.  При этом основной цвет -
       это число в диапазоне от 1 до 15 в 16-цветном режиме и от 1 до  3
       - в 4-цветном. Он не должен быть равным 0, который всегда исполь-
       зуется в качестве фонового цвета.
          Имеются специальные операторы для установки регистров палетты:
       PALETTE и PALETTE USING. PALETTE устанавливает цвет соответствую-
       щий любому атрибуты.  Например, PALETTE 9,11 приводит к тому, что
       точки нарисованные с цветом палетты  9 (обычно светлосиний) будут
       выведены в цвете 11 (светлый циан). Чтобы изменить установку всех
       регистров палетты к их  первоначальному  значению, т.е. чтобы ре-
       гистр 0 содержал 0, регистр 12 - 12 и т.д.  надо написать  просто
       PALETTE. Отметим,  что  в  режимах  SCREEN  4 и SCREEN 6 регистры
       палетты инициализируются таким образом, чтобы атрибуты цветов 1-3
       были такими же, как для палетты 1 на цветном графическом дисплее.
       Это делается в целях совместимости.
          Все 16 регистров палетты могут быть установлены одним операто-
       ром PALETTE USING.  PALETTE  USING  направляет содержимое 16-эле-
       ментного целого массива в регистры палетты.  Имея несколько таких
       массивов программа может быстро переключать  различные схемы цве-
       тов.   Каждый элемент массива должен быть числом в диапазоне от 0
       до 15, или -1, в  последнем  случае  соответствующий  регистр  не
       изменяется.   Например, для обращения привычной схемы цветов соз-
       дайте массив, в котором  ARRAYNAME(0)  =  15, ARRAYNAME(1) = 14 и
       т.д.  Затем напишите PALETTE USING ARRAYNAME(0) и содержимое мас-
       сива ARRAYNAME будет передано в  регистры  палетты.  0 индицирует
       начальную позицию в массиве, с которой надо брать данные посылае-
       мые в регистры.  Могут  использоваться  более длинные массивы, из
       которых  данные могут браться начиная с любой точки, при  условии
       что до конца массива еще есть 16 элементов.  PALETTE USING ARRAY-
       NAME(12) будет брать данные, начиная с 12-го байта массива. Отме-
       тим, что оператор PALETTE USING работает как в текстовом, так и в
       


                                     -138-
       графическом режимах. Вот пример:

       100 DEF INT A-Z         'все переменные целые
       110 DIM SCHEME1(16)     'массив для схемы цветов #1
       120 DIM SCHEME2(16)     'массив для схемы цветов #2
       130 DATA 3,5,9,2,4,12,15,1,6,7,14,13,8,11,10,0
       140 DATA 0,11,13,7,1,12,2,5,10,8,14,6,15,4,9,3
       150 FOR N = 0 TO 15     'для каждого регистра палетты
       160 READ Q              'прочитать код цвета
       170 SCHEME1(N) = Q      'и поместить его в массив
       180 NEXT                '
       190 FOR N = 0 TO 15     'то же самое со вторым массивом
       200 READ Q              '
       210 SCHEME2(N) = Q      '
       220 NEXT                '
       230 PALETTE USING SCHEME1(0)  'установка регистров
        .
       500 PALETTE USING SCHEME2(0)  'меняем их посреди программы

          Средний уровень.

          Функция  BH прерывания 10H устанавливает как фоновый цвет, так
       и цвета палетты - но  не  одновременно.  Для  установки  фонового
       цвета  надо поместить в BH 0, а затем код цвета от 0 до 15 в  BL.
       Для установки палетты надо  поместить  в  BH 1, а в BL 0 или 1. В
       данном примере устанавливается цвет фона циан и выбирается палет-
       та 0:

       ;---установка цвета фона и палетты
          MOV  AH,0BH        ;функция установки цвета
          MOV  BH,0          ;сначала устанавливаем фоновый цвет
          MOV  BL,3          ;код циана
          INT  10H           ;установка цвета
          MOV  BH,1          ;теперь устанавливаем палетту
          MOV  BL,1          ;выбираем палетту 1
          INT  10H           ;устанавливаем палетту

          На PCjr эта функция работает точно  так же в 4-цветном режиме,
       устанавливая  регистры  1-3 в одну из схем  цветов,  используемых
       цветным адаптором. В 2-цветном режиме 0 в BL соответствует белому
       цвету, как цвету 1, а 1 - черному.  Эта функция не влияет на наз-
       начения, используемые в 16-цветном режиме. Однако во всех случаях
       фоновый цвет может быть установлен засылкой в BH 0, а в BL - кода
       цвета.

          Низкий уровень.

          Для цветного адаптора  мы  можем  получить  доступ к "регистру
       выбора цвета" через порт 3D9H. В графических режимах этот регистр
       действует по-другому, чем в текстовых (описанных в [4.1.3]). Биты
       0-3 содержат информацию о фоновом цвете в обычном формате  (соот-
       ветственно синий, зеленый  икрасный  компоненты и интенсивность).
       Бит 5 выбирает палетту, когда этот бит равен 0, то палетта  номер
       0.  В графических режимах остальные биты не имеют значения.  Этот
       регистр только для записи, поэтому Вы должны указывать информацию
       и о фоновом цвете и о палетте, при изменении любого из них.

          MOV  DX,3D9H          ;адрес регистра выбора цвета
          MOV  AL,00100110B     ;цепочка битов для циана и палетты 1
          OUT  DX,AL            ;посылаем ее

          Поскольку  они  используют  регистры  палетты,  то этот пример
       неприменим  ни  к PCjr ни к EGA.  Для них надо  просто  загрузить
       


                                     -139-
       требуемые значения в эти регистры. У PCjr эти регистры нумеруются
       от 10H до 1FH. Доступ ко всем регистрам осуществляется через один
       порт с адресом 3DAH. Любое новое значение принимаемое этим портом
       воспринимается  адресным регистром.  Поэтому надо послать сначала
       номер регистра, а затем код цвета  для этого регистра. Чтобы быть
       уверенным,  что  порт ожидает номер регистра  надо  прочитать  из
       него. Например, чтобы поместить  яркосиний  цвет (1001) в регистр
       палетты 2:

       ;---помещаем код яркосинего цвета в регистр палетты 2
          MOV  DX,3DAH         ;адрес массива ворот дисплея
          IN   AL,DX           ;читаем из него
          MOV  AL,12H          ;номер регистра
          OUT  DX,AL           ;посылаем номер регистра
          MOV  AL,00001001B    ;код яркосинего цвета
          OUT  DX,AL           ;посылаем цвет

       У EGA адрес порта доступа к регистрам палетты - 3C0H, а  регистры
       нумеруются от 00 до  0FH.  Надо  прочитать  из  порта  3DAH (а не
       3C0H), чтобы быть уверенным, что ожидается номер регистра.  Когда
       к EGA  присоединен  улучшенный  цветной  дисплей  и переключатели
       установлены  соответствующим  образом, то в  регистры  помещаются
       6-битные значения.

          4.4.2 Рисование точки на экране (монохромный, цветной и PCjr).

          Вследствие  организации  графической информации в  видеобуфере
       вывод одной точки подразумевает изменение отдельных битов памяти.
       Режимы  двух,  четырех и шестнадцати цветов  требуют,  чтобы  для
       установки характеристик  одной  точки  были  изменены один, два и
       четыре бита соответственно. Эти операции могут требовать огромно-
       го количества процессорного  времени,  о  чем свидетельствует то,
       что  большинство графического программного  обеспечения  работает
       очень  медленно.  Тщательное  обдумывание  часто  позволяет сразу
       установить все биты одного байта, а не обращаться к одному и тому
       же байту 4 или 8 раз. Имейте это  ввиду, и не следуйте слепо при-
       веденной здесь технике поточечного вывода.

          Высокий уровень.

          Бейсик  предоставляет  операторы PSET и PRESET  для  изменения
       цвета отдельной точки. Эти имена образованы от PointSET (установ-
       ка точки) и PointRESET (сброс точки). Они очень похожи. За обоими
       должны следовать координаты  столбца и строки, указываемой точки,
       заключенные в скобки.  Отметим, что координаты следуют в  порядке
       x,y - т.е. сначала  идет  столбец,  а  затем строка; этот порядок
       обратный  по отношению к порядку оператора LOCATE, который  пози-
       ционирует текст на экране. PSET(50,80) или PRESET(50,80) устанав-
       ливают  цвет точки в столбце 50 и строке 80.  За оператором  PSET
       может следовать код цвета,  который лежит в диапазоне, определяе-
       мом текущим режимом экрана.  Если код цвета не указан, то исполь-
       зуется максимальный  номер  кода,  который  допустим  для данного
       режима. В PRESET цвет не указывается.  Он всегда возвращает точке
       цвет фона (код 0). Например:

       100 PSET(100,180),3   'установка цвета 3 текущей палетты
       110 PRESET(100,180)   'изменение цвета точки на фоновый

          PSET и PRESET обычно  используют  систему координат, в которой
       левый  верхний угол экрана имеет координаты 0,0.  Оператор WINDOW
       позволяет Вам переопределить систему координат так, что например,
       координаты  левого верхнего угла будут -100,100, центра экрана  -
       0,0, а правого нижнего угла  -  100,-100.  Для  этого случая надо
       


                                     -140-
       записать  оператор  в виде  WINDOW(-100,100)-(100,-100).   (Новые
       координаты не будут  влиять  на   систему  координат  25*80  (или
       25*40), в которой оператор LOCATE позиционирует символы на графи-
       ческом экране [4.2.1].)
          Как и в операторе  LINE  [4.4.5],  первое  число каждой пары в
       скобках указывает горизонтальную координату (по оси x). Координа-
       ты могут быть как  положительными,  так и отрицательными, лишь бы
       они  не  были равными.  Левому краю экрана  всегда  присваивается
       меньшее число (которое может быть  большим отрицательным).  Таким
       образом,  даже если Вы поменяете координаты в примере и  запишете
       оператор   WINDOW(100,-100)-(-100,100),   то  значение -100 будет
       взято для левой границы экрана.
          Второе  число каждой пары координат определяет границы  экрана
       по вертикали. И опять, меньшее значение будет относиться к нижней
       границе  экрана,  независимо от того, в какой паре координат  оно
       указано.  Большее  положительное  значение  (или  меньшее из двух
       отрицательных) присваивается в качестве значения оси y для  верх-
       ней строки экрана.  Направление  увеличения  значений  может быть
       обращено, с тем чтобы максимальные значения соответствовали  низу
       экрана и наоборот. Надо просто добавить к оператору слово SCREEN,
       например, WINDOW SCREEN(-100,100)-(100,-100).
          Программа  может  указывать  точки, относящиеся к  области  за
       пределами координат  экрана.   Например,  центр  окружности может
       находиться  за  пределами экрана, с тем чтобы видна  была  только
       часть дуги.  Отметим, что координаты, указываемые оператором WIN-
       DOW  могут непрерывно изменяться при изменении масштаба или  угла
       зрения на объект. Изображение  должно  перерисовываться, а иногда
       стираться, при изменении координат окна.
          Оператор  PMAP  преобразует координаты от  обычной  физической
       системы координат к "мировой" системе, устанавливаемой оператором
       WINDOW. PMAP использует четыре кодовых номера:

          0         преобразует x из "мировой" системы в физическую
          1         преобразует y из "мировой" системы в физическую
          2         преобразует x из физической системы в "мировую"
          3         преобразует y из физической системы в "мировую"

       Оператор  имеет форму PMAP(позиция,код).  Например,  предположим,
       что Вы установили систему  "мировых" координат оператором WINDOW.
       Координаты  левого  верхнего  угла экрана  (-100,100), а  правого
       нижнего - (100,-100). Какая будет позиция центральной точки экра-
       на (0,0) при использовании обычной физической системы 320*200,  в
       которой левый верхний  угол  имеет  координаты 0,0? Чтобы найти X
       напишите X = PMAP(0,0), а Y - напишите Y = PMAP(0,1). Вы получите
       значение X = 160, а Y = 100.

          Средний уровень.

          Функция CH прерывания 10H  устанавливает  точку.   DX содержит
       строку,  а CX - столбец, оба отсчитываемые от 0.  Код цвета поме-
       щается в AL.  Отметим, что  содержимое AX будет разрушено при вы-
       полнении прерывания.  Если Вы используете это прерывание в цикле,
       то не забудьте сохранить AX на стеке и каждый раз восстанавливать
       его.

       ;---вывод точки с координатами 100,180
          MOV  AH,0CH        ;функция установки точки
          MOV  AL,3          ;выбираем цвет 3 палетты
          MOV  CX,100        ;строка
          MOV  DX,180        ;столбец
          INT  10H           ;выводим точку
       ;---стираем точку
          MOV  AH,0CH        ;восстанавливаем функцию
       


                                     -141-
          MOV  AL,0          ;используем для стирания фоновый цвет
          MOV  DX,100        ;строка
          MOV  CX,180        ;столбец
          INT  10H           ;стираем точку

          В  то  время как цвет палетты помещается в  младшие  биты  AL,
       старший бит также имеет значение.  Если он равен 1, то над цветом
       производится  операция исключающего ИЛИ с текущим цветом.  Напом-
       ним, что операция исключающего ИЛИ устанавливает бит только в том
       случае  если из двух сравниваемых битов установлен  только  один.
       Если оба сравниваемые бита равны  1 или оба равны 0, то результат
       будет 0. Для двухцветного режима это означает, что такая операция
       обращает установку  бита.  Если  эту  операцию  применить ко всем
       точкам экрана, то будет обращен весь экран. В четырех- и 16-цвет-
       ном режимах, с другой стороны,  области  экрана могут менять свои
       цвета.  Например, пусть в 4-цветном режиме умеренного  разрешения
       область занята точками либо цвета 1 палетты (установка битов 01B)
       или цвета 2 палетты (10B). Что произойдет, если применить ко всем
       точкам этой области операцию исключающего ИЛИ с 11B? 01B перейдет
       в 10B, а 10B перейдет в 01B - цвета будут обращены.

          Низкий уровень.

          На низком уровне мы имеем возможность прямого доступа к видео-
       буферу (отображение в память). Сначала Вы должны вычислить смеще-
       ние точки (а) внутри буфера и (б) внутри байта, содержащего биты,
       относящиеся к данной точке.  После этого битовые операции обеспе-
       чат соответствующую установку.   Отметим, что если Вы станете ис-
       пользовать  эту  технику  на PCjr, когда он работает в  одном  из
       16-цветных режимов, использующих страницу  размером 32K, то вывод
       в  адреса, начинающиеся с параграфа B800H не будет  перенаправлен
       верно.  Вам необходимо прямо адресовать реальные ячейки, располо-
       женные в сегменте ниже 2000H.
          Для  нахождения точки необходимо прежде всего определить нахо-
       дится ли она в четной или нечетной строке. В данном примере стро-
       ка помещена в CX, а столбец - в DX.  Если бит 0 регистра CX равен
       0, то строка имеет четный  номер.   Четные  строки расположены со
       смещением  0  относительно начала буфера.  Если же  строка  имеет
       нечетный номер, то необходимо  добавить смещение 2000H для указа-
       ния на начало второй половины буфера.
          Затем разделите номер строки на 2, необходимо подсчитать число
       только четных или нечетных строк и умножьте результат на 80, т.к.
       на одну строку расходуется 80 байт.  Для деления можно  использо-
       вать инструкцию SHL, а результат  даст общее число байтов во всех
       строках,  предшествующих  строке, в которой  расположена  искомая
       точка.
          Вместо того, чтобы  затем  вычислять  число столбцов в текущей
       строке,  лучше  сначала  определить позицию пары  битов в  байте,
       которые содержат эту точку. Это достигается обращением всех битов
       в номере столбца (после того как сохранена его копия) и выделения
       двух младших битов.  Эта процедура покажет находятся ли два бита,
       относящиеся  к  точке  на первой, второй, третьей  или  четвертой
       позиции в байте.  Умножив это  значение  на 2 мы получаем номер в
       байте первого из двух битов, относящихся к данной точке.
          Затем  приходит время подсчитать число байтов в строке,  пред-
       шествующих байту, содержащему итнформацию  о требуемой точке. Для
       режима умеренного разрешения надо разделить число столбцов на  4,
       а для высокого разрешения - на  8.   После этого надо сложить три
       смещения:  смещение за счет номера строки, за счет номера столбца
       и смещение начала четных/нечетных  строк в буфере. После этого Вы
       можете получить требуемый байт из буфера.
          Наконец, надо произвести операцию над соответствующими  битами
       байта. Вращайте байт до  тех  пор,  пока пара битов относящихся к
       


                                     -142-
       точке  не станет младшими.  При вращении необходимо  использовать
       ранее подсчитанное значение  позиции  битов.  Затем выключите оба
       бита поместите в них инструкцией OR требуемый код палетты.  Затем
       надо произвести обратное вращение и послать байт обратно в буфер.

       ;---в сегменте данных
       PALETTE_COLOR  DB   2

       ;---вызов процедуры
          MOV  AX,0B800H         ;указываем на видеобуфер
          MOV  ES,AX             ;
          MOV  CX,100            ;номер строки
          MOV  DX,180            ;номер столбца
          CALL SET_DOT           ;
           .
           .

       ;---определяем число байтов в предшествующих строках
       SET_DOT     PROC
                   TEST CL,1              ;номер строки нечетный?
                   JZ   EVEN_ROW          ;если нет, то вперед
                   MOV  BX,2000H          ;смещение для нечетных строк
                   JMP  SHORT CONTINUE    ;переход вперед
       EVEN_ROW:   MOV  BX,0              ;смещение для четных строк
       CONTINUE:   SHR  CX,1              ;делим число строк на 2
                   MOV  AL,80             ;умножаем на 80
                   MUL  CL                ;в AX - число байтов
       ;---определяем положение пары бит в байте
                   MOV  CX,DX             ;копируем номер столбца
                   NOT  CL                ;обращаем биты
                   AND  CL,00000011B      ;в CL - позиция битов (0-3)
                   SHL  CL,1              ;позиция первого бита пары
       ;---подсчитываем смещение столбца в байтах
                   SHR  DX,1              ;делим номер столбца на 4
                   SHR  DX,1              ;(нужны два младших бита)
       ;---вычисляем смещение для изменяемого байта
                   ADD  AX,DX             ;складываем все три смещения
                   ADD  BX,AX             ;
       ;---изменяем биты нужного байта
                   MOV  AH,ES:[BX]        ;читаем нужный байт
                   ROR  AH,CL             ;сдвигаем нужные биты вниз
                   AND  AH,11111100B      ;чистим младшие 2 бита
                   MOV  AL,PALETTE_COLOR  ;изменяем их на цвет палетты
                   OR   AH,AL             ;
                   ROL  AH,CL             ;обратное вращение
                   MOV  ES:[BX],AH        ;возвращаем байт
                   RET                    ;
       SET_DOT     ENDP

          4.4.3 Рисование точки на экране (EGA).

          У EGA графика более сложная. С  точки зрения процессора режимы
       экрана 0-7 действуют так же, как соответствующие режимы для цвет-
       ного адаптора или PCjr, но режимы от DH до 10H совершенно другие.
       Организация  памяти  для этих режимов меняется, в зависимости  от
       числа используемых цветов и количества памяти, имеющейся на плате
       дисплея. Смотрите рис. 4-4 в [4.4.0].
          В  режимах D, E и 10H память разбита на 4  битовые  плоскости.
       Каждая плоскость организована таким же образом, как для черно-бе-
       лого режима высокого разрешения цветного адаптора, который обсуж-
       дался в [4.4.2]:  когда  байт  данных  посылается  в определенный
       адрес  видеобуфера, то каждый бит соответствует точке на  экране,
       причем весь байт соответствует  горизонтальному сегменту линии, а
       


                                     -143-
       бит  7 соответствует самой левой точке.  Выводятся  четыре  таких
       битовых плоскости, относящиеся к  одним и тем же адресам в видео-
       буфере. Это приводит к тому, что каждая точка описывается четырь-
       мя битами (давая 16 цветов), причем каждый бит находится вотдель-
       ном байте отдельной битовой плоскости.
          Но  как Вы можете записать 4 различных байта данных,  располо-
       женных по одному и тому же адресу? Ответ на этот вопрос состоит в
       том,  что  Вы не посылаете последовательно четыре байта по  этому
       адресу.  Вместо этого один из трех режимов записи позволяет изме-
       нить все 4 байта, на основании одного байта данных полученного от
       процессора. Влияние данных посланных процессором зависит от уста-
       новки  нескольких регистров, включающих два регистра маски, кото-
       рые определяют на какие биты и  в  каких битовых плоскостях будут
       изменяться биты.
          Для  понимания этих регистров мы должны сначала разобраться  с
       четырьмя регистрами задвижки (latch register).  Они содержат дан-
       ные для четырех битовых плоскостей в той позиции, к которой  было
       последнее обращение. (Заметим,  что  термин битовая плоскость ис-
       пользуется как для целой области видеобуфера, так и для однобайт-
       ного буфера,  временно  хранящегося  в  регистре задвижки.) Когда
       процессор  посылает данные по определенному адресу, то эти данные
       могут изменить или полностью сменить  данные регистра задвижки, а
       впоследствии  именно данные из регистра задвижки  записываются  в
       видеобуфер. Каким  образом  данные  процессора  влияют на регистр
       задвижки зависит от используемого режима записи, а также от уста-
       новки некоторых других регистров. При чтении адреса из видеобуфе-
       ра  регистры  задвижки заполняются четырьмя  байтами  из  четырех
       битовых плоскостей по данному адресу.   Регистрами задвижки легко
       манипулировать,  производя  их  содержимым  различные  логические
       операции, что позволяет устраивать различные графические трюки.
          Регистр маски битов и регистр маски карты действуют на регист-
       ры  задвижки, защищая определенные биты или битовые плоскости  от
       изменения под действием данных,  поступающих  от процессора.  Ре-
       гистр  маски  битов  это регистр только для записи,  адрес  порта
       которого 3CFH.  Сначала надо послать 8 в порт 3CEH, чтобы указать
       на этот регистр. Установка бита этого регистра в 1 маскирует этот
       бит во всех четырех  битовых  плоскостях,  делая  соответствующую
       точку  недоступной для изменения.  Однако, поскольку оборудование
       работает  в  байтовых  терминах, то реально  "неизменяемые"  биты
       перезаписываются в  четыре  битовые  плоскости.  Данные  для этих
       маскируемых битов хранятся в регистрах задвижки, поэтому програм-
       ма должна быть уверена, что текущее содержимое регистров задвижки
       относится  к  правильному адресу памяти.  По этой  причине  перед
       записью по данному адресу надо считывать из него.
          Регистр маски карты имеет адрес порта 3C5H. Этот регистр толь-
       ко для записи. Перед посылкой данных надо послать по этому адресу
       2 как указатель. Биты 0-3  этого  регистра  соответствуют битовым
       плоскостям  0-3; старшие 4 бита регистра не используются.   Когда
       биты 0-3 равны 0, то  сответствующие  битовые  плоскости не изме-
       няются при операциях записи. Это свойство используется по-разному
       в различных режимах записи, как Вы увидите в дальнейшем.
          Три режима записи  устанавливаются  регистром  режима, который
       является  регистром  только для записи, а адрес  порта  для  него
       3CFH, который  индексируется  предварительной  засылкой  5 в этот
       порт.  Режим записи устанавливается в битах 0 и 1, как число от 0
       до 2.  Бит 2 должен быть равным  0, так же как и биты 4-7.  Бит 3
       устанавливает  один из двух режимов чтения из видеобуфера.   Этот
       бит может быть 0 или 1. BIOS EGA устанавливает режим записи в 00.

          Режим записи 0:
          В простейшем случае режим записи  0 копирует данные процессора
       в каждую из четырех битовых плоскостей.  Например, пусть по опре-
       деленному адресу видеобуфера  послано  11111111B  и разрешены все
       


                                     -144-
       биты и все битовые плоскости (т.е. ничто не маскировано описанны-
       ми выше регистрами масок). Тогда каждый бит во всех четырех плос-
       костях будет установлен в 1, так что цепочка битов для каждой  из
       соответствующих точек  будет  1111B.   Это  означает, что 8 точек
       будут выведены в цвете 15, который изначально соответствует ярко-
       белому цвету, хотя  регистры  палетты  позволяют,  чтобы на самом
       деле это был любой из допустимых цветов.
          Теперь  рассмотрим  тот  же  случай,  но  посылается  значение
       00001000B. Цепочка битов для  точки 3 будет 1111, а для остальных
       - 0000, что соответствует черному (изначально).  Поэтому в данном
       случае только точка 3 появится на экране (яркобелая), а остальные
       7  точек будут выключены.  Даже если остальные 7 точек перед этим
       выводились в каком-то цвете, то теперь  все они будут переключены
       на 0000.
          Теперь  рассмотрим другие цвета, кроме 1111B.  Если Вы пошлете
       код палетты желаемого цвета  в  регистр  маски  карты, то регистр
       маскирует определенные битовые плоскости таким образом, что будет
       воспроизведен требуемый  цвет.  Например,  если  Вы хотите цвет с
       кодом 0100, то пошлите 0100 в регистр маски карты.  Тогда битовые
       плоскости 0, 1 и 3 не будут изменяться.  Когда Вы пошлете по нуж-
       ному  адресу 11111111B, то это значение будет помещено  только  в
       битовую плоскость 2 и цепочка  битов для каждой точки будет 0100.
       Если Вы пошлете по этому адресу 00001000B, то точка 3 будет иметь
       цепочку битов 0100, а остальные точки - 0000.
          Имеется, однако, одна сложность. Регистр маски карты запрещает
       изменение битовых плоскостей, но не обнуляет их. Предположим, что
       битовая плоскость 0 была заполнена единицами, а битовые плоскости
       1  и 3 были заполнены нулями.  Если Вы запретите изменения в этих
       трех плоскостях, а затем пошлете 11111111B по определенному адре-
       су,  то битовая плоскость 2 будет заполнена 11111111B, а  битовая
       плоскость 0 сохранит  свои  единицы,  поэтому  результирующий код
       цвета  каждой точки станет 0101B.  Встречаются случаи, когда  это
       свойство можно использовать для изменения цветов экрана. Но вооб-
       ще говоря, необходимо очищать  все четыре битовые плоскости (т.е.
       все  четыре  регистра задвижки) перед тем, как писать туда  любые
       цвета кроме 1111B или 0000B.   Это  делается просто посылкой 0 по
       указанному  адресу.  Необходимо чтобы при этом была разрешена за-
       пись во все четыре битовые плоскости.
          Вышеприведенное  обсуждение   касалось  одновременного  вывода
       восьми точек.  Ну а как вывести меньшее количество точек? В  этом
       случае, конечно,  необходимо  сохранить  существующие  данные для
       некоторых  точек,  а чтобы это было возможно  текущее  содержимое
       данного адреса сохраняется в  регистрах  задвижки.  Затем исполь-
       зуется регистр маски битов для маскирования тех точек, которые не
       должны изменяться. Если бит этого регистра сброшен в 0, то данные
       получаемые от процессора для этого бита игнорируются и вместо них
       используются данные, хранящиеся  в  регистрах  задвижки. Равен ли
       этот бит в данных процессора 0 или 1 - не имеет значения; если Вы
       изменяете только бит 2, а все  остальные  маскированы, то данные,
       которые  приходят от процессора могут быть 0FFH или 4H, или любое
       другое значение, для которого бит  2 установлен. Если бит 2 сьро-
       шен,  то 0 помещается в этой позиции во всех разрешенных  битовых
       плоскостях.
          Вообще говоря, программа должна  сначала прочитать любую ячей-
       ку, в которую она собирается записать меньше чем 8 точек. Имеются
       два режима чтения (обсуждаемые в [4.4.4])  и безразлично какой из
       них выбран.  Операция чтения загружает регистры задвижки четырьмя
       байтами данных для данного  адреса  памяти.  Данные, возвращаемые
       процессору операцией чтения, могут быть отброшены.
          До  сих пор были рассмотрены самые простые возможности  режима
       записи 0. При  желании  Вы  можете  делать  намного более сложные
       манипуляции. Одна из возможностей состоит в модификации регистров
       задвижки с помощью логических операций перед записью.  Для реали-
       


                                     -145-
       зации этой возможности регистр вращения данных использует следую-
       щие биты:

          биты 2-0        число вращений
               4-3        00     данные не модифицируются
                          01     логическое И с регистром задвижки
                          10     логическое ИЛИ с регистром задвижки
                          11     исключающее ИЛИ с регистром задвижки
               7-5        не используются
          Число  вращений,  которое  может  быть  от  0 до 7, показывает
       сколько битов данных должны вращаться перед тем, как поместить их
       в регистр задвижки.  Обычно это значение равно нулю.  Аналогично,
       биты 4-3, как правило равны 00, кроме случаев, когда производятся
       логические операции. За счет  манипуляций с этим регистром одни и
       те  же данные могут давать различные цвета и изображения без  до-
       полнительной  процессорной  обработки.   Регистр  вращения данных
       индексируется  посылкой 3 в порт 3CEH; затем данные посылаются  в
       3CFH.
          Наконец, режим записи 0 может  работать совсем по-другому если
       разрешены  установка/сброс.  В этом случае определенные  цвета  в
       младших четырех битах  регистра  установки/сброса  (который  тоже
       имеет адрес порта 3CFH, а индексируется посылкой 0 в 3CEH).  Име-
       ется соответствующий регистр разрешения установки/сброса, который
       разрешает  любой из этих четырех битов, устанавливая свои младшие
       биты в 1. Когда все 4 бита в регистре установки/сброса разрешены,
       то они помещаются во все 8 адресов битовой плоскости при  получе-
       нии данных от процессора, при  этом сами данные процессора отбра-
       сываются.  Если разрешены не все биты установки/сброса, то данные
       процессора помещаются для запрещенных точек. Отметим, что регистр
       маски битов запрещает запись данных установки/сброса в определен-
       ные точки, но  установка  регистра  маски  карты игнорируется при
       использовании установки/сброса.  BIOS инициализирует регистр раз-
       решения установки/сброса в  0,  так  что  он неактивен. Его адрес
       порта 3CFH, а индексируется он посылкой 1 в порт 3CEH.

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

       Режим записи 2:
          Режим  записи 2 предоставляет альтернативный способ  установки
       отдельных точек.  Процессор посылает данные, у которых имеют зна-
       чение  только  4 младших бита, которые рассматриваются  как  цвет
       (индекс регистра палетты).  Можно  сказать, что эта цепочка битов
       вставляется  поперек битовых плоскостей.  Цепочка дублируется  на
       все восемь точек, относящихся к  данному  адресу, до тех пор пока
       регистр маски битов не предохраняет определенные точки от измене-
       ния. Регистр маски карты активен, как и в режиме записи 0. Конеч-
       но процессор должен послать полный байт, но только младшие 4 бита
       существенны.

          Высокий уровень.

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


                                     -146-
       который начинается с  адреса  A000:0000.  Самая  тяжелая проблема
       состоит  в установке режима дисплея.  Для ее решения  используйте
       следующую процедуру на машинном языке:

       10 S$ = CHR$(&H2A)+CHR$(&HE4)+CHR$(&HB0)+CHR$(&H0D)
               +CHR$(&HCD)+CHR$(&H10)+CHR$(&HCB)
       20 DEF SEG                  'установка сегмента
       30 Y = VARPTR(S$)           'указатель на строку
       40 Z = PEEK(Y+1)+PEEK(Y+2)*256  'вычисление адреса строки
       50 CALL Z                   'вызов процедуры
       Четвертый байт S$ содержит номер режима, в данном случае режим D.
       Вы можете выбрать другой режим.   В  приложении Г объясняется как
       эта  процедура работает в Бейсике.  Она полностью завершенная, не
       нужно никакой побочной памяти,  в  которой содержался бы машинный
       код.   Не  забудьте восстановить режим дисплея  после  завершения
       своих манипуляций.
          Затем надо установить  соответствующий  режим  записи. Вот как
       устанавливается режим записи 2:

       50 OUT &H3CE,5         'индексируем регистр режима записи
       60 OUT &H3CF,2         'выбираем режим 2

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

       Режим записи 0:

       100 'рисуем красную точку в левом верхнем углу экрана
       110 DEF SEG = &HA000     'указываем на видеобуфер
       120 OUT &H3CE,8          'адресуем регистр маски битов
       130 OUT &H3CF,128        'маскируем все биты, кроме седьмого
       140 X = PEEK(0)          'читаем текущее значение в задвижку
       150 POKE 0,0             'чистим
       160 OUT &H3C4,2          'адресуем регистр маски карты
       170 OUT &H3C5,4          'устанавливаем красный цвет
       180 POKE 0,&HFF          'рисуем точку

       Режим записи 1:

       100 'копируем верхнюю строчку точек в следующую
       110 DEF SEG = &HA000     'указываем на видеобуфер
       120 FOR N = 0 TO 79      'для всех 80 байтов строки
       130 X = PEEK(N)          'заполняем задвижки
       140 POKE N+80,Y          'копируем в следующую строку
       150 NEXT                 'переходим к следующему сегменту

       Режим записи 2:

       100 'рисуем красную точку в левом верхнем углу экрана
       110 DEF SEG = &HA000     'указываем на видеобуфер
       120 OUT &H3CE,8          'адресуем регистр маски битов
       130 OUT &H3CF,128        'маскируем все биты, кроме седьмого
       140 X = PEEK(0)          'читаем текущее значение в задвижку
       150 POKE 0,4             'посылаем красный цвет

          Средний уровень.

          EGA поддерживает стандартные графические функции BIOS.   Можно
       вывести точку с помощью функции CH прерывания 10H, так же как для
       цветного  дисплея или PCjr.  При входе DX должен содержать  номер
       строки, а CX - номер столбца, и  то  и другое отсчитывается от 0.
       


                                     -147-
       Код цвета помещается в AL.  Содержимое AX меняется при выполнении
       прерывания.

       ;---рисуем точку по адресу 50,100
          MOV  AH,0CH        ;функция вывода точки
          MOV  AL,12         ;выбираем регистр палетты 12
          MOV  CX,100        ;номер строки
          MOV  DX,50         ;номер столбца
          INT  10H           ;рисуем точку
          Низкий уровень.

          Ниже приведены примеры для  трех  режимов записи. Перед их ис-
       пользованием  необходимо  установить режим дисплея,  использующий
       видеобуфер с адреса A000:0000. Для этого можно использовать стан-
       дартную функцию BIOS, например, для установки режима D:

          MOV  AH,0       ;функция установки режима
          MOV  AL,0DH     ;выбираем режим D
          INT  10H        ;устанавливаем режим

       Не забудьте восстановить режим перед завершением программы. Кроме
       того, Вам необходимо установить требуемый  режим записи. Вот при-
       мер установки режима записи 2:

          MOV  DX,3CEH    ;указываем на регистр адреса
          MOV  AL,5       ;инедксируем регистр 5
          OUT  DX,AL      ;посылаем индекс
          INC  DX         ;указываем на регистр режима
          MOV  AL,2       ;выбираем режим записи 2
          OUT  DX,AL      ;устанавливаем режим

          И, наконец, примеры трех режимов записи:

       Режим записи 0:

       ;---рисуем красную точку в левом верхнем углу экрана
          MOV  AX,0A000H      ;указываем на видеобуфер
          MOV  ES,AX          ;
          MOV  BX,0           ;указываем на первый байт буфера
       ;---маскируем все биты, кроме седьмого
          MOV  DX,3CEH        ;указываем на адресный регистр
          MOV  AL,8           ;номер регистра
          OUT  DX,AL          ;посылаем его
          INC  DX             ;указываем на регистр данных
          MOV  AL,10000000B   ;маска
          OUT  DX,AL          ;посылаем данные
       ;---чистим текущее содержимое задвижки
          MOV  AL,ES:[BX]     ;читаем содержимое в задвижку
          MOV  AL,0           ;готовимся к очистке
          MOV  ES:[BX],AL     ;чистим задвижку
       ;---установка регистра маски карты для красного цвета
          MOV  DX,3C4H        ;указываем на адресный регистр
          MOV  AL,2           ;индекс регистра маски карты
          OUT  DX,AL          ;установка адреса
          INC  DX             ;указываем на регистр данных
          MOV  AL,4           ;код цвета
          OUT  DX,AL          ;посылаем код цвета
       ;---рисуем точку
          MOV  AL,0FFH        ;любое значение с установленным 7 битом
          MOV  ES:[BX],AL     ;выводим точку

       Режим записи 1:

       


                                     -148-
       ;---копируем строку в следующую строку
                 MOV  CX,80       ;число байтов в строке
                 MOV  BX,0        ;начинаем с 1-го байта буфера
                 MOV  AX,0A000H   ;адрес буфера
                 MOV  ES,AX       ;
       NEXT_BYTE:   MOV  AL,ES:[BX]   ;заполняем задвижку
                 MOV  ES:[BX]+80,AL   ;выводим в следующую строку
                 INC  BX          ;переходим к следующему байту
                 LOOP NEXT_BYTE   ;
       Режим записи 2:

       ;---рисуем красную точку в левом верхнем углу экрана
          MOV  AX,0A000H        ;адрес буфера
          MOV  ES,AX            ;
          MOV  BX,0             ;указываем на первый байт буфера
       ;---установка регистра маски битов
          MOV  DX,3CEH          ;указываем на адресный регистр
          MOV  AL,8             ;регистр маски битов
          OUT  DX,AL            ;адресуем регистр
          INC  DX               ;указываем на регистр данных
          MOV  AL,10000000B     ;маскируем все биты, кроме 7-го
          OUT  DX,AL            ;посылаем данные
       ;---рисуем красную точку
          MOV  AL,ES:[BX]       ;заполняем регистры задвижки
          MOV  AL,4             ;красный цвет
          MOV  ES:[BX],AL       ;рисуем точку

          4.4.4 Определение цвета точки экрана.

          Для графических режимов цветного адаптора или PCjr определение
       цвета точки на низком уровне состоит в обращении процедуры вывода
       точки:  программа  читает из видеобуфера и выделяет  интересующие
       биты.  Однако для EGA этот метод  непригоден, поскольку в режимах
       DH  - 10H каждому адресу памяти соответствует два или четыре бай-
       та.  EGA имеет два режима чтения, чтобы преодолеть эту трудность.
       Имейте  ввиду, что для PCjr и EGA, после того, как Вы  определили
       код цвета точки, необходимо еще  проверить установку текущего ре-
       гистра  палетты  для этого кода, чтобы определить какой цвет  ему
       приписан.
          Любой язык программирования имеет доступ к двум режимам чтения
       EGA.   В режиме 0 возвращается байт, содержащийся во всех четырех
       битовых плоскостях, по указанному  адресу. Режим 1 ищет указанный
       код цвета и возвращает байт, в котором бит установлен в 1,  когда
       соответствующая точка имеет  данный  цвет.  Бит 3 регистра режима
       определяет какой режим чтения установлен (0 = режим 0).  Доступ к
       этому регистру осуществляется через порт 3CFH и Вы должны предва-
       рительно послать 5 в порт 3CEH, чтобы выбрать этот регистр. Обыч-
       но все остальные биты этого  регистра,  который  можно только пи-
       сать,  сброшены в 0, кроме битов 0 и 1, которые определяют  режим
       записи. Поскольку при инициализации BIOS устанавливает эти биты в
       режим  записи  0 (так что они оба равны 0), то обычно  Вам  нужно
       просто послать в этот регистр 0, чтобы  установить режим чтения 0
       и послать 8, чтобы установить режим чтения 1.
          Режим  чтения  0 требует, чтобы Вы  предварительно  установили
       регистр выбора карты.  Единственная задача этого регистра - уста-
       новить, какая из карт битов должна быть прочитана. Поэтому в него
       надо послать число от 0 до 3. Этот регистр имеет адрес порта 3CFH
       и  надо предварительно послать 4 в порт 3CEH, чтобы указать  этот
       регистр.
          Режим чтения 1 более сложен.  Сначала регистр сравнения цветов
       должен  быть  заполнен цепочкой битов для кода цвета, который  Вы
       ищете.  Этот код помещается в  младшие 4 бита регистра; старшие 4
       бита - несущественны. Этот регистр имеет адрес порта 3CFHи указы-
       


                                     -149-
       вается предварительной засылкой 2 в порт 3CEH. После чтения ячей-
       ки памяти возвращается байт, который имеет биты установленные в 1
       для каждой точки, имеющей нужный цвет. Однако за счет использова-
       ния  регистра безразличия цвета (color don't care register)  один
       или более битов кода  цвета  могут  при сравнении игнорироваться.
       Обычно  4 младших бита этого регистра установлены в 1;  обнуление
       одного из этих битов приведет к  тому,  что содержимое соответст-
       вующей  битовой  плоскости будет игнорироваться.  Например,  если
       цепочка битов для точки 3 (бит 3) по указанному адресу равна 0110
       и регистр сравнения цветов содержит значение 0010, то при сравне-
       нии будет возвращен байт, у  которого  бит  3 равен 0, если в ре-
       гистре безразличия цветов все биты равны 1.  Но если регистр без-
       различия цветов содержит 1011, то в байте, возвращаемом процессо-
       ру бит 3 будет равен 1.
          Регистр  безразличия цветов имеет адрес порта 3CFH и  индекси-
       руется засылкой 7 в порт 3CEH. Старшие 4 его бита не играют ника-
       кой  роли.  Отметим, что документация IBM (от 2 августа 1984  г.)
       утверждает что регистр действует обратным  образом, т.е., что 1 в
       регистре заставляет операцию сравнения игнорировать соответствую-
       щую битовую плоскость. Эксперимент показывает обратное.
          Ни один из этих двух  режимов  чтения  не  может  дать быстрый
       ответ  на  вопрос о цвете определенной точки.  В режиме чтения  0
       необходимы 4 отдельных чтения, по одному для каждой битовой плос-
       кости, после чего надо еще выделить соответствующие биты из  каж-
       дого байта. В режиме чтения 1,  с  другой стороны, может потребо-
       ваться до 16 чтений, прежде чем для требуемой точки будет возвра-
       щен установленный  бит,  указывающий  что  эта точка имеет данный
       цвет.  Но хотя EGA относительно медленно выполняет данную задачу,
       зато для других целей он работает очень быстро.

          Высокий уровень.

          Бейсик предоставляет функцию  POINT,  которая  возвращает цвет
       точки. Цвет палетты точки, находящейся в столбце 200 и строке 100
       находится путем Q = POINT(200,100).  Значение, возвращаемое в Q -
       это обычный кодовый номер цвета.  Если указана точка, находящаяся
       за пределами экрана,  то  функция  POINT  возвращает значение -1.
       Когда  координатная система экрана изменяется  оператором  WINDOW
       [4.4.2], то функция POINT переходит к новой системе.
          POINT может также сообщить позицию последней выведенной точки.
       При  использовании  обычной координатной системы, в  которой  0,0
       соответствует левому верхнему  углу  экрана, Q = POINT(1) возвра-
       щает в Q x-координату точки, а Q = POINT(2) - y-координату.  Если
       действует оператор WINDOW, то Q = POINT(3) и Q = POINT(4) возвра-
       щает x- и y-координаты в новой системе.  Когда нет активного опе-
       ратора WINDOW, то последние два оператора действуют так же, как и
       первые два.
          К  моменту выхода этой книги Бейсик не поддерживал  улучшенные
       графические режимы EGA (D-10H).  В  этих режимах программа должна
       прямо  читать  содержимое видеобуфера.  Вот пример  использования
       режима чтения 1 для поиска кодов цветов 0001 и 1001:

       100 OUT &H3CE,5         'адрес регистра режима
       110 OUT &H3CF,8         'устанавливаем режим чтения 0
       120 OUT &H3CE,2         'адрес регистра сравнения цветов
       130 OUT &H3CF,1         'ищем цвет 0001
       140 OUT &H3CE,7         'адрес регистра безразличия цветов
       150 OUT &H3CF,7         '7 = 0111B, поэтому м. б. 0001 и 1001
       160 DEF SEG = &HA000    'адрес видеобуфера для EGA
       170 X = PEEK(0)         'читаем первый байт
       180 IF X <> 0 THEN...   '..то цвет 0001 или 1001 найден

          Средний уровень.
       


                                     -150-
          Функция D прерывания 10H возвращает код цвета указанной точки.
       BIOS  имеющийся на плате EGA обеспечивает, что эта функция  рабо-
       тает в любом режиме дисплея.  Надо поместить номер строки (отсчи-
       тываемый от 0) в DX, а номер столбца (также отсчитываемый от 0) -
       в CX. Результат возвращается в AL.
       ;---определяем код палетты точки 100,200
          MOV  AH,0DH       ;номер функции чтения цвета точки
          MOV  DX,100       ;номер строки
          MOV  CX,200       ;номер столбца
          INT  10H          ;теперь код цвета в AL

          Низкий уровень.

          Для графических режимов  цветного  адаптора и PCjr надо просто
       обратить процесс прямого отображения в память, которым устанавли-
       вается цвет точки,  как  показано  в  [4.4.2].  Можно испоьзовать
       приведенный там пример, который надо завершить следующим кодом:

       ;---изменение битов (место для вставки изменений)
          MOV  AH,ES:[BX]     ;берем байт из нужной позиции
          ROR  AH,CL          ;сдвигаем 2 нужных бита вниз
          AND  AH,00000011B   ;выключаем остальные биты
          RET                 ;теперь в AH - код палетты

          Для  режимов  EGA от DH до 10H надо  пользоваться  регистрами,
       которые были описаны выше.   В  следующем  примере режим чтения 0
       испоьзуется для чтения битовой плоскости 2 по адресу A000:0012.

       ;---установка режима чтения
          MOV  DX,3CEH       ;индексный регистр
          MOV  AL,5          ;сначала адресуем регистр режима
          OUT  DX,AL         ;посылаем индекс
          INC  DX            ;указываем на сам регистр
          MOV  AL,0          ;устанавливаем режим чтения 0
          OUT  DX,AL         ;
       ;---установка битовой плоскости, которую будем читать
          DEC  DX            ;назад к индексному регистру
          MOV  AL,4          ;адрес регистра выбора карты
          OUT  DX,AL         ;посылаем индекс
          INC  DX            ;указываем на сам регистр
          MOV  AL,2          ;запрос битовой плоскости 2
          OUT  DX,AL         ;посылаем значение
       ;---чтение битовой плоскости
          MOV  AX,0A000H     ;адрес видеобуфера
          MOV  ES,AX         ;
          MOV  BX,12         ;смещение в буфере
          MOV  AL,ES:[BX]    ;читаем из битовой плоскости 2
       И,  наконец, пример поиска кодов цвета 0010 и 1010 с  использова-
       нием режима чтения 1:

       ;---установка режима чтения
          MOV  DX,3CEH       ;регистр индекса
          MOV  AL,5          ;адресуем сначала регистр режима
          OUT  DX,AL         ;посылаем индекс
          INC  DX            ;указываем на сам регистр
          MOV  AL,8          ;устанавливаем бит 3 для режима 1
          OUT  DX,AL         ;устанавливаем режим
       ;---установка регистра сравнения цветов
          DEC  DX            ;возвращаемся к индексному регистру
          MOV  AL,2          ;адрес регистра сравнения цветов
          OUT  DX,AL         ;посылаем индекс
          INC  DX            ;указываем на сам регистр
          MOV  AL,0010B      ;код цвета
       


                                     -151-
          OUT  DX,AL         ;посылаем код
       ;---установка регистра безразличия цветов
          DEC  DX            ;возвращаемся к индексному регистру
          MOV  AL,7          ;адрес регистра безразличия цветов
          OUT  DX,AL         ;посылаем индекс
          INC  DX            ;указываем на сам регистр
          MOV  AL,0111B      ;принимаем коды 1010 или 0010
          OUT  DX,AL         ;посылаем данные
       ;---поиск цвета
          MOV  AX,0A000H     ;адрес видеобуфера
          MOV  ES,AX         ;
          MOV  BX,12         ;смещение в буфере
          MOV  AL,ES:[BX]    ;читаем позицию буфера
          CMP  AL,0          ;установлены биты?
          JNZ  FOUND_IT      ;если да, то ищем у какой точки

          4.4.5 Рисование линий на экране.

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

          Высокий уровень.

          Бейсик позволяет  рисовать  прямые  линии  с помощью оператора
       LINE. LINE (20,10)-(40,30) рисует линию от столбца 20 и строки 10
       к столбцу 40 и строке 30. И  строки и столбцы нумеруются от нуля.
       Вы  можете опустить координаты первой точки, в этом случае  линия
       будет начинаться с последней  точки,  которая была ранее выведена
       графическим  оператором.  Вторая пара координат может  задаваться
       также относительно первой, с помощью конструкции LINE -STEP(xoff-
       set,yoffset).
          Оператор  LINE может указывать также цвет и стиль линии.   Код
       цвета следует сразу за списком координат;  LINE (50,50)-(60,60),2
       выводит  линию  цветом 2.  Когда цвет не указан, то по  умолчанию
       берется цвет 3.  Возможность выбора стиля линии предполагает ука-
       зание чередования ее точек. Образец может даваться как в десятич-
       ной, так  и   в   шестнадцатиричной   форме.   Например,  образец
       1010101010101010, который соответствует &HAAAA, дает линию, точки
       которой имеют по очереди данный цвет и фоновый. Стиль линии опре-
       деляется  третьим  параметром после  координат.   Например,  LINE
       (30,30)-(40,40),3,,&HAAAA выводит линию с указанным стилем цветом
       3.
          Бейсик предоставляет также процедуры для рисования прямоуголь-
       ников и окружностей. Прямоугольники выводятся с помощью оператора
       LINE. В данном случае координаты должны описывать левый верхний и
       правый нижний угол рамки. Надо просто указать B (box - т.е.  рам-
       ка)   в  качестве  второго  параметра  за   координатами.    LINE
       (50,50)-(100,100),1,B,&HAAAA  рисует квадрат со стороной 50 точек
       цветом 1 палетты, используя вышеописанный стиль.  Для вывода пря-
       моугольника, заполненного  определенным  цветом надо использовать
       параметр BF (при этом стиль линии указывать не надо).
          Окружности рисуются оператором CIRCLE.  Их вывод  основывается
       на формуле CIRCLE (x,y),r,цвет,нач-угол,кон-угол,аспект.  Коорди-
       наты  x,y  дают адрес центра окружности на экране, а  r -  радиус
       окружности в точках; вся остальная информация необязательна. Цвет
       -  это  код цвета, который по умолчанию берется  равным 3.   Если
       необходимо  вывести  только  дугу  окружности,  то  можно указать
       


                                     -152-
       нач-угол и кон-угол (когда они опущены, то выводится целая окруж-
       ность). Углы измеряются как  положительные  или отрицательные ве-
       личины,  отсчитываемые от направления по горизонтали вправо.  Они
       измеряются в радианах (в 360 градусах  содержится 6.292 радиан, а
       один  градус = 0.0174532 радиан).  Аспект это отношение  горизон-
       тальных и вертикальных размеров. Круглая окружность получается на
       дисплее,  когда Вы укажете его равным 5/6 для умеренного разреше-
       ния и 5/12 для высокого  разрешения.  Меньшие значения приводят к
       эллипсам, вытянутым по горизонтали, а большие - по вертикали. Для
       примера PI=3.14159:  CIRCLE(200,50),30,2,PI/2,PI,6  выводит дугу,
       центр которой находится в точке 50,200, с радиусом 30 точек  цве-
       том 2, причем будет выведен  только левый верхний квадрант верти-
       кально вытянутого эллипса.
          Более сложные линии могут выводиться с помощью оператора DRAW,
       который необычайно гибок. За оператором DRAW следует строка (зак-
       люченная  в  скобки), в которой  закодирована  последовательность
       ориентаций и длин сегментов, составляющих  линию.  Например, DRAW
       "E12F12G12H12"  выводит  бубну.  Начальная точка  устанавливается
       оператором PSET (обсуждаемым в  [4.4.2]);  в противном случае, по
       умолчанию берется центр экрана.  Основные коды состоят из  буквы,
       за которой следует длина сегмента в точках. Коды следующие:

          Ux        вверх (на x точек)
          Dx        вниз
          Rx        вправо
          Lx        влево
          Ex        по диагонали вверх и вправо
          Fx        по диагонали вниз и вправо
          Gx        по диагонали вниз и влево
          Hx        по диагонали вверх и влево

       При умеренном разрешении 100  точек по горизонтали и 100 точек по
       вертикали  дают отрезки примерно одинаковой длины (на самом  деле
       отношение y к x равно 5/6). При высоком разрешении горизонтальная
       линия будет приблизительно вдвое меньше, чем вертикальная.  Из-за
       большего расстояния между точками диагональ прямоугольника содер-
       жит  ровно столько же точек, сколько и максимальная сторона  пря-
       моугольника, хотя сам отрезок длиннее.
            Для рисования диагоналей с углами, отличными от 45 градусов,
       используется кодовая буква M.  Этот  код рисует следующий сегмент
       линии  в абсолютную или относительную позицию экрана.  Чтобы ука-
       зать абсолютную  позицию  надо  указать  координаты  x  и y. DRAW
       "M50,60"  проведет линию в точку, имеющую координаты столбца 50 и
       строки 60.  Для указания относительных координат добавьте знаки +
       или  - перед числами.  Если текущее значение  координаты x  равно
       100, то +50 продолжит линию  до  столбца  150, а -50 - до столбца
       50.    Чтобы   сдвинуться  из  100,100 в  120,70  напишите   DRAW
       "M+20,-30".
          Линия не обязана быть  непрерывной.  Когда перед кодом указана
       буква  B, то указатель перемещается как указано, но сегмент линии
       при этом не рисуется. Например, DRAW "L10BU5R10" рисует две пара-
       ллельные  горизонтальные линии.  Чтобы из одной точки  начиналось
       несколько сегментов  надо  указать  перед  кодом букву N.  В этом
       случае указатель будет возвращаться в начальную точку после выво-
       да сегмента.
          Имеется  ряд  специальных  кодов,  которые  будучи помещенными
       внутри  строки, действуют на все последующие коды (пока следующий
       аналогичный код не укажет  другое  действие). Цвет сегмента линии
       устанавливается  буквой  C, за которой следует код  цвета.   DRAW
       "C2D5" рисует линию, направленную  вниз цветом 2. Установка масш-
       табного фактора меняет масштаб, в котором будет выводиться фигура
       или ее часть.  Надо добавить к строке букву S, за которой следует
       фактор.  Фактор это число, которое для получения масштаба делится
       


                                     -153-
       на 4.  Обычно фактор равен  4,  что  соответствует  масштабу 1:1.
       Изменение  фактора  на 8 приведет к тому,  что  размер  выводимой
       фигуры будет вдвое больше. Для  этого  напишите DRAW "S8U12D12" и
       т.д.
          Используя один их двух кодов Вы можете вращать оси  координат-
       ной системы. Кодовая буква A вращает оси против часовой стрелки с
       90-градусными инкриментами. A0 не вращет оси вообще. A1 - повора-
       чивает их на 90 градусов,  A2  -  на  180  градусов и A3 - на 270
       градусов.  Аналогично, код TA поворачивает оси на указанное число
       градусов от 0 до 360 (против часовой  стрелки) и от 0 до -360 (по
       часовой стрелке).  DRAW "A1L10" и DRAW "TA90L10" приведут к тому,
       что линия, которая  должна  была  быть  направленной  влево будет
       вместо  этого  нарисована повернутой на 90 градусов и  направлена
       вниз.
          Оператор DRAW может  включать  строковые  переменные,  которые
       состоят из набора допустимых кодов.  Это свойство позволяет прог-
       рамме повторно использовать  части  фигур в различных рисунках. В
       операторе  DRAW имя строки должно быть помещено за буквой X и  за
       ним должны следовать точка с запятой. Например:

       100 S$ = "U12R15U45L32"
       110 DRAW "XS$;"

       В одном операторе DRAW может содержаться несколько строк, переме-
       жаемых другими кодами.  Отметим, что любые числа, используемые  с
       кодами в операторах DRAW могут сами быть переменными. Таким обра-
       зом  с  помощью  одного оператора DRAW могут  выводиться  фигуры,
       отличающиеся по форме, цвету, масштабу  и ориентации. Надо помес-
       тить знак равенства между буквенным кодом и именем переменной,  а
       за именем поместить точку с запятой.   Например, чтобы установить
       код  цвета, определяемый переменной, напишите  DRAW  "C=PCOLOR;".
       Компилятор Бейсика требует,  чтобы  ссылка на эти переменные осу-
       ществлялась с помощью функции VARPTR$. В этом случае такой опера-
       тор будет иметь вид  DRAW  "X"  +  VARPTR$(S$)  или  DRAW  "C=" +
       VARPTR$(PCOLOR). Сложные рисунки могут быть сохранены в массиве и
       затем возвращены на экран в любой момент. Обсуждение этого вопро-
       са см. в [4.4.6].

          Низкий уровень.

          Нижеприведенная  процедура использует алгоритм Брезенхэма  для
       вывода прямой линии, соединяющей любые две точки.  Она использует
       функцию  BIOS установки точек и ее можно убыстрить если  заменить
       эту функцию на встроенную процедуру,  использующую прямое отобра-
       жение  в  память.  Как и все быстрые алгоритмы  данная  процедура
       избегает операций умножения и деления.  Линия рассматривается как
       набор сегментов двух типов: тех которые расположены диагонально и
       тех, которые расположены горизонтально или вертикально. Для линий
       с  наклоном  больше 1 прямые  сегменты  вертикальны, в  противном
       случае они горизонтальны;  первая  задача алгоритма состоит в вы-
       числении наклона. Затем вычисляется выравнивающий фактор, который
       следит чтобы некоторое число прямых  сегментов имело большую дли-
       ну,  чем остальные.  И, наконец, сложный цикл поочередно  выводит
       диагональные и прямые сегменты.  BX поочередно принимает то поло-
       жительные,  то отрицательные значения, отмечая какой тип сегмента
       выводится.  Ниже готовятся  данные для вывода диагонали из одного
       угла экрана в противоположный:
       ;---в сегменте данных
       START_X                   DW   0
       END_X                     DW   319
       START_Y                   DW   0
       END_Y                     DW   199
       COLOR                     DB   2
       


                                     -154-
       DIAGONAL_Y_INCREMENT      DW   ?
       DIAGONAL_X_INCREMENT      DW   ?
       SHORT_DISTANCE            DW   ?
       STRAIGHT_X_INCREMENT      DW   ?
       STRAIGHT_Y_INCREMENT      DW   ?
       STRAIGHT_COUNT            DW   ?
       DIAGONAL_COUNT            DW   ?
       ;---установка режима дисплея
                      MOV  AH,0       ;функция установки режима
                      MOV  AL,4       ;цветной 320*200
                      INT  10H        ;установка режима
       ;---установка начальных инкрементов для каждой позиции точки
                      MOV  CX,1       ;инкремент для оси x
                      MOV  DX,1       ;инкремент для оси y
       ;---вычисление вертикальной дистанции
                      MOV  DI,END_Y   ;вычитаем координату начальной
                      SUB  DI,START_Y ;точки из координаты конечной
                      JGE  KEEP_Y     ;вперед если наклон < 0
                      NEG  DX         ;иначе инкремент равен -1
                      NEG  DI         ;а дистанция должна быть > 0
       KEEP_Y:        MOV  DIAGONAL_Y_INCREMENT,DX
       ;---вычисление горизонтальной дистанции
                      MOV  SI,END_X   ;вычитаем координату начальной
                      SUB  SI,START_X ;точки из координаты конечной
                      JGE  KEEP_X     ;вперед если наклон < 0
                      NEG  CX         ;иначе инкремент равен -1
                      NEG  SI         ;а дистанция должна быть > 0
       KEEP_X:        MOV  DIAGONAL_Y_INCREMENT,CX
       ;---определяем горизонтальны или вертикальны прямые сегменты
                      CMP  SI,DI      ;горизонтальные длиннее?
                      JGE  HORZ_SEG   ;если да, то вперед
                      MOV  CX,0       ;иначе для прямых x не меняется
                      XCHG SI,DI      ;помещаем большее в CX
                      JMP  SAVE_VALUES;сохраняем значения
       HORZ_SEG:      MOV  DX,0       ;теперь для прямых не меняется y
       SAVE_VALUES:   MOV  SHORT_DISTANCE,DI  ;меньшее расстояние
                      MOV  STRAIGHT_X_INCREMENT,CX  ;один из них 0,
                      MOV  STRAIGHT_Y_INCREMENT,DX  ;а другой - 1.
       ;---вычисляем выравнивающий фактор
                      MOV  AX,SHORT_DISTANCE  ;меньшее расстояние в AX
                      SHL  AX,1       ;удваиваем его
                      MOV  STRAIGHT_COUNT,AX  ;запоминаем его
                      SUB  AX,SI      ;2*меньшее - большее
                      MOV  BX,AX      ;запоминаем как счетчик цикла
                      SUB  AX,SI      ;2*меньшее - 2*большее
                      MOV  DIAGONAL_COUNT,AX  ;запоминаем
       ;---подготовка к выводу линии
                      MOV  CX,START_X ;начальная координата x
                      MOV  CX,START_Y ;начальная координата y
                      INC  SI         ;прибавляем 1 для конца
                      MOV  AL,COLOR   ;берем код цвета
       ;---теперь выводим линию
       MAINLOOP:      DEC  SI         ;счетчик для большего расстояния
                      JZ   LINE_FINISHED  ;выход после последней точки
                      MOV  AH,12      ;функция вывода точки
                      INT  10H        ;выводим точку
                      CMP  BX,0       ;если BX < 0, то прямой сегмент
                      JGE  DIAGONAL_LINE  ;иначе диагональный сегмент
       ;---выводим прямые сегменты
                      ADD  CX,STRAIGHT_X_INCREMENT  ;определяем инкре-
                      ADD  DX,STRAIGHT_Y_INCREMENT  ;менты по осям
                      ADD  BX,STRAIGHT_COUNT  ;фактор выравнивания
                      JMP  SHORT MAINLOOP  ;на следующую точку
       


                                     -155-
       ;---выводим диагональные сегменты
       DIAGONAL_LINE: ADD  CX,DIAGONAL_X_INCREMENT  ;определяем инкре-
                      ADD  DX,DIAGONAL_Y_INCREMENT  ;менты по осям
                      ADD  BX,DIAGONAL_COUNT  ;фактор выравнивания
                      JMP  SHORT MAINLOOP  ;на следующую точку
       LINE_FINISHED:
          4.4.6 Заполнение областей экрана.

          Тщательное  обдумывание  позволяет  исключить  много  излишней
       медлительности, которая свойственна  многим программам заполнения
       областей  для графического экрана.  Когда заполнение основано  на
       простых  вычислениях,  которые  действуют  по  очереди для каждой
       точки,  то требуются расходующие много времени битовые  операции.
       Более экономный код может определять все ли битовые позиции опре-
       деленного  байта  видеобуфера должны иметь один и тот же  цвет  и
       когда это условие выполняется, то этому байту присваивается зара-
       нее  заготовленное  значение, которое устанавливает все  точки  в
       правильный цвет. При этом  нет  необходимости  повторять операции
       над  одним  и тем же байтом, каждый раз устанавливая биты  только
       для одной из точек, информацию о которой содержит данный байт.
          В [4.3.4] объяснено как создать описание символа в виде матри-
       цы  8*8  точек, имеющего требуемый Вам вид.  Хотя  такие  символы
       могут выводиться только в  стандартные  символьные позиции, но их
       использование  может существенно облегчить  заполнение  графиков.
       Образец высвечивающий все 8*8 точек может быть выведен в интерва-
       ле нескольких строк и столбцов, заполняя область намного быстрее,
       чем это достигается при поточечной зарисовке.  Этот тип графичес-
       ких символов может использоваться совместно с точечной  графикой.
       Псевдографические символы могут  использоваться  также для вывода
       вращающихся или колеблющихся объектов.

          Высокий уровень.

          Бейсик  предоставляет оператор PAINT для заполнения  замкнутой
       фигуры произвольной формы.  Вам  необходимо  указать только точку
       внутри области, а об остальном позаботится процедура.  Может быть
       указан цвет палетты, которым  надо  заполнить  область, например,
       PAINT  (100,110),2 заполняет область цветом 2 палетты.   Закраска
       ведется начиная от указанной точки до тех пор, пока не встретятся
       точки  с цветом, отличающимся от фонового.  Вы можете,  наоборот,
       указать цвет границы и закраска  будет  продолжаться во всех нап-
       равлениях,  пока не будут встречены точки указанного цвета.   При
       такой закраске линии других  цветов,  находящиеся внутри границы,
       могут  быть также закрашены.  Код цвета границы следует за  кодом
       цвета заполнения, таким образом  PAINT  (100,180),2,3 закрашивает
       область цветом 2 до линий цвета 3.  Отметим, однако, что эта про-
       цедура не заполняет области,  находящиеся  "за  углом", т.е. если
       вдоль   какой-либо  горизонтальной  или  вертикальной  траектории
       встретилась точка, имеющая цвет границы, то все последующие точки
       вдоль  этой  траектории не заполняются, даже  если  фигура  имеет
       причудливую форму и эти точки  принадлежат внутренней части фигу-
       ры.  В следующем примере выводятся две перекрывающихся рамки цве-
       тами циан и магента, а затем  последняя  рамка  заполняется белым
       цветом.   Сегменты  первой рамки, которые попадают в  закрашенную
       область также заполняются белым.

       100 LINE (50,70)-(270,130),1,B   'рисуем рамку цветом циан
       110 LINE (100,30)-(220,170),2,B  'рисуем рамку цветом магента
       120 PAINT (101,31),3,2           'заполняем вторую рамку белым

       Помните, что команда  LINE  может  сама  заполнить рамку, если Вы
       укажете в качестве параметра 'BF', а не 'B'. Смотрите [4.4.5].
          Оператор  PAINT  имеет "орнаментальные"  возможности,  которые
       


                                     -156-
       позволяют Вам заполнять  области  указанной  картинкой.  Элементы
       орнамента, которые в режиме умеренного разрешения имеют размер  4
       точки в ширину и 8 в высоту (8*8  для высокого разрешения) повто-
       ряются  по всей указанной области.  Рисунок  описывается  набором
       байтов, содержащих цепочку битов  для последовательных рядов эле-
       мента  орнамента.  В режиме умеренного разрешения  цепочка  битов
       10000011 описывает 4 точки,  первая из которых имеет цвет 2, сле-
       дующие 2 - фоновый цвет, а последняя - цвет 3.  Эта цепочка соот-
       ветствует числу 131 или &H83 (см.  приложение Б, в котором обсуж-
       даются  битовые  операции в Бейсике).  Обращение этой  цепочки  в
       11000010 даст 193 (&HC1).  Они  могут  быть  объединены в элемент
       орнамента  шириной в 4 точки и высотой в 2 строкой  CHR$(&H83)  +
       CHR$(&HC1). В такую строку  могут  включаться до 8 байтов, доводя
       высоту  до 8 точек.  Такая строка используется в операторе  PAINT
       вместо цвета. Вот вывод квадрата, заполненного описанным орнамен-
       том:

       100 LINE (100,110)-(150,150),1,B     'рисуем рамку
       110 PAINT (125,125),CHR$(&H83)+CHR$(&HC1),1   'заполняем ее

       Отметим,  что нерегулярности элемента орнамента могут приводить к
       тому, что процедура  PAINT  завершается,  не  закончив заполнения
       области.  Бейсик решает эту проблему указанием параметра фона для
       оператора PAINT.  Если у  Вас  возникнут  проблемы, обращайтесь к
       руководству по Бейсику за деталями.
          Оператор DRAW, позволяющий рисовать сложные линии, также может
       заполнять области. Он обсуждается в [4.4.5].  "Текущая точка" (из
       которой  будет  рисоваться следующий сегмент линии)  должна  быть
       помещена внутрь области, ограниченной  границей указанного цвета.
       В строку оператора DRAW надо поместить кодовую букву P, за  кото-
       рой должен следовать код цвета закраски и код цвета границы.  Для
       вывода  рамки  цветом 1 палетты, а затем ее заполнения  цветом  3
       напишите  DRAW  "U10R10D10L10BH1P3,1".  Здесь  первые четыре кода
       рисуют  границы  рамки, затем код 'BH' перемещает  текущую  точку
       внутрь рамки, не рисуя линии, а затем код 'P' приводит к заполне-
       нию  рамки.  Таким образом могут быть заполнены и  более  сложные
       формы. Отметим,  что  необязательно  при перемещении точки внутрь
       области отменять рисование линии вдоль этого пути. Однако, в этом
       случае надо использовать для  этого  сегмента код цвета, отличный
       от цвета заполняемой границы.
          Бейсик  имеет  также возможность  заполнения  областей  экрана
       заранее подготовленным изображением. Изображение может быть любо-
       го размера, может быть выведено в любой позиции экрана и хранится
       в массиве. Обычно, изображение создается с помощью всех доступных
       средств,  а затем запоминается в массиве оператором GET.   Массив
       может быть помещен в  последовательный  файл [5.4.3], из которого
       программа может загрузить его и вывести изображение. Оператор GET
       перечисляет координаты  левого  верхнего  и  правого нижнего угла
       рамки, содержащей изображение, причем сначала идет номер столбца,
       а затем номер строки  для  каждой  пары  координат.  Затем должно
       следовать  имя массива, которое не заключается в кавычки.  Напри-
       мер, GET (80,40)-(120,60),ARRAY3 помещает  все точки, находящиеся
       внутри указанной области в массив с именем ARRAY3.
       Одномерные  массивы, как и все остальные, должны  быть  предвари-
       тельно  описаны оператором DIM.  Массив может содержать  элементы
       любой точности.  Для вычисления  требуемых  размеров массива надо
       сначала определить сколько байтов потребуется для хранения  изоб-
       ражения. Это можно вычислить по  формуле 4 + INT ((x*битовнаточку
       + 7)/8)* y.  Здесь "битовнаточку" равно 1 для высокого разрешения
       и 2 - для умеренного  разрешения.  Буквы  x и y относятся к числу
       точек вдоль горизонтальной и вертикальной сторон блока  изображе-
       ния.  INT обозначает целую часть числа.  Наконец, надо определить
       сколько  элементов  массива требуется для хранения данного  числа
       


                                     -157-
       байтов.  Каждый элемент занимает  2 байта в целом массиве, но 4 -
       для  чисел  с обычной точностью и 8 - для чисел с двойной  точно-
       стью.
          Для получения изображения  из  массива  и  вывода его на экран
       используйте оператор PUT. Этот оператор требует только координаты
       левого верхнего угла области  экрана,  в которую будет выводиться
       изображение.   За  координатами должно быть указано имя  массива.
       Например, PUT (40,30),ARRAY1 помещает  изображение, левый верхний
       угол которого будет находиться в столбце 40 и строке 30. Оператор
       PUT может иметь еще и необязательный параметр, определяющий цвет,
       которым будет выводиться изображение.  Если этот параметр опущен,
       то изображение будет выводиться  точно  в том виде, в котором оно
       было  записано  оператором  GET.   Это  эквивалентно  записи  PUT
       (40,30),ARRAY1,PSET.  В противном случае имеются некоторые другие
       возможности. Если Вы вместо PSET укажете PRESET, то цвет 0 палет-
       ты будет заменен на цвет 3 и наоборот, а цвет 1 палетты - на цвет
       2 и наоборот.
          Имеются  еще три случая, использующие логические операции AND,
       OR или XOR.  Как и PRESET эти  слова могут заменять PSET в приве-
       денном примере. Обсуждение этих трех операций смотрите в приложе-
       нии Б.  Каждая  операция  включает  сравнение  битов существующей
       точки  на  экране с битами точки накладываемого  изображения.   В
       режиме высокого разрешения, когда на точку отводится только 1 бит
       операция простая. Но в режиме умеренного разрешения, в котором на
       каждую точку отводится 2 бита, могут происходить различные транс-
       формации цветов.
          AND устанавливает бит только если он был установлен и у  точки
       экрана и у точки изображения (взятой из массива).  В режиме высо-
       кого  разрешения это означает, что точка изображения появится  на
       экране только если  соответствующая  точка экрана уже "включена".
       Все остальные точки области будут выключены.  В режиме умеренного
       разрешения  операция  производится  над  обоими  битами. Если для
       точки  экрана  установка битов 01, а  для  соответствующей  точки
       изображения - 10, то оба бита будут сброшены и точка экрана полу-
       чит код 00, что соответствует фоновому цвету.
          OR  устанавливает  бит, если он был установлен либо для  точки
       экрана, либо для точки изображения.  В черно-белом режиме OR нак-
       ладывает  изображение  на существующее изображение на экране.   В
       цветном режиме для определения эффекта Вы опять должны прибегнуть
       к вычислениям. Комбинация кодов палетты 1(01) и 2(10) дает 3(11),
       также как и комбинация 0(00) и 3(11).
          И, наконец, XOR устанавливает  бит,  если из двух сравниваемых
       только  один был установлен.  Применение этой операции  для  чер-
       но-белого экрана с массивом единиц дает негативное изображение (1
       и 1 дает 0, а 1 и 0 - дает 1). В режиме умеренного разрешения эта
       операция меняет все цвета. В  результате  получаем наложение двух
       изображений.   Но  более важно, что при повторении этой  операции
       экран принимает в точности такой же вид, который он имел первона-
       чально.  При этом изображение стирается.  Эта техника полезна для
       мультипликации, когда над изображением дважды производится опера-
       ция XOR в одной позиции, затем в соседней и т.д.

          Низкий уровень.

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


                                     -158-
       процедур требует существенно больше времени, чем установка целого
       байта  за один доступ к ячейке видеобуфера.  Например, поточечная
       очистка экрана требует на IBM PC нескольких  секунд при использо-
       вании функции BIOS, в то время как прямой доступ в память  произ-
       водит эту операцию мгновенно:
             MOV  AX,0B800H     ;ES указывает на буфер экрана
             MOV  ES,AX         ;
             MOV  CX,8192       ;заполняем все байты
             MOV  AX,0          ;в каждый байт пишем 0
             MOV  DI,0          ;DI поочередно указывает на все байты
       REP   STOSW              ;повторяем запись 8192 раза

          Многие процедуры заполняют  по  одной  горизонтальной  строке,
       проверяя на цвет границы справа и слева. Поскольку строки состоят
       из смежных байтов данных, то  надо  поочередно брать байты из ви-
       деобуфера  и проверять присутствует ли в них цвет границы.   Если
       цвет границы отсутствует, то  можно  заменить  сразу весь байт на
       цвет  заполнения.  В противном случае к данному байту применяется
       поточечный подход.
          Имеется очень быстрый способ определения  присутствует ли гра-
       ничный цвет в данном байте видеобуфера. Предположим, что процеду-
       ра ищет цвет 1 палетты в режиме умеренного  разрешения с четырьмя
       цветами. Этому цвету соответствует код 01, поэтому сначала запол-
       ним весь байт этим кодом: 01010101. Затем используем операцию NOT
       для обращения каждого бита, после чего байт примет вид  10101010.
       Проделаем операцию  XOR  со  значением  взятым  из видеобуфера; в
       результате получим байт, у которого оба бита, относящиеся к одной
       точке равны 1 только для  точек,  имеющих  граничный цвет.  Затем
       снова используем операцию NOT с тем, чтобы пара битов, относящих-
       ся к точке граничного цвета имела  код 00. После этого используем
       операцию  TEST для нахождения полей со значением 00.  Если  такое
       поле найдено, то граничный цвет обнаружен и процедура переходит к
       обычному поточечному анализу данного байта.  Эту процедуру  можно
       еще убыстрить, если использовать словные данные.

          MOV  AL,ES:[BX]    ;берем байт из видеобуфера
          XOR  AL,10101010B  ;устанавливаем биты для цвета границы
          NOT  AL            ;обращаем биты
          TEST AL,11000000B  ;проверяем биты 7-6
          JZ   FOUND_BOUND   ;переход если граничный цвет
          TEST AL,00110000B  ;проверяем биты 5-4
          JZ   FOUND_BOUND   ;переход если граничный цвет
          TEST AL,00001100B  ;проверяем биты 3-2
          JZ   FOUND_BOUND   ;переход если граничный цвет
          TEST AL,00000011B  ;проверяем биты 1-0
          JZ   FOUND_BOUND   ;переход если граничный цвет
          MOV  AL,FILL_COLOR ;граничного цвета нет, заполняем байт
          MOV  ES:[BX],AL    ;возвращаем байт в видеобуфер
           .
           .
       FOUND_BOUND:

          Когда это возможно,  постарайтесь, чтобы границы прямоугольных
       областей  Ваших картинок были выравнены на границу двух,  четырех
       или восьми точек, с тем чтобы  прямое  отображение в память имело
       дело с целыми байтами.  Другая возможность, хотя и не столь быст-
       рая, состоит в создании  определяемых  пользователем псевдографи-
       ческих  символов [4.3.4] и выводе их на границе области  заполне-
       ния.  Короче, в данной области Вы имеете все возможности проявить
       сообразительность,  а  зачастую  стоит подумать, а нужна  ли  Вам
       столь сложная графика в данной задаче.
          4.4.7 Графический вывод с использованием символов псевдографи-
       ки.
       


                                     -159-
          Когда Вы выводите изображение точка за точкой, то это отнимает
       очень много времени, особенно когда  создаются эффекты мультипли-
       кации.  Один из способов экономии времени состоит в сведении всех
       или части выводимых форм к  фигурам, которые могут быть построены
       на матрице точек 8*8.  Такие фигуры могут быть созданы, как опре-
       деляемые  пользователем  символы,  как  показано в [4.3.4]. После
       того,  как  эти символы определены они выводятся на  экран  очень
       быстро и просто. Эти символы  могут выводиться вперемешку с пото-
       чечными графиками, как обычные буквы.  Один из способов  быстрого
       заполнения фигуры состоит в последовательном выводе внутри фигуры
       полностью  закрашенного блока.  Отметим, что эти  символы  всегда
       располагаются в стандартных позициях курсора.

          Средний уровень.

          В этом примере рисуется фигура  человека, занимающая 2 символа
       в  высоту и 2 символа в ширину.  Как объяснено в  [4.3.4]  вектор
       прерывания 1FH указывает на  начало  области данных, определяющих
       символы.  Четыре символа могут быть выведены обычными процедурами
       DOS или BIOS. Легко  создать  другой  набор  символов, для вывода
       фигуры с руками и ногами в другом месте экрана. Два набора симво-
       лов могут поочередно меняться в соседних позициях курсора, созда-
       вая иллюзию человека, идущего по экрану.

       ;---в сегменте данных
       CHARACTER_DATA  DB   00110000B   ;левый верхний квадрант
                       DB   01100111B
                       DB   01100111B
                       DB   00110011B
                       DB   00011111B
                       DB   00001111B
                       DB   00001111B
                       DB   00000111B

                       DB   00000011B   ;правый верхний квадрант
                       DB   10001100B
                       DB   10011000B
                       DB   00110000B
                       DB   11100000B
                       DB   11000000B
                       DB   11000000B
                       DB   10000000B

                       DB   00001111B   ;левый нижний квадрант
                       DB   00011111B
                       DB   00011100B
                       DB   00011000B
                       DB   00011000B
                       DB   00110000B
                       DB   01100000B
                       DB   00010000B

                       DB   11000000B   ;правый нижний квадрант
                       DB   11000000B
                       DB   11000000B
                       DB   11000000B
                       DB   01100000B
                       DB   01100000B
                       DB   00010000B
                       DB   00011110B
                       DB   00000000B
       ;---установка вектора прерывания
          PUSH DS                ;сохраняем DS
       


                                     -160-
          MOV  DX,OFFSET CHAR_DATA  ;смещение для данных в DX
          MOV  AX,SEG CHAR_DATA  ;сегмент для данных в DS
          MOV  DS,AX             ;
          MOV  AH,25H            ;функция установки вектора
          MOV  AL,1FH            ;номер вектора
          INT  21H               ;устанавливаем вектор
          POP  DS                ;восстанавливаем DS

       ;---рисуем фигуру
       ;---позиционируем курсор на верхний ряд
          MOV  AH,2         ;функция установки курсора
          MOV  DH,13        ;строка 13
          MOV  DL,20        ;столбец 20
          MOV  BH,0         ;страница 0
          INT  10H          ;установка курсора
       ;---рисуем верхние два символа
          MOV  DL,128       ;берем символ 128
          MOV  AH,2         ;функция вывода/курсор вперед
          INT  21H          ;вывод символа
          MOV  DL,129       ;берем символ 129
          INT  21H          ;выводим его
       ;---позиционируем курсор на нижнюю строку
          MOV  DH,14        ;строка 14
          MOV  DL,20        ;столбец 20
          MOV  AH,2         ;функция установки курсора
          INT  10H          ;устанавливаем курсор
       ;---рисуем нижние два символа
          MOV  DL,130       ;берем символ 130
          MOV  AH,2         ;функция вывода/курсор вперед
          INT  21H          ;вывод символа
          MOV  DL,131       ;берем символ 131
          INT  21H          ;выводим его

                        Раздел 5. Сдвиг экрана и страницы.

          Сдвиг экрана и разбиение на страницы - это два способа перено-
       са блока информации из памяти на экран. При сдвиге одна из границ
       экрана  сдвигается  внутрь, стирая информацию на  противоположной
       стороне.  Затем  освободившаяся  область  заполняется  из памяти.
       Повторение этого действия строка за строкой создает иллюзию сдви-
       га экрана.
          С другой стороны, разбиение на  страницы  основано на одновре-
       менном  хранении  нескольких экранов информации в  видеобуфере  и
       переключении вывода с одной  страницы  на  другую.  Использование
       дисплейных страниц невозможно на монохромном адапторе,  поскольку
       его памяти хватает только для одного  символьного экрана.  Другие
       видеосистемы  в  большинстве экранных режимов  могут  работать  с
       несколькими  страницами.  Использование  страниц дисплея особенно
       полезно  при построении сложных картин "за кулисами"; после  того
       как эта  работа  завершена,  новый  экран  выводится моментально.
       Процедура,  имитирующая  работу  со страницами  для  монохромного
       адаптора приведена в  [4.5.3].   Она  особенно  полезна, когда Вы
       имеете дело с медленным выводом на экран в Бейсике.

          4.5.1 Вертикальный сдвиг текстового экрана.

          Когда  текстовый экран сдвигается вверх, то строки со  2-й  по
       25-ю переписываются на строки с  1-й  по 24-ю, а следующая строка
       данных  выводится в 25-й строке.  При этом верхняя строка, поверх
       которой осуществлется  вывод  теряется,  хотя  она продолжает су-
       ществовать в памяти. Сдвиг вниз устроен аналогично.

          Высокий уровень.
       


                                     -161-
          Бейсик  утомительно медлителен при своих манипуляциях с  экра-
       ном. Для быстрого сдвига Вы можете пожелать использовать процеду-
       ру на машинном языке, которая не делает ничего другого, кроме как
       использует прерывание  10H,  как  описано  ниже  в пункте средний
       уровень. Процедура позволяет сдвигать весь экран или любое окно в
       нем. Приложение Г показывает как  включать подпрограммы на машин-
       ном  языке  в Ваши программы.  Ваша программа на  Бейсике  должна
       указывать координаты  верхнего  левого  и  нижнего  правого углов
       окна,  которые могут лежать в диапазоне от 0 до 24 и от 0 до  79.
       Требуется также параметр,  указывающий  направление сдвига: вверх
       или  вниз  (6 и 7, соответственно), число строк на которое  нужно
       сдвинуть (если 0, то окно  очищается)  и значение байта атрибутов
       для очищаемых строк (для "нормальных" - 7).  Используйте для  них
       целые переменные. В  нижеприведенно примере экран сдвигается вниз
       на одну строку, а затем освободившаяся строка освобождается.

       100 '''данные для подпрограммы
       110 DATA &H55, &H8B, &HEC, &H8B, &H76, &H12, &H8A
       120 DATA &H24, &H8B, &H76, &H10, &H8A, &H04, &H8B
       130 DATA &H76, &H0E, &H8A, &H2C, &H8B, &H76, &H0C
       140 DATA &H8A, &H0C, &H8B, &H76, &H0A, &H8A, &H34
       150 DATA &H8B, &H76, &H08, &H8A, &H14, &H8B, &H76
       160 DATA &H06, &H8A, &H3C, &HCD, &H10, &H5D, &HCA
       170 DATA &H0E, &H00
       180 '''помещаем данные в сегмент &H2000
       190 DEF SEG = &H2000      'помещаем данные начиная с &H2000
       200 FOR N = 0 TO 43       '44 байта
       210 READ Q                'читаем один байт
       220 POKE N,Q              'помещаем его в память
       230 NEXT                  'следующий
       300 '''в программе
       310 GOSUB 500             'сдвигаем на строку
       320 LOCATE 1,1: PRINT TEXT$(LINEPTR);  'выводим строку текста

       500 '''подпрограмма сдвига
       510 DEFINT A-Z            'используем целые переменные
       520 TLR = 0               'левая верхняя строка
       530 TLC = 0               'левый верхний столбец
       540 BRR = 24              'нижняя правая строка
       550 BRC = 79              'нижний правый столбец
       560 NUMROWS = 1           'число строк сдвига
       570 DIR = 7               'направление сдвига вниз
       580 FILL = 7              'заполнение обычным атрибутом
       590 DEF SEG = &H2000      'указываем на подпрограмму
       600 SCROLL = 0            'начинаем с 1-го байта
       610 CALL SCROLL(DIR,NUMROWS,TLR,TLC,BRR,BRC,FILL)
       620 RETURN                'все сделано

          Средний уровень.

          Функция  6 прерывания 10H сдвигает любую часть экрана вверх, а
       функция 7 - вниз. В обоих случаях AL содержит число строк сдвига,
       а  когда AL = 0, то весь экран чистится, а не сдвигается.   CH:CL
       содержат строку и столбец левого  верхнего угла, а DH:DL - содер-
       жат  координаты правого нижнего угла.  Появлящиеся  из-за  сдвига
       строки чистые и они выводятся с кодом атрибутов из BH.

       ;---сдвиг вверх на одну строку
          MOV  AH,6      ;номер функции сдвига вверх
          MOV  AL,1      ;число строк сдвига
          MOV  CH,0      ;строка левого верхнего угла
          MOV  CL,0      ;столбец левого верхнего угла
          MOV  DH,24     ;строка правого нижнего угла
       


                                     -162-
          MOV  DL,79     ;столбец правого нижнего угла
          MOV  BH,7      ;атрибуты очищаемой строки
          INT  10H       ;делаем сдвиг

          Низкий уровень.

          Вертикальный сдвиг всего  экрана  это тривиальная задача, пос-
       кольку  правая граница одной строки в памяти  продолжается  левой
       границей следующей строки. Сдвиг всего содержимого видеобуфера на
       160  байт вверх по памяти (80 символов в строке * 2 байта на сим-
       вол) приводит к сдвигу экрана вниз на одну строку. Если Вы пишете
       свою  собственную  процедуру сдвига экрана,  использующую  прямое
       отображение в память, то не  забывайте  об интерференции, которая
       возникает на цветном дисплее и PCjr.  Эта проблема обсуждается  в
       [4.3.1]. Обычное решение этой проблемы состоит в проверке статус-
       ного  байта,  ожидая пока он разрешит запись в  видеобуфер.   Вам
       придется поэкспериментировать, чтобы определить сколько данных Вы
       можете записать за один цикл.
          Другое  решение этой проблемы состоит в выключении  экрана  на
       время операции сдвига, а затем в его восстановлении.  "Выключение
       экрана" подразумевает, что вывод содержащихся в видеобуфере  дан-
       ных запрещен, но сам буфер при этом  не изменяется.  Этот процесс
       используется функцией сдвига BIOS, использованной выше.  Хотя это
       не очень приятно для  глаз,  но  все-таки  не  так плохо, как уже
       упоминавшаяся интерференция.
          Для  выключения  экрана у цветного графического  дисплея  надо
       сбросить бит 3 порта с адресом  3D8H.   Изменение бита назад на 1
       моментально включает экран обратно.  Этот адрес порта  соответст-
       вует регистру выбора режима цветного графического адаптора.  Этот
       однобайтный регистр только для записи, поэтому программа не может
       просто прочитать его, изменить  значение  бита 3 и вернуть прочи-
       танный  байт.  Вместо этого Вам необходимо определить также  пра-
       вильную установку всех остальных битов (перечисленных в [4.1.2]).
       Для  PCjr  этот бит расположен в регистре  управления  режимом  1
       массива ворот дисплея. В [4.1.1]  объяснено как получить доступ и
       запрограммировать этот регистр.

          4.5.2 Сдвиг текстового экрана горизонтально.

          Горизонтальный сдвиг иногда требуется в специальных программах
       обработки текста, таких как  текстовые  редакторы.   Операционная
       система  не имеет для этого специальных средств.  По этой причине
       данная  задача немного сложнее чем вертикальный сдвиг - но  несу-
       щественно.  Рассмотрим случай, когда Вы хотите, чтобы экран сдви-
       гался  влево  на 5 позиций.  При этом левые 5 столбцов  исчезнут,
       весь остальной текст сдвигается влево,  а самые правые 5 столбцов
       должны  быть очищены.  Поскольку видеобуфер представляет из  себя
       одну длинную строку, то если  каждый символ буфера сдвинуть на 10
       байтов  вниз, то суммарный эффект будет состоять в том, что самые
       левые 5 символов каждой строки  будут  передвинуты  в последние 5
       позиций предыдущей строки.  Таким образом, весь экран будет сдви-
       нут влево на 5 позиций,  передвигая  5 ненужных столбцов в правую
       часть  экрана.   Все что после остается - это очистить  правые  5
       столбцов. Это легко делается  с  помощью  процедуры вертикального
       сдвига  [4.5.1], которая может выполняться для любой части экрана
       и которая  очищает  указанную  область  если  указать  сдвиг на 0
       строк. Рисунок 4-6 иллюстрирует этот метод.

          Низкий уровень.

          В этом примере осуществляется сдвиг на 5 позиций влево.  Легко
       изменить его для сдвига вправо  или  для другого значения позиций
       сдвига. При использовании прямого отображения в память этот метод
       


                                     -163-
       дает практически моментальный сдвиг экрана.

       ;---сдвигаем все вниз на 10 байтов
             MOV  AX,0B000H      ;указываем на буфер монохромного
             MOV  ES,AX          ;дисплея
             MOV  DS,AX          ;
             MOV  SI,10          ;сдвигаем из SI ...
             MOV  DI,0           ;... в DI
             MOV  CX,1995        ;сдвигаем все кроме последних 5 байт
       REP   MOVSW               ;осуществляем сдвиг
       ;---очищаем правый край
             MOV  AH,6           ;функция вертикального сдвига
             MOV  AL,0           ;сдвиг на 0 строк чистит окно
             MOV  CH,0           ;строка левого верхнего угла
             MOV  CL,75          ;столбец левого верхнего угла
             MOV  DH,24          ;строка правого нижнего угла
             MOV  DL,79          ;столбец правого нижнего угла
             MOV  BH,7           ;атрибут для очищаемых позиций
             INT  10H            ;чистим окно

             4.5.3 Переключение между текстовыми страницами.

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

          Режим     Тип               Число страниц     Начало буфера

            0     алфавитноцифровой         8               B800
            1     алфавитноцифровой         8               B800
            2     алфавитноцифровой         8               B800
            3     алфавитноцифровой         8               B800
            4     графический               1               B800
            5     графический               1               B800
            6     графический               1               B800
            7     алфавитноцифровой         1/8             B800
            8     графический            переменное         B800
            9     графический            переменное         B800
            A     графический            переменное         B800
            D     графический              2/4/8            A000
            E     графический              1/2/4            A000
            F     графический               1/2             A000
           10     графический               1/2             A000

       Режимы  8-A - графические режимы PCjr; число страниц для них  ме-
       няется в зависимости от того, сколько оперативной памяти отведено
       под видеобуфер. Размер страницы равен 2K или 4K для алфавитноциф-
       ровых режимов, 32K -  для  четырех  цветов при высоком разрешении
       или 16 цветов при умеренном разрешении и 16K - для всех остальных
       режимов.  Режимы D-10 поддерживаются EGA.  Количество страниц ме-
       няется в зависимости от установленной памяти.  Режимы F и 10 тре-
       буют наличия не менее 128K памяти. Режим 7 разрешает одну страни-
       цу для монохромного адаптора и 8 страниц для EGA.
          Монохромный  адаптор не имеет памяти для дополнительных  стра-
       ниц.  Однако нет никаких причин, по которым часть основной памяти
       нельзя  было  бы использовать как буфер дисплея.  В  этом  случае
       страничная  организация  осуществляется  за  счет быстрого обмена
       всего  содержимого буфера в памяти с видеобуфером (адрес которого
       


                                     -164-
       B000:0000).  Буфер  в  основной  памяти  можно  рассматривать как
       "псевдостраницу".  Хотя это и не настоящее разбиение на страницы,
       но результат будет почти такой  же,  если для пересылки данных Вы
       будете использовать ассемблерную процедуру.
          При  использовании страниц надо позаботиться о том, чтобы опе-
       рации вывода на экран направлялись  на нужную страницу. Программа
       не  обязана выводить данные на ту страницу, которая в данный  мо-
       мент изображается на экране.  На самом деле, часто наоборот жела-
       тельно  конструировать  экран "за кулисами", а затем  моментально
       выводить уже готовое  изображение.  Этот  метод особенно полезен,
       когда необходимо конструировать сложный вывод в Бейсике, у  кото-
       рого вывод очень  медленный.  BIOS  хранит в своей области данных
       однобайтную переменную, указывающую, какая из страниц выводится в
       данный момент.  Диапазон значений этой переменной от 0 до 7.  Она
       расположена по адресу 0040:0062.

          Высокий уровень.

          Бейсик  использует  команду SCREEN для установки страницы,  на
       которую будет идти вывод (активной страницы) и выводимой страницы
       (видимой  страницы).  Страницы нумеруются от 0 до 3 для текстов с
       80 символами в строке и от 0 до 7 для 40-символьных. Третий пара-
       метр   за  командой  SCREEN  устанавливает   активную   страницу.
       SCREEN,,2 приводит к тому, что все операторы PRINT будут работать
       со страницей 2.  Четвертый параметр устанавливает видимую страни-
       цу.  SCREEN,,,1 приводит  к  тому,  что на экран будет выводиться
       страница 1.  Когда видимая страница не указывается, то  автомати-
       чески принимается, что она совпадает с активной.
          Для выделения памяти под  страницы на PCjr используется опера-
       тор CLEAR.  Этот оператор устанавливает общее количество  памяти,
       отводимое под буфер экрана, которое при старте равно 16384 байта.
       Чтобы   добавить   вторую   страницу   размером   16K,   напишите
       CLEAR,,,32768. Добавочные  текстовые страницы требуют 4096 байтов
       каждая.   При  условии, что таким образом была  отведена  память,
       команды оператора SCREEN для  работы  со страницами работают опи-
       санным образом.  Только PCjr имеет добавочный параметр  оператора
       SCREEN, который стирает страницу (т.е. переводит ее в цвет фона).
       Детали  описаны в руководстве по Бейсику.  Оператор  PCOPY  также
       уникален для PCjr. Он  копирует  изображение  из одной страницы в
       другую.  Например, PCOPY 2,1 целиком копирует страницу 2 на стра-
       ницу 1.
          Хотя монохромный адаптор не имеет  памяти для страниц дисплея,
       однако  имеется  способ устроить  своего  рода  "псевдостраницы".
       Нижеприведенная процедура  на  машинном  языке рассматривает блок
       памяти  как дисплейную страницу.  При вызове этой  процедуры  она
       обменивает содержимое видеобуфера с содержимым этой области памя-
       ти.   В  результате мы имеем как бы две дисплейные страницы.   (В
       приложении Г объясняется как  включать  подпрограммы  на машинном
       языке в программы на Бейсике.)
          Вы должны отвести блок памяти размером 4000 байт для псевдост-
       раницы, помимо памяти, содержащей  программу на машинном языке. В
       примере  блок  начинается с адреса сегмента  &H2000, а  процедура
       помещена по адресу &H2200.   Сегментный  адрес блока содержится в
       9-м и 10-м байтах машинного кода и Вы легко можете изменить  его.
       Видно, что адрес  &H2000  представлен  как &H00, &H20 в операторе
       DATA.  Это следствие того, что младшие цифры всегда размещаются в
       младших ячейках памяти. Если Вы хотите разместить блок, скажем по
       адресу 1234:0000, то надо изменить байты 9 и 10 на &H34, &H12.
          Вам  может  потребоваться очистить  псевдостарницу  от  всякой
       ерунды, оставшейся от  других  программ.   В  строках 230-260 это
       достигается  за счет засылки символа пробела (ASCII 32) в  каждый
       байт (32 служит "нормальным" байтом  атрибутов).  Программа может
       осуществлять  вывод на экран обычным образом, а затем  переносить
       


                                     -165-
       содержимое на псевдостраницу.   Но если хотите, то Вы можете осу-
       ществлять вывод прямо на псевдостраницу, используя прямое отобра-
       жение в память.

       100 '''машинный код
       110 DATA &H1E, &H06, &HB8, &H00, &HB0, &H8E, &HC0
       120 DATA &HB8, (3&H00, &H20), &H8E, &HD8, &HBF, &H00
       130 DATA &H00, &HBE, &H00, &H00, &HFC, &HB9, &HD0
       140 DATA &H07, &H26, &H8B, &H1D, &HAD, &HAB, &H89
       150 DATA &H5D, &HFE, &HE2, &HF6, &H07, &H1F, &HCB
       160 '''помещаем код в память
       170 DEF SEG = &H2200   'указываем адрес процедуры
       180 FOR N = 0 TO 34    'начинаем с первого байта
       190 READ Q             'читаем байт процедуры
       200 POKE N,Q           'пишем его в память
       210 NEXT               '
       220 '''чистим псведостраницу
       230 DEF SEG = &H2000   'адрес начала псевдостраницы
       240 FOR N = 0 TO 3999  'для каждого символа и атрибута
       250 POKE N,32          'помещаем код 32
       260 NEXT               'пока не очистим весь буфер

       500 '''пишем прямо в псевдостраницу
       510 DEF SEG = &H2000   'указываем на ее адрес
       520 S$ = "PSEUDOPAGE"  'выводим слово посреди страницы
       530 M = LEN(S$)        'получаем длину строки
       540 FOR N = 1 TO M     'для каждого символа строки
       550 POKE N*2+2000, ASC(MID$(S$,N,1))   'помещаем его в буфер
       560 NEXT               '

       600 '''теперь используем процедуру
       610 PRINT "SCRREN 1"   'печатаем сообшение на экран
       620 DEF SEG = &H2200   'указываем на процедуру
       630 PSEUDOPAGE = 0     'начинаем с начала процедуры
       640 CALL PSEUDOPAGE    'обмениваем страницы
       650 CALL PSEUDOPAGE    'повторяем обмен
       660 ...

          Средний уровень.

          Функция 5 прерывания 10H выбирает текущую страницу дисплея для
       вывода. Надо просто поместить номер страницы в AL:

       ;---установка видимой страницы
          MOV  AH,5       ;номер функции
          MOV  AL,2       ;номер страницы (начиная с 0)
          INT  10H        ;устанавливаем страницу

       Однако эта функция  не  устанавливает  страницу, на которую будет
       идти  вывод.  Любое из прерываний BIOS, которые выводят на  экран
       (функции прерывания 10H), требует чтобы номер страницы был указан
       в  качестве входного параметра в одном из регистров.  Однако  все
       прерывания вывода на экран MS  DOS пишут на текущую видимую стра-
       ницу.   Таким образом, для "закулисных" операций  Вам  необходимо
       пользоваться прерыванием 10H.
          Для получения информации  о  текущей  странице  надо выполнить
       функцию  F  прерывания 10H, которая  возвращает  статус  дисплея.
       Номер страницы при этом возвращается в BH.

          Низкий уровень.

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


                                     -166-
       памяти устанавливается регистрами 12 (старший байт) и 13 (младший
       байт)  микросхемы 6845, которые называются регистрами  стартового
       адреса. Значения адресов раздела страниц для буфера, начинающего-
       ся с B800 такие:

                              40 символов            80 символов

          страница 0             0000H                  0000H
                   1             0400H                  0800H
                   2             0800H                  1000H
                   3             0C00H                  1800H
                   4             1000H
                   5             1400H
                   6             1800H
                   7             1C00H
       В  [4.1.1]  объясняется как программировать  регистры  микросхемы
       6845, а в [4.5.4] содержится  пример  программирования стартового
       адреса.   В  последнем примере надо просто присвоить BX  одно  из
       значений вышеприведенной  таблицы.   Конечно, при этом устанавли-
       вается только выводимая страница. Для записи в определенную стра-
       ницу на низком уровне надо  использовать одно из значений таблицы
       в  качестве  смещения в видеобуфере при прямом отображении в  па-
       мять.
          Поскольку прямое отображение в  память  работает очень быстро,
       то  иллюзия страниц может быть легко создана на монохромном дисп-
       лее.  Выделите блок размером  4000  байтов для хранения страницы.
       Хотя монохромный адаптор не может непосредственно читать из обыч-
       ной памяти, содержимое этого буфера  и видеобуфера можно обменять
       настолько быстро, что никто не зметит разницы. Следующая процеду-
       ра обменивает содержимое этих двух областей.

       ;---в сегменте данных
       PPAGE  DW   2000  DUP(720H)  ;заполняем буфер пробелами

       ;---пересылка между псевдостраницей и видеобуфером
                   MOV  AX,0B000H   ;указываем на видеобуфер
                   MOV  ES,AX       ;
                   MOV  AX,SEG PPAGE  ;указываем на псевдостраницу
                   MOV  DS,AX       ;
       REPEAT:     MOV  DI,0        ;DI на начало видеобуфера
                   MOV  SI,OFFSET PPAGE  ;SI на начало псевдостраницы
                   CLD              ;направление вперед
                   MOV  CX,2000     ;будем пересылать 2000 слов
       NEXT_WORD:  MOV  BX,ES:[DI]  ;берем слово из видеобуфера в BX
                   LODSW            ;слово из псевдостраницы в AX
                   STOSW            ;слово из AX в видеобуфер
                   MOV  DS:[DI]-2,BX  ;слово из BX в псевдостраницу
                   LOOP NEXT_WORD   ;

          PCjr хранит регистр страницы в порте  с адресом 3DFH. Значение
       битов этого регистра следующее:

          биты 2-0   какая страница выводится (от 0 до 7)
               5-3   какая страница пишется (от 0 до 7) при выводе
                     по адресу сегмента B800H
               7-6   = 00 для всех текстовых режимов
                     = 01 для графических режимов с 16K
                     = 11 для графических режимов с 32K

          4.5.4 Сдвиг между страницами текста.

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


                                     -167-
       памяти.  В этом случае текст сдвигаться вверх и вниз по экрану не
       передвигаясь реально в буфере.  Вместо этого экран начинает пока-
       зывать  содержимое буфера, начиная с различных точек и тем  самым
       создавая иллюзию сдвига. Этот  метод  называется аппаратным сдви-
       гом.
          Аппаратный  сдвиг  достигается за  счет  изменения  стартового
       адреса дисплея, который является  числом, указывающим на символ в
       видеобуфере,  который будет выводиться в левом верхнем углу экра-
       на.  Добавление 80 к  этому  числу  "сдвигает" весь экран на одну
       строку вверх, а вычитание 80 - на одну строку вниз. В режиме с 40
       символами в строке надо вместо 80 прибавлять или вычитать 40.  На
       рис. 4-7 приведена диаграмма аппаратного сдвига.
          Отметим, что регистр стартового адреса не считает байты  атри-
       бутов, поэтому Вы должны  вычислять адреса памяти по-другому, чем
       при прямом отображении в память. Имейте также ввиду, что несмотря
       на наличие разрывов  памяти  между  границами  страниц (96 байтов
       между 80-символьными страницами и 48 байтов между  40-символьными
       страницами) микросхема 6845 пропускает эти области и сдвиг непре-
       рывно происходит с одной страницы на следующую.  Аппаратный сдвиг
       происходит настолько быстро, что Вам  может оказаться необходимым
       вставить процедуру задержки, чтобы пользователь имел  возможность
       увидеть насколько сдвинулся экран.
          BIOS хранит  текущее  значение  регистра  стартового  адреса в
       переменной  в своей области данных.  Эта  двухбайтная  переменная
       расположена по адресу 0040:004EH.

          Низкий уровень.

          Стартовый адрес содержится в  регистрах 12 (старший байт) и 13
       (младший байт) микросхемы 6845. В [4.1.1] объясняется работа этой
       микросхемы.  Прежде  чем  адресуемый  байт  направляется в порт с
       адресом  3D5H,  необходимо послать номер адресуемого  регистра  в
       порт 3D4H. В данном примере  экран сдвигается вверх на одну стро-
       ку. Переменная START_ADDRESS содержит адрес первого символа теку-
       щей верхней строки экрана.

          MOV  BX,START_ADDRESS  ;начинаем с начала буфера
          ADD  BX,80             ;сдвигаем на 1 строку (80 символов)
          MOV  DX,3D4H           ;вывод в адресный регистр
          MOV  AL,12             ;адресуем регистр 12
          OUT  DX,AL             ;посылаем запрос
          INC  DX                ;теперь выводим в командный регистр
          MOV  AL,BH             ;старшее слово в AL
          OUT  DX,AL             ;посылаем его в регистр 12
          DEC  DX                ;обратно к адресному регистру
          MOV  AL,13             ;адресуем регистр 13
          OUT  DX,AL             ;посылаем запрос
          INC  DX                ;снова командный регистр
          MOV  AL,BL             ;младшее слово в AL
          OUT  DX,AL             ;посылаем в регистр 13

          Глава 5. Дисковые накопители.

                    Раздел 1. Управление распределением диска.

          Все  диски, как гибкие, так и жесткие, организованы одинаковым
       образом.  Поверхность диска  разделена на ряд концентрических ко-
       лец,  называемых дорожками, а дорожки делятся радиально на секто-
       ра.  Например, стандартная  дискета с диаметром 5 1/4 дюйма имеет
       40  дорожек  и в системе MS DOS 2.0 каждая дорожка разбита  на  9
       секторов (15  секторов  на  дискете  емкостью  1.2 Мбайта и 17 на
       фиксированном  диске).  Размер сектора 512 байт, и 512  байт *  9
       секторов * 40 дорожек *  2  стороны  дает в итоге емкость дискеты
       


                                     -168-
       360K.   Все типы дисков используют размер сектора 512  байт в  MS
       DOS.
          Файл распределен по такому  количеству секторов, которое необ-
       ходимо, чтобы вместить его.  Только несколько секторов на внешнем
       ободе дискеты  зарезервированы  для  специальных  нужд. Остальные
       доступны  на основе правила "первый подошел - первого  обслужат".
       Это означает, что по мере заполнения диска данными сектора посте-
       пенно заполняются по направлению к центру диска.  При уничтожении
       файла сектора освобождаются и со временем  свободные области ста-
       новятся  разбросанными по диску, разбивая новые файлы и  замедляя
       доступ к ним для чтения и записи.
          Фиксированные диски имеют некоторые  специальные характеристи-
       ки.   Часто они состоят из двух или более параллельных пластин, у
       каждой из которых есть две головки,  чтобы читать обе их стороны.
       Все дорожки, расположенные на данном расстоянии от центра, вместе
       называются  цилиндром.   Поскольку  головки всех дисков двигаются
       тандемом,  то достигается экономия перемещений если заполнять все
       дорожки одного  цилиндра,  прежде  чем  переходить  к следующему.
       Группы цилиндров могут относиться к различным операционным систе-
       мам.  Программа DOS FDISK может  разбивать  фиксированный диск на
       несколько разделов (до четырех) разного размера.  По этой причине
       параметры фиксированного диска могут сильно отличаться.
          Дисковые сектора определяются  магнитной  информацией, которую
       записывает утилита форматизации диска.  Информация включает иден-
       тификационный номер каждого сектора.   BIOS нумерует сектора 1-8,
       1-9  или 1-15, в зависимости от емкости диска.  Дорожки не марки-
       руются, вместо этого  они  определяются  механически  по смещению
       головки чтения/записи от внешнего края диска.  Дорожки нумеруются
       от 0 до 39 для дискет диаметром 5 1/4 дюйма, а для дисков большей
       емкости их может быть больше.  Дисковые функции BIOS обращаются к
       определенному сектору, указывая номера дорожки и сектора.  Однако
       функции DOS рассматривают все сектора диска, как одну цепь, кото-
       рая нумеруется подряд, начиная от  0, поэтому каждый сектор имеет
       свой логический номер сектора.
          Для дискет первый сектор (дорожка 0, сектор 1) содержит запись
       начальной загрузки, которая является небольшой программой, позво-
       ляющей компьютеру считать с дискового накопителя остальные  части
       MS DOS. Затем идут две копии  таблицы  размещения файлов, которые
       содержат информацию о распределении дискового пространства  (вто-
       рая копия хранится из соображений безопасности).  Затем идет кор-
       невой каталог, который содержит список файлов и ссылок на  подка-
       талоги, а также указывает  в  каком  месте  диска они начинаются.
       Наконец,  далее  идут две небольшие программы  DOS  IBMBIO.COM  и
       IBMDOS.COM, которые считываются при старте и обеспечивают компью-
       тер  возможностями  необходимыми для нахождения и загрузки  файла
       COMMAND.COM, который несомненно  является  основной частью опера-
       ционной системы.
          Фиксированные  диски  имеют главную запись  загрузки,  которая
       содержит таблицу разделов,  позволяющую разделить диск между нес-
       колькими операционными системами.  Таблица разделов содержит  ин-
       формацию о том, где на диске  начинается раздел DOS, а также пер-
       вый сектор какого раздела содержит запись начальной загрузки.   В
       остальном раздел организован так же, как и дискета.

          5.1.1 Чтение таблицы размещения файлов.

          Диск использует таблицу  размещения файлов (FAT) для отведения
       дискового  пространства файлам и хранения информации о  свободных
       секторах. Из соображений безопасности на всех дисках хранятся две
       копии  FAT.   Они хранятся последовательно, в  секторах с  самыми
       младшими доступными логическими  номерами,  начиная со стороны 0,
       дорожки  0,  сектора  2 (сектор 1 также занят  записью  начальной
       загрузки). Число секторов, занимаемых FAT определяется размером и
       


                                     -169-
       типом  диска.  Отметим, что в MS DOS 3.0 размер записи FAT  может
       быть 16 битов для фиксированного диска. Здесь мы будем рассматри-
       вать только 12-битные записи; для получения информации о 16-бито-
       вых записях, смотрите Техническое руководство по MS DOS.
          Таблица  размещения файлов хранит информацию о каждом кластере
       секторов на диске. Кластер это группа стандартных секторов разме-
       ром  512 байт (независимо от типа диска MS DOS всегда работает  с
       512-байтными  секторами).   Группа  секторов  используется, чтобы
       уменьшить  размер FAT.  Однако большие кластеры, используемые  на
       фиксированном диске напрасно  расходуют дисковое пространство при
       записи  маленьких файлов (утилита размером 500 байт берет 4K дис-
       кового пространства). Имеется набор размеров кластеров и размеров
       FAT, используемых в IBM PC:

          Тип диска         Секторов на кластер    Размер FAT

         дискета 160K                1                  1
         дискета 180K                1                  1
         дискета 320K                2                  2
         дискета 360K                2                  2
         дискета 1.2M                1                  7
         винчестер 10M               8                  8
         винчестер 20M               4                 40

          При  большем  размере кластера напрасно  расходуется  дисковое
       пространство, но когда большие диски имеют малый размер кластера,
       то  таблица  размещения файлов становится слишком  большой.   При
       работе с дисками DOS загружает копию FAT в память, по возможности
       сохраняя ее там, поэтому при  большом  размере FAT может расходо-
       ваться  много оперативной памяти.  Поскольку большинство AT имеют
       достаточно много памяти,  то  для  них  приемлемы намного большие
       FAT.  Поэтому для 20M винчестера взяты меньшие размеры кластеров,
       чем для 10M,  обеспечивая  экономию  дискового  пространства. Для
       дискет  емкостью 1.2M выбран кластер размером в 1 сектор, так как
       их основное назначение состоит в хранении копий жесткого диска, а
       следовательно компактность очень важна.
          Каждая позиция в таблице размещения файлов соответствует опре-
       деленной позиции кластера на диске. Обычно файл занимает несколь-
       ко кластеров и запись в каталоге файлов содержит номер стартового
       кластера, в котором  записано  начало  файла.  Просмотрев позицию
       FAT,  соответствующую первому кластеру, DOS находит номер класте-
       ра, в котором хранится следующая порция  этого файла. Этому клас-
       теру  соответствует  своя запись в FAT,  которая в  свою  очередь
       содержит номер  следующего  кластера  в  цепочке.  Для последнего
       кластера,  занятого файлом FAT содержит значения от FF8H до FFFH.
       Неиспользуемым (или освобожденным)  кластерам записывается значе-
       ние  000, а плохим секторам - FF7H.  Наконец, значения от FF0H до
       FF7H приписываются резервным кластерам.
          Номер кластера содержит 3 шестнадцатиричные  цифры, для хране-
       ния  которых требуется 1 1/2 байта.  Для уменьшения размеров  FAT
       числа для двух соседних  кластеров хранятся в трех последователь-
       ных байтах таблицы. MS DOS автоматически производит все необходи-
       мые вычисления.
          Первые три байта FAT  не  используются  для номеров кластеров.
       Первый байт содержит код, определяющий тип диска (см. [1.1.5]), а
       следующие 2 байта оба равны  FFH.   Поскольку эти позиции таблицы
       заняты,  то кластеры нумеруются, начиная с 2, причем кластеры 2 и
       3 занимают вторую тройку байт таблицы.
          MS DOS 3.0 может  создавать  FAT  с  записями размером 16 бит.
       Такие  записи необходимы для фиксированных дисков размером  более
       10M, которые имеют больше, чем 4086 кластеров.  На рис. 5-1 пока-
       зана связь между FAT и кластерами на диске.
          Очень редко имеются причины вносить изменения прямо в  таблицу
       


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

       Для нахождения следующего кластера файла:

       1. Умножьте номер кластера на 1.5.
       2. Прочитайте 2 байта с полученным смещением (окгругляя вниз).
       3.  Если номер кластера четный, то возьмите младшие 12 бит, иначе
       возьмите старшие 12 бит.

       Для преобразования номера кластера в логический номер сектора:

       1. Вычтите 2 из номера кластера.
       2. Умножьте результат на число секторов в кластере.

          Высокий уровень.

          В данном примере читается FAT и  поределяется значение, храня-
       щееся для кластера номер 6.  В [5.4.2] объясняется начальный код,
       читающий  сектора  FAT.  Результатом  является  12-битное  число,
       представленное  в виде трех шестнадцатиричных цифр (4  бита  каж-
       дая), возвращаемое в виде строки. В примере пары чисел, состоящих
       из двух цифр, объединены и в  качестве  результата берутся правые
       или  левые  три цифры.  Когда Бейсик преобразует символ в  16-ную
       форму, то он возвращает только  одну  цифру, если первая была ну-
       лем,  поэтому удаленный ноль должен быть восстановлен, чтобы этот
       метод работал правильно.

       100 '''чтение секторов FAT
       110 DEFINT A-Z
       120 DATA &H55, &H8B, &HEC, &H1E, &H8B, &H76, &H0C, &H8B
       130 DATA &H04, &H8B, &H76, &H0A, &H8B, &H14, &H8B, &H76
       140 DATA &H08, &H8B, &H0C, &H8B, &H76, &H06, &H8A, &H1C
       150 DATA &H8E, &HD8, &H8B, &HC3, &HBB, &H00, &H00, &HCD
       160 DATA &H25, &H59, &H1F, &H5D, &HCA, &H08, &H00
       170 DEF SEG = &H1000     'помещаем машинный код с этого адреса
       180 FOR N = 0 TO 38      'читаем 39 байтов данных
       190 READ Q: POKE N,Q     'переносим их в память
       200 NEXT                 '
       210 READSECTOR = 0       'начинаем процедуру с 1-го байта
       220 BUFFER = &H2000      'адрес буфера приема данных
       230 LOGICALNUMBER = 1    'начальные сектора FAT
       240 NUMBERSECTORS = 2    '2 сектора в FAT
       250 DRIVE = 0            'читаем накопитель A
       260 CALL READSECTOR(BUFFER,LOGICALNUMBER,NUMBERSECTORS,DRIVE)
       270 '''определяем номер следующего кластера файла
       280 DEF SEG = &H2000     'буфер, где хранится FAT
       290 CLUSTERNUMBER! = 6   'кластер номер 6
       300 C! = CLUSTERNUMBER!  'делаем копию
       310 C! = INT (C!*1.5)    'умножаем на 1.5 и округляем
       320 X = PEEK(C!)         'читаем 2 байта с этой позиции
       330 Y = PEEK(C!+1)       '
       340 X$ = HEX$(X): Y$ = HEX$(Y)  'переводим в 16-ные строки
       350 IF LEN(X$) = 1 THEN X$ = "0"+X$  'делаем из 2-символьными
       360 IF LEN(Y$) = 1 THEN Y$ = "0"+Y$  '
       370 H$ = Y$ + X$         'объединяем числа в одну строку
       380 '''проверяем кластер на четность
       390 IF CLUSTERNUMBER! MOD 2 <> 0 THEN 420  'уход, если нечетный
       400 NEXTCLUSTER$ = RIGHT$(H$,3)  'если четный, то правые 3 цифры
       


                                     -171-
       410 GOTO 430
       420 NEXTCLUSYER$ = LEFT$(H$,3)   'если нечетный, то левые
       430 PRINT NEXTCLUSTER$   'печатаем результат

          Средний уровень.

          Функция  DOS 1CH дает информацию о таблице размещения  файлов,
       но не дает саму FAT.  Поместите  номер  накопителя  в DL, где 0 =
       накопитель  по умолчанию, 1 = A, и т.д.  При возврате DX содержит
       число кластеров в FAT, а CX - число  байтов в секторе. DS:BX ука-
       зывает  на байт, содержащий первый байт FAT, т.е.  на код, указы-
       вающий тип диска; эти коды перечислены в [1.1.5].

          Низкий уровень.

          Намного легче получить доступ к FAT в языке ассемблера.  Отме-
       тим, что умножение номера кластера  на 1.5 производится копирова-
       нием  числа, сдвигом копии вправо на 1 бит для деления пополам  и
       сложением копии с оригиналом.  Этот метод автоматически окгруляет
       результат вниз. Код, считывающий сектора FAT в память, обсуждает-
       ся в [5.4.2].

       ;---в сегменте данных
       BUFFER    DB   1024  DUP(0)  ;отводим место для 2 секторов

       ;---читаем FAT в память
                 LEA  BX,BUFFER      ;указываем на буфер данных
                 MOV  DX,1           ;логический номер сектора
                 MOV  CX,2           ;2 сектора
                 MOV  AL,0           ;накопитель A
                 INT  25H            ;читаем сектора
                 POP  CX             ;восстанавливаем стек
       ;---получаем номер кластера
                 MOV  AX,3           ;номер кластера в AX
                 MOV  CX,AX          ;делаем копию
                 MOV  DX,AX          ;делаем вторую копию
                 SHR  DX,1           ;делим вторую копию на 2
                 ADD  CX,DX          ;складываем между собой
                 ADD  BX,CX          ;добавляем как смещение
                 MOV  DX,[BX]        ;получаем 2 байта из этого места
                 TEST AX,1           ;номер кластера нечетный?
                 JNZ  ODD_CLUSTER    ;уход, если да
                 AND  DX,0000111111111111B    ;получаем номер
                 JMP  SHORT CONTINUE   ;уход через обработку нечетного
       ODD_CLUSTER:   MOV  CL,4      ;подготовка к сдвигу вправо
                 SHR  DX,CL          ;сдвигаем вниз старшие 12 битов
       CONTINUE:

       5.1.2 Определение доступного дискового пространства.

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

          Высокий уровень.

          Следующая  ассемблерная  подпрограмма возвращает в  переменную
       CLUSTERS число свободных  кластеров  на указанном диске. Надо по-
       местить  номер накопителя в DRIVENUM, где 1 = A, 2 = B и т.д.   В
       приложении Г объясняется как ассемблерные подпрограммы включаются
       


                                     -172-
       в программы на Бейсике.

        10 DEFINT A-Z         'используем целые переменные
        20 DRIVENUM = 1       'сюда помещаем номер накопителя
        30 CLUSTERS = 0       'инициализируем переменную
        40 DATA &H55, &H8B, &HEC, &H8B, &H76, &H06, &H8B
        50 DATA &H14, &HB4, &H36, &HCD, &H21, &H8B, &H7E
        60 DATA &H08, &H89, &H1D, &H5D, &HCA, &H04, &H00
        70 DEF SEG = &H1000   'помещаем подпрограмму
        80 FOR N = 0 TO 20    'берем каждый байт
        90 READ Q: POKE N,Q   'читаем его и помещаем в память
       100 NEXT               '
       110 FREESPACE = 0      'указатель на начало процедуры
       120 CALL FREESPACE(CLUSTERS,DRIVENUM)  'вызов процедуры
       130 PRINT "CLUSTERS: ";CLUSTERS   'печать числа кластеров

          Средний уровень.

          Функция 36H прерывания 21H сообщает сколько имеется свободного
       пространства на диске.  Единственный  входной регистр DL, который
       должен содержать номер накопителя.  Накопитель по умолчанию обоз-
       начается 0, накопитель A - 1  и  т.д.   При  возврате BX содержит
       число доступных кластеров, AX - число секторов в кластере, а CX -
       количество байт в секторе.  Небольшое упражнение в умножении дает
       желаемый  результат.   В следующем примере  проверяется,  что  на
       двухсторонней  дискете   осталось   по  меньшей мере 2K дискового
       пространства:

          MOV  AH,36H          ;номер функции
          MOV  DL,1            ;накопитель A
          INT  21H             ;получаем информацию
          CMP  BX,2            ;имеется ли 2 свободных кластера?
          JL   RUNNING_OUT     ;если нет, то сообщаем об этом

          5.1.3 Получение/установка размера файла.

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

          В Бейсике функция LOF  (длина  файла)  возвращает точное число
       байтов,  отведенных файлу (предупреждаем, однако, что старые вер-
       сии Бейсика - 1.х - возвращают число  128-байтных блоков, исполь-
       зуемых файлом).  Файл должен быть открыт и ссылаться на него надо
       по номеру, под которым  был  открыт  файл.   Формат X = LOF(1). В
       следующем  примере определяется сколько 64-байтных записей содер-
       жится в файле, открытом как #3:

       100 OPEN "FILENAME" AS #3   'открываем файл
       110 RECORDLEN = 64          'определяем длину записи
       120 NUMBREC = LOF(3)/RECORDLEN  'вычисляем число записей

       


                                     -173-
          Средний уровень.

          FCB функция 23H прерывания 21H сообщает число записей в файле.
       Если  приписать файлу длину записи в 1 байт, то его размер  будет
       возвращен в байтах.  DS:DX  должны  указывать на управляющий блок
       открытого файла. Затем вызовите функцию.  Если файл не найден, то
       в AL возвращается FF.  В противном случае в AL возвращается 0,  а
       число записей помещается в поле номера записи прямого доступа FCB
       (байты 33-36). Для правильной работы поле длины записи FCB должно
       быть установлено после открытия  файла, но перед вызовом функции;
       это двухбайтное поле расположено по смещению 14 в FCB.  Если раз-
       мер файла неточно делится на  длину  записи,  то сообщаемое число
       записей  округляется  вверх.  Вот пример, в котором  используется
       длина записи равная 1:

       ;---определение размера файла
          LEA  DX,FCB        ;DS:DX указывает на FCB
          MOV  BX,DX         ;копируем указатель в BX
          MOV  CX,1          ;размер записи в CX
          MOV  [BX]+14,CX    ;пишем в поле размера записи FCB
          MOV  AH,23H        ;функция сообщающая размер файла
          INT  21H           ;вызов функции
          MOV  AX,[BX]+33    ;получаем младшую часть размера файла
          MOV  CX,[BX]+35    ;получаем старшую часть размера файла

          Можно также устанавливать  длину  файла, используя управляющие
       блоки файла.  Для этого надо использовать функцию записи блока  с
       прямым доступом, которая  обсуждается  в [5.4.5].  У этой функции
       имеется частный случай, когда число записанных записей устанавли-
       вается равным нулю, то длина  файла  устанавливается равной числу
       записей, указанному в поле записи прямого доступа.
          Метод,  использующий дескриптор файла (file handle)  не  имеет
       функции, которая непосредственно  сообщала бы длину файла, однако
       имеется  возможность вычислить размер, передвинув указатель файла
       с начала на конец файла. При открытии файла указатель файла авто-
       матически  устанавливается на первый байт файла.  Указатель файла
       перемещается функцией  42H  прерывания  21H.  Надо поместить в AL
       кодовое число 2, напраляющее указатель на конец файла.  В BX дол-
       жен быть указан номер файла, а  CX:DX  содержит смещение от конца
       файла  до  позиции, в которую должен быть  установлен  указатель,
       поэтому поместите 0 в оба этих регистра.  Затем вызовите функцию.
       При возврате DX:AX будет содержать новую позицию указателя, отно-
       сительно его  предыдущей  позиции  -  т.е.  будет содержать длину
       файла (DX содержит старший байт).  При возникновении ошибки будет
       установлен флаг переноса, а в AX  будет возвращено 1, если непра-
       вилен номер функции и 6, если неправилен номер файла. Не забудьте
       затем снова вернуть указатель на начало файла, если это необходи-
       мо.  Поместите 0 в AL, CX и DX и вызовите функцию снова. Вот при-
       мер:
       ;---открываем файл
          LEA  DX,FILE_PATH     ;DS:DX указывают на путь файла
          MOV  AL,0             ;открываем для чтения
          MOV  AH,3DH           ;функция открытия файла
          INT  21H              ;открываем его
          JC   OPEN_ERROR       ;проверка на ошибку
          MOV  HANDLE,AX        ;запоминаем номер файла
       ;---определяем длину файла
          MOV  AH,42H           ;функция перемещения указателя
          MOV  AL,2             ;код установки на конец файла
          MOV  BX,HANDLE        ;номер файла в BX
          MOV  CX,0             ;0 в CX и DX
          MOV  DX,0             ;
          INT  21H              ;сдвигаем указатель
       


                                     -174-
          JC   POINTER_ERROR    ;ошибка?
          MOV  FSIZE_HIGH,DX    ;запоминаем размер файла
          MOV  FSIZE_LOW,DX     ;

          5.1.4  Восстановление  после  ошибок,  связанных  с  нехваткой
       пространства на диске.

          При  попытке записи на полный диск может произойти крах  прог-
       раммы. Часто легко избежать этого, даже в Бейсике, проверив пред-
       варительно наличие дискового пространства [5.1.2].  Однако,  если
       ошибка произошла, то  постарайтесь  дать пользователю возможность
       исправить  ее.   Позвольте ему сохранить только часть данных  или
       стереть какой-нибудь другой  файл  и  повторить попытку. Или, еще
       более радикальное средство, позвольте пользователю вставить  дру-
       гую дискету. Последний  подход  должен  реализовываться с большой
       осторожностью. Сначала закройте все открытые файлы. Затем выдайте
       запрос на смену дискеты.  После  того,  как пользователь сообщит,
       что  новая дискета на месте, создайте новый файл и запишите  туда
       данные.

          Высокий уровень.

          В Бейсике  надо  установить  процедуру  обработки  ошибок, как
       показано в [7.2.5].  Если оператор Бейсика делает попытку  писать
       на полный диск, то возвращается код ошибки #61. При этом управле-
       ние  может быть передано процедуре обработки ошибок, которая  ин-
       формирует пользователя  о  проблеме  и позволяет ему справиться с
       ней, не теряя данных.

       100 ON ERROR GOTO 5000     'разрешаем обработку ошибок
        .
        .
       200 OPEN FNAME$ FOR OUTPUT AS #1  'открываем файл
       210 FOR N = 1 TO ARRLEN    'начинаем писать массив на диск
       220 PRINT #1, ARRAY$(N)    'записываем один элемент
       230 NEXT                   '
        .
        .
       5000 IF ERR = 61 THEN 5100  'диск полон?
       5100 IF ERR = ...           'другие ошибки ...
        .
       5100 '''восстановление при переполнении диска
       5110 BEEP: PRINT "Disk full - choose an option:"
       5120 PRINT "(A) - Re-edit the file"
       5130 PRINT "(B) - Delete some other file from disk"
       5140 PRINT "(C) - Use different diskette"
        .       (здесь идет процедура восстановления)
        .
       5500 RESUME

          Средний уровень.

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

          Метод доступа  Функция        Название            Код ошибки

             FCB          15H      Последовательная запись    AL = 1
             FCB          22H      Прямая запись              AL = 1
             FCB          27H      Прямая запись блока        AL = 1
          Дескриптор      40H      Запись в файл/устройство   CX <> BX

       


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

                       Раздел 2. Работа с каталогами диска.

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

       Тип диска    Размер каталога   Число элементов  Начальный сектор

       дискета 160K     4 сектора           64                9
       дискета 180K     4 сектора           64                9
       дискета 320K     7 секторов         112               15
       дискета 360K     7 секторов         112               15
       дискета 1.2M    14 секторов         224               29
       жесткий 10M         ----------переменные------------
       жесткий 20M         ----------переменные------------

       В  зависимости  от разбиения на разделы фиксированный диск  может
       иметь различные размеры каталога и номер начального сектора. Если
       весь диск отведен для MS DOS, то на XT и AT под корневой  каталог
       отводится 32 сектора, что позволяет иметь в нем 512 элементов.
          Как корневой каталог, так и  подкаталоги,  используют 32 байта
       для хранения информации об одном файле, независимо от типа диска.
       Таким образом в каждом секторе может храниться информация о 16-ти
       элементах  каталога.   Каждое 32-байтное поле  разбито  следующим
       образом:

          байты 0-7   Имя файла
               8-10   Расширение файла
                 11   Атрибут файла
              12-21   Зарезервировано
              22-23   Время последнего доступа к файлу
              24-25   Дата последнего доступа к файлу
              26-27   Начальный кластер
              28-31   Размер файла

       Точка между именем файла и его 3-байтным расширением не хранится.
       Все  поля выравнены на левую границу, а пустые байты  заполняются
       пробелами (код ASCII  32).  Атрибут  файла определяет является ли
       файл спрятанным, защищенным от записи и т.д.  [5.2.6].  Он опред-
       ляет также специальные элементы  каталога,  такие как подкаталоги
       или  метка тома.  Информация о времени и дате упакована,  поэтому
       для чтения этих значений требуются битовые операции [5.2.5].
          Начальный кластер указывает  на  позицию  в таблице размещения
       файлов (FAT), которая обсуждалась в [5.1.1].  FAT хранит информа-
       цию о свободном пространстве  на  диске,  а также отводит сектора
       при  записи файла.  FAT отводит дисковое  пространство  порциями,
       большими чем 1 сектор, которые называются кластерами. Файл распо-
       


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

          5.2.1 Чтение/изменение корневого каталога.

          Каталоги диска подразделяются на корневой каталог (обсуждаемый
       здесь) и подкаталоги (обсуждаемые в [5.2.3]).  Когда пользователь
       программы  вводит  имя какого-либо файла для  работы,  то  бывает
       полезным проверить, имеется ли  этот  файл на самом деле.  Обычно
       изменения  в корневом каталоге производятся в ходе обычных файло-
       вых операций или с помощью специальных  функций DOS. Однако можно
       работать  с  каталогом напрямую.  Большая нужда в  таком  подходе
       возникает при работе на языках  высокого  уровня, где утилиты DOS
       по большей части недоступны.
          Корневой каталог читается и изменяется загрузкой его в  память
       с использованием подхода,  показанного  в [5.4.2], когда читаются
       абсолютные  сектора диска.  Эти операции не оставляют места между
       секторами, когда  они  загружаются  в  память.  Буфер, содержащий
       данные сектора, может рассматриваться как набор 32-байтных  полей
       и пара указателей, которые  могут  использоваться для движения по
       каталогу.   Один указатель всегда кратен 32 и указывает на начало
       элемента каталога.  Второй указатель добавляется к первому и ука-
       зывает  на одно из полей в 32-байтном элементе.  Данные в  памяти
       могут быть изменены требуемым образом, а затем весь буфер записы-
       вается обратно на диск.
          Имеется два метода чтения абсолютных секторов диска и в  обоих
       случаях только одно число отличает  случаи чтения и записи.  Пос-
       кольку ошибка при записи на диск может легко повредить все содер-
       жимое диска, то надо  действовать  аккуратно.  Сначала убедитесь,
       что  операция чтения сектора выполнена верно во всех  отношениях.
       После этого можно попробовать записать на диск, взяв точную копию
       кода, использованного для чтения и заменив только номер функции.

          Высокий уровень.

          Бейсик  выводит каталог по команде FILES.  При этом  выводятся
       только имена файлов. FILES дает  каталог накопителя по умолчанию;
       для указания накопителя напишите FILES "A:" и т.д. Можно потребо-
       вать, чтобы была выведена  информация об отдельном файле, написав
       FILES  "A:MYFILE12/06/91T".   Как и в операционной системе  имя  файла
       может содержать * и ?.  Оператор FILES снабжает информацией поль-
       зователя,  но  иногда  наличие некоторого файла  хочет  проверить
       программа.  В этом случае надо открыть файл для последовательного
       чтения  и если он не существует, то возникнет ошибочная ситуация.
       Смотрите обсуждение и пример в [5.2.3].
          Для поиска любой информации, относящейся к корневому каталогу,
       используйте  процедуру на машинном языке, приведенную в  [5.4.2].
       После того как данные каталога  в  памяти,  установите указатели,
       как  описано выше, и ведите поиск по буферу  памяти с  32-байтным
       интервалом. Нижеприведенный пример ищет элемент каталога, относя-
       щийся  к  стертому файлу.  Когда файл стирается, то  первый  байт
       имени файла заменяется на E5H, но все остальное содержимое данно-
       го элемента остается неизменным.  Конечно, при этом освобождается
       дисковое пространство, отведенное файлу в FAT.  Процедура восста-
       новления удаленного файла должна знать номер начального  кластера
       в FAT. В  примере  этот  2-байтный  номер  кластера помещается со
       смещением 26 в элементе каталога.
       100 '''чтение секторов каталога в память с сегмента &H2000
       


                                     -177-
       110 INPUT "Enter erased filename ", FNAME$
       120 IF LEN(FNAME$) > 12 THEN BEEP: GOTO 110
       130 IF INSTR(FNAME$,".") > 9 THEN BEEP: GOTO 110
       140 '''дополнение имени и расширения файла нулями
       150 Y = INSTR(FNAME$,".")
       160 IF Y = 0 THEN FIRSTPART$ = FNAME$: GOTO 230
       170 EXTEN$ = LEFT$(FNAME$, LEN(FNAME$) - Y)
       180 EXTEN$ = EXTEN$ + STRING$(3 - LEN(EXTEN$),"")
       190 FIRSTPART$ = RIGHT$(FNAME$,Y - 1)
       200 FIRSTPART$ = FIRSTPART$ + STRING$(8 - LEN(FIRSTPART$),"")
       210 FNAME$ = FIRSTPART$ + EXTEN$
       220 '''теперь хотим найти удаленный файл
       230 MID$(FNAME$,1,1) = CHR$(&HE5)  'заменяем первый символ
       240 DIRPTR = 0                     'указатель на элемент
       250 FIELDPTR = 26                  'указатель на номер кластера
       260 FOR N = 1 TO 112               'на дискете 112 элементов
       270 X$ = ""                        'чистим X$
       280 FOR M = 0 TO 10                'читаем имя файла из каталога
       290 X$ = X$ + PEEK(DIRPTR + M)     'берем по символу
       300 NEXT                           '
       310 IF X$ = FNAME$ THEN 340        'совпадает с введенной строкой
       320 NEXT                           'если нет, то следующий
       330 PRINT "Too late - file entry obliterated": END  'уже нет
       340 X = PEEK(DIRPTR + FIELDPTR)    'нашли его, берем 1-й байт и
       350 Y = PEEK(DIRPTR + FIELDPTR + 1)  '2-й байт номера кластера
       360 Z = X + 256*Y                  'теперь номер кластера в Z

          Средний уровень.

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

       Метод FCB:

          Функция 11H прерывания 21H ищет первое появление файла.  Уста-
       новите DS:DX на неоткрытый FCB и выполните  функцию. При возврате
       AL  будет  содержать 0, если файл найден, и FF - если  нет.   DTA
       заполняется информацией из каталога.  Для обычных FCB первый байт
       DTA  содержит  номер накопителя (1 = A и  т.д.), а  следующие  32
       байта содержат элемент  каталога.   Для расширенного FCB первые 7
       байтов файла копируются в первые 7 байтов расширенного FCB, вось-
       мой байт указывает на  накопитель, а следующие 32 байта - элемент
       каталога.

       ;---в сегменте данных
       FCB     DB    1,'NEWDATABAK',25DUP(0)

       ;---ищем файл
          MOV  AH,11H    ;функция поиска в каталоге
          LEA  DX,FCB    ;указываем на FCB
          INT  21H       ;ищем
          CMP  AL,0      ;успешно?
          JNE  NO_FILE   ;если нет, то процедура обработки ошибки
          LEA  BX,DTA    ;теперь DS:BX указывает на элемент каталога
          После использования функции 11H можно использовать функцию 12H
       для поиска следующих подходящих элементов, когда имя файла содер-
       жит джокеры. В данном случае в имени файла допустим только символ
       "?", но не "*". Эта  функция  работает  в  точности так же, как и
       


                                     -178-
       первая, и если найден второй файл, то информация о первом файле в
       DTA будет уничтожена повторной записью.

       Метод дескриптора файлов:

          Функция 4EH прерывания 21H  ищет  файл  с данным именем. DS:DX
       должны указывать на строку, дающую путь файла. Например, B:\EURO-
       PE\FRANCE\PARIS указывает на  файл  PARIS. Строка может содержать
       до  63 символов и завершаться символом ASCII 0.  Имя файла  может
       содержать джокеры, включая как "?",  так и "*". Поместите атрибут
       файла в CX; если он обычный то 0, в противном случае  проконсуль-
       тируйтесь в [5.2.6] относительно значений атрибута.
          При возврает устанавливается  флаг переноса, если файл не най-
       ден.   Если файл найден, то функция заполняет DTA  информацией  о
       файле. Отметим частный случай  использования DTA методом дескрип-
       тора файлов - обычно, DTA используется функциями MS DOS для рабо-
       ты через FCB. Первые 21 байт  DTA  зарезервированы DOS для поиска
       следующих  совпадающих файлов.  Двадцать второй байт дает атрибут
       файла, за ним следуют два байта, содержащие время и еще два байта
       содержащие дату. Следующие 4 байта содержат размер файла (младшее
       слово сначала).  И, наконец, дается имя файла в виде строки пере-
       менной  длины, заканчивающейся байтом ASCII 0.  Точка (ASCII  46)
       разделяет имя и расширение и не один  из этих элементов не запол-
       нен пробелами.

       ;---в сегменте данных
       PATH       DB     'B:FRANCE\PARIS\4EME',0

       ;---ищем файл
          MOV  AH,4EH        ;номер функции
          LEA  DX,PATH       ;DS:DX указывают на путь
          MOV  CX,0          ;обычный атрибут файла
          INT  21H           ;ищем файл
          JC   NO_FILE       ;уход, если не найден
          LEA  BX,DTA        ;DS:BX указывают на DTA
          MOV  AL,[BX]+21    ;теперь атрибут файла в AL

          Следующее  появление имени файла (когда используются  джокеры)
       ищется с помощью функции  4FH  прерывания  21H.  Она  готовится в
       точности  так  же, как и функция 4EH, при этом указатель  DTA  не
       должен меняться.  Когда других совпадений не найдено, то устанав-
       ливается флаг переноса, а в AX появляется 18.

          5.2.2 Создание/удаление подкаталога.

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

          Высокий уровень.

          Бейсик  предоставляет  команды MKDIR (создай каталог) и  RMDIR
       (удали каталог).  За  обеими  должны  следовать  стандартные пути
       указания каталога, содержащие до 63 символов, включая имя накопи-
       теля.  Путь должен быть заключен в кавычки. Чтобы добавить подка-
       талог  с именем STORKS в подкаталог BIRDS напишите MKDIR  "B:MAM-
       MALS\BIRDS\STORKS".  После  выполнения  этой команды будет создан
       файл STORKS, используемый как подкаталог и факт его существования
       


                                     -179-
       будет отражен в создании элемента с именем STORKS в подкаталоге с
       именем BIRDS. Для удаления этого подкаталога надо сначала удалить
       из него все файлы [5.3.2]. Затем  надо использовать команду RMDIR
       "B:MAMMALS\BIRDS\STORKS".
          В  этих  примерах предполагалось, что Вашим текущим  каталогом
       являлся корневой каталог.  Однако, если Ваш текущий каталог нахо-
       дится  где-то  на пути к подкаталогу, над которым  осуществляются
       операции, то нет необходимости указывать весь путь. Поэтому, если
       Вашим текущим каталогом является BIRDS, то для создания или  уда-
       ления  подкаталога  STORKS   можно   использовать  команды  MKDIR
       "\STORKS" или RMDIR "\STORKS".

          Средний уровень.

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

       Создание подкаталога:
          DS:DX  должны указывать на строку, дающую накопитель и путь  к
       каталогу, в котором должен быть создан подкаталог.  Строка должна
       завершаться  байтом  ASCII 0.  Для открытия подкаталога с  именем
       PRIMATES в корневом каталоге накопителя A: надо записать строку в
       виде "A:\PRIMATES". Для открытия подкаталога в другом подкаталоге
       с именем MAMMALS напишите "A:\MAMMALS\PRIMATES".  Имя  накопителя
       A: может быть опущено если Вы работаете с накопителем, используе-
       мым  по  умолчанию, и путь может начинаться с текущего  каталога.
       Поместите в AH 39H и выполните  прерывание  21H; если указан пра-
       вильный путь, то будет создан новый каталог.  В противном  случае
       будет установлен флаг переноса, а AX будет содержать код ошибки 3
       (путь неверен) или 5 (нет доступа).  В примере создается подката-
       лог PRIMATES:

       ;---в сегменте данных
       PATH    DB   'A:MAMMALS\PRIMATES',0

       ;---создаем подкаталог с именем PRIMATES
          LEA  DX,PATH     ;DS:DX должны указывать на путь
          MOV  AH,39H      ;номер функции
          INT  21H         ;создаем подкаталог
          JC   ERROR_ROUT  ;обработка ошибок

       Удаление подкаталога:
          Для удаления подкаталога надо сформировать строку, в точностью
       совпадающую  с той, которую Вы указывали при  создании  каталога.
       Затем поместите в AH 3AH и  выполните  прерывание 21H.  Опять при
       невыполнении  функции  в AX будут возвращены коды 3 или 5 (код  5
       может указывать, что каталог непустой).

          5.2.3 Чтение/изменение подкаталога.

          Подкаталоги во многом подобны корневому  каталогу, за исключе-
       нием  того,  что они хранятся как обычные файлы, а  не в  заранее
       предопределенных секторах. Подкаталоги невозможно спутать с обыч-
       ными файлами, поскольку объект каталога, относящийся к подкатало-
       гу, имеет специальный байт  атрибутов  (с установленным битом 5 -
       см.  [5.2.6]). Подкаталоги начинаются с двух специальных 32-байт-
       ных объектов, первый из которых  имеет  имя точка, а второй - две
       точки.   Они ориентируют подкаталог среди  окружающих  каталогов.
       Ссылки на подкаталоги  нижнего  уровня  записываются  как обычные
       ссылки на файлы.
          Предполагается,  что подкаталог может быть прочитан как  любой
       другой файл, поэтому вроде бы не составляет труда загрузить его в
       


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

          Высокий уровень.

          В Бейсике команда FILES может  использовать  стандартные имена
       путей  для вывода подкаталога; например, FILES  "B:MAMMALS\BIRDS"
       выводит все файлы, содержащиеся в подкаталоге BIRDS.  Эта команда
       может  быть использована и для получения информации о  наличии  в
       каталоге определенного  файла.  Например,  FILES "LEVEL1\NEWDATA"
       ищет  файл NEWDATA и выводит его имя, если он найден.   Хотя  это
       может быть полезным  для  пользователя,  но часто самой программе
       необходимо знать существует или нет указанный файл. Чтобы устано-
       вить это попытайтесь открыть  файл  для последовательного чтения.
       Если он не будет найден, то возникнет ошибочное условие 63.  Соз-
       дайте процедуру обработки  ошибок,  как описано в [5.4.8].  Затем
       используйте  переменную,  чтобы отметить был ли найден  требуемый
       файл (в нашем  примере  переменная  "EXISTS").  Если программе не
       нужно,  что  этот файл был открыт, то закройте его перед тем  как
       двинуться дальше.

       100 ON ERROR GOTO 1000      'процедура обработки ошибок
       110 EXISTS = 1              'начальное значение "флага"
       120 INPUT "Enter filename: ",S$  'запрос имени файла
       130 OPEN S$ FOR INPUT AS #3 'открываем его для послед. чтения
       140 IF EXISTS = 0 THEN BEEP: PRINT "File does not exist"
        .
        .
       1000 IF ERR = 53 THEN 1500  'файл не существует?
       1010 IF ERR = 64 THEN ...   'другие ошибки
        .
       1500 EXISTS = 0             'меняем значение флага
       1510 RESUME 140             'продолжаем выполнение программы

          Средний уровень.

          Функции работы через  дескрипторы  файлов, которые использова-
       лись для доступа к корневому каталогу [5.2.1] могут так же просто
       обращаться к любому  подкаталогу.   Чтобы  вывести все содержимое
       каталога  надо просто использовать функцию 4EH для поиска  файлов
       *.*, а затем повторять поиск, используя функцию 4FH. Когда больше
       не  будет файлов, то будет установлен флаг переноса, а  AL  будет
       содержать  18.  Каждый раз, когда будет обнаружен очередной  эле-
       мент, в DTA будет записана информация о файле, включая полный его
       путь  (отмечаем использование DTA в функциях, использующих  деск-
       риптор файла).  Следующий пример выводит полные пути всех обычных
       файлов подкаталога.
       ;---в сегменте данных
       PATH     DB   'A:MAMMALS\*.*',0
       DTAH     DB   256 DUP(?)
       ;---установка DTA
                   LEA  DX,DTA        ;DS:DX указывают на DTA
                   MOV  AH,1AH        ;функция установки DTA
                   INT  21H           ;устанавливаем DTA
       ;---ищем первый файл
                   MOV  AH,4EH        ;номер функции
                   LEA  DX,PATH       ;указываем на строку пути
                   MOV  CX,0          ;только нормальные атрибуты
                   INT  21H           ;ищем *.*
                   JC   ERROR         ;обработка ошибок
       ;---выводим имя файла
       


                                     -181-
       NEXT_LINE:  LEA  BX,DTA        ;BX указывает на DTA
                   ADD  BX,30         ;смещение для имени файла
       NEXT_CHAR:  MOV  DL,[BX]       ;получаем символ из имени
                   CMP  DL,0          ;проверка на конец строки
                   JE   END_STR       ;уход, если конец
                   MOV  AH,2          ;иначе, выодим символ
                   INT  21H           ;
                   INC  BX            ;увеличиваем указатель
                   JMP  SHORT NEXT_CHAR  ;следующий символ
       ;---возврат каретки/перевод строки в конце строки
       END_STR:    MOV  AH,2          ;функция вывода символа
                   MOV  DL,13         ;код возврата каретки
                   INT  21H           ;выводим
                   MOV  DL,10         ;код перевода строки
                   INT  21H           ;выводим
       ;---ищем следующий файл
                   LEA  DX,PATH       ;указываем на строку пути
                   MOV  AH,4FH        ;номер функции
                   INT  21H           ;ищем следующий файл
                   JC   FINISHED      ;если нет, то выход
                   JMP  SHORT NEXT_LINE  ;иначе выводим имя файла
       FINISHED:

          5.2.4 Получение/установка текущего каталога.

          Текущий  каталог  это  каталог, в котором DOS ищет  файл,  для
       которого не указан путь. Если не установлено противного, то теку-
       щий каталог является корневым каталогом.

          Высокий уровень.

          Бейсик устанавливает текущий каталог с помощью команды  CHDIR.
       За командой должна следовать строка, указывающая путь к каталогу,
       на который надо перейти. Строка может содержать до 63-х символов,
       включая имя накопителя, и должна быть заключена в кавычки.  CHDIR
       "C:MAMMALS\PRIMATES\GIBBONS"  делает  подкталог  GIBBONS  текущим
       каталогом.  Чтобы перейти в  корневой  каталог напишите CHDIR "\"
       или CHDIR "B:\".
          Бейсик версии 3.0 может сообщать путь к текущему каталогу, как
       это  делает  команда  DOS   PATH.   Просто  введите  PRINT  ENVI-
       RON$("PATH").

          Средний уровень.

          Функция  3BH  прерывания 21H  устанавливает  текущий  каталог.
       DS:DX должны указывать на  путь  к  каталогу в стандартном виде и
       эта строка должна завершаться байтом ASCII 0. Например, B:BIRDS\-
       PARROTS\POLLY делает POLLY текущим каталогом.  B: может быть опу-
       щено,  если  это текущий накопитель по умолчанию [5.3.1].   Чтобы
       сделать текущим корневой  каталог  накопителя  A: напишите A:\. В
       примере текущим каталогом устанавливается POLLY:

       ;---в сегменте данных
       PATH     DB   'B:BIRDS\PARROTS\POLLY',0
       ;---делаем POLLY текущим каталогом
          MOV  AH,3BH        ;номер функции
          LEA  DX,PATH       ;DS:DX должны указывать на путь
          INT  21H           ;устанавливаем текущий каталог

          Чтобы определить какой каталог является текущим надо использо-
       вать функцию 47H прерывания  21H.  DS:SI  должны указывать на об-
       ласть  данных размером 64 байта, в которую будет записан путь.  В
       DL указывается накопитель, причем  0 = "по умолчанию", 1 = A, 2 =
       


                                     -182-
       B и т.д. При возврате функция возвращает строку без имени накопи-
       теля.  Если был указан несуществующий накопитель, то в AL возвра-
       щается код ошибки 15.  Строка начинается с имени первого подката-
       лога цепочки, а не с обратной косой  черты. Байт ASCII 0 сигнали-
       зирует  о  конце строки.  В данном примере имя текущего  каталога
       присваивается переменной "CURRENT_DIR":

       ;---в сегменте данных
       CURRENT_DIR   DB   64 DUP(?)

       ;---получить текущий каталог
          MOV  AH,47H         ;номер функции
          LEA  SI,CURRENT_DIR ;указываем на область данных
          MOV  DL,1           ;накопитель A
          INT  21H            ;помещает строку по адресу DS:SI

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

          Если отсчитывать от нуля, то байты 22-23 32-байтного  элемента
       каталога содержат время последнего доступа к файлу. Байты 24-25 -
       содержат дату. Значение битов следующее:

       Время:  биты 11-15    часы (0-23)
                     5-10    минуты (0-59)
                      0-4    секунды (0-29 с 2-секундным интервалом)

       Дата:   биты  9-15    год (0-119, смещение с 1980 года)
                      5-8    месяц (1-12)
                      0-4    число (1-31)

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

          Средний уровень.

          Метод  доступа  к  файлу с использованием  управляющего  блока
       файла позволяет получить дату  последнего  доступа к файлу, но не
       время.   Когда  FCB открывается функцией 0FH прерывания  21H,  то
       заполняется двухбайтное поле даты  в вышеприведенном формате. Это
       поле расположено в FCB со смещением 14H [5.3.5].
          С  другой стороны, доступ к файлу с помощью дескриптора  файла
       позволяет как получить, так и установить  дату и время последнего
       доступа к файлу.  Функция 57H прерывания 21H выполняет все опера-
       ции. Для установки времени и даты поместите номер файла в BX, и 0
       в AL. Для получения даты и времени надо поместить в AL 1. В обоих
       случаях дата содержится в DX, а время в CX. Значение битов совпа-
       дает с тем, что описано в таблице.  В техническом руководстве  по
       MS DOS утверждается, что младшие  байты информации находятся в CH
       и  DH, и наоборот.  На самом деле это не так.  При  возникновении
       ошибки устанавливается флаг переноса, а в AX возвращается 1, если
       в  AL указано неправильное число и 6, если плохой дескриптор фай-
       ла. В следующем примере определяется час, в который был последний
       лоступ к файлу:

       ;---в сегменте данных
       PATH   DB   'B:NEWDATA.BAK',0
       ;---открываем файл
          LEA  DX,PATH         ;указываем на строку пути
          MOV  AH,3DH          ;функция открытия файла
          MOV  AL,0            ;открываем для чтения
          INT  21H             ;открываем файл
       


                                     -183-
          JC   OPEN_ERROR      ;переход на обработку ошибки
       ;---получаем дату и время доступа к файлу
          MOV  BX,AX           ;помещаем номер файла в BX
          MOV  AL,0            ;код для чтения времени
          MOV  AH,57H          ;номер функции
          INT  21H             ;получаем время доступа
          JC   TIME_ERROR      ;переход на обработку ошибок
       ;---сдвигаем биты, относящиеся к часам, в начало CH
          MOV  CL,3            ;готовим сдвиг
          SHR  CH,CL           ;теперь CH содержит час доступа

          5.2.6 Спрятанные и защищенные от записи файлы.

          DOS  использует шесть различных атрибутов файлов, которые дают
       данному файлу определенный статус.  Файл может иметь несколько из
       этих атрибутов одновременно (но не все). Атрибуты устанавливаются
       12-м байтом 32-байтного  элемента  каталога.  Младшие шесть битов
       имеют значение, а остальные должны быть равны нулю. Биты такие:

          если бит 5 = 1,   то файл был изменен со времени последней
                            архивации
                   4 = 1,   то файл является подкаталогом
                   3 = 1,   то этот элемент является не файлом, а меткой
                            тома
                   2 = 1,   то файл является "системным"
                   1 = 1,   то файл спрятан при поиске по каталогу
                   0 = 1,   то файл объявлен только для чтения

       Бит 5 это архивный бит, используемый программами BACKUP и RESTORE
       DOS. Этот бит сьрасывается в 0 после архивации и устанавливается,
       когда с файлом снова работали. При следующей архивации неизменен-
       ные файлы могут быть обнаружены и проигнорированы.

          Высокий уровень.

          Бейсик не позволяет Вам  устанавливать  атрибуты  файла прямо.
       Справьтесь в [5.2.1], как считать каталог в память, найти  нужный
       файл, сделать изменения и снова записать его на диск.  Как только
       каталог помещается в память, байты  атрибутов находятся по смеще-
       ниям 11, 43, 75 и т.д. Если нужно, то Вы можете прочитать текущие
       атрибуты и изменить только один  бит,  используя  технику битовых
       операций,  описанную в приложении Б.  Но легче просто  переписать
       все атрибуты заново.  Будьте  внимательны,  ошибки могут быть фа-
       тальными.   В данном примере считываются атрибуты файла с  именем
       "NEWDATA.AAA".
       100 'читаем сектора каталога, начиная с &H2000 и затем ...
       110 DEF SEG = &H2000         'указываем на область каталога
       120 FILENAME$ = "NEWDATAAAA" 'ищем имя файла без точки
       130 DIRPTR = 0               'указатель в каталоге
       140 FOR N = 1 TO 112         'проверяем все элементы
       150 X$ = ""                  'временная строка для имени файла
       160 FOR M = 0 TO 10          'для каждого символа имени
       170 X$ = X$+PEEK(DIRPTR+M)   'добавляем его к строке
       180 NEXT                     '
       190 IF X$ = FILENAME$ THEN 220  'если имя найдено, то уходим
       200 NEXT                     '
       210 PRINT "File not found": END  'нет такого файла
       220 X = PEEK(DIRPTR+11)      'получаем атрибуты нужного файла
       230 IF X AND 32 <> 0 THEN PRINT "File not baked up"
       240 IF X AND 16 <> 0 THEN PRINT "File is a subdirectory"
       250 IF X AND 8 <> 0 THEN PRINT "Volume label - not a file"
       260 IF X AND 4 <> 0 THEN PRINT "File is a system file"
       270 IF X AND 2 <> 0 THEN PRINT "File is a hidden file"
       


                                     -184-
       280 IF X AND 1 <> 0 THEN PRINT "File is read-only"

          Средний уровень.

          Функция 43H прерывания 21H может  как находить, так и изменять
       атрибуты  файла, но только если файл был открыт с помощью  метода
       дескриптора файлов, а не  с  помощью  метода  управляющего  блока
       файла. Нет аналогичной функции для FCB. Байт атрибутов может быть
       установлен при создании  файла   [5.3.2],  используя  расширенный
       управляющий блок файла.  Но если Вы последовательно откроете FCB,
       измените установку атрибутов  и  затем  закроете  файл, то у него
       останутся первоначальные атрибуты. Хотя, конечно, Вы можете изме-
       нить атрибуты каким-нибудь  обходным  путем, но намного проще ис-
       пользовать функцию, использующую метод дескриптора файлов.
          Чтобы использовать функцию 43H, поместите 1 в AL, чтобы  прис-
       воить файлу байт  атрибутов,  содержащийся  в CX (на самом деле в
       CL, поскольку CH равен 0). Можно наоборот поместить в AL 0, чтобы
       в CX был возвращен текущий байт атрибутов файла.  В обоих случаях
       DS:DX  должны  указывать на строку, дающую путь к  файлу.   Конец
       строки отмечается байтом ASCII 0  (который не входит в число 63-х
       символов).  В примере статус "hidden" (спрятанный)  присваивается
       файлу OVERDUE:

       ;---в сегменте данных
       PATH   DB   'A:ACCOUNTS',0
       ;---включаем признак спрятанного файла
          MOV  AH,43H          ;номер функции
          MOV  AL,0            ;читаем байт атрибутов
          LEA  DX,PATH         ;DS:DX указывают на путь
          INT  21H             ;байт атрибутов в CX
          JC   ERROR_ROUTINE   ;обработка ошибок
          OR   CL,10B          ;включаем бит 1
          MOV  AH,43H          ;номер функции
          MOV  AL,1            ;заменяем байт атрибутов
          INT  21H             ;теперь файл стал спрятанным
       Флаг переноса устанавливается  при  возникновении ошибки.  В этом
       случае  в  AX возвращается 2 - если файл не найден, 3 -  если  не
       найден путь и 5 - при других ошибках (нет доступа).

          5.2.7 Чтение/изменение метки тома.

          Метка тома для дискеты -  это  элемент  каталога, имеющий спе-
       циальный атрибут. Метка занимает первые 11 байтов элемента, отно-
       сящиеся к имени и расширению файла. Байт атрибутов по смещению 11
       содержит значение 8 (бит 3 = 1).  Поля времени и даты заполняются
       обычным образом. Одним из свойств этого атрибута является то, что
       данный элемент не выводится по команде DIR.
          Метка  может  занимать любую позицию в каталоге.   Она  ищется
       перебором всех байтов  атрибутов,  пока не будет найдено значение
       8.   Чтобы  стереть метку надо просто поместить E5 в первый  байт
       соответствующего элемента - сам  байт  атрибутов можно не менять.
       Чтобы  изменить  метку надо записать новые 11  символов  (остаток
       надо  заполнить  пробелами).  Чтобы  присвоить  метку тома диску,
       который не имел ее, надо найти пустое место в каталоге и записать
       туда метку и соответствующий атрибут, ничего больше не требуется.

          Высокий уровень.

          Обсуждение в [5.4.2] объясняет  как читать и писать абсолютные
       сектора  в Бейсике.  Для стандартной двухсторонней  дискеты  надо
       использовать номер стороны 0,  номер дорожки 0, номер сектора - 6
       и  число секторов для чтения/записи - 7.  После того, как  данные
       записаны в отведенный  буфер,  примеры,  приведенные  здесь могут
       


                                     -185-
       быть использованы для изменения или добавления метки тома.  Затем
       сектора должны  быть  перезаписаны  на  диск. Будьте внимательны:
       ошибка может привести к потере всей информации на диске.   Данный
       пример ищет метку тома и изменяет ее:

       100 'сектора загружены, начиная скажем с &H1000
       110 DEF SEG = &H1000
       120 DIRPTR = 11           'указатель на байт атрибутов
       130 FOR N = 1 TO 112      'проверяем все элементы каталога
       140 IF PEEK(DIRPTR) = 8 THEN 180  'уход если метка тома
       150 DIRPTR = DIRPTR + 32  'указываем на след. элемент
       160 NEXT                  'проверяем его атрибут
       170 PRINT "No volume label found": END  'метки нет
       180 INPUT "Enter new volume label", V$  'запрос метки
       190 IF LEN(V$) > 11 THEN BEEP: PRINT "11 chars only": GOTO 180
       200 V$ = V$ + STRING$(11-LEN(V$),32)  'дополняем пробелами
       210 DIRPTR = DIRPTR - 11  'возвращаемся на начало элемента
       220 FOR N = 1 TO LEN(V$)  'помещаем все символы метки
       230 POKE N,MID$(V$,N,1)   'в память
       240 NEXT                  '
       250 'теперь осталось перезаписать сектора на диск

          Низкий уровень.

          В нижеприведенном примере предполагается, что Вы создали буфер
       данных  размером 3584 байт, для хранения всех семи секторов ката-
       лога дискеты емкостью 360K.  Буфер называется DIR_AREA.  В первом
       примере метка тома ищется и выводится, или, если она не  найдена,
       то выводится сообщение об ее  отсутствии.   Для  удобства область
       буфера  для секторов отводится в сегменте данных;  лучше  отвести
       память для задачи, а затем освободить ее [1.3.1].

       ;---в сегменте данных
       VOL_STRING   DB    'The volume label is $'
       NO_LABEL     DB    'There is no volume label $'
       DIR_AREA     DB    3584 DUP(?)
       ;---читаем 7 секторов каталога
                MOV  AX,SEG DIR_AREA         ;сегмент буфера
                MOV  ES,AX                   ;
                MOV  BX,OFFSET DIR_AREA      ;смещение буфера
                MOV  DL,0                    ;номер накопителя
                MOV  DH,0                    ;номер головки
                MOV  CH,0                    ;номер дорожки
                MOV  CL,6                    ;стартовый сектор
                MOV  AL,7                    ;число секторов каталога
                MOV  AH,2                    ;номер функции чтения
                INT  13H                     ;читаем каталог в память
       ;---ищем метку тома, сравнивая байт атрибутов с 8
                MOV  CX,112                  ;число элементов
                ADD  BX,11                   ;смещение для атрибутов
       TRY_AGAIN:   MOV  AL,[BX]             ;берем 1-й элемент
                CMP  AL,8                    ;это метка тома?
                JE   GOT_IT                  ;если да, то уход
                ADD  BX,32                   ;иначе на след. элемент
                LOOP TRY_AGAIN               ;
       ;---выводим сообщение об отсутствии метки тома
                MOV  AH,9                    ;функция вывода строки
                LEA  DX,NO_LABEL             ;указываем на строку
                INT  21H                     ;выводим ее
                JMP  SHORT CONTINUE          ;на конец
       ;---выводим строку, дающую метку тома
       GOT_IT:  MOV  AH,9                    ;функция вывода строки
                LEA  DX,VOL_STRING           ;указываем на строку
       


                                     -186-
                INT  21H                     ;выводим ее
                SUB  BX,11                   ;указатель на метку
                MOV  CX,11                   ;пишем 11 символов
                MOV  AH,2                    ;функция вывода символов
       NEXT_CHAR:   MOV  DL,[BX]             ;символ в DL
                INT  21H                     ;выводим символ
                INC  BX                      ;переходим к следующему
                LOOP NEXT_CHAR               ;
       CONTINUE:

       Чтобы стереть метку поместите следующий код в GOT_IT:

       GOT_IT:   MOV  AL,0E5H     ;код отметки пустого элемента
                 SUB  BX,11       ;указатель на начало элемента
                 MOV  [BX],AL     ;меняем первый байт

       Чтобы изменить  метку  тома,  надо  вместо  этого  использовать в
       GOT_IT следующий код.  Предполагается, что Вы подготовили  где-то
       11-байтную строку NEW_LABEL.
       GOT_IT:   LEA  SI,NEW_LABEL  ;SI должен указывать на строку
                 SUB  BX,11         ;BX указывает на начало метки
                 MOV  DI,BX         ;помещаем указатель в DI
                 MOV  CX,11         ;пересылка 11 символов
       REP       MOVSB              ;пересылаем строку

          Чтобы создать метку  можно  использовать  тот же самый код, но
       надо  также установить байт атрибутов равный 8 (Вы можете  просто
       добавить ASCII 8 к строке,  содержащей  новую метку, так как байт
       атрибутов непосредственно следует за самой меткой).
          И,  наконец,  во всех случаях изменения  каталога,  необходимо
       записать каталог обратно на диск. Ошибки при этом непростительны.

       ;---запись измененных секторов назад на диск
          MOV  AX,SEG DIR_AREA        ;регистры как и при чтении
          MOV  ES,AX                  ;
          MOV  BX,OFFSET DIR_AREA     ;
          MOV  DL,0                   ;
          MOV  DH,0                   ;
          MOV  CH,0                   ;
          MOV  CL,6                   ;
          MOV  AL,7                   ;
          MOV  AH,3                   ;номер функции записи секторов
          INT  13H                    ;

                     Раздел 3. Подготовка к работе с файлами.

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


                                     -187-
       дескриптора файла.  Метод дескриптора файла имеет  дополнительное
       преимущество в том, что он  требует меньше подготовительной рабо-
       ты. Однако в некоторых приложениях сами операции ввода/вывода при
       его использовании  могут  оказаться  более сложными, чем в методе
       FCB.  Например, операции чтения файла с прямым доступом с исполь-
       зованием метода дескриптора файла  требуют чтобы программа вычис-
       ляла смещение каждой записи в файле, в то время как соответствую-
       щая функция FCB получает номер записи и делает необходимые вычис-
       ления сама.
          Прежде  чем читать или писать данные файл должен быть  открыт.
       Открыть файл это значит создать  и  инициализировать  специальную
       область  данных,  используемую MS DOS,  которая  содержит  важную
       информацию о файле, такую как  имя  файла, имя накопителя, размер
       записи  файла  и т.д.  Языки высокого уровня, такие капк  Бейсик,
       создают эти области  автоматически.  Одной  из таких областей яв-
       ляется управляющий блок файла и когда используется метод FCB,  то
       программа создает этот блок, а  MS  DOS читает и манипулирует его
       содержимым.   Первоначально  FCB содержит только имя файла и  имя
       накопителя; после того как файл  открывается  в  него добавляется
       информация о размере записи файла и о текущей позиции, с  которой
       к нему будет осуществляться доступ.
          С другой стороны, при доступе  с  помощью дескриптора файла MS
       DOS автоматически создает область данных для файла в произвольном
       месте. Затем MS DOS создает уникальный 16-битный код номера файла
       и впоследствии этот "номер" используется функциями DOS для  иден-
       тификации того, с каким из открытых файлов производится операция.
       Все что нужно для нахождения файла - это стандартная строка пути,
       в которой может быть необязательное имя накопителя и имена подка-
       талогов должны быть разделены обратной  косой чертой.  Эти строки
       отличаются  от  стандартного запроса MS DOS только тем,  что  они
       должны завершаться байтом ASCII  0,  с  тем чтобы программа могла
       найти конец строки (такие строки называются строками ASCIIZ).
          Операции по пересылке данных из или в файл требуют, чтобы была
       указана область памяти в которую или из которой будут направлять-
       ся данные. Такой буфер определяется отведением ему места в памяти
       и установкой указателя на его первый  байт (т.е. на младший адрес
       буфера  в памяти).  Если передано слишком много данных, то  буфер
       переполняется и может разрушить данные, расположенные в следующих
       адресах  памяти.   Буфер может использоваться  как  промежуточный
       буфер, работающий только с небольшой  порцией данных для операций
       чтения или записи. Или буфер может помещаться в область памяти, в
       которой программа действительно хранит и обрабатывает данные.
          Функции доступа через управляющий блок файла определяют проме-
       жуточный  буфер с помощью указателя, которой все  время  хранится
       операционной системой.  Этот  буфер  называется  область обмена с
       диском  (disk  transfer area) или DTA.  К сожалению,  техническая
       документация по IBM PC часто  называет  термином DTA указатель на
       буфер,  хотя  на самом деле правильно называть его указателем  на
       DTA.  После того как указатель  на  DTA установлен с помощью спе-
       циальной функции, все файловые операции используют его до тех пор
       пока он не будет изменен. С другой стороны, функции, использующие
       дескриптор файла, должны указывать стартовый адрес буфера  обмена
       каждый раз при вызове функции и  они игнорируют указатель на DTA,
       используемый  функциями  управляющего блока файла.   Рисунок  5-2
       показывает два метода доступа к файлам.

          5.3.1 Установка/проверка накопителя по умолчанию.

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


                                     -188-
          Высокий уровень.

          В приведенной программе на Бейсике текущий накопитель по умол-
       чанию переключается с помощью процедуры на машинном языке. Проце-
       дура  имеет длину всего 7 байтов.  Она помещается в строку X$,  а
       переменная Z служит указателем на первый байт процедуры. В прило-
       жении  Г объясняется как вставлять ассемблерные процедуры в прог-
       раммы на Бейсике.  Номер накопителя устанавливается в строке 110,
       причем 0 = A, 1 = B и т.д.  Если назначить накопителем по умолча-
       нию несуществующий накопитель, то ошибки не будет, поэтому будьте
       внимательны. Не пытайтесь объединить строки 120 и 130 этой проце-
       дуры, поскольку в этом случае интерпретатор Бейсика будет обраба-
       тывать их неправильно.

       100 DEF SEG         'сегмент на начало области Бейсика
       110 NUM = 0         'выбираем накопитель A
       120 X$ = CHR$(180)+CHR$(14)+CHR$(178)+CHR$(NUM)+CHR$(205)+
                CHR(33)+CHR$(223)
       130 Y = VARPTR(X$)  'получаем дескриптор строки (адрес в Y+1)
       140 Z = PEEK(Y+1)+PEEK(Y+2)*256  'вычисляем адрес строки
       150 CALL Z          'выполняем машинную процедуру

          Средний уровень.

          Функция EH прерывания 21H устанавливает накопитель по  умолча-
       нию. Надо просто поместить номер накопителя (0 = A, 1 = B и т.д.)
       в  DL и выполнить прерывание.  Эта функция возвращает в AL  число
       накопителей на машине. Отметим, что когда у машины имеется только
       один накопитель, то возвращается число 2. Лучший способ определе-
       ния числа накопителей у машины описан в [1.1.5].

          MOV  AH,0EH       ;номер функции
          MOV  DL,1         ;код для накопителя B
          INT  21H          ;устанавливаем накопитель по умолчанию

          Функция 19H прерывания 21H  сообщает  какой из накопителей яв-
       ляется  накопителем по умолчанию.  Для этой функции  нет  входных
       регистров. При возврате в AL содержится кодовый номер, где 0 = A,
       1 = B и т.д.

          5.3.2 Создание/удаление файла.

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

          Высокий уровень.

          Бейсик не имеет специальной команды для создания файла. Вместо
       этого при открытии файла указанное имя ищется в каталоге и,  если
       оно не найдено, то создается новый файл. Если открыть новый файл,
       а  затем закрыть его не производя в него записи, то он  останется
       в каталоге с длиной 1 байт и ему  будет отведен кластер дискового
       пространства  (единственный байт - это символ Ctrl-Z - ASCII 26 -
       


                                     -189-
       который используется в качестве признака конца стандартного текс-
       тового файла). Детали оператора OPEN см. в [5.3.3].
          Наоборот, оператор CLOSE не уничтожает файл.  Вместо этого для
       уничтожения файла  используется  оператор  KILL.   Для того чтобы
       уничтожить файл его не надо открывать. Просто поместите имя файла
       в кавычках, например KILL "A:ACCOUNT12/06/91T".   Или, если файл нахо-
       дится в другом подкаталоге, то надо использовать стандартный путь
       к файлу, например KILL "A:\FINANCES\ACCOUNT12/06/91T". В обоих случаях
       имя накопителя необходимо указывать только если файл находится не
       на накопителе по умолчанию.  Отметим, что Вы не можете воспользо-
       ваться  этим методом, чтобы удалить подкаталог (который  является
       одним из видов файла) - вместо этого используйте RMDIR.

          Средний уровень.

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

       Метод FCB:
          Функция 16H прерывания 21H создает и открывает файл.  Создайте
       FCB с именем файла и накопителя  и пусть DS:DX указывает на него.
       Затем  вызовите функцию.  Просматривается каталог и  если  найден
       совпадающий элемент, то  снова  используется  именно этот элемент
       каталога, при этом новый файл перекрывает старый с тем же именем.
       Чтобы избежать непреднамеренного  разрушения файлов, сначала про-
       верьте  на  наличие файла с таким именем, используя  функцию  11H
       прерывания 21H [5.2.1].  Если  нет  файла с таким именем, то соз-
       дается новый элемент каталога и в AL возвращается 0; если каталог
       полон, то в AL возвращается FF. Чтобы присвоить файлу специальные
       атрибуты (например, статус только для чтения) [5.2.6] используйте
       расширенный управляющий блок  файла  [5.3.5].  При открытии новый
       файл  инициализируется  с нулевой длиной и ему отводится  кластер
       дискового пространства. Вот пример:

       ;---в сегменте данных
       FCB       DB    1,'MYFILE  DAT',25 DUP(0)
       ;---проверка наличия такого файла
          MOV  AH,11H         ;функция поиска файла
          LEA  DX,FCB         ;DS:DX указывают на FCB
          INT  21H            ;ищем файл
          CMP  AL,0           ;AL = 0 если файл существует
          JE   WARN_USER      ;если да, то сообщаем об этом
       ;---создание файла
          MOV  AH,16H         ;номер функции создания файла
          INT  21H            ;создаем файл

          Для создания файла со специальными атрибутами, например стату-
       сом только для чтения, надо использовать расширенный  управляющий
       блок файла. Байт атрибутов файла обсуждается в [5.2.6]. К обычно-
       му  FCB  надо добавить 7-байтный заголовок, начиная с байта  FFH,
       затем должны следовать  5  байтов  ASCII  0,  а затем нужный байт
       атрибутов.   Для создания спрятанного файла необходимо, чтобы был
       


                                     -190-
       установлен бит 1 байта атрибутов. Чтобы спрятать файл, открытый в
       приведенном примере, напишите:

       FCB     DB     0FFH,5 DUP(0),2,1,'MYFILE  DAT',25 DUP(0)

          Функция  13H прерывания 21H уничтожает файл.  Надо чтобы DS:DX
       указывали на неоткрытый FCB и выполнить  функцию. Если не найдено
       файла  с  указанным именем, то в AL возвращается FF, иначе 0.   В
       имени файла могут  использоваться  джокеры  (знаки вопроса, но не
       звездочки) и в этом случае за одно обращение к функции может быть
       удалено несколько файлов. Вот пример:

       ;---в сегменте данных
       FCB    DB     1,'MYFILE  DAT',25 DUP(0)
       ;---удаляем файл
          MOV  AH,13H           ;номер функции удаления файла
          LEA  DX,FCB           ;DS:DX указывают на FCB
          INT  21H              ;удаляем файл
          CMP  AL,0FFH          ;проверка на ошибку
          JE   DELETE_ERROR     ;уход на обработку ошибки

       Метод дескриптора файла:
          Функция 3CH прерывания  21H  создает  и  открывает  новый файл
       методом  дескриптора  файла.  DS:DX должны указывать  на  строку,
       дающую путь к файлу и  имя  файла  в  стандартном формате MS DOS,
       включая  имя накопителя, если файл находится не на накопителе  по
       умолчанию. Строка должна завершаться байтом ASCII 0. Байт атрибу-
       тов  файла  [5.2.6] поместите в CX (0 - для  нормального  файла).
       Затем выполните функцию.  При  успешном  выполнении флаг переноса
       будет равен нулю, а в AX будет возвращен номер нового файла.  При
       возникновении ошибки  флаг  переноса  устанавливается в 1, а в AX
       содержится код ошибки, который может быть равен 3, если не найден
       путь, 4 - если уже открыты все буфера для файлов и 5 - если ката-
       лог  полон или файл уже существует со статусом только для чтения.
       Отметим, что если в каталоге уже существует  файл с таким именем,
       то  существующий  файл обрезается до нулевой длины, и  тем  самым
       разрушается.  Для избежания  ошибок надо предварительно использо-
       вать функцию 4EH прерывания 21H для проверки.

       ;---в сегменте данных
       PATH     DB     'B:LEVEL1\LEVEL2\FILENAME.EXT',0
       ;---проверка наличия файла в каталоге
          MOV  AH,4EH         ;функция поиска в каталоге
          LEA  DX,PATH        ;DS:DX указывают на путь
          INT  21H            ;проверка наличия файла
          JNC  WARN_USER      ;если есть, то сообщаем
       ;---создание файла
          MOV  AH,3CH         ;функция создания файла
          MOV  CX,0           ;нормальные атрибуты
          INT  21H            ;создаем файл
          JC   OPEN_ERROR     ;уход на обработку ошибки
          MOV  HANDLE,AX      ;запоминаем номер файла

          В  MS DOS 3.0 добавлена новая функция для создания файла мето-
       дом дескриптора файла.  Это функция номер 5BH прерывания 21H. Она
       работает  в точности так же, как и описанная функция 3CH, за иск-
       лючением того, что она  возвращает  расширенные  коды ошибок, что
       позволяет лучше обрабатывать ошибочные ситуации.  Они объяснены в
       [7.2.5].
          Для уничтожения файла  методом  дескриптора  файла используйте
       функцию  41H прерывания 21H.  И опять DS:DX должны  указывать  на
       строку, дающую путь и имя файла.  Джокеры в имени файла не допус-
       каются.  Затем вызовите функцию.  Если при возврате флаг переноса
       


                                     -191-
       установлен, то функция не  выполнена;  в этом случае AL будет со-
       держать  2,  если файл не найден и 5 - если произошла  ошиька  на
       диске.  Отметим, что с помощью  этой функции Вы не можете удалить
       файл  со  статусом  только для чтения;  измените  атрибуты  файла
       [5.2.6] перед его удалением. Вот пример:

       ;---в сегменте данных
       PATH    DB    'B:LEVEL1\LEVEL2\FILENAME.EXT',0
       ;---уничтожаем файл
          MOV  AH,41H         ;номер функции уничтожения
          LEA  DX,PATH        ;DS:DX указывают на путь
          INT  21H            ;уничтожаем файл
          JC   DELETE_ERROR   ;на обработку ошибки

          MS DOS версии 3.0  имеет  специальную  функцию (5AH прерывания
       21H)  для создания временного "безымянного" файла.   Операционная
       система сама генерирует  имя  для  файла  и проверяет, что такого
       файла еще нет в каталоге. При этом исключается всякая возможность
       что при создании  временного  файла  будет  разрушен существующий
       файл  с совпадающим именем.  При входе DS:DX должны указывать  на
       строку пути к каталогу,  в  котором  должен быть создан временный
       файл.  Строка должна завершаться обратной косой чертой. Поместите
       атрибуты файла в CX (обычно 0).   При возврате AX будет содержать
       номер  файла,  если  только флаг переноса не  установлен, в  этом
       случае AX содержит информацию  об  ошибке. Произвольное имя файла
       добавляется  к концу строки пути.  Эта функция  может  возвращать
       расширенные коды ошибок, которые  существуют только в MS DOS 3.0;
       они объясняются в [7.2.5]. Файл, созданный этой функцией не унич-
       тожается автоматически  -  программа  должна использовать функцию
       41H  (см.   выше).  В данном примере программа создает  временный
       файл, а затем уничтожает его:

       ;---в сегменте данных
       PATH   DB    'B:LEVEL1\LEVEL2\',12 DUP(0)
       ;---создаем временный файл
          MOV  AH,5AH         ;номер функции
          LEA  DX,PATH        ;DS:DX указывают на путь
          INT  21H            ;создаем временный файл
          JC   CREATION_ERROR ;уход на обработку ошибки
           .
           .
          MOV  AH,41H         ;номер функции
          LEA  DX,PATH        ;DS:DX указывают на путь
          INT  21H            ;уничтожаем временный файл
          JC   DELETION_ERROR ;уход на обработку ошибки

          5.3.3 Открытие/закрытие файла.

          "Открыть" файл - это значит  создать  небольшие  блоки памяти,
       которые  будут содержать информацию о файле и служить промежуточ-
       ной станцией (буфером), через  которую  данные будут передаваться
       между файлом и памятью.  Языки высокого уровня автоматически соз-
       дают для Вас эти блоки, а  язык  ассемблера  -  нет. При открытии
       файла каталог проверяется на его наличие.  Если файла найден,  то
       MS DOS берет информацию  из  каталога  о  размере и дате создания
       файла.  Затем, при закрытии файла, система обновляет информацию в
       каталоге. Закрытие файла также очищает все системные буфера пере-
       носа, посылая на диск оставшуюся информацию.  Если Вы не закроете
       файл перед завершением программы,  то это может привести к потере
       данных.
          Если программа работает со многими файлами, то надо  постоянно
       иметь ввиду сколько имеется одновременно открытых файлов.  MS DOS
       2.1 позволяет иметь до 99 одновременно открытых файлов, причем по
       


                                     -192-
       умолчанию только 8 (измените  это  число с помощью команды MS DOS
       FILES).  Бейсик позволяет иметь не более 15 открытых файлов. Каж-
       дый файл занимает место для блока  параметров и буфера. Поскольку
       память для каждого файла отводится отдельно перед тем, как  файлы
       открываются, то эта  память  недоступна  для  программ, даже если
       указанное  число  файлов не используется в настоящий момент.   По
       этой причине Вы можете экономить память, устанавливая максимально
       допустимое число открытых файлов именно таким, которое требуется,
       с помощью описанного метода.

          Высокий уровень.

          При открытии файла в Бейсике идет поиск в каталоге и если файл
       не найден, то создается новый файл  с данным именем.  Имеется два
       способа  записи оператора открытия файла и в большинстве  случаев
       оба эти способа эквивалентны. Единственное отличие состоит в том,
       что один из этих способов более закодирован, в то время как  дру-
       гой ближе к естественному  языку.   В  обоих операторах Вы должны
       указать по меньшей мере три сорта информации. Во-первых требуется
       имя файла и, поскольку это  строка,  оно  должно быть заключено в
       кавычки.  Во-вторых, число, начиная с 1, присваивается файлу, как
       идентификационный номер, по  которому другие операторы читают или
       пишут в файл. И в-третьих Вы должны указать для какой цели откры-
       вается данный файл, т.е. открыт  ли он для прямого или для после-
       довательного доступа.  Для открытия файла MYFILE.TXT для записи в
       последовательный файл,  причем  этот  файл  будет  иметь номер 2,
       запишите или

          OPEN "O",#2,"MYFILE.TXT"
       или
          OPEN "MYFILE.TXT" FOR OUTPUT AS #2

       Отметим, что в обоих случаях номер 2 относится к буферу файла #2.
       Число может быть любым, не превосходящим  числа разрешенных буфе-
       ров для файлов. Если поддерживается одновременная работа с шестью
       файлами, то число должно быть в интервале от 0 до 6. Однако буфер
       файла номер 1 не обязательно использовать раньше, чем файла номер
       2.  По умолчанию Бейсик устанавливает  число буферов равное 8, но
       Вы можете изменить это число на другое от 4 до 15. Из этих файлов
       четыре используются Бейсиком для своих нужд, поэтому по умолчанию
       только  4  файла доступны Вам для ввода/вывода.  Для  того  чтобы
       установить число  доступных  буферов  используйте параметр F: при
       запуске  Бейсика.  Например, если Вы при старте Бейсика  напишите
       BASICA/F:10, то будет создано  10  буферов, шесть из которых дос-
       тупны для файловых операций.
          Второй параметр, S:, устанавливает размер буферов файла.   Все
       буфера имеют один и тот же размер.  По умолчанию берется размер в
       128 байтов, однако допустимы размеры до 32767 байтов.  Для файлов
       последовательного доступа этот  размер может быть установлен рав-
       ным 0, что позволяет немного сэкономить память. Для файлов прямо-
       го доступа он должен быть не меньше максимального размера записи.
       Отметим,  что есчи размер записи равен 512 байтам и размер буфера
       тоже 512 байт, то это  приводит  к  ускорению  дисковых операций.
       Команда BASICA/S:512/F:10 открывает 10 буферов размером 512 байт.
       Каждый файл требует 188  байтов  плюс  размер буфера, поэтому для
       такой конфигурации потребуется 7K памяти.  Число буферов не может
       быть больше, чем разрешено иметь открытых файлов в DOS.

       Кодированная форма:
          Первая из форм оператора OPEN использует  одну букву для обоз-
       начения  желаемого типа операций над файлом.  Имеется три возмож-
       ности:

       


                                     -193-
          "O"   открыть файл с последовательным доступом для вывода
          "I"   открыть файл с последовательным доступом для ввода
          "R"   открыть файл с прямым доступом для ввода/вывода

          Пoследовательные файлы не могут  записываться,  когда они открыты
       для чтения и наоборот. В типичных случаях, последовательные файлы
       открываются для  чтения,  затем  считываются  целиком  в память и
       закрываются.  После того как необходимые изменения внесены,  файл
       снова открывается, но теперь для  вывода и записываетсяобратно на
       диск,  перекрывая то, что было записано в его секторах и, возмож-
       но, захватывая новые сектора.
          Следует отметить несколько моментов,  относящихся к этой форме
       оператора  OPEN.  Имя файла должно содержать имя накопителя, если
       файл не найден на накопителе  по  умолчанию  (т.е.  накопителе, с
       которого запущен Бейсик).  Имя файла может также содержать путь к
       файлу, находящемуся в  подкаталоге,  например OPEN "I",#1,"A:\LE-
       VEL1\LEVEL2\MYFILE.TXT". Кроме того, Вы можете поместить указание
       размера записи в конце оператора  OPEN "R",#3,"MYFILE.TXT",52.  В
       этом  случае  каждая  запись будет занимать  52  байта  дискового
       пространства.  Если в  операторе  FIELDS  не  используются все 52
       байта, то остаток пропадет.  Этот параметр существенен при опера-
       циях с файлами прямого  доступа.  Большинство  операций с файлами
       последовательного доступа не требуют указания длины записи, одна-
       ко Вы можете ускорить файловые  операции, установив размер записи
       равным  512 байтам.  Длина записи может быть в диапазоне от 1  до
       32767 байтов и по умолчанию равна 128 байтам.

       Форма естественного языка:
          Вторая форма оператора OPEN делает совершенно то же самое, что
       и  первая, но использует полные слова.  Вместо того, чтобы писать
       "O" или "I", Вы должны  писать  INPUT  или  OUTPUT (без кавычек),
       например,  OPEN "FILENAME" FOR INPUT AS #1.  Для файлов с  прямым
       доступом не указывается этот  параметр:  OPEN "MYFILE.TXT" AS #2.
       Кроме того, Вы можете указать режим APPEND, чтобы записать данные
       начиная с конца последовательного файла, не уничтожая уже сущест-
       вующих  данных: OPEN "B:MYFILE.TXT" FOR APPEND AS #3.  Как и  для
       первой формы в операторе может быть  указана необязательная длина
       записи. Надо просто добавить в кгнце оператора LEN = число.  Нап-
       ример OPEN "C:MYFILE.TXT" AS #1  LEN  = 52 открывает файл прямого
       доступа с записями длиной 52 байта.
          Часто  программа  должна получать имя  файла  от  пользователя
       программы.  Чтобы  использовать  это  имя  файла в операторе OPEN
       просто  подставьте вместо строки имени файла имя строки, содержа-
       щей это имя. При этом необходима проверка на правильность введен-
       ного имени.

       100 INPUT "Enter file name: ",F$  'получаем имя файла
       110 IF INSTR(F$,".") <> 0 THEN 130  'есть ли расширение?
       120 IF LEN(F$) > 8 THEN 500 ELSE 150  'длиннее 8 символов?
       130 IF LEN(F$) > 12 THEN 500      'длиннее 12 символов?
       140 IF LEN(F$) - INSTR(F$,".") > 3 THEN 500 'тип длиннее 3-х
       150 OPEN F$ FOR INPUT AS #1       'открываем файл
        .
        .
       500 INPUT "Improper filename - enter another: ",F$
       510 GOTO 110                 'если имя неверное, новый запрос

       Закрытие файла:
          Закрытие  файла тривиально.  Чтобы закрыть все открытые  файлы
       напишите CLOSE. Чтобы  закрыть  определенный  файл  или несколько
       файлов  напишите  CLOSE #1 или CLOSE #1, #3.  Важно  закрыть  все
       файлы перед завершением  программы.   Если этого не сделать, то в
       файле могут остаться данные, которые не записаны на диск.   Отме-
       


                                     -194-
       тим, что команды  END,  NEW,  RESET,  SYSTEM  и RUN закрывают все
       буфера файлов, но не очищают эти буфера. Уже закрытый файл всегда
       может быть снова открыт с использованием любого доступного буфера
       файла.

          Средний уровень.

          MS  DOS обеспечмвает различные функции для открытия и закрытия
       файла, в зависимости от того использовала ли программа для досту-
       па  к файлу метод управляющего блока файла или метод  дескриптора
       файла.  В обоих случаях могут  быть открыты только файлы, которые
       существовали до этого.  Для создания новых файлов существует спе-
       циальная функция [5.3.2].

       Метод FCB:
          Функция 0FH прерывания  21H  открывает  существующий  файл. Вы
       должны  сначала  создать управляющий блок файла, как  показано  в
       [5.3.5]. Перед открытием FCB  должен содержать только имя файла и
       имя  накопителя (0 = по умолчанию, 1 = A и т.д.).   DS:DX  должны
       указывать на FCB, а затем надо выполнить функцию. При возврате AL
       будет  содержать  0, если файл успешно открыт и FF, если файл  не
       найден.  Если для указания накопителя используется 0, то он будет
       заменен на код, соответствующий накопителю по умолчанию.
          Только  после того как файл открыт Вы должны установить размер
       записи (по умолчанию -  128  байт),  а  также поля записи прямого
       доступа  и текущей записи (они обсуждаются в разделе, относящемся
       к операциям с последовательным  и  прямым доступом). При открытии
       поле  текущего  блока  заполняется нулем, а поля  даты и  времени
       информацией из каталога.
          Чтобы закрыть файл с помощью метода FCB, надо установить DS:DX
       на  открытый FCB и вызвать функцию 10H прерывания 21H.  При удаче
       информация о размере файла, дате и времени будет записана в ката-
       лог,  а  в AL будет возвращен 0.  Однако если имя файла не  будет
       обнаружено в каталоге или оно будет  найдено в другой позиции, то
       изменения на диске будут индицированы возвратом FF в AL.

       ;---в сегменте данных
       FCB     DB     1,'FILENAMEEXT',25 DUP(0)
       ;---открытие файла
          MOV  AH,0FH        ;номер функции
          LEA  DX,FCB        ;DS:DX указывают на FCB
          INT  21H           ;открываем файл
          CMP  AL,0          ;проверка на ошибку
          JNE  OPEN_ERROR    ;на обработку ошибки
           .
           .
       ;---закрытие файла
          MOV  AH,10H        ;номер функции
          LEA  DX,FCB        ;DS:DX указывают на FCB
          INT  21H           ;закрываем файл
          CMP  AL,0          ;проверка на ошибку
          JNE  CLOSE_ERROR   ;на обработку ошибки

       Метод дескриптора файла:
          Для  открытия  файлов используйте функцию 3DH прерывания  21H.
       DS:DX должны указывать на строку,  дающую путь и имя файла, вклю-
       чая имя нкакопителя, если это необходимо.  Вся строка должна быть
       не длиннее 63-х байтов и завершаться символом ASCII 0.  В AL надо
       поместить  код доступа, причем 0 открывает файл для  чтения, 1  -
       для записи, а 2 - для чтения/записи. При возврате AX будет содер-
       жать 16-битный номер файла, по которому файл впоследствии иденти-
       фицируется. Файловый указатель  устанавливается  на начало файла.
       Размер записи устанавливается равным 1 байту - это связано с тем,
       


                                     -195-
       что операции прямого доступа при использовании метода дескриптора
       файла не имеют специальных буферов: на самом деле файлы с  прямым
       доступом рассматриваются как  последовательные  и с ними работают
       одни  и те же функции.  Эта функция позволяет открывать как обыч-
       ные, так и спрятанные файлы.  При возврате флаг переноса равен 0,
       если файл открыт успешно.  В противном случае флаг переноса уста-
       навливается, а AX содержит 2  -  если  файл  не  найден, 4 - если
       программа  хочет открыть слишком много файлов, 6 - при ошибке  на
       диске и 12 - если неправильно  указан  код доступа в AL. Вот при-
       мер:

       ;---в сегменте данных
       PATH    DB    'A:LEVEL1\FILENAME.EXT',0
       ;---открываем файл для чтения/записи
          MOV  AH,3DH         ;номер функции
          MOV  AL,2           ;открываем для чтения/записи
          LEA  DX,PATH        ;DS:DX указывают на путь
          INT  21H            ;открываем файл
          JC   OPEN_ERROR     ;уход на обработку ошибок
          MOV  HANDLE,AX      ;сохраняем номер файла

          Функция  3EH  прерывания 21H закрывает файл, открытый  методом
       дескриптора файла.  Надо просто  поместить номер файла в BX и вы-
       полнить функцию.  При возврате флаг переноса равен 0, если все  в
       порядке, иначе он равен 1, а AX  =  6, если указан неверный номер
       файла.

       ;---закрытие файла
          MOV  AH,3EH       ;номер функции
          MOV  BX,HANDLE    ;номер файла
          INT  21H          ;закрываем файл
          JC   CLOSE_ERROR  ;уход на обработку ошибки

          Функция 45H прерывания 21H создает второй дескриптор файла  из
       существующего  открытого  дескриптора.   В  BX должен быть указан
       существующий  номер, а в AX будет возвращен новый.   Функция  46H
       прерывания 21H связывает  второй  дескриптор  (помещаемый в CX) с
       открытым  файлом (номер которого в BX) таким образом, что  первый
       будет относиться к тому же файлу и устройству, что и последний.

          5.3.4 Переименование файла;  изменение позиции файла в катало-
       ге.

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

          Высокий уровень.

          В Бейсике файл переименовывается командой NAME. С помощью этой
       команды он может быть также перенесен  в другой каталог. Напишите
       сначала существующее имя, а затем новое имя файла, оба  заключен-
       ные в кавычки, например NAME  "OLDFILE.EXT"  AS "NEWFILE.EXT".  В
       этом  случае  будет переименован файл в корневом  каталоге.   Для
       изменения имен файлов,  расположенных  в подкаталогах, могут быть
       использованы пути к файлу.  Например, NAME "B:LEVEL1\OLDFILE.EXT"
       AS "B:LEVEL1\NEWFILE.EXT"  изменяет  имя  файла в подкаталоге LE-
       VEL1.
          Отметим, что для нового имени файла должен быть указан  полный
       путь.  Если Вы запишете  NAME  "B:LEVEL1\OLDFILE.EXT"  AS "NEWFI-
       LE.EXT",  то файл будет не только переименован, но и перенесен  в
       


                                     -196-
       корневой каталог.  Для  переноса  файла  из  одного подкаталога в
       другой  без изменения его имени напишите команду NAME "A:SUBDIR1-
       \OLDFILE.EXT" AS  "A:SUBDIR2\OLDFILE.EXT".  Таким  методом нельзя
       перенести файл с диска на диск.  Поскольку файлы, расположенные в
       разных каталогах могут иметь одно и то же имя, то возможна ошибка
       при попытке переноса файлов с одинаковыми именами.  В этом случае
       будет возвращен код ошибки 58 [5.4.8].

          Средний уровень.

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

       Метод FCB:
          Используйте функцию 17H прерывания 21H. DS:DX должны указывать
       на открытый управляющий блок  файла.  Поместите новое имя файла в
       FCB,  начиная  со смещения 11H (это "резервная"  область  блока).
       Новое имя может использовать  символ  "?", в этом случае символы,
       находящиеся  в этих позициях, не будут изменяться.  При возврате,
       если новое имя уже существовало в каталоге, то AL будет равно FF,
       иначе  AL  =  0.  В примере имя файла  ACCOUNTS12/06/91T  меняется  на
       DEBTS12/06/91T.

       ;---в сегменте данных
       FCB       DB      'FILENAMEEXT',25 DUP(0)
       NEWNAME   DB      'NEWNAME EXT',   ;11 символов нового имени
       ;---помещаем новое имя файла в переменную NEWNAME
             MOV  SI,OFFSET NEWNAME   ;DS:SI указывают на новое имя
             MOV  AX,SEG FCB          ;ES:DI указывают на FCB
             MOV  ES,AX               ;
             MOV  DI,OFFSET FCB       ;
             ADD  DI,11H              ;начинаем со смещения 11H
             MOV  CX,11               ;имя файла содержит 11 символов
       REP   MOVSB                    ;переносим 11 байтов
             LEA  DX,FCB              ;DS:DX указывают на FCB
             MOV  AH,17H              ;функция изменения имени
             INT  21H                 ;изменяем имя
             CMP  AL,0FFH             ;проверка на ошибку
             JE   RENAME_ERROR        ;уход на обработку ошибки

       Метод дескриптора файла:
          Функция 56H прерывания 21H переименовывает и перемещает файлы.
       DS:DX должны указывать на строку, дающую путь и имя переименуемо-
       го  файла  (до 63-х символов) и завершающуюся символом  ASCII  0.
       ES:DI должны указывать на вторую строку, которая дает новые имя и
       путь файла.  Имена  накопителей,  если  они  присутствуют, должны
       совпадать.   Если  пути различны, то файл переноносится в  другой
       подкаталог.  Чтобы перенести файл без переименования надо во вто-
       рой строке указать то же самое имя, но другой путь. При возврате,
       если произошла ошибка,  то  устанавливается  флаг  переноса, а AX
       будет содержать 3 - если один из путей не найден, 5 - при  ошибке
       на диске и 17 - при попытке  переноса между разными накопителями.
       В  примере  файл ACCOUNTS12/06/91T переносится из подкаталога GAINS  в
       подкаталог LOSSES.

       ;---в сегменте данных
       OLDPATH   DB   'A:GAINS\ACCOUNTS12/06/91T',0
       NEWPATH   DB   'A:LOSSES\ACCOUNTS12/06/91T',0
       ;---изменение пути файла
          LEA  DX,OLDPATH          ;DS:DX указывают на старый путь
          MOV  AX,SEG NEWPATH      ;ES:DI указывают на новый путь
       


                                     -197-
          MOV  ES,AX               ;
          MOV  DI,OFFSET NEWPATH   ;
          MOV  AH,56H              ;номер функции
          INT  21H                 ;переносим файл
          JC   ERROR_ROUTINE       ;уход на обработку ошибки

          5.3.5 Подготовка к файловым операциям.

          Языки высокого уровня, такие как  Бейсик, выполняют подготови-
       тельную работу для файловых операций автоматически.  Однако прог-
       раммы на языке ассемблера имеют  достаточно  работы перед тем как
       создать или открыть файл. Требования отличаются, в зависимости от
       того используется ли для доступа к файлу метод управляющего блока
       файла или метод дескриптора файла.  Для обоих методов Вам необхо-
       димо строку или блок параметров,  указывающих на файл и буфер для
       переноса  данных.  MS DOS предоставляет различные наборы  функций
       чтения/записи для двух методов.

          Средний уровень.

       Метод управляющего блока файла:
          Этот метод доступа к  файлам  требует,  чтобы  Вы создали блок
       параметров,  котрый первоначально должен содержать такую информа-
       цию, которая позволяет  найти  файл  в  каталоге.  Хотя FCB имеет
       много  полей, вообще говоря, только некоторые из них должны  быть
       заполнены; MS DOS заполняет большинство  остальных полей информа-
       цией после того, как файл открывается.  Отметим, что к началу FCB
       может добавляться специальное поле для создания расширенного FCB,
       который объяняется ниже. Вот структура FCB:

       Накопитель (DB)      Число,   определяющее  на  каком  накопителе
                            будет искаться  файл,  1  =  A, 2 = B и т.д.
                            Если  указан  0,  то берется  накопитель  по
                            умолчанию, а затем система заменяет 0 на код
                            этого накопителя.

       Имя и расширение     Восьмибайтное  имя  файла,  выравненное   по
       (11 байтов)          левому краю должно  быть дополнено пробелами
                            (ASCII 32), если оно меньше 8 байтов.  То же
                            относится и к трехбайтному расширению. Между
                            ними не должна стоять точка.

       Текущий блок (DW)    DOS организует файлы блоками по 128 записей,
                            пронумерованных от 0 до  127. Например, сис-
                            тема рассматривает запись #129 файла прямого
                            доступа, как запись #0  блока #1 (отсчет как
                            для записей, так и для блоков ведется с  0).
                            В файлах  нет  специальных  ограничителей ни
                            для  блоков  ни для записей.   Вместо  этого
                            смещение для блоков  и  записей  вычисляется
                            исходя  из длины записи, которая  устанавли-
                            вается следующим полем FCB.

       Размер записи (DW)   Все функции MS DOS, связанные  с чтением или
                            записью в файл, работают в терминах  записи.
                            Для  файлов  прямого  доступа  важно,  чтобы
                            размер  записи был установлен равным размеру
                            записей, помещенных в  файл.  Для последова-
                            тельных файлов размер записи не столь важен,
                            однако маленький  размер записи будет замед-
                            лять  дисковые операции.   Поскольку  размер
                            сектора 512 байтов,  то оптимальным является
                            размер записи 512 байтов.  Система автомати-
       


                                     -198-
                            чески  помещает  значение  по  умолчанию 80H
                            (128)  в поле длины записи при открытии фай-
                            ла.  Поэтому не забудьте установить это поле
                            после открытия файла.

       Размер файла (DD)    Размер указывается с точностью до байта. Это
                            поле заполняется  системой при открытии фай-
                            ла.

       Дата файла (DW)      Дата записывается системой при открытии FCB.
                            Ее формат приведен в [5.2.5].

       Текущая запись (DB)  Текущая  запись  используется   совместно  с
                            полем текущего блока. Записи нумеруются от 0
                            до 127.  Запись прямого доступа #200, распо-
                            ложенная  в  блоке  1, имеет  номер  текущей
                            записи равный 71 ((200 - 128) - 1).

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

                            MS DOS поместит нужные значения в поля теку-
                            щего блока и текущей  записи.   Помните, что
                            старший байт расположен в старшей ячейке.

       Связь  между полями текущей записи, текущего блока и номер записи
       прямого доступа показана на рис. 5-3.
          Простейший путь создать FCB  как  переменную в сегменте данных
       программы.   Если имя открываемого файла не меняется, то это  имя
       может быть прямо записано в это  поле.  Остаток блока инициализи-
       руйте байтами ASCII 0.  Только после того как FCB будет открыт (с
       помощью функции 0FH прерывания  21H,  как  показано в [5.3.3]) Вы
       должны записать в блок остальную информацию. Отметим, что FCB для
       работы с  простым  последовательным  файлом  с  длиной записи 128
       байтов  не требует дальнейших приготовлений.  После создания  FCB
       дальнейшие операции требуют, чтобы DS:DX указывали на него. Прос-
       тейшая форма его такая:

       FCB      DB     1,'FILENAMEEXT',25 DUP(0)
       Можно также создать FCB как структуру:

       FCB           STRUC
       DRIVE_NUM     DB     0
       FILE_NAME     DB     8 DUP(?)
       FILE_EXT      DB     3 DUP(?)
       BLOCK_NUM     DW     0
       RECORD_SIZE   DW     0
       FILE_SIZE     DD     0
       FILE_DATE     DW     0
       RESERVED      DB     10 DUP(0)
       CURRENT_REC   DB     0
       RANDOM_REC    DD     0
       FCB           ENDS

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

       


                                     -199-
          1.   Для  файлов прямого доступа Вы должны  установить  размер
       записи и номер записи в поле записи прямого доступа.
          2. Для доступа  к  последовательным  файлам с начала Вы должны
       установить  только размер записи, при условии, что Вы инициализи-
       ровали поля текущего блока и текущей  записи в 0 (просто обнулите
       весь  FCB, за исключением имен накопителя и файла).  При открытии
       поле размера записи будет установлено равным 128, если это значе-
       ние устаривает Вас, то дальнейшая подготовка не нужна.
          3.   Для  доступа к последовательному файлу с середины  или  с
       конца Вы должны установить поля  текущего  блока и текущей записи
       (в этом случае Ваша программа должна будет производить вычисления
       сама).
          Префикс программного сегмента [1.3.0] имеет достаточно большое
       поле,  чтобы содержать управляющий блок файла.  Это  пространство
       предоставляется для каждой  программы, поэтому экономно использо-
       вать его, особенно в программах типа .COM.  Поле FCB  расположено
       со смещением 5CH в префиксе программного  сегмента.  В программах
       COM  используйте  ORG для создания FCB следующим  образом  (здесь
       помечен также используемый по умолчанию DTA, который будет обсуж-
       даться ниже):

       ;---в начале кодового сегмента
                    ORG  5CH
       FCB          LABEL   BYTE
       DRIVE_NUM    DB   0
       FILE_NAME    DB   8 DUP(?)
       FILE_EXT     DB   3 DUP(?)
       BLOCK_NUM    DW   0
       RECORD_SIZE  DW   0
       FILE_SIZE    DD   0
       FILE_DATE    DW   0
       RESERVED     DB   10 DUP(0)
       CURRENT_REC  DB   0
       RANDOM_REC   DD   0
                    ORG  80H
       DTA          LABEL   BYTE
                    ORG  100H
                    ASSUME CS:CSEG, DS:DSEG, SS:SSEG
                     ...

          Расширенный FCB используется для создания или доступа к файлу,
       имеющему специальные атрибуты, например,  к спрятанному файлу или
       файлу только для чтения.  Различные атрибуты объяснены в [5.2.6].
       Расширенный FCB на 7  байтов  длиннее,  причем эти 7 байтов пред-
       шетсвуют обычному блоку.  Первый байт равен FF, что указывает  на
       специальный статус. За ним следуют 5 байтов ASCII 0, а затем байт
       атрибутов.  При открытии файла с использованием расширенного  FCB
       DS:DX должны указывать на первый из дополнительных семи байтов, а
       не  на имя накопителя, как для обычного FCB.  Вот обычная  форма,
       где 2 - значение байта атрибутов, а 1 - указывает на накопитель:

       FCB     DB     0FFH, 5 DUP(0),2,1,'FILENAMEEXT',25 DUP(0)

       Метод дескриптора файла:
          Этот метод требует меньшей подготовки  чем метод FCB. Для него
       Вы должны только создать строку, указывающую путь к файлу,  такую
       как в стандартных командах DOS. Например B:COMPILE\UTILITY\PASCAL
       указывает на файл PASCAL в подкаталоге UTILITY. Строка ограничена
       длиной в 63 символа, включая имя  накопителя.  При открытии файла
       (с  использованием  функции 3DH прерывания 21H -  см.   [5.3.3]),
       DS:DX должны указывать на первый байт этой строки. Система выпол-
       няет  всю  работу по анализу строки и нахождению  файла, а  после
       того как файл открыт она  возвращает  16-битный идентификационный
       


                                     -200-
       номер файла в AX. Его называют номером файла и он используется во
       всех последующих операциях с этим файлом.

       Буфера данных:
          Программа должна указать место в памяти, куда должны помещать-
       ся  принимаемые данные или откуда должны браться выводимые.   Это
       пространство в памяти может быть временным буфером, который будет
       использоваться данными как промежуточная станция.  Или это прост-
       ранство может быть именно тем местом,  где данные реально обраба-
       тываются.  Обычно временный буфер устанавливается размером в одну
       запись и бывает удобно  описать  его  как  строковую переменную в
       сегменте  данных,  как это сделано в нижеприведенном примере.   С
       другой стороны, большие рабочие  области  данных должны распреде-
       ляться  с  помощью методов распределения памяти,  предоставляемых
       операционной системой [1.3.1].   Ведь создание, например, области
       данных  размером в 10000 байт в сегменте данных сделает программу
       на диске на 10000 байт длиннее, что совершенно ненужно.
          Буфер  используемый  методом FCB доступа к  файлам  называется
       областью обмена с диском или DTA. На этот буфер указывает словный
       указатель, который хранится операционной системой и который может
       быть изменен  Вашей  программой.  В  фирменной  документации этот
       указатель на DTA часто сам называют DTA. Поскольку указано только
       начало буфера, то ничто не мешает данным занять область прилегаю-
       щую  к DTA, поэтому Вы сами должны следить, чтобы этого не  прои-
       зошло. Указатель на DTA  устанавливается специальной функцией DOS
       и  после того как он установлен все функции чтения/записи автома-
       тически обращаются к нему.   Это  означает,  что  сами функции не
       должны содержать адрес временного буфера.
          Когда DTA совпадает с областью данных, в которой обрабатывают-
       ся данные, то необходимо постоянно менять DTA, с тем чтобы файло-
       вые операции могли получать доступ к различным фрагментам данных.
       При простой операции  последовательного  чтения  или при операции
       чтения одного блока с прямым доступом система автоматически поме-
       щает в DTA одну запись за другой.  Необходимо отвести пространст-
       во,  достаточное  для числа записей,  которые  будут  затребованы
       программой. DTA не может  иметь  размеры  больше  одного сегмента
       (64K).
          Для установки указателя на DTA используйте функцию 1AH  преры-
       вания 21H. DS:DX  должны  указывать  на  первый байт DTA, а затем
       надо выполнить функцию. Это все что нужно. Вот пример:

       ;---в сегменте данных
       DTA      256 DUP (?)
       ;---установка DTA
          LEA  DX,DTA      ;DS:DX указывают на DTA
          MOV  AH,1AH      ;функция установки DTA
          INT  21H         ;установка DTA

          Функция 2FH прерывания 21H сообщает текущую установку указате-
       ля DTA.  У нее нет входных регистров. При возврате ES:BX содержат
       сегмент и смещение DTA.
          Префикс  программного  сегмента  [1.3.0]  обеспечивает  каждую
       программу 128-байтным  встроенным  DTA, начиная со смещения 80H и
       до 9FH.  Вы можете использовать его при нехватке памяти. Первона-
       чально указатель на DTA  указывает  именно на этот буфер, поэтому
       если  Вы будете использовать его, то нет нужды устанавливать ука-
       затель.  Этот буфер по умолчанию  особенно  удобно использовать с
       COM  файлами,  где DS указывает на начало  префикса  программного
       сегмента. Для файлов EXE может потребоваться небольшой добавочный
       код, чтобы использовать DTA по умолчанию.  Отметим, что для опре-
       деления текущей установки указателя на DTA Вы должны использовать
       функцию  2FH прерывания 21H.  У нее нет входных регистров, а  при
       выходе ES:BX указывают на DTA.
       


                                     -201-
          Указатель на DTA не  используется  при доступе к файлу методом
       дескриптора файла. Функции чтения или записи данных всегда содер-
       жат адрес, по которому расположен буфер данных.  Целиком на Вашей
       совести  лежит  определение того, будут  ли  данные  передаваться
       через временный буфер  или  непосредственно  в  то место, где они
       будут использоваться.

          5.3.6 Анализ информации командной строки.

          При  запуске многие программы позволяют пользователю поместить
       добавлчную информацию в командной  строке, обычно указывающую имя
       файла, с которым программа будет работать. Эта информация записы-
       вается в 128-байтную  область,  начинающуюся  со  смещения  80H в
       префиксе  программного сегмента [1.3.0].  (Эта же область исполь-
       зуется как DTA по умолчанию,  как  обсуждалось в [5.3.5].) Первый
       байт содержит длину строки, а затем идет сама строка.
          Для  программ, использующих метод дескриптора файла для работы
       с файлами, имя файла, вводимое  в  командной строке, должно иметь
       адекватную форму. Требуется, чтобы пользователь программы исполь-
       зовал стандартный протокол MS DOS для строки пути.  С другой сто-
       роны, управляющий блок файла требует, чтобы строка вида 'A:ACCT.-
       BAK' была преобразована к  виду  1,'ACCT     BAK'.  MS  DOS имеет
       специальную  функцию, которая выполняет такое преобразование  над
       первой порцией информации,  следующей  за  именем программы в ко-
       мандной  строке.  Эта процедура называется разбором строки  (par-
       sing).

          Средний уровень.

          Имя файла должно быть первой  информацией, следующей за именем
       загружаемой программы. Оно должно быть отделено от имени програм-
       мы одним из следующих символов : .  ; , = + табуляцией или пробе-
       лом. Конец имени файла должен быть указан одним из символов : . ;
       , = + \ < > | / " [ ] табуляцией, пробелом или одним из управляю-
       щих символов (коды ASCII от 1 до 31).
          Функция  29H  прерывания 21H производит  разбор  имени  файла.
       DS:SI  должны указывать на смещение 81H в PSP.  Помните, что  при
       загрузке  программы  как DS, так и ES указывают  на  начало  PSP.
       ES:DI должны указывать на  область  памяти, которая будет служить
       управляющим блоком для нового файла. Установка битов в AL опреде-
       ляет как будет выполняться  разборка.  Имеют значение только биты
       0-3:
          бит 0   1 = начальный ограничитель игнорируется
              1   1 = байт, идентифицирующий накопитель, устанавливается
                      в FCB, только если он указан в командной строке
              2   1 = имя файла в FCB меняется только если командная
                      строка содержит имя файла
              3   1 = расширение файла в FCB меняется только если коман-
                      ная строка содержит расширение файла

       После  того как эта информация установлена, программа может вызы-
       вать функцию.  Если в командной  строке  не указан накопитель, то
       берется  накопитель  по умолчанию.  Если  отсутствует  расширение
       файла, то предполагается, что оно  пробельное (ASCII 32).  Если в
       имени файла указана звездочка, то она заменяется на нужное  число
       вопросительных знаков в поле  имени  файла FCB.  AL возвращает 1,
       если  имя файла содержит * или ? и FF, если указан неверный нако-
       питель.
          При возврате DS:SI указывают  на  первый  символ, следующий за
       именем  файла,  которое начинается со смещения  81H.   Дальнейшая
       информация, содержащаяся  в  командной  строке должна расшифровы-
       ваться  Вашей программой.  ES:DI указывают на первый  байт  вновь
       сформированного FCB.  Если  в  FCB  не  создано допустимого имени
       


                                     -202-
       файла, то содержимое ES:[DI]+1 равно пробелу. Вот пример, который
       помещает код в область FCB в PSP, начиная со смещения 5CH:

       ;---разбираем командную строку, создавая FCB со смещением 5CH
       ;---в PSP
          MOV  AH,29H          ;
          MOV  SI,81H          ;
          MOV  DI,5CH          ;
          MOV  AL,1111B        ;
          INT  21H             ;
          MOV  AL,ES:[DI]+1    ;
          CMP  AL,32           ;
          JE   ERROR_ROUTINE   ;

                         Раздел 4. Чтение и запись файла.

          Имеются два основных метода доступа к файлу - последовательный
       и прямой. Хотя в вычислительной  литературе часто используют тер-
       мины  "последовательный"  файл и файл "прямого доступа", сами  по
       себе файлы хранятся на диске одинаково:  как непрерывная последо-
       вательность  байтов.  Ни в каталоге ни в каком-либо другом  месте
       нет индикатора, указывающего, что данный файл является последова-
       тельным или файлом прямого доступа.  Реально эти два типа  файлов
       различаются по  расположению  данных  в них и по методу доступа к
       ним. К любому файлу прямого доступа можно получить последователь-
       ный доступ, а к любому  последовательному  файлу - прямой доступ,
       хотя редко имеются причины делать это, особенно во втором случае.
          Последовательные  файлы помещают элементы данных один за  дру-
       гим, независимо от их длины,  разделяя  эти элементы парой симво-
       лов,  сначала  возвратом  каретки (ASCII 13), а  затем  переводом
       строки (ASCII  10).   Языки  высокого  уровня,  такие как Бейсик,
       вставляют  эти символы автоматически, в то время как программы на
       ассемблере должны сами  заботиться  о вставке этих символов после
       записи каждой переменной в файл.  В последовательных файлах могут
       храниться как числа, так и строки. Строки требуют по одному байту
       на каждый символ строки. Числа по соглашению записываются в стро-
       ковом виде, хотя они  могут  писаться  и  в  числовом виде. Таким
       образом  Бейсик записывает значение "128" в виде строки  из  трех
       цифр, хотя программа  на  ассемблере  может  записать это число в
       виде двухбайтного целого или даже однобайтного кода - все опреде-
       ляется тем, что при повторном чтении файла программа должна пони-
       мать используемый формат.  Для соместимости рекомендуется записы-
       вать числа в виде строк.
          Необязательно, чтобы каждое  число  строки было отделено парой
       возврат каретки/перевод строки, однако если эта пара опущена,  то
       программа должна обеспечить способ отделения данных. Например, 10
       целых чисел могут быть записаны как 20-байтный элемент данных.  С
       другой стороны, очень большие элементы данных, такие как парагра-
       фы  текста,  могут быть разделены на несколько  элементов  данных
       (стандартный текстовый файл представляет из себя документ, разби-
       тый  на  строки удобного  размера,  записанные  последовательно).
       Поскольку элементы данных имеют  переменную  длину, то невозможно
       узнать  где в файле расположен определенный элемент.  Поэтому для
       того чтобы найти  нужный  элемент  программа  должна читать файл,
       начиная с начала и  отсчитывая  нужное  число  пар возврат карет-
       ки/перевод строки.  По этой причине файлы такого формата называют
       последовательными.  Как правило  с диска в память передается весь
       такой файл.
          Файлы  прямого доступа заранее отводят фиксированное место под
       каждый элемент данных.  Если  какой-то элемент данных не занимает
       все  отведенное  пространство, то остаток заполняется  пробелами.
       Если каждый элемент занимает 10  байтов,  то легко можно просмот-
       реть  сразу 50-й элемент, поскольку можно вычислить что он  начи-
       


                                     -203-
       нается с 491-го байта файла (т.е.  с байта #490, поскольку отсчет
       начинается  с 0).  Как правило связанный набор элементов  группи-
       руется в запись. Каждая запись  содержит несколько полей, которые
       создают  набор  номеров байтов, начиная с которых пишутся  данные
       элементы.  Например, запись может иметь поля возраст, вес и рост.
       Соответствующие поля могут занимать 2, 3 и 5 байтов.  Вместе  они
       образуют запись длиной в 10  байтов.   Файл прямого доступа может
       состоять  из тысяч таких записей.  Каждая запись  следует  непос-
       редственно за предшествующей без  всяких ограничителей, таких как
       пары  возврат  каретки/перевод строки, используемые в  последова-
       тельных файлах.  При этом записи могут писаться в любом порядке и
       можно  записать  запись 74, хотя запись 73 еще не  была  записана
       (при этом записи 73  отведено  дисковое  пространство и она будет
       содержать  те данные, которые случайно оказались в том секторе, в
       котором отведено место для этой записи).  В отличии от последова-
       тельных файлов файлы прямого доступа остаются на диске.  В памяти
       присутствуют только определенные записи, с которыми идет работа в
       данный момент времени.
          Когда  для  прямого доступа к файлу  используется  управляющий
       блок файла, то системе сообщается размер записи файла (все записи
       данного файла должны иметь одинаковую длину). Это позволяет прог-
       рамме запросить любую запись по  номеру,  а MS DOS точно вычислит
       где эта запись расположена на диске. При работе с файлами прямого
       доступа методом дескриптора файла программа должна сама вычислять
       положение требуемой записи.
          Система хранит файловый указатель для каждого буфера файла. Он
       указывает на n-ный байт файла, определяя место в файле, с которо-
       го  будет  начинаться следующая операция чтения или записи.   При
       последовательной операции перезаписи  файловый указатель первона-
       чально устанавливается на начало файла и постоянно сдвигается  по
       мере того, как все  новые  и  новые  данные  записываются в файл.
       Когда  данные добавляются к последовательному файлу, то  файловый
       указатель первоначально устанавливается на конец файла.  При дос-
       тупе  к  одной записи в файле прямого  доступа  положение  записи
       вычисляется в виде смещения относительно начала файла и указатель
       устанавливается  равным  этому значению; затем нужная запись  чи-
       тается или пишется. Обычно за файловым указателем следит система,
       однако программа может сама управлять им и манипулировать  указа-
       телем для своих специальных нужд.
          Единственным примером низкого уровня в данном разделе является
       чтение/запись  одного  сектора.  Чтение или запись  целых  файлов
       состоит в  последовательности  таких  чтений  или  записей одного
       сектора,  программируя  микросхему контроллера  НГМД  заново  для
       каждого сектора.  Полномасштабные  файловые операции очень сложны
       на  этом  уровне, что следует хотя бы из больших  размеров  файла
       COMMAND.COM. Однако, изучив обсуждение операций низкого уровня, а
       также имея информацию о таблице размещения файлов [5.1.1] и  дис-
       ковых каталогах [5.2.1] Вы  можете  представить как работает дис-
       ковая операционная система.

          5.4.1  Программирование контроллера НГМД 765 и микросхемы пря-
       мого доступа к памяти 8237.

          Микросхема контроллера НГМД 765  фирмы NEC управляет мотором и
       головками  накопителя на дискетах и обрабатывает  потоки  данных,
       направляемые в или из дисковых секторов.   Один контроллер, уста-
       новленный на плате адаптора дисков, может обслуживать до  четырех
       НГМД. За исключением случаев, связанных с защитой от копирования,
       программистам не приходится программировать микросхему контролле-
       ра НГМД прямо. Процедуры работы  с дисками, предоставляемые DOS и
       BIOS эффективны и удобны, кроме того, очень рисковано писать свои
       собственные  процедуры,  поскольку  ошибки  в них могут разрушить
       дисковый каталог или таблицу размещения файлов, что вызовет  пол-
       


                                     -204-
       ное разрушение информации на диске.
          Нижеследующее  обсуждение  служит  цели  дать Вам только общее
       представление.   Листинг  ROM-BIOS, приведенный в  конце  каждого
       технического руководства по MS DOS, содержит код тщательно разра-
       ботанных  процедур  для  форматирования дискет,  чтения и  записи
       секторов, а также сброса и  получения  статуса накопителей. После
       того, как Вы усвоите приведенный здесь материал, изучите процеду-
       ры ROM-BIOS для продолжения Вашего образования в области операций
       с дисками на низком уровне. Вам потребуется также документация по
       микросхеме контроллера НГМД 8272A фирмы Intel, которая аналогична
       микросхеме фирмы NEC.  В данной документации перечислены прерыва-
       ния, генерируемые контроллером НГМД,  в то время как в документа-
       ции  по  IBM PC этого списка нет.  Информация о микросхеме  8272A
       может быть найдена во  втором  томе  Справочника  по  компонентам
       микросистем (Microsystem Components Handbook).
          Контроллер НГМД может выполнять 15 операций, из которых  здесь
       будут обсуждаться только три: операции поиска и чтения или записи
       одного  сектора.   Понимание того как они работают  позволит  Вам
       выполнить любую из оставшихся двенадцати,  при условии, что у Вас
       будет  вышеупомянутая информация.  Чтение файла состоит в  поиске
       его в  каталоге  [5.2.1],  определении  его  положения на диске с
       помощью таблицы размещения файлов [5.1.1] и затем наборе операций
       чтения одного сектора. Эта процедура включает 6 шагов:

       1. Включение мотора и короткое ожидание, пока он наберет обороты.
       2. Выполнение операции поиска и ожидание прерывания, указывающего
       на завершение этой операции.
       3. Инициализация микросхемы DMA для пересылки данных в память.
       4. Посылка команды чтения контроллеру НГМД и ожидание прерывания,
       указывающего, что пересылка данных завершена.
       5. Получение информации о статусе контроллера НГМД.
       6. Выключение мотора.

          Контроллер  НГМД  работает через три порта  ввода/вывода.   На
       самом деле микросхема имеет  больше,  чем три регистра, но доступ
       к  большинству  из них осуществляется через один порт.   Эти  три
       порта такие:

          3F2H          регистр цифрового вывода
          3F4H          регистр статуса
          3F5H          регистр данных

          Первый шаг состоит в  доступе  к  регистру  цифрового  вывода.
       Значение его битов следующее:

          биты 1-0     выбор накопителя, где 00 = A
                                             01 = B
                                             10 = C
                                             11 = D
                 2     0 = сброс контроллера НГМД
                 3     1 = разрешение прерывания FDC и доступа DMA
               7-4     1 = включение мотора накопителя D-A (бит 4 = A)

       Это регистр только для записи, поэтому необходимо заботиться  обо
       всех его битах. В нижеприведенном примере используется накопитель
       A, поэтому цепочка битов должна выглядеть 00011100.  Такая  уста-
       новка битов выбирает накопитель A, сохраняет установленным бит 2,
       разрешающий работу с НГМД и включает мотор накопителя A. Не сбра-
       сывайте бит 2 в ноль, так как в этом случае Вам придется произво-
       дить  перекалибровку  накопителя,  действие,  которое  необходимо
       очень редко.
          "Перекалибровка" накопителя подразумевает  возврат его головки
       на нулевую дорожку.  Эта операция осуществляется посылкой простой
       


                                     -205-
       последовательности команд контроллеру  НГМД.  Контроллер НГМД уп-
       равляет текущей позицией головки, за счет запоминания всех  изме-
       нений позиции головки после  ее  начальной  установки  на нулевую
       дорожку.   Когда контроллер НГМД сбрасывается, за счет  изменения
       бита 2 регистра  цифрового  вывода,  то  значение текущей позиции
       головки  устанавливается  в ноль, независимо от  того,  на  какой
       дорожке находится головка на  самом  деле, что делает необходимым
       перекалибровку. Обычно сброс контроллера НГМД производится только
       в случае такой серьезной ошибки  накопителя,  после которой неиз-
       вестно текущее состояние контроллера НГМД и накопителя.
          Отметим,  что  выбор накопителя и включение его  мотора -  это
       отдельные действия. Контроллер  НГМД  может иметь доступ только к
       одному  накопителю в данный момент времени, но мотры  могут  быть
       включены у нескольких.  Моторы  могут  оставаться включенными еще
       несколько  секунд  после  завершения обмена  данными, в  ожидании
       следующего доступа к накопителю.  Такая стратегия позволяет избе-
       жать  потери  времени  на повторное ожидание пока  мотор  наберет
       скорость.  Напротив, мотор нельзя оставлять постоянно включенным,
       так как это приведет к преждевременному износу дискет.
          Работа  микросхемы  контроллера НГМД разделяется на три  фазы:
       командная фаза, фаза выполнения  и  фаза  результата. В командной
       фазе один или более байтов посылаются в регистр данных.  Последо-
       вательность байтов строго фиксирована и она меняется от команды к
       команде. Затем контроллер НГМД выполняет команду и в это время он
       находится в фазе выполнения.  Наконец,  во время фазы результата,
       ряд байтов статуса считываются из регистра данных.  При этом обя-
       зательно, чтобы не было ошибки в числе передаваемых или считывае-
       мых данных в регистр данных в фазах командной и результата.
          Число  байтов  команды и результата меняется в зависимости  от
       выполняемой контроллером  дисковой  операции. В техническом руко-
       водстве по IBM PC приведены данные для всех 15 операций.   Первый
       байт команды является  кодом,  определяющим  требуемую  операцию.
       Номер  кода содержится в младших 5-ти битах байта  и в  некоторых
       случаях в старших трех битах  закодирована добавочная информация.
       В  большинстве случаев второй байт команды содержит номер накопи-
       теля (0-3) в младших двух битах и  номер головки (0 или 1) в бите
       2, все остальные биты игнорируются контроллером НГМД.  При опера-
       ции поиска требуется  дополнительно еще только один байт, в кото-
       ром  должен содержаться номер новой дорожки.  Чтение  или  запись
       сектора требует  семи  дополнительных  командных  байтов, которые
       идентичны в этих двух случаях. Байты с третьего по пятый содержат
       текущий номер дорожки,  номер  головки  и  номер сектора. За ними
       следуют  четыре байта, содержащие техническую информацию, необхо-
       димую для контроллера НГМД.
          Первый байт этой  технической  информации  относится  к  числу
       байтов в секторе, которое кодируется как 0 для 128, 1 для 256,  2
       для 512 и 3 для 1024. Конечно  дискеты,  созданные в MS DOS имеют
       сектора размером 512 байт. Затем идут данные конца дорожки (EOT),
       которые дают максимальный номер сектора  для цилиндра; это значе-
       ние  равно 9 для дискет емкостью 360K.  Наконец, идет байт дающий
       длину сдвига (GPL, равный 2AH) и длину  данных (DTL, равный FFH).
       Техническое  руководство  по IBM PC содержит  таблицу, в  которой
       объясняются другие вхожные  параметры,  например  те, которые ис-
       пользуются при форматировании диска.  MS DOS хранит четыре техни-
       ческих параметра в  памяти,  в  специальной  таблице  параметров,
       называемой базой диска (disk base).  Вектор прерывания 1EH указы-
       вает на эту таблицу.  Четыре  значения  хранятся в том порядке, в
       котором  они  должны быть переданы контроллеру НГМД,  начиная  со
       смещения 3. В следующей таблице показана командная последователь-
       ность для трех операций, используемых в нижеприведенном  примере.
       В цепочках битов черех X  обозначены биты, значение которых несу-
       щественно,  через H - номер головки, а через DD - номер накопите-
       ля.
       


                                     -206-
          Операция   # байта   Функция           Установка для головки 0
                                                  дорожки 15, сектора 1

          Поиск         1      номер кода 00001111           1FH
                        2      головка и накопитель          00H
                               XXXXXHDD

          Чтение        1      номер кода 01100110           66H
          сектора       2      головка и накопитель          00H
                               XXXXXHDD
                        3      номер дорожки                 0FH
                        4      номер головки                 00H
                        5      номер сектора                 01H
                        6      байтов в секторе              02H
                        7      конец дорожки                 09H
                        8      длина сдвига                  1AH
                        9      длина данных                  FFH

          Запись        1      номер кода 01000101           45H
          сектора     2-9      те же, что и для чтения сектора

          Вы  должны быть уверены, что контроллер НГМД готов прежде  чем
       Вы пошлете или прочитаете байт  из  регистра  данных.  Биты 7 и 6
       регистра статуса предоставляют эту информацию. Вот значение битов
       этого регистра:

       биты 3-0    1 = накопитель D-A в режиме поиска
              4    1 = контроллер НГМД выполняет команду чтения/записи
              5    1 = контроллер НГМД не в режиме DMA
              6    1 = регистр данных контроллер НГМД готов к приему
                       данных
                   0 = готов к посылке данных
              7    1 = контроллер НГМД готов к посылке или приему данных

       Перед началом  дисковых  операций  неплохо  проверить,  что бит 6
       равен  нулю, индицируя что контроллер НГМД ожидает команду.  Если
       он ожидает посылки данных, то произошла ошибка. Когда байт данных
       посылается в регистр данных, то бит 7 регистра статуса становится
       равным нулю; продолжайте чтение регистра  до тех пор, пока бит не
       изменится обратно на 1, а затем посылайте следующий байт команды.
       Аналогично, проверяйте этот бит статуса  перед чтением байта ста-
       туса  в фазе результата.  Нижеприведенный пример кончается  двумя
       процедурами, которые выполняют эти функции.
          Когда операция поиска завершена, то контроллер НГМД инициирует
       прерывание 6, прерывание от НГМД. Хотя так же просто можно узнать
       об окончании операции поиска  проверяя регистр статуса, в примере
       это делается за счет обработки прерывания.  Когда происходит пре-
       рывание, то обработчик прерывания  BIOS устанавливает бит 7 байта
       статуса  поиска в области данных BIOS, расположенного  по  адресу
       0040:003E. Это единственный результат обработки прерывания. Можно
       проверять этот байт до тех пор, пока бит 7 не будет установлен, а
       затем переходить к следующему шагу операции чтения сектора.
          Следующий шаг состоит в  инициализации микросхеиы прямого дос-
       тупа  к  памяти 8237.  Эта микросхема занимается  обменом  данных
       между  периферийными  устройствами  и  памятью,  работой, которой
       может заниматься также процессор.  На самом деле, в PCjr, где нет
       микросхемы DMA, контроллер НГМД  посылает  данные прямо в процес-
       сор,  который  в свою очередь пересылает их в  память.   Тактовая
       частота процессора  адекватна  этой  задаче, однако при пересылке
       данных  все  прерывания  должны быть запрещены, с  тем  чтобы  не
       происходило потери данных. Это  означает, что в PCjr при передаче
       данных ввод с клавиатуры или из модема запрещен.  Прерывания тай-
       мера также  игнорируются,  однако  впоследствии  счетчик  времени
       


                                     -207-
       суток  обновляется специальной процедурой, использующей  канал  1
       микросхемы таймера  8253  для  подсчета  импульсов,  прошедших за
       время дисковых операций.  Все остальные модели IBM PC имеют  мик-
       росхему DMA, поэтому процессор свободен при передаче данных.
          IBM PC и XT используют 4-хканальную микросхему DMA 8237. Канал
       0  предназначен для "освежения" памяти (memory refresh); он  пос-
       тоянно восстанавливает заряд  ячеек  оперативной памяти.  Если Вы
       будете  работать по этому каналу, то это приведет скорее всего  к
       краху машины. Канал 2  предназначен  для дисковых операций, а два
       другие канала, с номерами 1 и 3, доступны (через разъемы расшире-
       ния) для  дополнительного  оборудования.  К  сожалению, обмен па-
       мять-память требует двух каналов и одним из них должен быть канал
       0, поэтому такой обмен недоступен на IBM PC и XT. Однако AT имеет
       7  каналов  прямого доступа к памяти и DMA автоматически  исполь-
       зуется инструкциями MOVS,  существенно  увеличивая производитель-
       ность.
          Перед инициализацией канала программа должна послать в микрос-
       хему код, сообщающий будет  ли  происходить  чтение  или запись в
       контроллер НГМД.  Этот однобайтный код равен 46H для чтения и 4AH
       - для записи. Этот код должен быть послан в каждый из двух портов
       с адресами 0BH и 0CH.
          Каждый  канал микросхемы 8237 использует три  регистра.   Один
       16-битный регистр, регистр  счетчика, содержит число передаваемых
       байтов  данных.  Его величина должна быть на единицу меньше,  чем
       требуемое число байтов.   Для  канала  2  доступ к этому регистру
       осуществляется через порт 05H; пошлите в него два  последователь-
       ных байта, причем сначала младший байт.
          Остальные два регистра содержат адрес буфера в памяти, с кото-
       рым  будет  происходить обмен данными.  Этот адрес  задается  как
       20-битное число, поэтому, например,  адрес 3000:ABCD задается как
       3ABCD.  Младшие 16 битов посылаются в регистр адреса, который для
       канала 2 имеет адрес порта 04H.  Сначала посылается младший байт.
       Старшие  4  бита  идут в регистр страницы, который для  канала  2
       имеет адрес порта 81H.  Когда байт посылается по этому адресу, то
       имеют  значение  только 4 младших бита.  Если буфер  создается  в
       сегменте данных, то Вам  нужно  сложить  значение  DS  и смещение
       буфера для получения 20-битного значения. Сложение может привести
       к переносу в значение регистра страницы.  Например, если DS равен
       1F00H,  а смещение буфера - 2000H, то результирующий адрес  будет
       равен 1F00 + 2000 = 21000H.
          После того как эти три регистра  установлены, пошлите 2 в порт
       с адресом 0AH, чтобы разрешить канал 2.  Это оставляет микросхему
       DMA в состоянии ожидания данных от накопителя, а программа должна
       немедленно начать посылку командных байтов в контроллер НГМД. Вот
       краткий перечень шагов при программировании микросхемы 8237:

       1. Послать код чтения или записи.
       2.  Вычислить 20-битный адрес памяти буфера, в который будут пос-
       ланы данные, и заслать его в регистры адреса и страницы канала 2.
       3.  Поместить значение числа передаваемых  байтов (минус 1) в ре-
       гистр счетчика канала 2.
       4. Разрешить канал.

          После посылки  командных  байтов,  снова ожидайте прерывания и
       обращайтесь  с  ним так же, как и после операции  поиска.   Затем
       прочитайте байты статуса. Они таковы:

          Операция    # байта      Функция

          Поиск         нет

          Чтение         1        байт статуса 0
                         2        байт статуса 1
       


                                     -208-
                         3        байт статуса 2
                         4        номер дорожки
                         5        номер головки
                         6        номер сектора
                         7        код байтов на сектор (0-3)

          Запись       1-7        то же, что и для чтения

       Вот значения битов трех байтов статуса:

       Байт статуса 0:
          биты 7-6   00 = нормальное завершение
                     01 = начато выполнение, не может завершиться
                     10 = неверная команда
                     11 = невыполнено, т.к. накопитель не подключен
                 5   1 = выполняется операция поиска
                 4   1 = ошибка накопителя
                 3   1 = накопитель не готов
                 2   номер выбранной головки
               1-0   номер выбранного накопителя

       Байт статуса 1:
          бит 7   1 = номер затребованного сектора больше максимума
              6   не используется (всегда 0)
              5   1 = ошибка передачи данных
              4   1 = переполнение данных
              3   не используется (всегда 0)
              2   1 = не может найти или прочитать сектор
              1   1 = не может записать из-за защиты от записи
              0   1 = отсутствует адресная метка при форматизации

       Байт статуса 2:
          бит 7   не используется (всегда 0)
              6   1 = встречена адресная метка удаленных данных
              5   1 = ошибка циклического контроля четности данных
              4   1 = проблема с идентификацией дорожки
              3   1 = условие команды сканирования удовлетворено
              2   1 = условие команды сканирования не удовлетворено
              1   1 = плохая дорожка
              0   1 = отсутствует адресная метка

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

       Байт статуса 3:
          бит 7   1 = ошибка накопителя
              6   1 = диск защищен от записи
              5   1 = накопитель готов
              4   1 = текущая позиция головки известна
              3   1 = дискета двухсторонняя
              2   номер выбранной головки
            1-0   номер выбранного накопителя

       Вы можете получить этот четвертый байт статуса, послав контролле-
       ру НГМД команду  "Определи  статус  накопителя" (Sense Drive Sta-
       tus).  Первый байт этой двухбайтной команды это число 4, а второй
       байт содержит номер накопителя  в  битах 1 и 0, и номер головки в
       бите  2.   Единственным результатом этой операции  является  байт
       статуса 3.  Отметим, что после  каждой дисковой операции, если Вы
       используете  процедуры DOS или BIOS, результирующие байты статуса
       помещаются в область  данных  BIOS,  начиная  с адреса 0040:0042.
       


                                     -209-
       Операционная  система хранит также байт статуса дискеты по адресу
       0040:0041, значение битов которого следующее:

          Значение бита             Ошибка

               80H           нет ответа на присоединение накопителя
               40H           операция поиска неуспешна
               20H           ошибка контроллера НГМД
               10H           ошибка данных при чтении (ошибка CRC)
               09H           попытка прямого доступа за границу 64K
               08H           переполнение DMA
               04H           затребованный сектор не найден
               02H           не найдена адресная марка
               01H           послана неверная команда контроллеру НГМД

          В заключение приводим полную  процедуру  чтения диска, которая
       читает один сектор данных с дорожки 12, сектор 1, сторона 0 нако-
       пителя A в 512-байтный буфер в сегменте данных.  Семь байтов ста-
       туса также считываются в отведенный буфер. Эта процедура предназ-
       начена для IBM PC и XT.  Вам необходимо воспользоваться техничес-
       ким  руководством по PCjr или AT, если Вы работаете на этих маши-
       нах.  На AT надо изменить  циклы  задержки,  чтобы учесть большую
       скорость  процессора, и не забывать добавлять оператор JMP  SHORT
       $+2 между последовательными  командами OUT, относящимися к одному
       и  тому же порту.  Работа с фиксированным  диском  осуществляется
       аналагично, поэтому Вы можете  перенести изученные Вами концепции
       на другие ситуации.

       ;---в сегменте данных
       BUFFER         DB 512 DUP(?)
       STATUS_BUFFER  DB 7 DUP(?)
       SECTOR_READ    PROC    ;начало процедуры чтения одного сектора
       ;---включение мотора
          STI              ;прерывания должны быть разрешены
          MOV  DX,3F2H     ;адрес регистра цифрового вывода
          MOV  AL,28       ;устанавливаем биты 2, 3 и 4
          OUT  DX,AL       ;посылаем команду
       ;---ожидаем пока мотор наберет скорость (около 1/2 сек.)
          MOV  CX,3500     ;счетчик цикла задержки (для IBM PC и XT)
       MOTOR_DELAY:  LOOP MOTOR_DELAY  ;ожидаем 1/2 секунды
       ;---выполняем операцию поиска
          MOV  AH,15       ;номер кода
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,0        ;номер накопителя
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,12       ;номер дорожки
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          CALL WAIT_INTERRUPT  ;ожидаем прерывания от НГМД
       ;---ожидаем установки головки (25 мсек.)
          MOV  CX,1750     ;счетчик цикла задержки (для IBM PC и XT)
       WAIT_SETTLE:  LOOP WAIT_SETTLE   ;ожидаем 25 мсек.
       ;---начинаем инициализацию микросхемы DMA
          MOV  AL,46H      ;код чтения данных контроллера НГМД
          OUT  12,AL       ;посылаем код по двум адресам
          OUT  11,AL       ;
       ;---вычисляем адрес буфера
          MOV  AX,OFFSET BUFFER   ;берем смещение буфера в DS
          MOV  BX,DS       ;помещаем DS в BX
          MOV  CL,4        ;готовим вращение старшего нибла
          ROL  BX,CL       ;вращаем младшие 4 бита
          MOV  DL,BL       ;копируем DL в BL
          AND  DL,0FH      ;чистим старший нибл в DL
          AND  BL,0F0H     ;чистим младший нибл в BX
       


                                     -210-
          ADD  AX,BX       ;складываем
          JNC  NO_CARRY    ;если не было переноса, то # страницы в DL
          INC  DL          ;увеличиваем DL, если был перенос

       NO_CARRY:   OUT  4,AL  ;посылаем младший байт адреса
          MOV  AL,AH       ;сдвигаем старший байт
          OUT  4,AL        ;посылаем младший байт адреса
          MOV  AL,DL       ;засылаем номер страницы
          OUT  81H,AL      ;посылаем номер страницы
       ;---конец инициализации
          MOV  AX,511      ;значение счетчика
          OUT  5,AL        ;посылаем младший байт
          MOV  AL,AH       ;готовим старший байт
          OUT  5,AL        ;посылаем старший байт
          MOV  AL,2        ;готовим разрешение канала 2
          OUT  10,AL       ;DMA ожидает данные
       ;---получаем указатель на базу диска
          MOV  AL,1EH      ;номер вектора, указывающего на таблицу
          MOV  AH,35H      ;номер функции
          INT  21H         ;выполняем функцию
       ;---посылаем параметры чтения
          MOV  AH,66H      ;код чтения одного сектора
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,0        ;номера головки и накопителя
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,12       ;номер дорожки
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,0        ;номер головки
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,1        ;номер записи
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,ES:[BX]+3  ;код размера сектора
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,ES:[BX]+4  ;номер конца дорожки
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,ES:[BX]+5  ;длина сдвига
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          MOV  AH,ES:[BX]+6  ;длина данных
          CALL OUT_FDC     ;посылаем контроллеру НГМД
          CALL WAIT_INTERRUPT  ;ожидаем прерывание от НГМД
       ;---читаем результирующие байты
          MOV  CX,7        ;берем 7 байтов статуса
          LEA  BX,STATUS_BUFFER  ;помещаем в буфер статуса
       NEXT:   CALL IN_FDC  ;получаем байт
          MOV  [BX],AL     ;помещаем в буфер
          INC  BX          ;указываем на следующий байт буфера
          LOOP NEXT        ;повторяем операцию
       ;---выключение мотора
          MOV  DX,3F2H     ;адрес регистра цифрового вывода
          MOV  AL,12       ;оставляем биты 3 и 4
          OUT  DX,AL       ;посылаем новую установку
          RET              ;конец процедуры
       SECTOR_READ      ENDP
       WAIT_INTERRUPT   PROC      ;ожидание прерывания от НГМД
       ;---управление статусом прерывания 6 в байте статуса BIOS
          MOV  AX,40H      ;сегмент области данных BIOS
          MOV  ES,AX       ;помещаем в ES
          MOV  BX,3EH      ;смещение для байта статуса
       AGAIN:   MOV  DL,ES:[BX]  ;получаем байт
          TEST DL,80H      ;проверяем бит 7
          JZ   AGAIN       ;до тех пор пока не установлен
          AND  DL,01111111B   ;сбрасываем бит 7
          MOV  ES:[BX],DL  ;заменяем байт статуса
       


                                     -211-
          RET
       WAIT_INTERRUPT   ENDP
       OUT_FDC          PROC      ;посылаем байт из AH FDC
          MOV  DX,3F4H     ;адрес порта регистра статуса
       KEEP_TRYING:  IN   AL,DX   ;получаем значение
          TEST AL,128      ;бит 7 установлен?
          JZ   KEEP_TRYING ;если нет, то снова проверяем
          INC  DX          ;указываем на регистр данных
          MOV  AL,AH       ;передаваемое значение в AH
          OUT  DX,AL       ;посылаем значение
          RET
       OUT_FDC          ENDP

       IN_FDC           PROC  ;получаем байт от FDC в AL
          MOV  DX,3F4H     ;адрес порта регистра статуса
       ONCE_AGAIN:  IN   AL,DX   ;получаем значение
          TEST AL,128      ;бит 7 установлен?
          JZ   KEEP_TRYING ;если нет, то проверяем снова
          INC  DX          ;указываем на регистр данных
          IN   AL,DX       ;читаем байт из регистра данных
          RET
       IN_FDC           ENDP

          5.4.2 Чтение/запись определенных секторов.

          Чтение  или запись определенных секторов диска в основном  ис-
       пользуется при доступе к каталогам диска или его таблице размеще-
       ния  файлов, сектора для которых всегда расположены в одном и том
       же месте.  В то время как чтение  секторов  достаточно безобидно,
       запись абсолютного сектора требует чтобы код был тщательно прове-
       рен перед первым использованием. Ошибка может сделать каталог или
       таблицу размещения файлов нечитаемыми, что эквивалентно  разруше-
       нию всех данных на диске.
          Как DOS так и BIOS  предоставляют  функции для чтения и записи
       определенных  секторов.  Однако они указывают сектора по-разному.
       Для IBM PC, XT и PCjr процедура BIOS  требует информации о номере
       стороны (0 или 1), номере дорожки (0-39) и номере сектора  (1-8).
       Из-за  ограничения  максимального  номера  сектора равного 8 этот
       метод практически бесполезен для этих машин.  Однако для AT номер
       сектора может меняться до  8,  9  или  15,  а число дорожек может
       меняться до 39 или 79.  Функции DOS указывают сектор одним  номе-
       ром, который называется  логическим  номером  сектора.  Начиная с
       наружного  обода  диска, секторам  присваиваются  последовательно
       возрастающие номера. Этот метод может быть использован для дисков
       произвольного размера и типа.
          Отсчет  логических секторов начинается со стороны 0  дорожки 0
       сектора 1 и продолжается  на  стороне  1  с дорожки 0, после чего
       переходит на сторону 0 дорожку 1 и т.д. (На больших фиксированных
       дисках сначала проходится весь внешний цилиндр.) В зависимости от
       того как был форматирован диск, при переходе на следующую дорожку
       логический номер сектора увеличивается на определенную  величину.
       Для дискет емкостью 360K каждая  дорожка  (с учетом обеих сторон)
       добавляет  к  логическому номеру 18.  Однако  вычисления  немного
       усложняются тем, что  отсчет  начинается  с  нуля.  Таким образом
       первый  сектор  на дорожке 3 стороны 2 должен иметь номер  равный
       3*18 для дорожек 0-2 плюс 9 для стороны 0 дорожки 3 плюс единица,
       указывающая на первый сектор дорожки 3 стороны 1. Эта сумма равна
       64. Логический номер сектора на 1 меньше этого числа. На рис. 5-4
       сравнивается методы указания сектора DOS и BIOS.

          Высокий уровень.

          Бейсик не предоставляет прямого доступа к секторам диска. Надо
       


                                     -212-
       использовать следующую процедуру на  машинном языке. В приложении
       Г объясняется логика взаимодействия с этой процедурой.  В примере
       читается 9 секторов дорожки  3  стороны  1 дискеты емкостью 360K.
       Сама  процедура  размещается в памяти, начиная с адреса  сегмента
       &H1000, а содержимое секторов размещается,  начиная с сегментного
       адреса &H2000 (напоминаем, что абсолютный адрес равен сегментному
       адресу, умноженному на 16).  Для  того чтобы записать на диск со-
       держимое  этого  буфера надо изменить в данных программы  седьмой
       байт с конца &H25 на &H26. Все остальное остается неизменным.

       100 DEFINT A-Z       'все переменные будут целыми
       110 DATA &H55, &H8B, &HEC, &H1E, &H8B, &H76, &H0C, &H8B
       120 DATA &H04, &H8B, &H76, &H0A, &H8B, &H14, &H8B, &H76
       130 DATA &H08, &H8B, &H0C, &H8B, &H76, &H06, &H8A, &H1C
       140 DATA &H8E, &HD8, &H8B, &HC3, &H8B, &H00, &H00, &HCD
       150 DATA &H25, &H59, &H1F, &H5D, &HCA, &H08, &H00
       160 DEF SEG = &H1000     'помещаем процедуру по адресу &H10000
       170 FOR N = 0 TO 38      'для каждого байта процедуры
       180 READ Q: POKE N,Q     'читаем байт и помещаем его в память
       190 NEXT                 'следующий байт
       200 READSECTOR = 0       'выполняем код, начиная с первого байта
       210 BUFFER = &H2000      'буфер для данных имеет адрес &H20000
       220 LOGICALNUMBER = 62   'логический номер сектора равен 62
       230 NSECTORS = 9         'число считываемых секторов
       240 DRIVE = 0            'номер накопителя (0 = A)
       250 CALL READSECTOR (BUFFER, LOGICALSECTORS, NSECTORS, DRIVE)
       260 'теперь сектора в памяти, начиная с адреса 2000:0000

          Средний уровень.

          BIOS использует функцию 2 прерывания 13H для чтения секторов и
       функцию 3 прерывания 13H для записи секторов.  В обоих случаях DL
       должен содержать номер  накопителя  от 0 до 3, где 0 = A, 1 = B и
       т.д.,  DH  - номер головки (стороны), 0-1.  CH  должен  содержать
       номер дорожки от 0 до 39, а  CL  -  номер  сектора от 0 до 8.  AL
       содержит число секторов, которое необходимо считать.  Допускается
       сразу читать не более восьми  секторов,  что более чем достаточно
       для большинства целей.  ES:BX должны указывать на начало буфера в
       памяти, куда будут помещаться данные  или откуда они будут брать-
       ся.   При возврате AL будет содержать число прочитанных или запи-
       санных секторов.  Если операция  успешна,  то флаг переноса будет
       равен  нулю.  Если он равен 1, то AH будет содержать байт статуса
       дисковой операции, описанный в [5.4.8].

       ;---в сегменте данных
       BUFFER     DB   4000 DUP(?)  ;создаем буфер
       ;---читаем сектора
          MOV  AX,SEG BUFFER       ;ES:BX должны указывать на буфер
          MOV  ES,AX               ;
          MOV  BX,OFFSET BUFFER    ;
          MOV  DL,0                ;номер накопителя
          MOV  DH,0                ;номер головки
          MOV  CH,0                ;номер дорожки
          MOV  CL,1                ;номер сектора
          MOV  AL,1                ;число секторов для чтения
          MOV  AH,2                ;номер функции чтения
          INT  13H                 ;

          Прерывания DOS 25H и 26H читают и записывают абсолютные секто-
       ра диска, соответственно. Надо поместить логический номер старто-
       вого сектора в DX, а DS:BX должны указывать на буфер. CX содержит
       число  секторов для чтения или записи, а AL -  номер  накопителя,
       где 0 = A, 1 = B  и  т.д.  Процедуры  портят  все регистры, кроме
       


                                     -213-
       сегментных. При возврате регистр флагов остается на стеке, остав-
       ляя стек невыровненным.  Не  забудьте  вытолкнуть это значение со
       стека  сразу после возврата (в примере это значение выталкивается
       в CX).

       ;---в сегменте данных
       BUFFER      DB  DUP 5000(?)   ;создаем буфер
       ;---читаем сектора
          PUSH DS                 ;сохраняем регистры
          MOV  AX,SEG BUFFER      ;DS:BX должны указывать на буфер
          MOV  DS,AX              ;
          MOV  BX,OFFSET BUFFER   ;
          MOV  DX,63              ;логический номер сектора
          MOV  CX,9               ;читаем всю дорожку
          MOV  AL,0               ;накопитель A
          INT  25H                ;функция чтения секторов
          POP  CX                 ;выталкиваем со стека флаги
          POP  DS                 ;восстанавливаем регистры
          JNC  NO_ERROR           ;если нет ошибки, то на продолжение
          CMP  AH,3               ;проверка возможных ошибок
           .
           .
       NO_ERROR:                  ;продолжение программы

          Если при возврате флаг переноса равен 1, то произошла ошибка и
       в этом случае AH и AL содержат два отдельных байта статуса  ошиб-
       ки. Если AH = 4, то указанный сектор не найден, а если AH = 2, то
       диск неверно отформатирован.  Если AH = 3, то была попытка записи
       на дискету, защищенную от записи. Все остальные значения AH гово-
       рят об аппаратной ошибке.

          Низкий уровень.

          Дисковые операции на низком уровне требуют прямого  программи-
       рования микросхем  контроллера  НГМД  и прямого доступа к памяти.
       Поскольку  эти  операции  взаимосвязаны, то  они  рассматриваются
       вместе в разделе [5.4.1].

          5.4.3 Запись в последовательные файлы.

          С  точки зрения программиста языки высокого уровня работают  с
       последовательными файлами  порциями  в одну единицу данных.  Один
       оператор  "записывает"  содержимое переменной в  последовательный
       файл, ограничивая ее  парой  возврат  каретки/перевод  строки.  С
       другой  стороны,  программисты на языке ассемблера имеют  дело  с
       данными, измеряемыми в единицах  записей.  Они  помещают данные в
       буфер, который может содержать одну или несколько записей, добав-
       ляя пары возврат каретки/перевод строки  между элементами данных,
       а не между записями. Некоторые элементы данных могут принадлежать
       двум записям.  Тогда для записи используется функция MS DOS, поз-
       воляющая  записать на диск одну или несколько записей.   На  всех
       уровнях  программирования  DOS  может  не  производить физической
       записи  на  диск  каждый раз, когда была подана  команда  вывода.
       Вместо этого, в  целях  экономии,  DOS  ожидает пока его выходной
       буфер будет заполнен, прежде чем записать данные на диск.
          Отметим, что Бейсик автоматически добавляет в конец записывае-
       мого им последовательного файла символ с кодом ASCII 26 (Ctrl-Z).
       Это требование стандартных текстовых файлов.  Функции DOS не  до-
       бавляют этот символ;  Ваша  программа  должна сама записать его в
       конец  элемента данных.  Файлы прямого доступа не  ограничиваются
       символом ASCII 26.

          Высокий уровень.
       


                                     -214-
          Бейсик готовит файлы к последовательной  записи, открывая файл
       в режиме последовательного доступа оператором OPEN. Этот оператор
       имеет две формы и какую из них Вы  выбираете это дело вкуса. Фор-
       маты этого оператора такие:

          100 OPEN "MYFILE" FOR OUTPUT AS #1
       или
          100 OPEN "O", #1, "MYFILE"

       Во  второй форме буква "O" обозначает вывод (output).  Символ  #1
       обозначает кодовый  номер,  по  которому  Вы  будете впоследствии
       обращаться  к файлу в операторах доступа, таких как WRITE #1  или
       INPUT #1. В обоих  случаях  открывается  файл с именем MYFILE для
       приема данных в последовательном режиме. Если файл с таким именем
       не найден на диске, то оператор OPEN  создаст его.  Если же такой
       файл существует, то он будет перезаписан, т.е. после его закрытия
       он будет содержать только новые  записанные  в него данные. Чтобы
       добавить данные в конец существующего последовательного файла, не
       изменяя его предыдущего содержимого, нужно открыть его, используя
       первую форму оператора OPEN  в  виде  OPEN "MYFILE" FOR APPEND AS
       #1. Более подробно об этом см. [5.3.3].
          Данные  записываются в файл с помощью операторов PRINT# и WRI-
       TE#. Они имеют одинаковую форму:

          100 PRINT #1, S$
       или
          100 WRITE #1, X

       #1 относится к идентификационному  номеру файла (дескриптору фай-
       ла), присваиваемому ему оператором OPEN.  В первом примере в файл
       записывается значение строковой переменной, а во втором численное
       значение, но можно любым из них записывать и то и другое. Числен-
       ные значения записываются  в  последовательные  файлы в строковом
       виде,  хотя они и берутся не из строковых переменных.   Например,
       232 является 2-хбайтным целым в строковой  форме, однако если X =
       232, то оператор PRINT #1, X помещает в файл три байта, используя
       коды ASCII для цифр 2, 3 и 2.
          Операторы PRINT# и WRITE#  отличаются  способом отделения эле-
       ментов данных в файле. Какой из них более подходящий определяется
       характеристиками данных. Основное различие между двумя оператора-
       ми состоит в том, что WRITE# вставляет дополнительные ограничите-
       ли между элементами  данных.  Рассмотрим  случай,  когда оператор
       выводит  несколько переменных в виде 100 PRINT #1, A$, Z, B$  или
       100 WRITE #1, A$, Z, B$. В этом случае пара возврат каретки/пере-
       вод  строки  будет помещена в файл только за  последней  из  трех
       переменных (отметим,  что  строковые  и числовые переменные могут
       быть  перемешаны).   Как же можно впоследствии выделить  эти  три
       переменные? Если был  использован  оператор PRINT#, то никак. Все
       три переменные будут объединены в непрерывную строку. Если же был
       использован оператор WRITE#, то  каждый элемент данных будет зак-
       лючен  в кавычки, а между ними будут стоять запятые.  Затем,  при
       чтении этих элементов из файла,  Бейсик  будет автоматически уда-
       лять кавычки и запятые, которые были добавлены оператором WRITE#.
          Имеется еще ряд менее важных вопросов.  Один из них состоит  в
       том, что вся проблема  с  ограничителями  может  быть снята, если
       использовать  для  вывода каждой  переменной  отдельный  оператор
       PRINT# или WRITE#. В этом  случае  PRINT# будет отделять все эле-
       менты  парами возврат каретки/перевод строки, а WRITE# будет  де-
       лать то же самое, но по-прежнему каждый  элемент будет заключен в
       кавычки  (что  напрасно расходует файловое пространство).   Более
       того, для вывода строк, которые  сами  содержат кавычки, оператор
       WRITE# использовать нельзя, поскольку первая же внутренняя кавыч-
       ка будет при чтении ошибочно  воспринята  как признак конца пере-
       


                                     -215-
       менной. И, наконец, отметим, что когда в одном операторе выводит-
       ся несколько переменных, то оба  оператора  форматируют  данные в
       точности так же, как они форматировались бы при выводе на  терми-
       нал. Таким образом PRINT #1, A$, B$ отделяет B$ от A$, в то время
       как  PRINT #1, A$; B$ - нет; файл будет добавляться нужным числом
       пробелов.  Оператор PRINT# может быть испоьзован в форме PRINT #1
       USING..., где могут быть использованы все обычные экранные форма-
       ты PRINT USING для форматирования вывода в файл.
          Вообще говоря, более  экономично использовать оператор PRINT#,
       записывая каждый раз по одной переменной. Этот метод избавляет от
       излишних ограничителей  и  позволяет  затем безошибочно считывать
       строки любого вида. Более сложные схемы ограничителей, используе-
       мые при записи нескольких  переменных одним оператором PRINT# или
       WRITE#  могут привести к проблемам, особенно если одна переменная
       будет считана как две, что  приведет  к  потере текущей позиции в
       файле.
          После  того как все данные будут записаны в файл, просто  зак-
       ройте его, чтобы обезопасить содержащиеся  в нем данные. Напишите
       CLOSE, чтобы закрыть все открытые файлы, CLOSE #1 - чтобы закрыть
       файл #1 и CLOSE #1, #3 -  чтобы  закрыть  файлы  #1  и #3. Хотя в
       некоторых случаях Бейсик прощает незакрытые файлы, но это не  тот
       случай. Операторы WRITE# и  PRINT#  выводят данные в файловый бу-
       фер, который записывается на диск только тогда, когда они  запол-
       нены информацией. Последние введенные данные записываются на диск
       оператором  CLOSE.  Отсутствие этого оператора может  привести  к
       потере данных. Вот пример:

       100 OPEN "A:NEWSEQ" FOR OUTPUT AS #1  'открываем файл
       110 A$ = "aaaaa"                      'готовим три строки
       120 B$ = "bbbbb"                      '
       130 C$ = "ccccc"                      '
       140 WRITE #1, A$, B$, C$              'запись строк
       150 CLOSE                             'очистка буфера

          Средний уровень.

          MS DOS может писать  последовательные файлы как методом управ-
       ляющего блока файла, так и методом дескриптора файлов.  Метод FCB
       предоставляет  функцию  специально  сконструированную  для записи
       последовательных файлов.  Метод дескриптора файлов, с другой сто-
       роны, имеет только функцию записи в файл общего назначения, но ее
       легко  использовать и для этой цели.  В любом случае, способ, ко-
       торым был открыт файл, важен при последовательных операциях. Если
       данные  должны добавляться к последовательному файлу,  то  должна
       быть использована обычная  функция  открытия файла.  Однако, если
       файл должен быть перезаписан заново, то требуется функция "созда-
       ния" файла. Эта функция  обрезает  файл до нулевой длины, поэтому
       его длина будет равна длине записанных в него данных.

       Метод FCB:
          Функция 15H прерывания 21H предназначена для записи в последо-
       вательный файл. Надо подготовить управляющий блок файла и область
       обмена  с диском, как показано в [5.3.5].  Если файл должен  быть
       перезаписан, то его надо  открыть  с помощью функции 16H, которая
       "создает"  файл, обрезая его до нулевой длины.  Если Вы  откроете
       файл с помощью функции 0FH, то остаток  старого файла останется в
       конце файла, если длина нового файла будет меньше, чем старого. С
       другой стороны, если Вы хотите  добавить  данные  к файлу, то ис-
       пользуйте функцию открытия файла.
          После  того  как файл открыт, Вы должны  установить  DS:DX  на
       начало FCB и вызвать  функцию  15H  для того чтобы заприсать одну
       запись  данных.  Количество данных в записи зависит от  величины,
       которая помещена в поле длины записи,  расположенное со смещением
       


                                     -216-
       14  в  обычном FCB, по умолчанию это значение равно  128  байтам.
       Если размер записи меньше, чем размер  сектора диска 512 байт, то
       данные будут буферизоваться, до тех пор пока не накопится  доста-
       точно данных, чтобы произвести  реальную  запись на диск; поэтому
       записи  в последовательный файл могут успешно  записываться  даже
       если накопитель не включен. При  закрытии файла все данные остав-
       шиеся в буфере сбрасываются на диск. При возврате из функции 15H,
       AL равен 0, если операция успешна, 1 - если диск полон и 2 - если
       сегмент области обмена данных слишком мал.
          В  следующем примере на диск записываются 5 записей длиной 256
       байтов.  Записи могут быть набором  текстовых данных.  Эти данные
       расположены в области памяти, помеченной меткой WORKAREA.  Указа-
       тель на DTA первоначально устанавливается на начало этой области,
       а  после записи каждой записи установка DTA меняется таким  обра-
       зом, чтобы он указывал на 256  байтов  выше.  Отметим, что обычно
       для  такой  рабочей области отводится специальная область  памяти
       [1.3.1], но в данном  примере  для  простоты  используется  буфер
       расположенный в сегменте данных.

       ;---в сегменте данных
       WORKAREA     DB  2000 DUP (?)   ;буфер данных
       FCB          DB  1,'FILENAMEEXT',25 DUP (0)
       ;---DTA должен указывать на рабочую область
          LEA  DX,WORKAREA   ;DS:DX указывают на DTA
          MOV  DI,DX         ;сохраняем копию
          MOV  AH,1AH        ;функция установки DTA
          INT  21H           ;устанавливаем DTA
       ;---открываем файл
          MOV  AH,16H        ;номер функции
          LEA  DX,FCB        ;DS:DX указывают на FCB
          INT  21H           ;открываем файл
       ;---устанавливаем размер записи
          LEA  BX,FCB        ;BX указывает на FCB
          MOV  AX,256        ;размер записи 256 байтов
          MOV  [BX]+14,AX    ;записываем в поле размера записи
       ;---посылаем данные в файл
          MOV  CX,5          ;число записей
       NEXT_REC:  MOV  AH,15H   ;функция записи
          LEA  DX,FCB        ;указываем на FCB
          INT  21H           ;записываем данные
          CMP  AL,2          ;проверка на ошибки
          JE   CONTINUE      ;и их обработка
          CMP  AL,1          ;
          JE   DISK_FULL     ;
       ;---перенос выполнен, переустанавливаем DTA
          ADD  DI,256        ;сдвигаемся на 1 запись
          MOV  DX,DI         ;DS:DX указывают на новый DTA
          MOV  AH,1AH        ;функция установки DTA
          INT  21H           ;установка новой позиции
          LOOP NEXT_REC:     ;идем на следующую запись
       ;---позднее, закрываем файл
          LEA  DX,FCB        ;DS:DX указывают на FCB
          MOV  AH,10H        ;функция закрытия файла
          INT  21H           ;закрываем файл

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


                                     -217-
       сколько хотите новых  записей.  Файл  должен быть открыт функцией
       0FH.

       Метод дескриптора файла:
          Необходима  внимательность  при открытии файла для  последова-
       тельного вывода методом дескриптора  файла. Поскольку та же самая
       функция  используется для записи в файл прямого доступа,  то  при
       закрытии файла его  длина  не  устанавливается  равной  последней
       позиции  файлового указателя.  Возьмем, например,  случай,  когда
       текстовый файл размером 2000  байтов считывается с диска, а затем
       в процессе обработки в памяти его длина уменьшается до 1000 байт.
       Если файл был открыт  простой  командой  открытия  файла (функция
       3DH),  то  после того, как новая, более  короткая,  версия  файла
       будет записана на диск и файл  будет  закрыт, его длина останется
       равной 2000 байтам, из которых новый текст будет занимать  первую
       тысячу байтов. По этой  причине,  при  открытии последовательного
       файла для перезаписи надо использовать функцию 3CH прерывания 21H
       [5.3.2].  Эта функция обычно создает новый файл, но если файл уже
       существует, то он  обрезается  до  нулевой  длины. Для добавления
       данных  в последовательный файл надо использовать обычную функцию
       открытия файла, 3DH прерывания 21H [5.3.3].
          Рассмотрим сначала случай полной перезаписи файла. После того,
       как файл открыт функцией 3CH, файловый указатель  устанавливается
       равным нулю, поэтому нет нужды устанавливать его. Поместите номер
       файла  в BX, а число записываемых байтов в CX.  Затем  установите
       DS:DX на первый байт  выводимых  данных  и  выполните функцию 40H
       прерывания 21H.  При возврате, если флаг переноса установлен,  то
       была ошибка и AX содержит 5, если была ошибка дискового накопите-
       ля и 6 - если неверный номер файла.  В противном случае, AX будет
       содержать число реально  записанных  байтов; при несовпадении ве-
       роятнее всего проблема состоит в том, что диск полон. Не забудьте
       о процедуре восстановления при сбоях, так как при крахе программы
       первоначальное  содержимое  файла будет утеряно, так как  он  был
       обрезан до нулевой длины.  Как  проверять  дисковое  пространство
       описано в [5.1.2]. Вот пример:

       ;---в сегменте данных
       PATH         DB    'B:FILENAME.EXT',0   ;путь к файлу
       DATA_BUFFER  DB    2000 DUP (?)
       ;---открываем файл с помощью функции "создания"
          LEA  DX,PATH          ;DS:DX указывают на путь к файлу
          MOV  CX,0             ;атрибуты файлы (здесь обычные)
          MOV  AH,3CH           ;номер функции
          INT  21H              ;открываем файл
          JC   OPEN_ERROR       ;проверка на ошибку
          MOV  HANDLE,AX        ;запоминаем номер файла
       ;---записываем в файл 1000 байтов
          MOV  AH,40H           ;номер функции
          MOV  BX,HANDLE        ;номер файла в BX
          MOV  CX,1000          ;число байт, которые надо записать
          LEA  DX,DATA_BUFFER   ;DS:DX указывают на буфер данных
          INT  21H              ;записываем данные
          JC   OUTPUT_ERROR     ;проверка на ошибки
          CMP  CX,2000          ;и их обработка
          JNE  FULL_DISK        ;

          Для  добавления записей в последовательный файл  надо  открыть
       файл с помощью функции 3DH  прерывания  21H, помещая 1 в AL, если
       программа будет только писать данные и 2, если программа будет  и
       читать и писать. Длина файла  остается  неизменной, хотя он будет
       увеличиваться по мере добавления данных.  Файловый указатель дол-
       жен быть установлен на  конец  файла,  иначе  существующие данные
       будут перезаписаны.  Это выполняется функцией 42H прерывания 21H.
       


                                     -218-
       Поместите номер подфункции 2  в  AL,  для  установки указателя на
       конец  файла,  а номер файла поместите в BX.  CX:DX указывают  на
       смещение  относительно  конца  файла,  начиная  с  которого будет
       производиться запись, поэтому обнулите эти регистры. Затем выпол-
       ните функцию установки указателя. При возврате установленный флаг
       переноса  индицирует  ошибку, при этом в AX будет 1,  если  номер
       подфункции в AL был неверен, и 6  - если неверно был указан номер
       файла.   После  того как файловый указатель  установлен  операция
       записи выполняется в точности как в предыдущем случае:

       ;---в сегменте данных
       PATH          DB  'B:FILENAME.EXT',0   ;путь к файлу
       DATA_BUFFER   DB  1000 DUP(?)
       ;---открываем файл
          LEA  DX,PATH       ;DS:DX указывают на путь
          MOV  AL,1          ;код открытия только для записи
          MOV  AH,3DH        ;номер функции
          INT  21H           ;открываем файл
          JC   OPEN_ERROR    ;уход по ошибке
          MOV  HANDLE,AX     ;сохраняем номер файла
       ;---установка файлового указателя на конец файла
          MOV  BX,AX         ;номер файла в BX
          MOV  CX,0          ;CX:DX дают смещение относительно конца
          MOV  DX,0          ;
          MOV  AL,2          ;код для конца файла
          MOV  AH,42H        ;функция установки указателя
          INT  21H           ;устанавливаем указатель
          JC   POINTER_ERROR ;проверка на ошибку
       ;---добавляем к файлу 300 байтов
          MOV  AH,40H        ;номер функции
          MOV  BX,HANDLE     ;номер файла в BX
          MOV  CX,300        ;число записываемых байтов
          LEA  DX,DATA_BUFFER   ;DS:DX указывают на буфер данных
          INT  21H           ;добавляем данные
          JC   OUTPUT_ERROR  ;проверка на ошибки
          CMP  CX,300        ;и их обработка
          JNE  FULL_DISK     ;

          5.4.4 Чтение из последовательных файлов.

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

          Высокий уровень.

          Чтение последовательных файлов в Бейсике проще, чем их запись,
       поскольку  имеется только две возможности, как обращаться с ними,
       в зависимости от того, какие  символы  в файле используются в ка-
       честве ограничителей элементов данных. Оператор INPUT# распознает
       запятые и  кавычки,  как  разделители  данных,  так же как и пары
       возврат каретки/перевод строки.  Оператор LINE INPUT#  распознает
       только  комбинации  CR/LF,  поэтому  он  может использоваться для
       чтения  целых строк текста, содержащих другие ограничители.   Эта
       возможность удобна при обработке текстов.
          Для чтения трех элементов оператором  INPUT#, сначала откройте
       файл,  как обсуждалось в [5.3.3] (например, OPEN  "A:NEWSEQ"  FOR
       INPUT AS #1). Если файл  был  открыт  под  номером 1, то оператор
       


                                     -219-
       INPUT  #1,  X$, Y$, Z$ присвоит значение  первых  трех  элементов
       данных трем строковым переменным.  При вводе числовых переменных,
       например,  INPUT  #1, X, Y, Z необходимо,  чтобы  соответствующие
       данные в файле были числовыми.   Число с двойной точностью должно
       считываться  в переменную двойной точности, с тем чтобы она могла
       хранить восемь байтов такого  числа.  Другой способ прочитать три
       элемента данных состоит в размещении их в массиве:

       100 DIM ITEM$(40)       'создаем массив строк из 40 элементов
       110 FOR N = 0 TO 39     'для каждого элемента
       120 INPUT #1, ITEM$(N)  'считываем его и помещаем в массив
       130 NEXT                '

       Чтобы  прочитать n-ный элемент последовательного файла  программа
       должна прочитать все  предшествующие  ему  элементы.  Надо просто
       создать цикл, в котором будут считываться элементы данных, но  не
       сохранять эти данные по мере их появления.
          Оператор LINE INPUT# действует в основном аналогично оператору
       INPUT#,  за исключением того, что он может принимать только  одну
       переменную и это всегда  строковая  переменная.  Переменная может
       быть  длиной до 254 символов и это максимально допустимый  размер
       строковых переменных  в  Бейсике.   Пара  возврат каретки/перевод
       строки,  содержащаяся в файле, включается в строку,  возвращаемую
       оператором LINE INPUT#. Это свойство позволяет обнаруживать конец
       параграфа в текстовом файле.
          Функция  EOF (конец файла) может быть использована для опреде-
       ления того, все ли  элементы  файла  были  прочитаны. Эта функция
       возвращает -1, если файл исчерпан и 0 - в противном случае. Функ-
       ции требуется номер файла, под  которым  он был открыт; например,
       если  был был открыт как #2, то X = EOF(2).  В следующем  примере
       весь текстовый файл считывается в массив:

       100 OPEN "TEXT.AAA" FOR INPUT AS #2   'открываем файл
       110 DIM TEXT$(500)                    'не более 500 строк
       120 LINECOUNT = 0                     'счетчик строк
       130 LINE INPUT #2, TEXT$(LINECOUNT)   'получаем строку
       140 IF EOF(2) THEN 170                'проверка на конец файла
       150 LINECOUNT = LINECOUNT + 1         'увеличиваем счетчик
       160 GOTO 130                          'на следующую строку
       170 ...                               'файл прочитан

          Оператор INPUT$ читает  из  последовательного  файла указанное
       число символов.  На самой программе лежит забота о выделении  от-
       дельных элементов  данных.  Формат  этого оператора для чтения 30
       символов  из файла #1 такой: S$ = INPUT$(30,#1).  Хотя Вы  можете
       указывать число байт для  чтения,  необходимо  чтобы это число не
       превосходило  254,  поскольку это максимальный  размер  строковой
       переменной, в которую помещаются данные. INPUT$ полезен при пере-
       даче массы данных в непрерывную область памяти.  Например, в сле-
       дующем примере делается дамп  первых 200 байтов последовательного
       файла в буфер монохромного дисплея, с тем чтобы они были выведены
       на экран, включая управляющие коды:

       100 OPEN "A:NEWFILE" FOR INPUT AS #1   'открываем файл
       110 CLS: DEF SEG = &HB000              'указываем на буфер
       120 FOR N = 0 TO 9                     'получаем 10 групп
       130 S$ = INPUT$(20,#1)                 'по 20 байтов
       140 FOR M = 1 TO 20                    'берем каждый байт
       150 POKE N*160 + M*2, ASC(MID$(S$,M,1) 'и помещаем его в буфер
       160 NEXT M       'переход к следующему байту
       170 NEXT N       'переход к следующей группе

          Средний уровень.
       


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

       Метод FCB:
          Функция 14H прерывания 21H читает последовательные файлы. Надо
       создать управляющий блок  файла  и  область  обмена с диском, как
       объяснено в [5.3.5].  Файл должен быть открыт функцией 0FH преры-
       вания 21H [5.3.3].   DS:DX  должны  указывать на первый байт FCB,
       после  чего функция 14H будет читать по одной записи из файла при
       каждом вызове.  Вы можете установить размер записи по смещению 14
       в FCB.  Это надо делать после того, как файл открыт, так как  при
       открытии файла DOS вставляет  в  это  поле значение по умолчанию,
       равное 128.
          Каждый  раз  при вызове функции данные  загружаются в  память,
       начиная с первого байта DTA.  Если DTA используется как небольшой
       временный буфер, то перед чтением следующей записи содержимое DTA
       должно быть перенесено в область данных файла, отведенную в памя-
       ти.   Можно наоборот установить указатель DTA на стартовый  адрес
       памяти, начиная с которого будет размещаться файл, а после чтения
       каждой записи указатель увеличивать на размер записи, с тем чтобы
       он указывал на место, где должна быть следующая запись.
          Установкой полей  текущей  записи  (DB,  смещение 1FH) и блока
       текущей  записи (DW, смещение 0CH) отличными от нуля,  последова-
       тельный может читаться,  начиная  с  любого требуемого места (ус-
       тановка  должна быть сделана после открытия FCB).  После  каждого
       чтения поле текущей записи  автоматически  увеличивается  на 1, а
       после  чтения 128 записей увеличивается поле текущего блока.  При
       возврате AL равен 0, если вся запись успешно прочитана. При обна-
       ружении конца файла AL будет содержать 1, если функция 14H вообще
       не возвратила данных и 3 - если запись прочитана частично.
          В приведенном примере из файла считываются две записи и после-
       довательно  помещаются  в нужную область памяти.   Размер  записи
       установлен равным 256 байтам.  Записи считываются в цикле и после
       того,  как  первая  запись считана, указатель на  DTA  изменяется
       таким образом, чтобы он  указывал  на следующий пустой байт в об-
       ласти данных.

       ;---помещаем FCB в сегмент данных
       FCB          DB  0,'OLDDATA DAT', 25 DUP(0)
       DATA_AREA    DB  512 DUP (?)    ;используем как DTA
       ;---устанавливаем DTA на начало области данных
          LEA  DX,DATA_AREA      ;DS:DX указывают на DTA
          MOV  DI,DX             ;сохраняем копию
          MOV  AH,1AH            ;функция установки DTA
          INT  21H               ;устанавливаем DTA
       ;---открываем файл
          LEA  DX,FCB            ;DS:DX указывают на FCB
          MOV  AH,0FH            ;функция открытия файла
          INT  21H               ;открываем файл
          CMP  AL,0              ;проверка на ошибку
          JNE  OPEN_ERROR        ;
       ;---устанавливаем размер записи 256 байт
          LEA  BX,FCB            ;DS:DX указывают на FCB
          MOV  AX,256            ;размер записи
          MOV  DS:[BX]+14,AX     ;посылаем в поле размера записи
       ;---чтение данных
          MOV  CX,2              ;число читаемых записей
       NEXT_REC:   MOV  AH,14H   ;функция чтения файла
          LEA  DX,FCB            ;DS:DX указывают на FCB
       


                                     -221-
          INT  21H               ;читаем одну запись
          CMP  AL,0              ;все в порядке?
          JE   CONTINUE          ;
          CMP  AL,2              ;проверка на ошибку
          JE   READ_ERROR        ;
           .
           .
       CONTINUE:  ADD  DI,256    ;увеличиваем указатель
          MOV  DX,DI             ;DX указывает на новую DTA
          MOV  AH,1AH            ;функция установки DTA
          INT  21H               ;устанавливаем DTA
          LOOP NEXT_REC          ;идем на чтение следующей записи
       ;---позднее, закрываем файл
          LEA  DX,FCB            ;DS:DX указывают на FCB
          MOV  AH,10H            ;функция закрытия файла
          INT  21H               ;закрываем файл
          CMP  AL,0FFH           ;проверка на ошибку
          JE   CLOSE_ERROR       ;

       Метод дескриптора файлов:
          Функция 3FH прерывания 21H может читать данные из файла после-
       довательно. Эта функция используется для любого чтения из файла с
       помощью метода дескриптора файлов, включая файлы прямого доступа.
       Файл должен быть открыт  функцией  3DH прерывания 21H с кодом 0 в
       AL, если он открывается только для чтения, и с кодом 2 - если  он
       открывается для чтения и записи.  При открытии файловый указатель
       автоматически устанавливается на первый байт файла.  Функция чте-
       ния из файла указывает сколько байтов должно быть считано и после
       того  как это сделано файловый указатель указывает на байт,  сле-
       дующий за последним  считанным  байтом,  подготавливая  следующее
       обращение к функции. Отметим, что файловый указатель уникален для
       каждого файла - операции над  другими файлами не меняют его пози-
       цию.
          Программа  может создать небольшой временный буфер,  размером,
       скажем, 512 байт, и постоянно  вызывать  функцию чтения, не забо-
       тясь о позиции файлового указателя. Другой метод состоит в считы-
       вании всего файла прямо в  то  место  памяти,  где он должен быть
       расположен.  В этом случае надо просто потребовать, чтобы функция
       прочитала больше байтов, чем реально  содержится в файле, так как
       чтение прекращается при достижении последнего байта файла. Однако
       Вам необходимо знать точную длину файла, чтобы знать где кончают-
       ся данные в буфере, в который Вы считали файл.
          Размер  файла можно определить, сдвинув файловый указатель  на
       конец файла.  Это надо  сделать  сразу  же  после открытия файла.
       Поместите  в  AL код 2 и вызовите функцию 42H,  для  того,  чтобы
       сдвинуть указатель на конец  файла.   CX и DX должны содержать 0,
       так  как в противном случае указатель будет сдвинут с конца файла
       на величину, которая содержится  в  этих  регистрах. При возврате
       DX:AX будут содержать новую позицию указателя, как смещение отно-
       сительно начала файла, т.е.,  в  данном  случае,  длину файла. Не
       забудьте  снова вернуть файловый указатель на начало файла, перед
       тем как читать его; это делается точно  таким же образом, за иск-
       лючением  того,  что в AL надо поместить 0.  Если при  выполнении
       функции 42H возникает ошибка, то устанавливается флаг переноса, а
       в AX возвращается 1, если неверен номер функции, и 6 - если  ука-
       зан неверный номер файла.
          Теперь программа готова для чтения файла. Надо поместить номер
       файла в BX, а требуемое число байтов в CX и выполнить прерывание.
       При возврате AX будет содержать число реально прочитанных байтов.
       Если AX равен нулю, то достигнут конец файла.  При других ошибках
       устанавливается флаг переноса, а AX  содержит 5 - при ошибке обо-
       рудования  и  6 - если указан неверный номер файла.  В  следующем
       примере в буфер памяти  считывается  весь  небольшой файл. Для у-
       


                                     -222-
       добства  буфер располагается в сегменте данных,  что  существенно
       увеличивает размер программы на диске.   В своих программах лучше
       создавать буфер, используя технику распределения памяти,  описан-
       ную в [1.3.1].

       ;---в сегменте данных
       PATH        DB   'A:FILENAME.EXT'0   ;строка пути к файлу
       DATA_BUFFER DB   1000 DUP (?)        ;буфер данных
       HANDLE      DW   ?                   ;номер файла
       FILESIZE    DW   ?                   ;размер файла
       ;---открываем файл
          LEA  DX,PATH            ;DS:DX указывают на путь
          MOV  AL,0               ;код открытия для чтения
          MOV  AH,3DH             ;функция открытия файла
          INT  21H                ;открываем файл
          JC   OPEN_ERROR         ;проверка на ошибку
          MOV  HANDLE,AX          ;запоминаем номер файла
       ;---устанавливаем файловый указатель на конец файла
          MOV  AH,42H             ;функция установки указателя
          MOV  AL,2               ;код для конца файла
          MOV  BX,HANDLE          ;номер файла
          MOV  CX,0               ;смещение равно нулю
          MOV  DX,0               ;
          INT  21H                ;устанавливаем указатель
          JC   POINTER_ERROR1     ;обработка ошибки
          MOV  FILESIZE,AX        ;запоминаем размер (меньше 64K)
       ;---возвращаем указатель на начало
          MOV  AH,42H             ;номер функции
          MOV  AL,0               ;код для начала файла
          MOV  CX,0               ;смещение равно нулю
          MOV  DX,0               ;
          INT  21H                ;устанавливаем указатель
          JC   POINTER_ERROR2     ;обработка ошибки
       ;---читаем весь файл
          MOV  AH,3FH             ;номер функции чтения файла
          MOV  BX,HANDLE          ;номер файла
          MOV  CX,FILESIZE        ;число считываемых байтов
          LEA  DX,DATA_BUFFER     ;DS:DX указывают на буфер
          INT  21H                ;читаем файл
          JC   READ_ERROR         ;обработка ошибки
       ;---позднее, закрываем файл
          MOV  BX,HANDLE          ;номер файла
          MOV  AH,3EH             ;функция закрытия файла
          INT  21H                ;закрываем файл
          JC   CLOSE_ERROR        ;обработка ошибки

          5.4.5 Запись в файлы прямого доступа.

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

          Высокий уровень.

          В [5.3.3] объяснен  формат  открытия  файдов прямого доступа в
       Бейсике.  В отличии от последовательного файла, файл прямого дос-
       


                                     -223-
       тупа может читаться  и  записываться  в  одно  и то же время, без
       закрытия  и  повторного его открытия.  Оператор OPEN  завершается
       числом, дающим размер записи файла.  Например, OPEN "R", 1, "NEW-
       DATA", 20 устанавливает для файла NEWDATA размер записи в 20 байт
       (при этом файл открывается как файл #1).
          После того как файл открыт,  его  записи могут быть разбиты на
       составляющие переменные с помощью оператора FIELD. Оператор FIELD
       указывает сколько байтов записи  отводится под каждую переменную.
       Например,  запись  длиной 20 байт может быть  разбита  оператором
       FIELD 1, 14 AS LASTNAME$, 2 AS  DEPOSIT$,  4 AS ACCTNUM$.  В этом
       операторе  первая  цифра 1 указывает, что данный  оператор  FIELD
       описывает разбиение записи для  файла,  открытого под номером #1.
       Данные  располагаются в записи точно в том порядке, в  каком  они
       описаны в операторе FIELD.  Опреаторы RSET и LSET сдвигают данные
       в полях, выравнивая их по правому (RSET) или левому (LSET) краю и
       заполняя остающиеся пустые места пробелами.   Например, для того,
       чтобы вставить фамилию "SMITH" в 14-байтное поле с именем LASTNA-
       ME$, надо записать RSET LASTNAME$  = "SMITH", или если переменной
       N$ было присвоено значение "SMITH", то RSET LASTNAME$ = N$. Вмес-
       то RSET может быть использовано  LSET.  Когда впоследствии данные
       считываются из поля в переменную, то переменной присваиваются все
       14 байтов. При  использовании  RSET  программа  удалит все лишние
       пробелы в начале строковой переменной, однако если будет  исполь-
       зоваться LSET, то пробелы будут удаляться справа.
          Отметим, что все имена  переменных в операторе FIELD относятся
       к строковым переменным.  В файлах прямого доступа Бейсик рассмат-
       ривает все переменные - включая числовые  - как строковые. Число-
       вая переменная должна быть преобразована в специальный вид, преж-
       де чем ее значение может быть  присвоено  полю, а когда она затем
       считывается из поля то необходимо обратное преобразование.  Слово
       преобразование стоило бы заключить в кавычки, поскольку Бейсик на
       самом  деле  не меняет способ представления  числа в  памяти;  он
       просто обрабатывает число особым  образом.  Числовые поля требуют
       двух байтов для целых чисел, четырех байтов - для чисел с обычной
       точностью и восьми байтов  -  для  чисел  с  двойной точностью. В
       точности  такое  же число байт требуется для  представления  этих
       чисел в памяти.   Для  преобразования  их  в строковую форму надо
       использовать  функции  MKI$, MKS$ и  MKD$,  которые  осуществляют
       преобразование  число-строка  для  целых,  вещественных и чисел с
       двойной  точностью, соответственно.  Обычно эти функции  комбини-
       руются с операторами RSET или  LSET,  например, RSET = ACCTNUM$ =
       MKI$(X),  где X - целая переменная, если полю ACCTNUM$ было отве-
       дено два байта в операторе FIELD.
          После  того как поля заполнены операторами RSET и LSET, запись
       записывается на диск с помощью оператора  PUT#. PUT #1, 245 поме-
       щает  данные в запись номер 245, файла открытого под номером  #1.
       Номер записи может быть опущен, в этом случае данные записываются
       в  запись с номером на единицу больше, чем номер последней  запи-
       санной записи (начиная с записи 1). Записывается вся запись цели-
       ком,  даже если не все поля были заполнены данными.  Отметим, что
       поля буфера не очищаются  при  выполении  операции  PUT,  поэтому
       элементы данных, такие как текущая дата, могут помещаться в буфер
       только один раз, а затем они будут  записаны во все записи, кото-
       рые будут записываться в течение данной сессии. Функция LOC возв-
       ращает номер последней  записанной  в  файл записи. Если файл был
       открыт под номером #3, то напишите X = LOC(3).
          Функция LOF (длина файла) возвращает длину файла в байтах. Для
       определения числа записей,  содержащихся  в файле, надо разделить
       это значение на длину записи.  Добавление 1 к этому значению дает
       номер записи, который надо  использовать,  чтобы добавить к файлу
       новые  записи.  Если файл был открыт под номером #2, а длина  его
       записей равна 32 байтам, то  требуемое  значение  вычисляется как
       RECORDNUM = LOF(2)/32 + 1.
       


                                     -224-
          В следующем примере файл прямого доступа открывается с  длиной
       записи 24 байта, причем запись разбита на три переменные. Пользо-
       ватель  программы  запрашивается о содержимом всех трех полей,  а
       когда все они введены, то  запись  добавляется  к файлу. В строке
       120 вычисляется начальный номер записи. Отметим, что данные могут
       не записываться физически на диск  каждый раз при выполнении опе-
       ратора PUT. В выходном буфере могут накапливаться несколько запи-
       сей, прежде чем это будет сделано.

       100 OPEN "R", 1, "A:NEWDATA12/06/91T", 24  'открываем файл
       110 FIELD 1, 18 AS LASTNAME$, 2 AS AGE$, 4 AS WEIGHT
       120 R = LOF(1)/24 + 1         'номер последней записи + 1
       130 CLS                       'чистим экран
       140 INPUT "Enter name:",N$    'получаем имя (строка)
       150 INPUT "Enter age:",A%     'получаем возраст (целое)
       160 INPUT "Enter weight:",W!  'получаем вес (вещественное)
       170 RSET LASTNAME$ = N$       'помещаем в поле имя
       180 RSET AGE$ = MKI$(A%)      'помещаем в поле возраст
       190 RSET WEIGHT$ = MKS$(W!)   'помещаем в поле вес
       200 PUT #1, R                 'записываем запись
       210 R = R + 1                 'увеличиваем счетчик
       220 PRINT: PRINT "Do another (y/n)?"  'запрос пользователя
       230 C$ = INKEY$: IF C$ = "" THEN 220  'ожидаем ответа
       240 IF C$ = "y" THEN CLS: GOTO 130    'если да, то на начало
       250 CLOSE                     'иначе закрываем файл

          Средний уровень.

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

       Метод управляющего блока файла:
          Откройте управляющий блок  файла с помощью функции 0FH и пусть
       DS:DX  указывают на него.  После того как файл  открыт  поместите
       номер записи для  прямого  доступа  в поле записи прямого доступа
       FCB.  Затем вызовите функцию 22H прерывания 21H, которая  пердаст
       данные из DTA в файловый буфер, созданный при создании FCB.  Дан-
       ные могут не быть немедленно записаны на диск, если размер записи
       меньше чем размер буфера.  Реальная запись на диск будет происхо-
       дить тогда, когда очередной вызов функции 22H заполнит буфер.
          При  возврате из функции 22H AL будет содержать 00, если обмен
       прошел успешно. В противном случае в нем будет 1, если не хватает
       пространства на диске и 2 - если область переноса мала для  того,
       чтобы записать одну запись (т.е.  если размер буфера, установлен-
       ный системой меньше, чем тот, который указан в FCB).

       ;---в сегменте данных
       FCB       DB   1, 'NEWDATA    ', 25 DUP (0)
       DTA       DB   256 DUP (?)
       ;---открываем файл и устанавливаем поля FCB
          MOV  AH,0FH          ;номер функции
          LEA  DX,FCB          ;DS:DX указывают на FCB
          MOV  BX,DX           ;копируем смещение для FCB
          INT  21H             ;открываем файл
          MOV  AX,256          ;размер записи
          MOV  [BX]+14,AX      ;помещаем в поле размера записи
          MOV  AX,233          ;номер записи
          MOV  [BX]+33,AX      ;помещаем в поле номера записи
          MOV  AX,0            ;обнуляем старший байт этого слова
          MOV  [BX]+35,AX      ;
       


                                     -225-
       ;---перенос данных из DTA в файл
          MOV  AH,22H          ;номер функции записи с прямым доступом
          LEA  DX,FCB          ;DS:DX указывают на FCB
          INT  21H             ;записываем данные
          CMP  AL,0            ;проверка на ошибку
          JNE  WRITE_ERROR     ;
       ;---позднее, закрываем файл
          LEA  DX,FCB          ;DS:DX указывают на FCB
          MOV  AH,10H          ;функция закрытия файла
          INT  21H             ;закрываем файл
          CMP  AL,0FFH         ;проверка на ошибку
          JE   CLOSE_ERROR     ;

          Часто  программа работает сразу с набором записей прямого дос-
       тупа, передавая их в память и из памяти  как единое целое. MS DOS
       предоставляет  специальную  функцию для этого  при  использовании
       метода FCB, называемую запись блока  с прямым доступом. Это функ-
       ция 28H прерывания 21H.  При входе DS:DX должны указывать на отк-
       рытый FCB, в  котором  поле  записи  прямого  доступа должно быть
       равно номеру первой из записываемых записей набора.  Эта  функция
       совершенно  аналогична  вышеприведенному  примеру.   Единственное
       отличие  (кроме  номера функции) состоит в том, что в  CX  должно
       быть указано число  записей  в  блоке  (не  путайте эти "блоки" с
       блоками по 128 записей, с помощью которых система находит требуе-
       мую запись - программа может  читать любое число записей, начиная
       с любого места).
          В CX возвращается число реально прочитанных записей.  AL будет
       содержать 0, если все записи  успешно  записаны, 1 - если не хва-
       тает  пространства  на диске, при этом не будет записана ни  одна
       запись. В отличии от функции 22H эта функция автоматически увели-
       чивает поля текущей записи, текущего блока и записи прямого  дос-
       тупа в FCB, так что они будут  указывать  на запись, следующую за
       последней  прочитанной.   Отметим, что если при  выполнении  этой
       функции установить CX = 0,  то  размер  файла  будет установлен в
       соответствии с числом записей, равным полю записи прямого  досту-
       па, и таким образом можно резервировать для файла дисковое прост-
       ранство.
       Метод дескриптора файлов:
          При использовании для доступа метода дескриптора файлов систе-
       ма не различает  последовательные  файлы и файлы прямого доступа.
       Ваша программа должна вычислить позицию в файле, с которой  начи-
       нается требуемая запись, и  установить на нее файловый указатель.
       Файловый указатель позиционируется с помощью функции 42H прерыва-
       ния 21H. Поместите номер файла  в  BX, а смещение в файле в CX:DX
       (CX будет содержать старший байт значения).  Затем поместите в AL
       кодовый номер от 0 до 2. При AL  =  0, указатель будет установлен
       со смещением CX:DX байтов относительно начала файла; при AL =  1,
       указатель будет установлен со  смещением CX:DX относительно теку-
       щей  позиции, а при AL = 2, указатель будет установлен со  смеще-
       нием CX:DX относительно  конца  файла  (т.е.   таким образом файл
       будет расширен).  Отрицательные числа недопустимы в качестве сме-
       щений.  При возврате DX:AX будут содержать новое положение указа-
       теля (старший байт в DX).  Если устанавливается флаг переноса, то
       произошла ошибка. В этом случае AX будет содержать 1, если указан
       неверный код в AL и 6 - если указан неверный номер файла.
          После  позиционирования  файлового  указателя  запись  прямого
       доступа записывается с помощью той же функции 40H прерывания 21H,
       которая  использовалась для записи в последовательный файл.   При
       входе BX содержит номер файла, а CX  - число байтов, которое надо
       записать.  При возврате AX будет содержать число реально записан-
       ных байтов. Если  оно  отличается  от  числа помещенного в CX, то
       вероятно диск полон (см. [5.1.4]).  Как обычно, при возникновении
       ошибки устанавливается  флаг  переноса.   В  этом случае AX будет
       


                                     -226-
       содержать  5  при  ошибке накопителя и 6 - если  указан  неверный
       номер файла.
          Файловый указатель играет ту же  роль для образа файла на дис-
       ке,  что DTA для образа файла в памяти.  Он может сдвигаться  как
       угодно для доступа к различным частям файла.  Будьте внимательны,
       манипулируя файловым указателем при работе с фалом прямого досту-
       па, содержимое любого поля любой записи может быть сразу прочита-
       но с диска и помещено в требуемое место в памяти.

       ;---в сегменте данных
       HANDLE        DW    ?           ;номер файла
       FILEPATH      DB    'A:NEWDATA',0   ;строка пути к файлу
       REC_BUFFER    DB    30 DUP (?)  ;буфер выводимых записей
       ;---открываем файл
          MOV  AH,3DH               ;номер функции
          MOV  AL,1                 ;код открытия для записи
          LEA  DX,FILEPATH          ;DS:DX указывают на путь
          INT  21H                  ;открываем файл
          JC   OPEN_ERROR           ;проверка на ошибку
          MOV  HANDLE,AX            ;сохраняем номер файла
       ;---вычисляем позицию записи и устанавливаем файловый указатель
          MOV  AX,30                ;размер записи 30 байтов
          MOV  CX,54                ;номер записи #54 (55-я запись)
          MUL  CX                   ;теперь смещение для нее в DX:AX
          MOV  CX,DX                ;помещаем старшее слово в DX
          MOV  DX,AX                ;помещаем младшее слово в CX
          MOV  AL,0                 ;устанавливаем указатель на начало
          MOV  AH,42H               ;функция установки указателя
          MOV  BX,HANDLE            ;номер файла
          INT  21H                  ;устанавливаем указатель
          JC   POINTER_ERROR        ;проверка на ошибку
       ;---пишем запись с прямым доступом
          MOV  AH,40H               ;номер функции
          MOV  BX,HANDLE            ;номер файла
          MOV  CX,30                ;размер записи
          LEA  DX,REC_BUFFER        ;DS:DX указывают на буфер
          INT  21H                  ;пишем запись
          JC   WRITE_ERROR          ;проверка на ошибку

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

          5.4.6 Чтение из файлов прямого доступа.

          Чтение  файлов прямого доступа является обратным процессом  по
       отношению к их записи. MS DOS вычисляет позицию в файле на диске,
       затем  считывает запись и помещает ее в память.  Затем  программа
       должна разделить запись на поля в точности того же размера, кото-
       рый был использован при конструировании записи.  Не забудьте уда-
       лить символы пробела, добавленные при заподнении полей.  Обсужде-
       ние записи данных в файлы прямого доступа [5.4.5] содержит инфор-
       мацию, которая поможет Вам лучше понять информацию данного разде-
       ла.

          Высокий уровень.

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


                                     -227-
       записи переменной, именованной  в  операторе FIELD, автоматически
       присваивается соответствующее значение из записи.  Например, если
       оператор FIELD имеет вид  FIELD  1,  20  AS X$, 2 AS Y$, то после
       выполнения  оператора GET 1,23 переменной X$ будет присвоено зна-
       чение первых 20-ти байтов записи 23,  а переменной Y$ - следующих
       10-ти байтов.  Операторы,  аналогичные  RSET и LSET для выделения
       полей данных отсутствуют.
          В  случае  числовых  полей, напоминаем, что  они  должны  быть
       преобразованы в строковый  вид  с  помощью  функций  MKI$, MKS$ и
       MKD$.   Для восстановления их оригинальных значений, с тем  чтобы
       над ними можно было проводить операции и печатать их, надо преоб-
       разовать  эти строки с помощью функций CVI, CVS и CVD.   Если  Y$
       содержит целое число, то для  выполнения обратного преобразования
       запишите  Y%  = CVI(Y$), при этом переменная Y%  будет  содержать
       значение, которое  имела  переменная  перед тем как она была спе-
       циально  обработана для записи в файл прямого доступа.   Если  Вы
       выведете строковое значение переменной, то увидите, что это число
       в интервале от 0 до 65535, закодированное в два символа ASCII.
          В  данном примере открывается файл, созданный в примере пункта
       [5.4.5], и выводится содержимое любой из затребованных записей:

       100 OPEN "A:NEWDATA" AS #1 LEN = 24    'открываем файл
       110 FIELD 1, 18 AS LASTNAME$, 2 AS AGE$, 4 AS WEIGHT$
       120 CLS: INPUT "What is the record number";R   'запрос записи
       130 IF R*24 > LOF(1) THEN BEEP: PRINT"No such record": GOTO 120
       140 GET #1,R                     'читаем запись из файла
       150 PRINT LASTNAME$, CVI(AGE$), CVS(WEIGHT$)   'выводим ее
       160 PRINT: PRINT "Do another (y/n)?"    'будем повторять?
       170 C$ = INKEY$: IF C$ = "" THEN 170    'ожидаем ввода
       180 IF C$ = "y" OR C$ = "Y" THEN 120    'повторяем, если надо
       190 CLOSE                               'иначе закрываем файл

          Средний уровень.

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

       Метод FCB:
          Функция 21H прерывания 21H читает одну запись из файла прямого
       доступа.  Вторая функция, 27H, читает блок последовательных запи-
       сей.   Создайте управляющий блок файла, как показано в [5.3.5]  и
       откройте его [5.3.3].  После  того как FCB открыт, введите в него
       значения полей размера записи (DW по смещению 14) и номера записи
       прямого доступа (DD  по  смещению  33).   Если DS:DX указывают на
       первый  байт FCB, то можно вызывать функцию 21H для чтения записи
       и запись будет помещена в паямть, начиная с первого байта DTA.
          Если запись  успешно  прочитана,  то  в  AL будет возвращен 0.
       Однако при этом нет гарантии, что чтение прошло без ошибок,  пос-
       кольку неверный размер  записи  может  привести к тому, что части
       прилегающих  записей  будут считаны, как будто это  одна  запись.
       Если запрошена  запись  с  номером  большим,  чем число записей в
       файле, то в AL будет возвращено 1 или 3.  Если был возвращен  код
       3, то был считан самый конец  файла и была прочитана часть записи
       данных. Если был возвращен код 1, то данные вообще не были счита-
       ны.
          Данный пример считывает одну запись и помещает ее в DTA:

       ;---в сегменте данных
       FCB         DB    1,'OLDDATA    ', 25 DUP (0)
       ;---открываем файл и устанавливаем поля FCB
          MOV  AH,0FH           ;номер функции
       


                                     -228-
          LEA  DX,FCB           ;DS:DX указывают на FCB
          MOV  BX,DX            ;копируем смещение FCB
          INT  21H              ;открываем файл
          MOV  AX,55            ;размер записи 55 байтов
          MOV  [BX]+14,AX       ;помещаем в поле размера записи
          MOV  AX,22            ;номер записи для чтения
          MOV  [BX]+33,AX       ;помещаем в поле номера записи
          MOV  AX,0             ;обнуляем старшее слово этого поля
          MOV  [BX]+35,AX       ;
       ;---перенос данных из файла в DTA
          MOV  AH,21H           ;номер функции чтения с прямым доступом
          LEA  DX,FCB           ;DS:DX указывают на FCB
          INT  21H              ;читаем данные, помещая их в DTA
          CMP  AL,0             ;проверка на ошибку
          JNE  READ_ERROR       ;
       ;---позднее, закрываем файл
          MOV  AH,10            ;номер функции закрытия файла
          LEA  DX,FCB           ;DS:DX указывают на FCB
          INT  21H              ;закрываем файл

          Для  чтения  блока последовательных записей в память  за  один
       прием надо использовать функцию 27H прерывания 21H. Ее выполнение
       подготавливается  в точности так же, как и функции 21H, за исклю-
       чением того, что вдобавок CX должен содержать число записей кото-
       рые надо прочитать за один прием. При возврате CX будет содержать
       число реально  прочитанных  записей.  Значения  возвращаемые в AL
       совпадают с теми, которые возвращаеются функцией 21H.  В  отличии
       от функции 21H поля FCB, в  которых хранится информация о положе-
       нии записи (поле записи прямого доступа, текущего блока и текущей
       записи) автоматически увеличиваются, с тем чтобы они указывали на
       следующую несчитанную запись после выполнения функции.
          Отметим,  что как в случае чтения одной, так и в случае чтения
       нескольких записей,  поля  текущего  блока  и  текущей записи FCB
       устанавливаются по значению поля записи прямого доступа.  Если Вы
       знаете значение текущего блока и  текущей записи, а не соответст-
       вующий  номер записи прямого доступа, то используйте функцию  24H
       прерывания 21H, чтобы она  проделала  вычисления  за Вас.  У этой
       функции нет входных регистров, надо только, чтобы DS:DX указывали
       на открытый FCB.  При возврате  поле записи прямого доступа будет
       заполнено значением, соответствующим установке двух других полей.
       Метод дескриптора файлов:
          В  предыдущем разделе показано, как писать записи прямого дос-
       тупа с помощью  метода  дескриптора  файлов.  Процедура чтения из
       файла  с прямым доступом подготавливается совершенно  аналогичным
       образом, путем  вычисления  смещения  в  файле, на которое должен
       указывать файловый указатель.  DS:DX должны указывать на буфер, в
       который будет помещена запись,  после чего надо выполнить функцию
       3FH прерывания 21H.  При входе CX должен содержать размер записи,
       а BX - номер файла.

       ;---в сегменте данных
       HANDLE       DB    ?
       FILEPATH     DB    'A:OLDDATA',0
       REC_BUFFER   DB    30 DUP(?)
       ;---открываем файл
          MOV  AH,3DH          ;номер функции
          MOV  AL,0            ;код открытия для чтения
          LEA  DX,FILEPATH     ;DS:DX указывают на путь к файлу
          INT  21H             ;открываем файл
          JC   OPEN_ERROR      ;проверка на ошибку
          MOV  HANDLE,AX       ;запоминаем номер файла
       ;---вычисляем позицию записи и устанавливаем файловый указатель
          MOV  AX,30           ;размер записи
       


                                     -229-
          MOV  CX,54           ;читаем запись #54 (55-ю запись)
          MUL  CX              ;смещение записи в DX:AX
          MOV  CX,DX           ;помещаем старшее слово смещения в DX
          MOV  DX,AX           ;помещаем младшее слово смещения в CX
          MOV  AL,0            ;устанавливаем указатель на начало файла
          MOV  AH,42H          ;функция установки указателя
          MOV  BX,HANDLE       ;номер файла
          INT  21H             ;устанавливаем указатель
          JC   POINTER_ERROR   ;обработка ошибки
       ;---читаем запись с прямым доступом
          MOV  AH,3FH          ;номер функции
          MOV  BX,HANDLE       ;номер файла
          MOV  CX,30           ;размер записи
          LEA  DX,REC_BUFFER   ;DS:DX указывают на буфер для записи
          INT  21H             ;читаем запись
          JC   READ_ERROR      ;обработка ошибки
       ;---позднее, закрываем файл
          MOV  BX,HANDLE       ;номер файла
          MOV  AH,3EH          ;функция закрытия файла
          INT  21H             ;закрываем файл
          JC   CLOSE_ERROR     ;проверка на ошибку

          5.4.7 Проверка данных после операций чтения/записи.

          MS DOS может  проверять  правильность  производимого  обмена с
       диском прямо во время обмена.  Ошибки происходят настолько редко,
       что средства проверки обычно не используются,  чтобы не замедлять
       обмен с диском.  Однако, если это необходимо, то имеется два спо-
       соба проверки. Один состоит  во  включении  команды VERIFY = ON в
       файл  CONFIG.SYS,  который  автоматически читается  при  загрузке
       операционной системы.   Впоследствии, все дисковые операции будут
       проверяться.  Это единственный способ проверки доступный в Бейси-
       ке.  Второй метод состоит  использовании  специальной функции DOS
       для верификации только критических дисковых операций. Если проце-
       дура верификации обнаруживает ошибку,  то она сообщает об условии
       критической ошибки, как описано в [7.2.5].

          Средний уровень.

          Функция  2EH  прерывания 21H  включает и  выключает  проверку.
       Поместите в AL 1 - для  включения  верификации и 0 - для выключе-
       ния. DL также должно быть равно 0.  Затем надо выполнить прерыва-
       ние. У этой функции нет выходных регистров.

       ;---включение верификации
          MOV  AL,1     ;номер кода
          MOV  DL,0     ;необходимый входной регистр
          MOV  AH,2EH   ;номер функции
          INT  21H      ;включаем проверку

          Для определения текущего режима верификации надо вызвать функ-
       цию 54H прерывания 21H. У нее нет входных регистров. При возврате
       AL = 1, если проверка включена и AL = 0, если выключена.

          5.4.8 Определение дисковых ошибок и восстановление после них.

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


                                     -230-
       мягкими (soft) и жесткими  (hard).  Мягкие ошибки возникают из-за
       неправильного  запроса на доступ к файлу: запрошенный файл  может
       отсутствовать или дисковое  пространство  может кончиться прежде,
       чем  будет  записан весь файл.  С другой стороны, жесткие  ошибки
       возникают при неверных последовательностях  или временных несоот-
       ветствий  при дисковых операциях, которые могут  быть  следствием
       неверного выравнивания или проблем с накопителем.  В этом случае,
       лучше всего произвести сброс диска перед обработкой.

          Высокий уровень.

          В  [7.2.5] объяснено как подготовить процедуру обработки  оши-
       бок.  Оператор ON ERROR  GOSUB  заставляет  программу  перейти на
       процедуру обработки ошибки при возникновении критической  ошибки.
       Процедура прежде всего определяет кодовый номер ошибки в Бейсике,
       который для дисковых ошибок может быть одним из следующих:

          52      Bad file number.  (Неверный номер файла.) Файл не отк-
                  рыт под тем номером, к  которому  идет  обращение (#1,
                  #2 и т.д.)
          53      File  not  found.  (Файл не найден.) Используется  при
                  выполнении операторов LOAD, KILL, NAME, FILES и OPEN.
          54      Bad file mode.  (Неверный режим доступа.) Попытка дос-
                  тупа  к файлу другим образом, по сравнению с тем,  для
                  чего он был открыт,  например, попытка записи в после-
                  довательный файл, открытый для чтения.
          55      File already open.  (Файл уже открыт.) Попытка открыть
                  файл, который уже открыт, или  уничтожить (KILL) файл,
                  который еще не закрыт.
          58      File  already exists.  (Файл уже существует.)  Попытка
                  переименовать файл  (с  помощью  NAME) на имя, которое
                  уже есть в каталоге.
          61      Disk full.  (Диск полон.) См. специальное обсуждение в
                  [5.1.4], относящееся к этой ошибке.
          62      Input past end.   (Чтение  за  концом  файла.) Попытка
                  прочитать  из последовательного файла больше  перемен-
                  ных, чем он содержит.   Чтобы избежать этой ошибки ис-
                  пользуйте функцию EOF, как объяснено в [5.4.4].
          63      Bad  record number.  (Неверный номер записи.)  Попытка
                  прочитать или записать  запись  с номером большим, чем
                  число записей в файле.
          64      Bad  file  name.  (Неверное имя  файла.)  Используется
                  операторами KILL, NAME и FILES.
          67      Too many files.   (Слишком  много  файлов.) В каталоге
                  больше нет места для записи информации о файлах.  Дру-
                  гой возможный вариант состоит  в том, что открытие еще
                  одного  файла  приведет к тому,  что  будет  превышено
                  максимально  допустимое  число  одновременно  открытых
                  файлов.
          70      Disk is write-protected. (Диск защищен от записи.)
          71      Disk is not ready. (Диск не готов.) Наиболее вероятно,
                  не закрыт дисковод с дискетой.
          72      Disk media error. (Диск  поврежден.)  Как правило, это
                  сообщение  выдается  при повреждении  дискеты,  однако
                  иногда оно появляется при сбоях оборудования.
          74      Specified wrong disk  in   RENAME  operation.  (Указан
                  неверный диск в операции RENAME.)
          75      Path/file  access  error.  (Ошибка  доступа к  файлу.)
                  Попытка открыть подкаталог  или  метку тома, как файл.
                  Или  попытка писать в файл, который защищен от записи.
                  Эта ошибка чаще  всего  выдается  при  попытке удалить
                  текущий каталог.  Появляется при операциях OPEN, NAME,
                  MKDIR, CHDIR и RMDIR.
       


                                     -231-
          76      Path not found.  (Путь не найден.) Неправильно  указан
                  путь или его не существует.   Появляется при операциях
                  OPEN, MKDIR, CHDIR и RMDIR.

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

       100 ON ERROR GOSUB 5000         '
        .
        .
       600 '''
        .
        .
       5000 '''
       5010 IF ERR = 61 PRINT "Disk full": GOTO 5100
       5020 IF ERR = 70 PRINT "Disk is write protected": GOTO 5100
        .
        .
       5100 PRINT "Correct the problem, then strike any key"
       5110 C$ = INKEY$: IF C$ = "" THEN 5110
       5120 RESUME 600

          Средний уровень.

          Функция 1 прерывания 13H  возвращает  в AL байт, дающий статус
       дискового накопителя. Значение его битов следующее:
          биты 0-1   01 = неверная команда, или, если бит 3 = 1, то
                          попытка обмена данными за границей 64K
                     10 = адресная метка не найдена
                     11 = попытка записи на защищенный от записи диск
                 2   1  = указанный сектор не найден
                 3   1  = переполнение DMA (потеря данных при обмене),
                          или, если бит 0 = 1, то попытка обмена дан-
                          ными за границей 64K
                 4   1  = данные прочитаны неверно, надо повторить
                 5   1  = ошибка контроллера
                 6   1  = ошибка операции поиска
                 7   1  = нет ответа от накопителя (тайм-аут)

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

          1      Неверный номер функции
          2      Файл не найден
          3      Путь не найден
          4      Уже открыто максимально допустимое число файлов
          5      Отрицание доступа (ошибка оборудования)
          6      Неверный номер файла
         15      Указан неверный накопитель
         16      Попытка удалить текущий каталог
         17      Не то же устройство
       


                                     -232-
         18      Больше нет файлов (при поиске  в каталоге с использова-
                 нием джокеров)

          Восстановление после этих "мягких" ошибок несложно.  Некоторые
       предупреждают Вас о программных ошибках.   Другие возникают из-за
       ошибочных действий пользователя.  Если же не отвечает сам накопи-
       тель, то произошла критическая ошибка. В разделе [7.2.5] показано
       как написать процедуру обработки критических ошибок.
          В MS DOS 3.0 введены расширенные коды ошибок.  Они могут  быть
       получены с помощью функции 59H прерывания 21H, когда флаг перено-
       са индицирует возникновение ошибки.  Обсуждение этого вопроса см.
       в [7.2.5].

                                Глава 6. Принтер.

          Раздел 1. Управление работой принтера.

          MS DOS может работать с тремя параллельными устройствами (LPT1
       - LPT3) и в этой главе показано как управлять ими. Последователь-
       ные  принтеры управляются в точности так же, как и  параллельные,
       за исключением способа, которым данные посылаются на принтер; эта
       информация  приведена в разделе 1 главы 7.   Каждое  параллельное
       устройство имеет свой адаптер.  Адаптер управляется тремя регист-
       рами  ввода/вывода  и адреса портов этих регистров  различны  для
       каждого адаптера. Область данных BIOS содержит базовые адреса для
       каждого  адаптера.  Базовый адрес соответствует  младшему  адресу
       группы  из  трех  адресов  портов.   Базовый  адрес  для  LPT1  -
       0040:0008,  для  LPT2 - 0040:000A и т.д.  Какой адаптер  назначен
       какому номеру LPT - не определено , как видно из  нижеприведенной
       таблицы. По этой  причине  программа,  котрая  прямо адресуется в
       параллельный  порт, должна выискивать адреса, которые он  исполь-
       зует. Отметим, что при инициализации базовому адресу присваивает-
       ся значение 0, когда соответствующий адаптер не установлен.

          Адаптер                  Выходных данных  Статуса  Управления

       Монохромная карта (PC/XT/AT)       3BCH        3BDH      3BEH

       Адаптер принтера PC/XT
       Адаптер принтера PCJr              378H        379H      37AH
       Последовательная/параллельная
       карта AT (установленная как LPT1)

       Последовательная/параллельная      278H        279H      27AH
       карта AT (установленная как LPT2)

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

       Регистр управления
          бит  0    0 = нормальная установка, 1 = вызывает  вывод  байта
                        данных
               1    0 = нормальная установка, 1 = автоматический перевод
                        строки после возврата каретки
       


                                     -233-
               2    0 = инициализировать порт принтера, 1 = нормальная
                        установка
               3    0 = отмена выбора принтера, 1 = нормальная установка
               4    0 = прерывание принтера запрещено, 1 = разрешено
             5-7    не используются

       Регистр статуса
          бит 0-2   не используются
                3   0 = ошибка принтера, 1 = нет ошибки
                4   0 = принтер off-line, 1 = принтер on-line
                5   0 = бумага вставлена, 1 = нет бумаги
                6   0 = принтер подтверждает прием символа, 1 = нормаль-
                        ная установка
                7   0 = принтер занят, 1 = принтер свободен

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

          6.1.1  Инициализация  порта  принтера/повторная  инициализация
       принтера.

          Программы должны  инициализировать порт каждого принтера (LPT1
       -  LPT3)  перед первым использованием принтера.   Порты  принтера
       должны также повторно инициализироваться  после устранения причин
       ошибки  принтера.  Не путайте инициализацию порта принтера с ини-
       циализацией самого принтера.  Инициализация принтера это внутрен-
       нее  дело принтера.  Она происходит автоматически при его включе-
       нии и в большинстве случаев  принтер  не может быть повторно ини-
       циализирован без его выключения и повторного включения.  Но прог-
       рамма может повторно инициализировать  принтер, в том смысле, что
       могут  быть  восстановлены начальные параметры,  которые  принтер
       использует для печати, отменяя  все  специальные шрифты, остановы
       табуляции  и т.д.  Считается правилом хорошего  тона  производить
       такой сброс принтера, когда программа завершает работу с ним.
          Языки высокого уровня  инициализируют  порт принтера автомати-
       чески,  но  программы на языке ассемблера требуют для  этой  цели
       короткую процедуру.  С другой  стороны,  восстановление начальных
       параметров печати требуется во всех программах. Некоторые принте-
       ры, такие как  новые  Эпсоновские  принтеры,  имеют  "главный код
       сброса", который приводит к полному сбросу принтера. Но поскольку
       не все принтеры имеют такой код, то программа должна предусматри-
       вать  в  своей завершающей части восстановление  всех  измененных
       параметров.  Например, она может  подать коды выключения курсива,
       выключения плотной печати и т.д.  Не забудьте включить вызов этой
       процедуры в процедуру выхода по Ctrl-Break.
          Имейте в виду, что на многих  принтерах  символы не печатаются
       до  тех  пор, пока не получен код возврата  каретки,  завершающий
       строку (или до тех пор  пока  не  введена  целая  строка данных).
       Символы  могут  спокойно  ожидать в буфере принтера,  даже  после
       того, как породившая их  программа  завершилась. Когда начинается
       новая  передача данных на принтер, то эти символы будут напечата-
       ны.  Чтобы избежать этой  проблемы,  не забывайте почистить буфер
       перед началом печати; а в качестве правил хорошего тона,  чистите
       буфер также при завершении  программы.   Это делается посылкой на
       принтер кода ASCII 24 (при этом параметры печати не меняются).

       


                                     -234-
          Средний уровень.

          Функция  1 прерывания 17H BIOS инициализирует порт принтера  и
       возвращает байт, дающий статус порта.  Поместите в DX номер порта
       -  число от 0 до 2 для LPT1 - LPT3, после чего вызовите  прерыва-
       ние.  Байт статуса принтера  (идентичный  обсуждаемому в [6.1.2])
       возвращается в AH.

       ;---инициализация LPT1
          MOV  AH,1       ;функция инициализации принтера
          MOV  DX,0       ;LPT1
          INT  17H        ;проводим инициализацию

          Низкий уровень.

          Ренистр  управления  выводом каждого адаптера  принтера  имеет
       бит, который вызывает инициализацию  адаптера. Этот регистр имеет
       адрес порта на 2 больше, чем базовый адрес адаптера.  Напоминаем,
       что базовый адрес для LPT1  хранится в ячейке 0040:0008, для LPT2
       -  в 0040:000A и т.д.  Имеют значение только младшие 5 битов  ре-
       гистра управления выводом.  Бит  2 - бит инициализации принтера и
       обычно  он устанавливается в 1.  Для инициализации адаптера  надо
       сбросить этот бит в 0 на тысячу тактов пустого цикла (3000 для AT
       или  на  1/20  секунды,  используя  счетчик  времени  суток  BIOS
       [2.1.5]).  В этот момент нужно, чтобы был установлен только бит 3
       (принтер  выбран).  Поэтому пошлите в порт значение 12,  сделайте
       задержку, а затем пошлите в порт обычное  (без прерываний) неини-
       циализонное значение, которое равно 8.
          В данном примере инициализируется LPT1:

       ;---инициализируем LPT1
                 MOV  DX,ES:[8]    ;считываем базовый адрес в DX
                 INC  DX           ;прибавляем 2 к базовому адресу
                 INC  DX           ;
                 MOV  AL,12        ;значение для инициализации
                 OUT  DX,AL        ;начинаем инициализацию
       DELAY:    MOV  AX,1000      ;начало пустого цикла
                 DEC  AX           ;уменьшаем счетчик
                 JNZ  DELAY        ;повторяем 1000 раз
                 MOV  AL,8         ;обычное значение для регистра
                 OUT  DX,AL        ;конец инициализации

          6.1.2 Проверка того, что принтер связан с машиной.

          Программа всегда должна проверить, что принтер связан с  маши-
       ной, перед тем, как послать на него вывод.  Легко установить, что
       принтер  не готов, так как бит 3 регистра статуса принтера  уста-
       навливается в 1 в этом случае.   Но намного сложнее точно опреде-
       лить почему принтер не готов: выключен ли он, отменен выбор прин-
       тера или в нем нет бумаги. Это происходит из-за того, что принте-
       ры  разных производителей посылают разные наборы битов в  регистр
       статуса принтера, даже когда  они  находятся в идентичном состоя-
       нии.   Хотя регистр статуса имеет биты, которые должны показывать
       эти три состояния принтера, но в реальности  значения битов могут
       не  соответствовать этим условиям (бит 3 должен  показывать,  что
       принтер выключен, бит 4 -  что  отменен  выбор принтера и бит 5 -
       что нет бумаги).  Нижеприведенные значения возвращаются в регистр
       статуса по стандарту "Эпсон", которому обычно следует IBM:

          Значение         Цепочка битов          Интерпретация

            223             11011111           принтер готов
             87             01010111           принтер не готов
       


                                     -235-
            119             01110111           нет бумаги в принтере
            247             11110111           принтер выключен

          Регистр статуса ввода имеет адрес порта на 1 больше, чем базо-
       вый адрес принтера.  Базовый  адрес  для  LPT1 хранится по адресу
       0040:0008,  для LPT2 - по адресу 0040:000A и т.д.  Имейте в виду,
       что если принтер был выключен,  то  ему требуется некоторое время
       на  инициализацию после включения.  Не начинайте печатать до  тех
       пор, пока регистр статуса ввода не  сообщит, что принтер связан с
       машиной и готов к приему данных.

          Высокий уровень.

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

       100 '''Получаем адрес LPT1 и проверяем готов ли принтер
       110 DEF SEG = &H40              'указываем на область BIOS
       120 PRTRBASE = PEEK(9)+256*PEEK(8)+1  'адрес регистра статуса
       130 IF INP(PRTRBASE) = 223 THEN 180   'если принтер готов
       140 BEEP                        'иначе звонок и проверки
       150 IF INP(PRTRBASE) = 87 THEN LOCATE 1,1: PRINT"Strike the
                                      SELECT key": GOTO 150
       160 IF INP(PRTRBASE) = 247 THEN LOCATE 1,1: PRINT"Turn the
                                       printer on": GOTO 160
       170 IF INP(PRTRBASE) <> 223 THEN 170  'ждем инициализации
       180 '''Теперь принтер on-line -- можно начинать печать
       190 LPRINT Z$

          Средний уровень.

          Для получения байта статуса из  порта  принтера надо использо-
       вать  функцию 2 прерывания 17H.  При входе DX содержит номер  LPT
       (0-2 для LPT1-3). Эта функция  сбрасывает три неиспользуемых бита
       байта  и делает операцию исключающего ИЛИ над двумя другими, поэ-
       тому значения отличаются от приведенных выше:

          Значение         Цепочка битов          Интерпретация

            144             10010000           принтер готов
             24             00011000           принтер не готов
            184             10111000           принтер выключен

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

          Низкий уровень.

          Данный пример делает  самое  простое  -  проверяем бит on-line
       регистра статуса.  Для получения байта статуса используется базо-
       вый адрес LPT1.

       ;---в сегменте
       MESSAGE    DB   'Printer not ready - strike any key when OK$'
       


                                     -236-
       ;---проверка связан ли принтер с машиной (on-line)
          MOV  AX,40H               ;ES указывает на область данных BIOS
          MOV  ES,AX                ;
          MOV  DX,ES:[8]            ;получаем базовый адрес
          INC  DX                   ;смещение для регистра статуса
          IN   AL,DX                ;получаем байт статуса в AL
          TEST AL,1000B             ;проверяем бит 3
          JNZ  GO_AHEAD             ;если принтер on-line, то вперед

       ;---печатаем сообщение об ошибке и ждем нажатия клавиши
          MOV  AH,9                 ;функция вывода строки
          LEA  DX,MESSAGE           ;DS:DX указывают на сообщение
          INT  21H                  ;печатаем сообщение
          MOV  AH,7                 ;функция ожидания ввода
          INT  21H                  ;ожидаем нажатия клавиши (без эха)
       GO_AHEAD:                    ;продолжение программы

          6.1.3  Интерпретация  ошибок принтера и  восстановление  после
       них.

          Проверка ошибок не должна  прекращаться  на том, что Вы убеди-
       лись, что принтер связан с машиной.  Ошибки принтера могут проис-
       ходить в любой момент печати и программа должна быть готова восс-
       тановить ситуацию при сбоях.  Хотя на принтере могут  происходить
       самые разнообразные  ошибки,  только  три  типа ошибок возвращают
       информацию  о себе в компьютер.  Это ошибка "отсутствия  бумаги",
       ошибка "отсутствия связи с  машиной" и общее сообщение "произошла
       ошибка".  Как уже говорилось в [6.1.2], не все принтеры  сообщают
       об этих ошибках одинаковым  образом, но теоретически регистр ста-
       туса ввода использует следующие биты:

          бит 3 = 0 когда произошла ошибка на принтере
          бит 4 = 0 когда принтер не связан с машиной (off-line)
          бит 5 = 1 когда кончилась бумага на принтере

       В  частности,  бит 4 может не использоваться  указанным  образом.
       Регистр статуса ввода имеет адрес порта, который на 1 больше, чем
       базовый адрес принтера. Базовый адрес для LPT1 хранится по адресу
       0040:0008, для LPT2 - по адресу 0040:000A и т.д.
          На низком уровне, когда  программа посылает данные на принтер,
       то она постоянно обращается к биту 7 этого регистра, чтобы прове-
       рить готов ли принтер принять очередной символ. Несложно при этом
       проверить  при этом и бит 3, чтобы узнать о произошедшей  ошибке.
       Если происходит ошибка,  индицируемая битами 4 и 5, то по крайней
       мере бит 3 будет равен 0.  Программа должна постараться проанали-
       зировать ошибку, а затем может  попросить  пользователя исправить
       ситуацию. Отметим, что  функцию  DOS,  которая выводит символы на
       принтер  (функция номер 5 прерывания 21H - см.   [6.3.1]),  можно
       заставить непрерывно  проверять  принтер  на ошибку таймаута пос-
       редством  команды MODE.  Перед загрузкой программы,  использующей
       функцию 5, надо ввести команду  MODE  LPT1: ,,P (еще лучше помес-
       тить  эту  команду  в файл AUTOEXEC.BAT, с тем чтобы  она  всегда
       выполнялась при загрузке системы).
          Все эти ошибки приводят к  тому,  что печать останавливается и
       должны  быть  предприняты какие-то действия прежде чем она  будет
       продолжена. Слишком огорчительно для пользователя программы, если
       большая порция документа должна будет печататься заново при  воз-
       никновении ошибки на принтере.  Тщательное продумывание процедуры
       восстановления  по ошибке позволит программе возобновить печать с
       начала той страницы,  на  которой  произошла  ошибка.  Необходимо
       всегда запоминать указатель  выводимых  данных  при начале печати
       новой  страницы.  При начале работы процедуры восстановления  она
       может попросить пользователя  вставить новый лист бумаги, а затем
       


                                     -237-
       продолжить  печать  с начала той страницы, на  которой  произошла
       ошибка.

          Высокий уровень.

          В Бейсике распознаются два ошибочных условия для принтера. Код
       ошибки 24 возвращается когда был отменен выбор принтера, а код 27
       - когда принтер выключен или в нем  отсутствует бумага.  Эти коды
       можно получить с помощью техники обнаружения ошибок,  приведенной
       в [7.2.5]. К  сожалению  эффективно  отлавливается только код 27.
       Чтобы  зарегистрировать  код 24 требуется примерно  полминуты,  в
       течение которых программа  заморожена.   Не слишком полезно прямо
       читать регистр статуса перед каждой операцией печати.  Этот метод
       сработает перед  началом  печати,  но  ничем  не поможет, если во
       время печати произойдет отмена выбора принтера. Приводим процеду-
       ру обработки ошибок принтера:

       100 ON ERROR GOTO 1000     'устанавливаем обработку ошибок
        .
        .
       1000 '''проверяем произошла ли ошибка на принтере
       1010 IF ERR = 24 OR IF ERR = 27 THEN GOSUB 2000: RESUME
        .
        .
       2000 BEEP: LOCATE 1,1: PRINT"Printer not ready"
       2010 PRINT "Strike any key when ready"
       2020 IF INKEY$ = "" THEN 2020     'ожидаем ввода
       2030 RETURN

          Средний уровень.

          Когда функция 0 прерывания 17H  выводит  символ на принтер, то
       она  возвращает байт статуса принтера в AH.  Проверяйте  значение
       этого байта после посылки каждого  символа. BIOS слегка модифици-
       рует  байт статуса.  Обычно бит 0 не имеет значения, но в  данном
       случае  он  устанавливается,  когда  происходит  ошибка  таймаута
       (принтер  не связан с машиной).  В следующем примере  проверяются
       два типа ошибок: общая  ошибка  "принтер  не готов" и ошибка "от-
       сутствия бумаги".  В примере предполагается, что в начале  каждой
       страницы (т.е. после каждого  перевода формата) программа запоми-
       нает указатель на начало выводимых данных, помещая его в перемен-
       ную STARTING_PTR. Это позволяет программе при возникновении ошиб-
       ки  повторить печать с начала страницы, а не с начала всего доку-
       мента. Конечно принтер должен быть повторно инициализирован перед
       повторной печатью и должны быть восстановлены все его  параметры.
       (Данный пример просто иллюстрирует проверку ошибок - он ни в коей
       мере не является рабочей процедурой.)

       ;---в сегменте данных
       MESSAGE1   DB 'Printer off-line - strike any key when ready$'
       MESSAGE2   DB 'Printer out of paper - strike any key when ready$'
       ;---посылаем символ и проверяем на ошибку
       NEXT_CHAR:  MOV  AH,0      ;номер функции
          MOV  DX,0               ;выбираем LPT1
          MOV  AL,[BX]            ;BX указывает на данные
          INC  BX                 ;увеличиваем указатель
          INT  17H                ;посылаем символ на принтер
          TEST AH,00001000B       ;выделяем бит 3 (флаг ошибки)
          JZ   NEXT_CHAR          ;если нет ошибки, то печатаем дальше
          TEST AH,00100000B       ;выделяем бит 5 (отсутствие бумаги)
          JZ   OFF_LINE           ;переход если с бумагой все в порядке
          MOV  AH,9               ;готовим печать сообщения
          LEA  DX,MESSAGE2        ;DS:DX указывает на строку
       


                                     -238-
          INT  21H                ;выводим строку
          JMP  SHORT RECOVER      ;уходим на восстановление
       OFF_LINE:  MOV  AH,9       ;готовим печать сообщения
          LEA  DX,MESSAGE1        ;DS:DX указывают на строку
          INT  21H                ;выводим строку
       RECOVER:   MOV  BX,STARTING_PTR  ;восстанавливаем указатель
          MOV  AH,0               ;функция ожидания ввода
          INT  16H                ;ждем
          CALL PRTR_INIT          ;инициализация принтера
          JMP  NEXT_CHAR          ;начинаем печать с начала страницы

          6.1.4 Переключение между двумя или несколькими принтерами.

          Компьютеры, оснащенные несколькими параллельными портами могут
       иметь одновременно подсоединенными два или более принтеров. Вывод
       может перенаправляться с одного принтера на другой двумя способа-
       ми. Один способ состоит в  том,  чтобы  использовать только такие
       операторы  вывода  на печать, которые указывают на какой  принтер
       надо осуществлять вывод.  Вы  можете  написать такой код, который
       позволит Вам изменять спецификацию.
          Второй  способ переключения принтеров состоит в  использовании
       вывода по умолчанию на LPT1, но указания  другого принтера, кото-
       рый будет использоваться в качестве LPT1. Это достигается измене-
       нием базового  адреса,  относящегося  к  LPT1. Этот базовый адрес
       хранится в области данных BIOS в ячейке 0040:0008.  Поменяйте его
       с базовым адресом для LPT2 или 3 (хранящимися в ячейках 0040:000A
       и 0040:000C) и в качестве LPT1 будет использоваться другой  адап-
       тер.

          Высокий уровень.

          В Бейсике, если принтер был  открыт  оператором OPEN "LPT1" AS
       #1,  то чтобы переключиться на другой принтер надо сначала  напи-
       сать оператор CLOSE #1, а затем открыть  другой принтер с помощью
       оператора OPEN "LPT2" AS #1.  Впоследствии все операторы PRINT #1
       будут направлять свой вывод  на  второй  принтер.   Это изменение
       труднее  осуществить в программах, использующих оператор  LPRINT,
       поскольку LPRINT по умолчанию посылает весь вывод на LPT1. В этом
       случае Вам необходимо поменять базовые адреса принтеров.  Следую-
       щая программа на  Бейсике  делает  именно  это, переключая LPT1 и
       LPT2.   Ее  повторное использование переключает  адреса  обратно,
       возвращая систему к первоначальной конфигурации.

       100 DEF SEG = &H40     'указываем на область данных BIOS
       110 X = PEEK(8)        'получаем младший байт адреса LPT1
       120 Y = PEEK(9)        'получаем старший байт адреса LPT1
       130 POKE 8,PEEK(10)    'переносим младший байт адреса LPT2
       140 POKE 9,PEEK(11)    'переносим старший байт адреса LPT2
       150 POKE 10,X          'посылаем младший байт LPT1 в LPT2
       160 POKE 11,Y          'посылаем старший байт LPT1 в LPT2
       170 SYSTEM             'выходим из Бейсика

          Эта программа будет  очень  кстати,  если  готовое программное
       обеспечение не адресуется к нужному принтеру. Ее можно откомпили-
       ровать и хранить на диске, скажем под именем OTHERPRN, после чего
       надо  будет  только  напечатать ее имя (в ответ на  запрос  DOS),
       чтобы переключиться с принтера на  принтер. Если у Вас нет транс-
       лятора  с Бейсика, то создайте командный файл OTHERPRN.BAT и  по-
       местите в него строку BASIC  OTHERPRN.  Когда  Вы напечатаете OT-
       HERPRN, то будет автоматически загружен Бейсик, который  загрузит
       и  выполнит  программу  OTHERPRN.BAS,  после  чего Вы вернетесь в
       операционную систему.  Необходимо, правда, чтобы на диске  имелся
       интерпретатор Бейсика BASIC.COM.   Помните, что Вы должны устоять
       


                                     -239-
       перед искушением испытать эту программу перед тем, как она  будет
       записана на диск, поскольку  если  Вы ее запустите, то она сотрет
       себя.

          Низкий уровень.

          Один  способ, которым программа на ассемблере  может  изменить
       принтер, на который она посылает  данные, состоит в использовании
       для печати только функции 0 прерывания 17H [6.3.1].  Эта  функция
       требует, чтобы номер принтера был помещен в DX. Заведите перемен-
       ную  для  этого номера, с тем чтобы он мог быть  изменен в  любой
       момент. Вторая возможность  состоит в обмене базовых адресов LPT1
       и LPT2 или LPT3. Следующая программа делает именно это. Как и все
       короткие утилиты, она должна  писаться в COM форме, как объяснено
       в [1.3.6].

       ;---обмен базовыми адресами LPT1 и LPT2
          MOV  AX,40H          ;сегмент области данных BIOS
          MOV  ES,AX           ;ES указывает на данные
          MOV  BX,8            ;смещение для базового адреса LPT1
          MOV  DX,ES:[BX]      ;сохраняем базовый адрес LPT1
          MOV  AX,ES:[BX]+2    ;сохраняем базовый адрес LPT2
          MOV  ES:[BX],AX      ;меняем базовый адрес LPT2
          MOV  ES:[BX]+2,DX    ;меняем базовый адрес LPT1

          Раздел 2. Установка спецификаций печати.

          Для  установки  различных спецификаций, относящихся к  формату
       страницы, стилю шрифта и т.п., на  принтер посылаются специальные
       управляющие  коды.   Эти коды посылаются на принтер  как и  любые
       другие данные. Некоторые из них  это  простые однобайтные коды из
       числа первых 32-х набора кодов ASCII. Эти управляющие коды (пере-
       численные в [7.1.9]) инициируют такие  простые действия принтера,
       как перевод строки или перевод формата (прогон страницы).  Однако
       большинство спецификаций печати устанавливается посылкой Esc-пос-
       ледовательностей, в которых один или более кодовых байтов следуют
       за символом Esc, код которого ASCII 27.  Начальный код Esc инфор-
       мирует  принтер,  что символ(ы) который следует  за  ним  следует
       интерпретировать как команду, а не как данные. Такие Esc-последо-
       вательности обычно не имеют символа-ограничителя, поскольку прин-
       тер "знает" длину каждой последовательности.   Только в некоторых
       случаях, когда последовательность может иметь разную длину,  тре-
       буется ограничивающий символ, в  качестве которого всегда исполь-
       зуется код ASCII 0.
          Почти  во всех случаях спецификации установленные этими кодами
       действуют до тех пор, пока они не будут явно отменены. Как только
       будет  получен  код, например, подчеркивания, то оно  будет  осу-
       ществляться до тех пор, пока не будет послан код отмены подчерки-
       вания.  Буфер принтера может быть очищен без отмены установленных
       спецификаций.  Но если произошла ошибка на принтере и принтер был
       выключен и включен, то необходимо  снова устанавливать все специ-
       фикации.
          Большинство кодов устанавливающих спецификации принтера  пере-
       мешаны с данными, на которые они действуют.  Например, данные для
       слова, которое должно быть выделено жирным шрифтом, должны  пред-
       варяться   Esc-последовательностью,   включающей  жирный шрифт, и
       завершаться  Esc-последовательностью, выключающей его.  Поскольку
       универсальный стандарт на эти  коды  отсутствует, то печать с ис-
       пользованием  мощных возможностей требует, чтобы для каждого под-
       держиваемого принтера  были  написаны  драйверы.  Каждый  драйвер
       преобразует инструкции, генерируеиые процедурой печати, в  прото-
       кол, используемый данным принтером.
          В ассемблере посылка кодов  осуществляется самым обычным обра-
       


                                     -240-
       зом,  но  в Бейсике Вы должны помнить, что операторы,  посылающие
       управляющие коды (LPRINT или PRINT#), должны завершаться точкой с
       запятой.  В противном случае операторы будут автоматически добав-
       лять к посылаемым кодам пару возврат каретки/перевод строки.
          Обсуждения и примеры  последующих страниц в основном относятся
       к графическому принтеру IBM.  Коды, используемые этим  принтером,
       настолько же "стандартны", насколько  и любой другой протокол.  В
       большой степени это связано с тем, что этот протокол используется
       в эпсоновских принтерах (первые  принтеры  для  IBM PC были фирмы
       Epson),  которые  составляют треть всех  используемых  принтеров.
       Управляющие коды,  используемые  принтерами  IBM  сравниваются  в
       разделе  [6.2.7].  Хотя информация, приведенная в данном разделе,
       может быть неприменима к тому  принтеру,  с которым Вы работаете,
       но большинство общих принципов применимо.

          6.2.1 Установка текстового и графического режимов.

          Принтер  всегда находится в текстовом режиме, до тех пор  пока
       он специально не переведен в графический режим. Команда, устанав-
       ливающая  графический  режим, должна сообщать какое число  байтов
       графических данных будет  передано  (но не больше одной строки) и
       после  того, как это число байтов будет интерпретировано как гра-
       фическое изображение, принтер вернется в текстовый режим. По этой
       причине нет команды, которая переводит принтер в текстовый режим.
          Число  графических режимов у разных принтеров разное.  Во всех
       случаях, за  кодом  устанавливающим  графический  режим следуют 2
       байта,  указывающие какое число графических байтов будет передано
       (сначала младший байт).  Чтобы  вычислить значение этих двух бай-
       тов,  разделите число байтов данных на 256 и поместите  результат
       во второй байт, а остаток - в первый байт. За этими двумя байтами
       должны сразу следовать байты данных.
          Каждый  байт определяет цепочку битов, соответствующих  восьми
       вертикальным точкам одной позиции в строке. Младший бит (1) соот-
       ветствует  низу колонки, а старший бит (128) - верху.   Например,
       чтобы напечатать пирамиду, пошлите сначала байт, у которого уста-
       новлен  только  нижний бит, затем байт у которого  установлены  2
       нижних бита и т.д. После восьмого байта расположите те же байты в
       обратном  порядке.   Значение первого байта будет 1, второго -  3
       (1+2), затем 7 (1+2+4), затем 15 (1+2+4+8)  и т.д. На рисунке 6-1
       изображена вся картина.
          Для  печати  пирамиды в Бейсике на  графическом  принтере  IBM
       напишите следующий код:

       100 LPRINT CHR$(27);CHR$(75);CHR$(15);CHR$(0);CHR$(1);CHR$(3);
               CHR$(7);CHR$(15);CHR$(31);CHR$(63);CHR$(127);CHR$(255);
               CHR$(127);CHR$(63);CHR$(31);CHR$(15);CHR$(3);CHR$(1);

       Первые два  байта  переводят  принтер  в  графический режим с 480
       точками,  следующие два - сообщают, что будет передано 15  байтов
       графических данных, а затем  идет  последовательность байтов дан-
       ных. Конечно то же самое можно запрограммировать умнее, организо-
       вав цикл, в котором будут передаваться байты данных. Отметим, что
       все проблемы в этом случае возникают, если указанное число байтов
       не соответствует числу  посылаемых  байтов.  Чтобы создать пробел
       между  графическими фигурами выведите несколько байтов с  нулевым
       значением.  В Бейсике, когда в  одной  строке выводится больше 80
       байтов графических данных, не забудьте предварительно  установить
       "бесконечную"  ширину  принтера.  Для  этого  надо ввести команду
       WIDTH "LPT1:",255.
          Графический  принтер  IBM  имеет четырек  графических  режима,
       которые более или менее "стандартны". Они такие:

       27,75  480 точек в строке. Нормальный режим. Максимум 480 байтов
       


                                     -241-
              данных на оператор.
       27,76  960 точек в строке. Удвоенное горизонтальное разрешение,
              но печать вдвое медленнее (двойная плотность). Максимум
              960 байтов данных на оператор.
       27,89  960 точек в строке, печать с нормальной скоростью (двой-
              ная плотность с высокой скоростью). Две точки, прилегаю-
              щие по горизонтали, не могут быть напечатаны, поскольку
              не будут успевать иголки печатающей головки. Если делается
              попытка их напечатать, то вторая точка будет игнорировать-
              ся. Максимум 960 байтов данных на оператор.
       27,90  1920 точек в строке, печать вдвое медленнее (четверная
              плотность). Соседние точки по горизонтали должны отстоять
              по крайней мере на 3 точки (т.е. 1 печатаем, 2 пропуска-
              ем). Максимум 1920 байтов данных на оператор.

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

         Коды         Графический  Цветной  Компактный  Пропринтер

        27,75          480 точек     1108      560          480
        27,76          960 точек     2216       -           960
        27,89          960 точек     2216       -           960
        27,90         1920 точек     4432       -          1920

          Цветной  принтер уникален из принтеров IBM тем, что  он  может
       устанавливать масштабный коэффициент (aspect ratio) для графичес-
       ких изображений. Этот коэффициент отражает разницу горизонтальных
       и вертикальных расстояний между точками. Обычно желателен коэффи-
       циент  1:1, поскольку в противном случае трудно проводить  графи-
       ческие вычисления. Но  при  копировании  графического экрана надо
       чтобы масштабный коэффициент был таким же, как у дисплея.  В  эк-
       ранном режиме умеренного разрешения 5 точек по вертикали занимают
       тот  же  размер, что 6 точек по горизонтали.   Это  соответствует
       масштабному коэффициенту 5:6 и  именно  это значение используется
       по умолчанию цветным принтером.  Допускаются только  коэффициенты
       1:1 и 5:6.

          6.2.2 Управление расстоянием между строками.

          Если  не  принимать во внимание принтеры, имеющие  специальные
       возможности графопостроителя, то вся  печать осуществляется стро-
       ками.   Даже  графические изображения рисуются построчно, хотя  в
       этом случае нет пустых мест между  строками. Код ASCII 10 - стан-
       дартный  управляющий код перевода строки.  Посылка его на принтер
       (без предшествующего кода Esc) приводит  к тому, что бумага будет
       продвинута  вперед  на указанный интервал.  Обычно, если  перевод
       строки не посылается за  кодом  возврата  каретки,  то печатающая
       головка  возвращается к левому краю бумаги и можно снова печатать
       на той же строке.  Однако можно сделать так, чтобы перевод строки
       делался автоматически при каждом возврате каретки. Этим управляют
       переключатели на принтере. Это  же  можно сделать установив бит 1
       регистра управления выводом (см. [6.1.0]).  Многие принтеры могут
       включать  и  выключать  автоматический  перевод  строки с помощью
       управляющих  кодов 27,53, а некоторые могут делать обратный пере-
       вод строки с пмощью кодов 27,93.
          По умолчанию графический  принтер   использует интервал печати
       равный 1/6 дюйма (т.е.  выводят 6 строк на дюйм) и к этому режиму
       всегда можно вернуться, посылая управляющие  коды 27,50 (эти коды
       


                                     -242-
       используются  также в сочетании с коды изменения интервала  между
       строками, обсуждаемыми ниже).  Для этого принтера имеются еще два
       предопределенных  межстрочных интервала, 1/8 дюйма и 7/72  дюйма.
       Соответствующие им управляющие коды 27,48 и 27,49.
          Возможна и более тонкая градация межстрочных интервалов.  Гра-
       фический принтер использует три кода, позволяющие изменить интер-
       вал на очень малую величину.  Все три управляющих кода используют
       2-хбайтную  Esc-последовательность, за которой следует число 72-х
       или 216-х долей дюйма, определяющих  межстрочный интервал. Верти-
       кальное  расстояние между центрами двух точек равно  1/72  дюйма.
       Интервал 8/72 дюйма  не  оставляет  промежутка  между строками (9
       строк  на  дюйм).  Стандартный интервал 6 строк на дюйм  задается
       числом 12/72 дюйма.  Наконец, 1/216 равна 1/3 от 1/72.  Изменение
       на такую величину позволяет печатающей головке слегка  сдвинуться
       от центра строки, с тем чтобы  точки при втором проходе заполнили
       промежутки, обеспечивая печать более высокого качества.  Вот  эти
       Esc-последовательности:

          Изменение         Esc-последовательность

           72-е дюйма       27,65,n (где n от 1 до 85)
          216-е дюйма       27,51,n (где n от 1 до 255)
          216-е дюйма       27,74,n (где n от 1 до 255)

       Команды для изменения интервала  в 72-х дюйма не станут активными
       до тех пор, пока не встретится второй управляющий код: 27,50. Как
       объяснялось выше, этот  код  может  также использоваться отдельно
       для восстановления стандартного интервала в 1/6 дюйма. Если ранее
       была использована команда 27,65,n, то для восстановления интерва-
       ла в 1/6 дюйма надо послать команду 27,65,12,27,50. Два управляю-
       щих кода для интервалов в 1/216  дюйма  не идентичны.  Первый код
       устанавливает,  что все последующие переводы строки будут  выпол-
       няться с указанным интервалом; второй же действует только на один
       перевод  строки, а затем возвращает интервал, который  действовал
       до этого.
          Следующая таблица сравнивает межстрочные интервалы, вызываемые
       одними и теми же управляющими кодами на различных принтерах IBM:

       Коды  Матричный  Графический  Цветной  Компактный  Струйный  Ромашка    Про-
              принтер      принтер   принтер   принтер     принтер            принтер

       27,48   1/8         1/8        1/8        1/9        1/8       1/8      1/8
       27,49   7/72        7/72       6/72       1/9                  9/96     7/72
       27,50   1/6         1/6        1/6        1/6        1/6       1/6      1/6
       27,51               n/216      n/144                                   n/216
       27,65   n/72        n/72       n/72                            n/72    n/72
       27,74               n/216      n/144                                   n/216

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

          6.2.3 Управление движением бумаги.

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


                                     -243-
       на начало страницы и считает число  переводов строки. Можно прог-
       раммно переопределить установку переключателей, посылая на  прин-
       тер управляющие коды 27,56,  чтобы принтер не делал пропуска пер-
       форации и 27,57, чтобы делал пропуск перфорации.
          Графический  принтер использует код,  который определяют число
       строк, пропускаемых между  страницами.  Этот код 27,78,n, где n -
       число строк от 1 до 127.  Например, код 27,78,10 приведет к тому,
       что принтер будет пропускать по 10 строк. Если межстрочный интер-
       вал равен 1/6 дюйма, то 11-тидюймовая страница будет содержать 66
       строк и после печати  каждых  56-ти  строк  принтер  будет делать
       пропуск  10-ти  строк.  Уже Ваша программа  должна  позаботиться,
       чтобы в самом начале прогнать  бумагу  на 5 строк, с тем чтобы 55
       строк текста были центрированы на каждой странице.
          Если  используется бумага, размер которой отличается от  стан-
       дартного 11-тидюймового, то можно  изменить длину страницы, с тем
       чтобы  пропуски  перфорации происходили в  нужном  месте и  чтобы
       перевод формата устанавливал бумагу в правильную позицию.  Размер
       страницы  может  устанавливаться либо числом строк  на  странице,
       либо размером в дюймах. Чтобы установить число строк на странице,
       пошлите  код 27,67,n, где n - число строк.  Та же  последователь-
       ность используется и  для  установки  длины страницы в дюймах, за
       исключением  того,  что длина страницы записывается в форме  0,n,
       где n может быть от 1 до 22 дюймов. Для стандартной страницы надо
       послать команду 27,67,0,11.

          6.2.4 Управление положением печатающей головки.

          Печатаемый  текст распределяется по странице частично за  счет
       движения бумаги [6.2.3], а  частично  за счет движения печатающей
       головки.  Головка может быть позиционирована в любое место, но не
       путем задания ее координат. Вместо этого указывается ее смещение,
       относительно самой левой позиции, которую она может достигать.  У
       принтера нет датчиков, сообщающих текущее положение головки. Ваша
       программа  должна отслеживать положение головки, если оно  должно
       быть известным. При  этом  хорошей  практикокй  является начинать
       печать с подачи управляющего кода 27,60, который сдвигает головку
       в самую левую позицию,  не  делая  перевода  строки  (то же самое
       делает и код возврата каретки).
          При  печати текста имеется несколько способов передвинуть  го-
       ловку в нужное положение.   Она  может  сдвигаться вправо подачей
       одного  или  нескольких  символво пробела или  табуляции и  влево
       подачей одного или нескольких символов  "возврат на шаг" или сим-
       вола  возврата каретки.  Движения осуществляются непрерывно -  не
       воспринимайте их как  соответствующие последовательности на обыч-
       ной  пишущей машинке.  До тех пор, пока Ваша программа знает  на-
       чальное положение печатающей головки  она может комбинацией пере-
       водов  строки, пробелов, табуляций и возвратов на шаг  форматиро-
       вать Ваш вывод в  соответствии  с  Вашими  пожеланиями. Принтеры,
       которые  умеют выполнять обратный пеервод строки могут  использо-
       ваться и как графопостроители.
          В графических  режимах  возможно  перемещение головки на малые
       доли дюйма.  При печати текста Вы можете войти в графический  ре-
       жим, чтобы добиться разных промежутков  между словами.  К сожале-
       нию, этот процесс существенно замедляет печать. Смотрите пример в
       пункте [6.3.2].
          Имеется специальный  код,  который  заставляет  головку всегда
       возвращаться  в  крайнюю  левую позицию перед  печатью  очередной
       строки,  отменяя  двунаправленную  печать.  Хотя  это значительно
       замедляет печать, однако при этом достигается более точное  пози-
       ционирование головки. Это особенно полезно при работе в графичес-
       ком режиме.  Чтобы включить однонаправленную печать надо  послать
       код 27,85,1, а чтобы  вернуться  к  двунаправленной  печати - код
       27,85,0.
       


                                     -244-
          6.2.5 Установка позиций табуляции.

          В зависимости от принтера могут устанавливаться позиции  гори-
       зонтальной и вертикальной  табуляции  (графический принтер IBM не
       имеет  вертикальной табуляции).  Горизонтальные табуляции опреде-
       ляются, как смещения от  левого  края,  выраженные  в пробелах. В
       некоторых случаях допускаются до 112 позиций горизонтальной табу-
       ляции. Аналогично, вертикальные табуляции определяются как смеще-
       ния  относительно верха страницы, а измеряются они в  межстрочных
       интервалах. Для большинства  принтеров  IBM допускается не больше
       64-х позиций вертикальных табуляций.
          Первые  два байта кода для установки горизонтальной  табуляции
       27,68, а для установки вертикальной  табуляции - 27,66. Для обоих
       типов табуляций далее идет строка байтов, дающая позиции  табуля-
       ции в возрастающем порядке. Эта  строка должна завершаться байтом
       ASCII 0, который служит ограничителем. Для установки горизонталь-
       ной табуляции в позициях 15, 30  и  60 пошлите на принтер код 27,
       68, 15, 30, 60, 0. Для установки вертикальной табуляции в строках
       8 и 12 - пошлите код 27, 66, 8,  12,  0. Отметим, что если размер
       страницы  отличается  от стандартных 11-ти дюймов, то  он  должен
       быть установлен перед установкой  позиций вертикальной табуляции.
       Вертикальная табуляция отменяется кодом 27,67.
          Отметим,  что  большинство принтеров не имеют установки  полей
       как таковой.  Левое поле может создаваться за счет вывода табуля-
       ции или ряда пробелов в начале каждой строки. Для точной установ-
       ки полей  перейдите  в  графический  режим  и выведите ряд байтов
       ASCII 0.  Правое поле создается просто за счет ограничения  длины
       строки.

          6.2.6 Изменение шрифта печати.

          Ширина страницы 8 1/2 дюйма  позволяет  напечатать в строке до
       80-ти  обычных  символов, если все они имеют  одинаковую  ширину.
       Пропорциональная печать [6.3.3] позволяет  поместить в строке еще
       несколько  символов.   С другой стороны, плотная печать позволяет
       вывести в строке 132 символа, печать  с двойной шириной - 40 сим-
       волов,  а плотная печать с двойной шириной - 64 символа.   Имейте
       ввиду, что использование печати  с  разной шириной в одной строке
       приведет к трудностям с форматированием.
          Большинство  матричных  принтеров предоставляют набор  режимов
       печати специальными шрифтами. Вот перечень стандартных возможнос-
       тей предоставляемых графическим принтером IBM:
       Плотная печать:
          Для  включения режима плотной печати надо послать  однобайтный
       управляющий код 15.  Для выключения этого режима - код 18.  Стан-
       дартная  страница  шириной 8 1/2 дюйма позволяет  напечатать  132
       символа в строке в этом режиме.
       Печать с двойной шириной:
          Для того, чтобы принтер начал печатать  с двойной шириной надо
       послать на него управляющий код 14.  Режим печати с двойной шири-
       ной необычен тем, что принтер автоматически выключает этот режим,
       когда  встречает  символ  возврата каретки или  перевода  строки.
       Поскольку такой вид печати  обычно  используется для однострочных
       заголовков, то это свойство удобно.  Чтобы выключить этот режим в
       середине строки пошлите код 20.
       Выделенная печать:
          При выделенной печати  каждый  символ  печатается  два  раза в
       одной  и  той же позиции.  Это делает точки темнее,  что  создает
       эффект выделения. Скорость печати при этом уменьшается вдвое. Для
       включения этого режима пошлите код 27,69. Для выключения - 27,70.
       Печать за два прохода:
          В  режиме  печати  за два прохода бумага сдвигается  на  1/216
       дюйма перед вторым проходом  печатающей  головки.  При этом полу-
       


                                     -245-
       чаются более заполненные буквы, которые к тому же выглядят  ярче.
       Скорость печати уменьшается вдвое.   Этот режим включается управ-
       ляющим кодом 27,71, а выключается кодом 27,72.
       Печать с подчеркиванием:
          Печать  с  подчеркиванием может выполняться  двумя  способами.
       Графический принтер имеет режим подчеркивания,  в котором подчерк
       печатается под каждым символом, включая пробелы. Для графического
       принтера IBM этот режим  включается  кодом 27,45,1, а выключается
       кодом  27,45,0.  Принтеры, не имеющие режима подчеркивания  могут
       сделать подчерки при  втором  проходе  по  той же строке, печатая
       символы  подчеркивания  (ASCII 95) в тех местах, где оно нужно  и
       пробелы (ASCII 32) во  всех  остальных  позцициях.  Второй проход
       достигается  тем, что после первого прохода подается код возврата
       каретки без кода перевода строки. Второй проход не мешает принте-
       ру правильно подсчитывать строки при вычислении размера страницы.
       Печать с верхними и нижними индексами:
          На графических принтерах текст с верхними и нижними  индексами
       сжимается вертикально. Для печати верхнего индекса пошлите управ-
       ляющий код 27,83,0, а для печати нижнего - 27,83,1.  Можно  прямо
       переходить от одних индексов  к  другим.   Для  выключения печати
       индексов, с тем, чтобы принтер оказался на текущей строке пошлите
       управляющий код 27,84.
          Некоторые режимы не могут использоваться в комбинации с други-
       ми. Если Вы хотите использовать 4 режима одновременно, то прокон-
       сультируйтесь со следующей таблицей.   В каждом из шести столбцов
       приведена допустимая комбинация.





























          Комбинация           1  2  3  4  5  6

          нормальный           Х  Х
          сжатый                     Х  Х
          выделенный                       Х  Х
          за два прохода       Х     Х     Х
          с индексами             Х     Х     Х
          двойной ширины       Х  Х  Х  Х  Х  Х
       


                                     -246-
          с подчеркиванием     Х  Х  Х  Х  Х  Х

          6.2.7 Сравнение возможностей принтеров IBM.

          В  следующей таблице сравниваются управляющие коды для принте-
       ров IBM.  Не вся информация относительно кодов точна (обращайтесь
       к  документации  IBM), а в ряде случаев уникальные коды  опущены.
       Целью настоящей  таблицы  является  показ  диапазона возможностей
       принтеров и указание тех кодов, которые можно считать стандартны-
       мия. Отметим, что коды для  первых  четырех принтеров приведены в
       выпуске "Возможности и адаптеры" (Options and Adapters) из  серии
       технических руководств, а коды  для остальных принтеров приведены
       в сопровождающих их руководств по эксплуатации.


       Код     Функция                          П  р  и  н  т  е  р  ы
                                           Матр.Граф.Цвет.Комп.Стр.Ром.Пропр.
       Перемещение бумаги:
       10     перевод строки                  Х    Х    Х    Х   Х   Х   Х
       11     вертикальная табуляция          Х         Х    Х   Х   Х   Х
       12     перевод формата                 Х    Х    Х    Х   Х   Х   Х
       13     возврат каретки                 Х    Х    Х    Х   Х   Х   Х
       27,52  установка начала страницы                 Х            Х   Х
       27,56  игнорировать отсутствие бумаги  Х    Х
       27,57  отмена игнор. отсутствия бумаги Х    Х
       27,66  установка вертикальных таб-ций  Х         Х    Х   Х       Х
       27,66  очистка вертикальных таб-ций                               Х
       27,88  установка пропуска перфорации        Х    Х    Х   Х       Х
       27,79  отмена пропуска перфорации           Х    Х    Х   Х       Х

       Перемещение печатающей головки:
       8      возврат на шаг                            Х        Х   Х   Х
       9      горизонтальная табуляция        Х    Х    Х    Х   Х   Х   Х
       27,60  сдвиг головки в левый конец     Х    Х    Х
       27,62  установка индекса горизонталь-                     Х
              ного движения
       27,68  установка горизонт. таб-ции     Х    Х    Х    Х   Х   Х   Х
       27,68  очистка горизонт. таб-ции                                  Х
       27,77  автоматическое форматирование             Х
       27,80  вкл./выкл. пропорц. печати           Х             Х
       27,82  восстан. таб-ций по умолчанию             Х    Х       Х   Х
       27,85  вкл./выкл. однонапр. печати          Х    Х
       27,88  установка левого/правого поля             Х            Х
       27,100 программируемый пробел                    Х
       27,101 программируемый возврат на шаг            Х

       Межстрочные и межсимвольные интервалы:
       27,48  межстрочный интервал 1/8 дюйма  Х    Х    Х        Х   Х   Х
       27,48  межстрочный интервал 1/9 дюйма                 Х
       27,48  межстрочный интервал 7/72 дюйма Х    Х
       27,49  межстрочный интервал 7/72 дюйма                            Х
       27,49  межстрочный интервал 9/96 дюйма                        Х
       27,49  межстрочный интервал 6/72 дюйма           Х
       27,49  межстрочный интервал 1/9 дюйма                 Х
       27,50  начать программируемый пере-    Х    Х    Х
              вод строки по 27,65
       27,50  межстрочный интервал 1/6 дюйма  Х    Х    Х    Х   Х   Х   Х
       27,51  программируемый перевод              Х                     Х
              строки (n/216)
       27,51  программируемый перевод                   Х
              строки (n/144)
       27,53  вкл./выкл. автоматич. пере-               Х    Х   Х   Х   Х
              вода строки
       


                                     -247-
       27,65  программируемый перевод         Х    Х    Х            Х   Х
              строки (n/72)
       27,67  установка длины страницы        Х    Х    Х    Х   Х   Х   Х
       27,74  программируемый перевод              Х                     Х
              строки (n/216)
       27,74  программируемый перевод                   Х
              строки (n/144)
       27,93  обратный перевод строки                                Х
       27,104 перевод на пол-строки вперед                           Х
       27,105 перевод на пол-строки назад                            Х

       Управление шрифтами:
       11     режим 15 символов на дюйм                              Х
       14     включение режима двойной ширины Х    Х    Х    Х   Х       Х
       15     включение плотной печати        Х    Х    Х    Х   Х       Х
       18     выключение плотной печати       Х    Х         Х   Х       Х
       18     режим 10 символов на дюйм                 Х        Х   Х
       20     выключ. режима двойной ширины   Х    Х    Х    Х   Х       Х
       27,45  вкл./выкл. подчеркивания             Х    Х    Х   Х   Х   Х
       27,58  режим 12 символов на дюйм                 Х            Х   Х
       27,69  включение жирной печати         Х    Х    Х                Х
       27,70  выключение жирной печати        Х    Х    Х                Х
       27,71  включение печати в 2 прохода    Х    Х    Х        Х       Х
       27,72  выключение печати в 2 прохода   Х    Х    Х        Х       Х
       27,83  включение печати индексов            Х    Х        Х   Х   Х
       27,84  выключение печати индексов           Х    Х        Х   Х   Х
       27,87  вкл./выкл. печати двойной            Х    Х    Х   Х       Х
              с шириной
       27,91  включение цветного подчеркив.                      Х
       27,95  вкл./выкл. overscore                                       Х

       Установка специальных шрифтов и цветов:
       27,54  выбор набора символов 2              Х    Х        Х   Х   Х
       27,55  выбор набора символов 1              Х    Х        Х   Х   Х
       27,61  загрузка шрифта                                        Х   Х
       27,73  изменение качества печати                 Х        Х       Х
       27,92  печатать управляющие символы              Х            Х   Х
       27,94  печатать все символы                      Х            Х   Х
       27,97  сдвиг ленты в конце страницы              Х
       27,98  выбор 4-й полосы ленты                    Х
       27,99  выбор 3-й полосы ленты                    Х
       27,109 выбор 2-й полосы ленты                    Х
       27,121 выбор 1-й полосы ленты                    Х

       Графические режимы:
       27,75  установка режима 480 точек           Х                     Х
       27,75  установка режима 560 точек                     Х
       27,75  установка режима 1108 точек               Х
       27,76  установка режима 960 точек           Х                     Х
       27,76  установка режима 2216 точек               Х
       27,89  установка режима 960 точек           Х                     Х
              с нормальной скоростью
       27,89  установка режима 2216 точек               Х
       27,90  установка режима 1920 точек          Х                     Х
       27,90  установка режима 4432 точек               Х
       27,91  установка разрешения/цвета                         Х
       27,110 установка масштабного коэф-нта            Х        Х

       Другие возможности:
       7      звонок                          Х    Х    Х            Х   Х
       20     выключ. режима двойной ширины   Х    Х    Х    Х   Х       Х
       17     выбор принтера                  Х         Х        Х   Х   Х
       19     отмена выбора принтера          Х         Х        Х   Х
       


                                     -248-
       24     очистка буфера                  Х    Х    Х    Х   Х   Х   Х
       27,81  отмена выбора указанного                       Х           Х
               принтера

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

          6.3.1 Вывод текстовых или графических данных на принтер.

          Процессор может заниматься  только  посылкой данных на принтер
       или  он  может печатать в фоновом режиме, за  счет  использования
       прерывания принтера. Возможна и  третья альтернатива, когда прог-
       рамма  посылает символы на принтер через определенные  интервалы,
       что можно рассматривать как "псевдопрерывание". Этот метод не так
       тесно  координируется с работой принтера, как настоящее  прерыва-
       ние, но во всяком случае работа принтера не критична ко времени.
          Независимо от того как выводятся данные, каждый раз на принтер
       посылается только 1 байт данных. Языки высокого уровня предостав-
       ляют функции, которые вроде бы выводят сразу целые строки, однако
       на  самом деле эти функции разбивают строки на отдельные символы.
       Обычно языки высокого  уровня  посылают  на  принтер пару возврат
       каретки/перевод строки в конце каждой строки.  С другой  стороны,
       программы на ассемблере  должны  сами  добавлять  эту пару кодов.
       Из-за этого приходится немного больше программировать, но  взамен
       Вы получаете намного большую  гибкость, особенно в отношении про-
       верки ошибок.

          Высокий уровень.

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


                                     -249-
       LPRINT и PRINT#. LPRINT  не  требует  никакой  подготовки, но для
       вывода оператором PRINT# Вы должны предварительно открыть принтер
       в точности так же как и файл, с помощью  оператора OPEN "LPT1" AS
       #1  или  OPEN "LPT3" AS #2.  Оператор LPRINT всегда адресуется  к
       LPT1, в то время как PRINT# может адресоваться к любому принтеру.
          Пара возврат каретки/перевод строки  автоматически добавляется
       в  конце  любого оператора LPRINT или PRINT#, если только  он  не
       завершается точкой с запятой.   Для  избежания ненужных переводов
       строки  не  забывайте завершать посылки любых  управляющих  кодов
       точкой с запятой.  То же самое надо делать, если Вы хотите, чтобы
       строки текста печатались подряд, прилегающие одна к другой. Одна-
       ко имейте ввиду, что многие  принтеры не начинают печатать до тех
       пор, пока они не получат данные для целой строки. Это определяет-
       ся либо символом возврат  каретки, либо тем, что число переданных
       символов достигло 80-ти (или другого числа). Не забывайте послать
       завершающий  код  возврата  каретки,  чтобы  вытолкнуть последнюю
       порцию символов из буфера принтера.
          Принтер  автоматически переходит на слдеующую строку по дости-
       жению конца строки.  По умолчанию размер строки принтера равен 80
       символам,  но у широких принтеров это значение может быть больше.
       Строки, выводимые в  режимах  плотной  печати  или печати двойной
       ширины, также меняют длину строки.  Для изменения номера столбца,
       по достижении которого  головка  принтера  перейдет  на следующую
       строку,  можно установить ширину принтера командой WIDTH "LPT1",n
       - где n требуемый номер  столбца.  Когда печатается строка, длина
       которой  больше или равна ширине принтера, то печатающая  головка
       переходит на следующую строку,  что эквивалентно выполнению кодов
       возврат каретки/перевод строки. Это означает, что в случае, когда
       длина строки в точности равна  ширине  принтера, то будет сделано
       два  перевода  строки, если эта строка завершается,  как  обычно,
       парой возврат каретки/перевод строки.
          При графической печати принтер обычно  устанавливают на беско-
       нечную  ширину.  Чтобы сделать это, надо подать команду установки
       ширины, равной 255, WIDTH  "LPT1",255.  Если Вы забудете включить
       эту  команду, то при выводе длинных последовательностей графичес-
       ких данных Бейсик будет  вставлять  пару  возврат каретки/перевод
       строки  после  каждых 80 байтов данных.  Эти  добавочные  символы
       будут включаться в  общее  число  байтов  данных  для графической
       печати,  поэтому  конец передаваемых данных будет просто  опущен.
          Один оператор LPRINT может содержать несколько  элементов дан-
       ных в различных видах.  Информация может содержаться в самом опе-
       раторе,  как  например в LPRINT "The rain in Spain", или  на  нее
       можно ссылаться по имени  переменной, как в случае X$ = "The rain
       in  Spain":  LPRINT X$.  Специальные символы могут включаться  за
       счет использования функции  CHR$.   Управляющие коды обычно посы-
       лаются  именно этим способом, например, LPRINT CHR$(10)  посылает
       на принтер управляющий код перевода  строки.  Чаще всего CHR$ ис-
       пользуется при посылке кодов ASCII, которые нельзя ввести с  кла-
       виатуры. Любые из перечисленных типов данных могут быть объедине-
       ны  в одном операторе.  Если Вы хотите, чтобы различные  элементы
       данных печатались подряд, то разделяйте их точкой с запятой; если
       же Вы разделите их запятыми, то следующий элемент будет выводить-
       ся со следующей позиции табуляции.  Это говорит о том, что опера-
       тор  LPRINT форматирует печать в точности так же, как это  делает
       оператор PRINT при выводе на экран. Вот несколько примеров:

       100 LPRINT S$;" and ";Y$     'комбинация трех строк
       110 LPRINT X, Y, Z           'вывод трех чисел
       120 LPRINT "The total is "; X  'комбинация строки и числа
       130 LPRINT "The ";CHR$(27);CHR$(45);CHR$(1);"real";
                   CHR$(27);CHR$(45);CHR$(0);" thing."
                   'подчеркивание среднего слова

       


                                     -250-
          Оператор PRINT# может  использовать  те  же типы данных, что и
       оператор LPRINT, и он также позволяет включать несколько  элемен-
       тов данных в один оператор  и  смешивать  различные  типы данных.
       Точки  с запятой и запятые действуют в нем  аналогичным  образом.
       Вот примеры, эквивалентные вышеприведенным:

       100 OPEN "LPT1:" AS #2
       110 PRINT #2,S$;" and ";Y$
       120 PRINT #2,X, Y, Z
       130 PRINT #2,"The total is "; X
       140 PRINT #2,"The ";CHR$(27);CHR$(45);CHR$(1);"real";
                   CHR$(27);CHR$(45);CHR$(0);" thing."

          Средний уровень.

          Функция 0  прерывания  17H  посылает  один  символ на принтер.
       Поместите  символ в AL, а номер принтера в DX.  При  возврате  AH
       будет содержать регистр статуса, который надо постоянно проверять
       для обнаружения ошибок. В [6.1.3] объясняется как это делать. Для
       вывода потока данных  установите  указатель  на буфер, содержащий
       данные, и напишите процедуру типа следующей:

       ;---вывод данных на LPT1
          MOV  CX,NUMBER_CHARS    ;CX содержит число байт для вывода
          MOV  DX,0               ;выбираем LPT1
       NEXT_CHAR:  MOV  AH,0      ;функция посылки символа на принтер
          MOV  AL,[BX]            ;BX указывает на буфер данных
          INT  17H                ;посылаем символ
          TEST AH,8               ;проверяем бит ошибки
          JNZ  PRNTR_ERROR        ;на обработку ошибки
          INC  BX                 ;увеличиваем указатель
          LOOP NEXT_CHAR          ;выводим следующий символ

          Стандартное  прерывание MS DOS для вывода на принтер это функ-
       ция 5 прерывания 21H.  Просто  поместите  символ в DL и выполните
       прерывание.  Эта функция всегда выводит на LPT1 и у нее нет возв-
       ращаемых регистров.

       ;---вывод данных на LPT1
          MOV  AH,5       ;номер функции
          MOV  DL,CHAR    ;готовим печатаемый символ
          INT  21H        ;посылаем его на принтер

          Другой способ вывода данных на  принтер это функция 40H преры-
       вания  21H.   Это функция стандартного  вывода, с  использованием
       метода дескриптора файлов  для  доступа  к  файлу  или устройству
       [5.3.0].  В данном случае эта функция использует специальный пре-
       допределенный номер файла для принтера.  Этот номер #4 и его надо
       поместить  в BX.  Функция имеет доступ только к LPT1, поэтому для
       вывода на другой  принтер  Вам   надо   поменять  базовые  адреса
       [6.1.4].  DS:DX должны указывать на выводимые данные, а CX содер-
       жать число посылаемых байтов. Например:

       ;---вывод 120 байтов данных на LPT1
          MOV  AH,40H       ;номер функции
          MOV  BX,4         ;номер файла для принтера
          MOV  CX,120       ;число посылаемых байтов
          LEA  DX,PRTR_DATA ;DS:DX указывают на данные
          INT  21H          ;посылаем данные
          JC   PRTR_ERROR   ;на обработку ошибки

       При возврате установленный флаг  переноса  индицирует ошибку, при
       этом AX будет содержать 5, если принтер не связан с машиной и 6 -
       


                                     -251-
       если указан неверный номер файла.  Отметим, что при использовании
       предопределенного номера файла ненужно открывать устройство.

          Низкий уровень.

          Байт данных посылается на принтер, путем посылки его в регистр
       выводимых данных, адрес порта которого  совпадает с базовым адре-
       сом принтера.  Помните, что базовые адреса для LPT1-3 хранятся со
       смещениями 8, 10 и  12  в  области  данных  BIOS  (начинающейся с
       0040:0000).   После того как данные посланы в регистр на короткое
       время включается бит строба  регистра  управления  выводом, адрес
       порта  которого на 2 больше, чем для регистра данных.  Номер бита
       строба равен 0 и он должен быть установлен только на очень корот-
       кое  время,  чтобы инициировать передачу  данных,  находящихся  в
       регистре данных. Процедура  печати  может немедленно сбросить бит
       строба обратно в 0.
          После  того как байт данных послан, программа должна  ожидать,
       пока принтер не сообщит, что он  готов  к  приему следующего. Это
       делается  двумя способами.  При готовности принтер дает импульс в
       бит подтверждения регистра статуса ввода, адрес порта которого на
       1 больше базового адреса принтера. Номер бита подтверждения равен
       6 и обычно он установлен в 1.   Импульс  подтверждения сбрасывает
       этот бит в 0 на достаточно долгое время, чтобы программа на языке
       ассемблера могла увидеть это,  если  она  постоянно следит за ре-
       гистром.
          Другой  способ узнать, что принтер готов к  приему  следующего
       байта данных состоит в  непрерывной проверке бита 7 регистра ста-
       туса,  который сбрасывается в 0, когда принтер занят и устанавли-
       вается в 1, когда он готов принять  данные. Если Вы пишите проце-
       дуру печати низкого уровня, которая должна работать в интерпрети-
       руемом Бейсике или другом очень медленном  языке, то надо исполь-
       зовать этот метод.
          Следующий пример получает базовый адрес LPT1 из области данных
       BIOS и затем выводит данные из  буфера,  на который указывает ре-
       гистр BX.  Программа постоянно проверяет регистр статуса на заня-
       тость и одновременно  проверяет  бит  3,  чтобы проверить наличие
       ошибки на принтере.

       ;---подготовка
          MOV  AX,40H          ;ES указывает на область данных BIOS
          MOV  ES,AX           ;
          MOV  DX,ES:[8]       ;базовый адрес LPT1 в DX
          MOV  BX,DATA_START   ;BX указывает на буфер данных
       ;---посылаем символ
       NEXTCHAR:  MOV  AL,[BX]  ;помещаем символ в AL
          OUT  DX,AL           ;посылаем символ
          INC  DX              ;DX будет указывать на регистр
          INC  DX              ;управления выводом
          MOV  AL,13           ;цепочка битов для импульса строба
          OUT  DX,AL           ;посылаем сигнал строба
          DEC  AL              ;нормальное ссотояние регистра
          OUT  DX,AL           ;посылаем его
       ;---проверка на ошибку и ожидание готовности принтера
          DEC  DX              ;DX указывает на регистр статуса
       NOT_YET:  IN   AL,DX    ;получаем байт статуса
          TEST AL,8            ;ошибка?
          JNZ  PRTR_ERROR      ;переход на обработку ошибки
          TEST AL,80H          ;принтер занят?
          JZ   NOT_YET         ;если занят, то назад
          INC  BX              ;увеличиваем указатель в буфере данных
          DEC  DX              ;DX указывает на регистр данных
          JMP  NEXTCHAR        ;идем на печать следующего символа

       


                                     -252-
          Когда  установлен  бит  4 управляющего регистра  принтера,  то
       разрешено прерывание принтера.  Когда используется прерывание, то
       программа  не должна ожидать сигнала готовности от принтера, неп-
       рерывно опрашивая регистр  статуса  принтера. Вместо этого, прог-
       рамма  может  послать символ и заниматься другими  делами;  когда
       принтер будет готов для приема  следующего  символа, то он пошлет
       сигнал  подтверждения (бит 6 регистра статуса на  короткое  время
       будет установлен в 1)  и  автоматически  будет вызвано прерывание
       принтера.  Процедура обработки прерывания пошлет на принтер  сле-
       дующий символ и  вернет  управление  в программу, чтобы она могла
       продолжать  свою работу, до тех пор пока не произойдет следующего
       прерывания. Когда все данные будут выведены, то прерывание должно
       отключить себя. Прерывание принтера во многом аналогично коммуни-
       кационному прерыванию, которое обсуждается в [7.1.8].
          К сожалению, оборудование сделано так, что Вы не всегда можете
       полагаться  на  это свойство для первого адаптера  принтера.   На
       некоторых адапторах оно работает, а на других нет.  Только в слу-
       чае последовательной/параллельной карты AT Вы может полагаться на
       него полностью.  Вместо него можно использовать прерывание тайме-
       ра, как объяснено в [2.1.7].  Установите микросхему таймера  8253
       так,  чтобы  прерывание  происходило  медленнее,  чем скорость, с
       которой  принтер обрабатывает данные.  Затем  напишите  процедуру
       обработки прерывания, которая  посылает на принтер очередной сим-
       вол  каждый раз, когда происходит прерывание времени суток.   Для
       того чтобы обеспечить надежную синхронизацию  заставьте процедуру
       проверять бит занятости принтера регистра статуса (бит 7) и  если
       принтер еще занят, то пусть процедура не посылает символ.

          6.3.2 Выравнивание правого поля.

          Реальное выравнивание правого поля заключается в распределении
       пробелов, находящихся в конце  строки,  равномерно по промежуткам
       между словами.  Некоторые принтеры имеют специальный режим, кото-
       рый автоматически осуществляет  это  выравнивание.  Такую возмож-
       ность  имеет  цветной  принтер  IBM,  посылка  управляющего  кода
       27,77,0 заставляет электронику  принтера  интерпретировать посту-
       пающие данные и форматировать их. В других случаях принтер должен
       менять ширину пробелов между  словами, переключаясь в графический
       режим,  когда  выводится символ пробела.  В  графических  режимах
       ширина пробела может изменяться на размер до 1/6 размера символа.
       К  сожалению, многие принтеры на некоторое время  останавливаются
       при переключении между текстовым и  графическим режимами, поэтому
       такой  метод  может оказаться слишком медленным.   Другой  подход
       состоит во вставке обычных  символов  пробела, распределяя их как
       можно более равномерно по строке.  Более сложный графический под-
       ход описан ниже.
          Шаги, которые необходимо выполнить для форматирования с вырав-
       ниванием правого поля, следующие. Во-первых, из установок формата
       страницы должно быть вычислено  число  символов  в строке.  Затем
       необходимо подсчитать число символов, занимаемое каждым из после-
       довательно введенных слов, включая пробелы между словами. Отдель-
       ный счетчик должен подсчитывать число пробелов. Когда общая сумма
       символов превзойдет 80 (или ту ширину  принтера, которая установ-
       лена),  то последнее слово должно быть отброшено из  этой  суммы,
       вместе с предшествующим ему пробелом. Число оставшихся свободными
       позиций  в строке умножается на 6, поскольку каждый символ  зани-
       мает размер шести  точек  по  горизонтали,  а  получившееся число
       делится на число пробелов между словами.
          После печати каждого слова принтер устанавливается в графичес-
       кий режим 480 точек в  строке  и  посылает  на  принтер ряд кодов
       ASCII  0.  Каждый такой байт сдвигает печатающую головку на  одну
       точку вправо.  Посылаемое число должно быть равно шести для обыч-
       ного  пробела, плюс результат распределения пустого пространства.
       


                                     -253-
       Наконец, если остаток от  деления  ненулевой, то надо добавить по
       одному добавочному байту к первым пробелам, до тех пор пока оста-
       ток не будет исчерпан.
          Для  примера  рассмотрим  случай,  когда  строка состоит из 12
       слов,  содержащих 61 букву, плюс 11 пробелов между словами.   Это
       оставляет в 80-тисимвольной  строке  8 свободных позиций. Эти во-
       семь позиций, умноженные на 6 точек, составляют 48 точек дополни-
       тельного пространства строки.  Поскольку в строке 11 пробелов, то
       к  каждому из них должно быть добавлено по 4 дополнительные точки
       и после этого останутся еще 4 лишние точки, которые надо добавить
       по одной к первым четырем пробелам.  Тогда первые 4 пробела будут
       иметь размер 6 точек нормального  пробела, плюс добавочные 5, что
       в сумме равно 11.  Остальные пробелы этой строки будут иметь раз-
       мер 10 точек.  Чтобы послать эти  данные  на принтер, подготовьте
       сначала код, посылающий на принтер 1 байт ASCII 0, а затем помес-
       тите его в цикл, который  выполняйте  столько  раз, сколько нужно
       послать таких байтов. На рис. 6-2 показан этот процесс.
          В  нижеприведенном  примере показаны  основы  выравнивания  по
       правому полю. Не забудьте об обработке специальных случаев, таких
       как  слово,  которое  длиннее строки (напр., длинный  ряд  тире).
       Процедура нуждается в модификации,  которая позволяла бы ей иметь
       дело со случаем, когда строка содержит всего несколько слов,  как
       это бывает в конце параграфа.  Не  позволяйте ей распределить эти
       слова равномерно по всей ширине страницы.

          Высокий уровень.

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

       100 S$ = "This text will be printed with right justification
        using the printer alternately in text modes and graphics modes."
       110 STRINGPTR = 1         'указатель в строке данных S$
       120 COLUMNS = 1           'счетчик позиции в строке
       130 SPACES = 0            'счетчик пробелов в строке
       140 '''вычисляем сколько слов помещается в строке
       150 C$ = MID$(S$,STRINGPTR,1)  'получаем символ
       160 IF C$ <> " " THEN 190  'если не пробел, то вперед
       170 LASTSPACE = COLUMNS   'иначе зпоминаем позицию
       180 SPACES = SPACES + 1   'увеличиваем число пробелов
       190 COLUMNS = COLUMNS+1   'увеличиваем указатель столбца
       200 STRINGPTR = STRINGPTR + 1  'увеличиваем указатель данных
       210 IF COLUMNS = 81 THEN 230   'уход по концу строки
       220 GOTO 150              'иначе к следующему символу
       230 IF C$ <> " " THEN 270 'если последний не пробел, то уход
       240 COLUMNS = 79          'иначе длина строки равна 79
       250 SPACES = SPACES - 1   'отнимаем последний пробел
       260 GOTO 340              'идем на вычисление пробелов
       270 C$ = MID$(S$,STRINGPTR+1,1)  'проверяем на конец слова
       280 IF C$ <> " " THEN 300 'если не пробел, то уход
       290 GOTO 340              'иначе на вычисление пробелов
       300 COLUMNS = COLUMNS - LASTSPACE  'возвращаемся к концу слова
       310 STRINGPTR = STRINGPTR - COLUMNS + 1  'возвращаем указатель
       320 COLUMNS = LASTSPACE - 1  'убираем последний пробел
       330 SPACES = SPACES - 1   'уменьшаем число пробелов
       340 '''вычисляем число точек на пробел
       350 EXTRASPACES = 80 - COLUMNS  'вычисляем число пробелов в конце
       360 TOTALSPACES = EXTRASPACES + SPACES   'добавляем к пробелам
       370 TOTALDOTS = 6*TOTALSPACES   'вычисляем число точек
       380 DOTSPERSPC = TOTALDOTS/SPACES 'получаем число точек на пробел
       390 EXTRADOTS = TOTALDOTS MOD SPACES  'остаток от деления
       400 '''теперь печатаем первую строчку нашего текста
       410 OPEN "LPT1:" AS #1    'открываем принтер
       


                                     -254-
       420 PRINTPTR = 1          'указатель на начало буфера данных
       430 C$ = MID$(S$,PRINTPTR,1)  'берем символ
       440 PRINTPTR = PRINTPTR + 1   'увеличиваем указатель
       450 IF C$ = " " THEN 500  'если пробел, то на обработку пробела
       460 PRINT #1, C$          'иначе печатаем символ
       470 IF PRINTPTR = COLUMNS + 1 THEN 590  'выход по концу строки
       480 GOTO 430              'иначе печатаем следующий символ
       490 '''вот процедура печати пробелов
       500 PRINT #1, CHR$(27) + "K";  'переход в графический режим
       510 NUMBERDOTS = DOTSPERSPC  'вычисляем число байтов ASCII 0
       520 IF EXTRADOTS = 0 THEN 550  'если нет добавочных точек, уход
       530 NUMBERDOTS = DOTSPERSPC + 1  'иначе добавляем точку
       540 EXTRADOTS = EXTRADOTS - 1  'уменьшаем число добавочных точек
       550 PRINT #1, CHR$(NUMBERDOTS);  'посылаем число графических байт
       560 PRINT #1, CHR$(0);    '
       570 FOR N = 1 TO NUMBERDOTS: PRINT #1, CHR$(0): NEXT
       580 GOTO 430              'пробел окончен, идем на след. символ
       590 PRINT #1, CHR$(13)    'в конце печатаем возврат каретки

          Низкий уровень.

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

          6.3.3 Пропорциональная печать.

          Вообще  говоря,  пропорциональная печать требует  специального
       принтера, который хранит в ПЗУ информацию о ширине каждого симво-
       ла.   Цветной  принтер IBM имеет режим  пропорциональной  печати,
       который включается  последовательностью  27,78,1, а выключается -
       27,78,0.   Программа, которая форматирует вывод на такой принтер,
       должна знать информацию о ширине каждого  символа (ее можно найти
       в документации). Имея эту информацию, она может вычислить сколько
       слов поместится на одной строке.
          Имейте ввиду, что некоторые  матричные  принтеры автоматически
       выводят  пропорциональный  текст в режиме за два  прохода.   Если
       слова в строке разделяются  добавочными  пробелами  в графическом
       режиме,  то  принтер может переходить ко  второму  проходу  после
       печати каждого  слова,  вместо  того,  чтобы  повторить сразу всю
       строку. Поскольку принтеры относительно медленно меняют направле-
       ние перемещения печатающей головки, то в этом случае печать текс-
       та, выравненного по правому краю, в пропорциональном режиме может
       занимать очень много времени и  оказывается непосильной ношей для
       принтера.  Эта проблема не возникает при однонаправленной пропор-
       циональной печати. Отметим, что цветной принтер IBM может автома-
       тически  комбинировать пропорциональную  печать с  автоматическим
       выравниванием правого края, что делает специальное программирова-
       ние ненужным.
          Изощренные  программисты  могут  заставить  любой  графический
       принтер печатать  в  пропорциональном  режиме.  Программа  должна
       иметь  в памяти картину битов для каждого символа (см.  [6.3.4]).
       Вместо того, чтобы посылать на  принтер  код ASCII, который вызы-
       вает  изображение  символа  из ПЗУ, используется  данная  цепочка
       битов для создания графического изображения  строки текста. Затем
       вся нужная строка данных выводится на принтер в графическом режи-
       ме.  Этот подход расходует много  памяти  на хранение графических
       образов  символов, однако он позволяет  полностью  контролировать
       выводимое изображение.

       


                                     -255-
          Высокий уровень.

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

       100 '''считываем массив ширин шрифта
       110 DIM FONTWIDTH(127)     'отводим массив для ширин
       120 OPEN "FONTS" FOR INPUT AS #1  'открываем файл ширин
       130 FOR N = 32 TO 127      'хранятся ширины для кодов 32-127
       140 INPUT #1, FONTWIDTH(N) 'читаем ширину из массива
       150 NEXT                   'следующий элемент
       160 '''вычисляем сколько символов поместится в строке
       170 CHARPTR = 0            'указатель в буфере
       180 LINE$ = ""             'хранит строку для вывода
       190 LINELENGTH = 0         'счетчик длины в точках
       200 WHILE LINELENGTH <480  'добавляем до заполнения строки
       210 C$ = PEEK(BUFFERPTR+CHARPTR)  'берем символ из буфера данных
       220 LINELENGTH = LINELENGTH + FONTWIDTH(ASC(C$))
       230 LINE$ = LINE$+C$       'добавляем к строке вывода
       240 CHARPTR = CHARPTR+1    'увеличиваем указатель
       250 WEND                   'на обработку следующего символа
       260 '''по концу строки возвращаемся к концу последнего слова
       270 IF C$ = "" THEN 310    'если последний пробел, то уход
       280 FOR N = LEN(LINE$) TO 1 STEP -1  'идем назад от конца
       290 IF MID$(LINE$,N,1) = " " THEN 310 'этот символ пробел?
       300 NEXT                   'есчли нет, то берем следующий
       310 LINELENGTH = N - 1     'если да, то предыдущий - последний
       320 '''инициализируем пропорциональную печать и посылаем данные
       330 LPRINT CHR$(27);CHR$(78);CHR$(1);  'управляющие коды
       340 FOR N = 1 TO LINELENGTH  'для каждого символа
       350 LPRINT PEEK(BUFFERPTR+N-1);  'печатаем его
       360 NEXT                   'и идем на следующий символ

          Низкий уровень.

          Программа на языке ассемблера должна работать совершенно  ана-
       логично приведенному бейсиковскому  примеру.  Одно из преимуществ
       ассемблера состоит в том, что для просмотра ширин символов  можно
       использовать инструкцию XLAT. Поместите символ в AL, DS:DX должны
       указывать на таблицу, после чего можно использовать XLAT.  Ширина
       символа будет возвращена в AL:

       ;---просмотр ширин символов
          LEA  SI,DATA_BUFFER     ;указываем на буфер данных
          LEA  BX,WIDTH_TABLE     ;указываем на таблицу ширин
          MOV  AL,[SI]            ;получаем байт данных
          XLAT WIDTH_TABLE        ;теперь его ширина в AL

          6.3.4 Печать специальных символов.

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


                                     -256-
       щих  символ.   Например, чтобы напечатать  символ  горизонтальной
       двойной черты, код ASCII  которого  205, программа должна вывести
       цепочку битов 00100100 шесть раз в режиме 480 точек в строке. Это
       количество в  точности  соответствует  ширине  символа, поскольку
       6/480  равно 1/80 строки.  Чтобы перевести принтер именно в  этот
       графический режим необходимо подать управляющий код 27,75.  Затем
       пошлите число идущих вслед графических данных, которое передается
       в виде пары байт, причем младший байт  первый. Наконец, идут сами
       6  битов  данных, которые в данном случае  равны  сумме  значений
       битов 2 и 5 (4 + 32 = 36). Вся  последовательность целиком выгля-
       дит так: 27, 75, 6, 0, 36, 36, 36, 36, 36, 36. Для более высокого
       разрешения могут быть использованы более точные графические режи-
       мы;  вообще говоря добавочные расходы времени машины ничтожны, по
       сравнению со скоростью операций принтера.
          Имеется  частная проблема, когда символы псевдографики  должны
       соприкасаться друг с другом по вертикали.   Обычно принтеры печа-
       тают строку, состоящую из столбца восьми точек, затем  спускаются
       вниз на высоту 12  точек,  оставляя  тем  самым поле размером в 4
       точки между строками символов. Символы псевдографики должны печа-
       таться и в этом поле, а в некоторых случаях они занимают в высоту
       12 точек. Поскольку большинство печатающих головок имеет только 8
       иголок, то единственным  решением  проблемы является печать таких
       символов  за  два прохода, продвигая бумагу вперед  перед  вторым
       проходом. В этом случае символ  перевода строки (ASCII 10) вообще
       не используется.  Вместо этого, принтер попеременно делает интер-
       валы высотой то в 8, то в 4 точки.  При втором проходе часть иго-
       лок будут на том месте, где уже имеются отпечатанные точки,  поэ-
       тому надо чтобы биты для этих иголок были сброшены в 0, чтобы они
       не работали.
          Чтобы  продвинуть бумагу на высоту четырех точек надо  послать
       код 27, 65, 4, 27, 50, а на  высоту восьми точек - 27, 65, 8, 27,
       50.   При  этом вызывается автоматический возврат каретки.  В  то
       время когда выполняется первый проход, готовится временная строка
       текста, которая будет печататься при втором проходе.  Если данный
       символ обычный, то  в  соответствующую  позицию  временной второй
       строки  символов  надо  поместить  пробел (ASCII  32).   Но  если
       встречается специальный  графический символ, который должен печа-
       таться  в четырехточечном поле, то надо поместить его код ASCII в
       соответствующюю позицию второй строки. Например:

       Позиция символа   1    2    3    4    5    6    7    8    9   10

       Код ASCII        205  32   98   111  114  105  110  103  32   205
       Код 2-й строки   205  32   32   32   32   32   32   32   32   205

       В памяти должна храниться  отдельная  таблица  цепочек битов этих
       символов  для  второго прохода.  Для двойной  вертикальной  черты
       содержимое таблицы для первого  прохода  будет 0, 255, 0, 255, 0,
       0,  а для второго - 0, 15, 0, 15, 0, 0.  Отметим, что во втором и
       четвертом байте для  второго  прохода  верхние  4  бита сброшены,
       чтобы не было надпечатки.
          Короче,  когда начинается печать, то в первую  очередь  прове-
       ряется является ли данный символ пседографическим, и если нет, то
       он  посылается  на печать, как обычный код ASCII.   Во  временную
       строку, используемую для печати второго  прохода вставляется про-
       бел.   Затем обрабатывается следующий символ.  Когда  встречается
       символ псевдографики, то 6 кодирующих  его байтов берутся из таб-
       лицы, принтер переводится в графический режим для вывода 6 байтов
       и посылаются данные.  Затем  принтер автоматически возвращается в
       текстовый  режим.  В соответствующую позицию строки  для  второго
       прохода помещается код ASCII  этого  символа псевдографики.  Этот
       процесс  продолжается до конца строки, после чего делается прогон
       бумаги на высоту четырех точек.  При повторном проходе надо опять
       


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

          Высокий уровень.

          В данном примере текст разделен  на две колонки, при этом неп-
       рерывная  линия разделяет страницу посредине.  Для простоты печа-
       тается только одна строка,  однако  этот  пример может печатать и
       целую страницу, если вставить цикл FOR/NEXT в строках 325 и  505.
       Для демонстрации двух подходов  при  первом проходе печатается по
       одному  символу,  в то время как при  втором  проходе  печатается
       целая строка.

       100 '''таблица данных для первого прохода (только коды 178 и 179)
       110 DATA 0, 0, 255, 0, 0, 0
       120 DATA 4, 4, 255, 0, 0, 0
       130 '''аналогичная таблица для второго прохода
       140 DATA 0, 0, 15, 0, 0, 0
       150 DATA 0, 0, 15, 0, 0, 0
       160 '''помещаем первую таблицу в массив
       170 DIM FIRSTPASS$(45)       'описываем массив
       180 FOR N = 1 TO 2           'заполняем его
       190 Y$ = ""                  'Y$ хранит 6 байтов на символ
       200 FOR M=1 TO 6: READ X: Y$ = Y$+CHR$(X): NEXT
       210 FIRSTPASS$(N) = Y$: NEXT 'помещаем в массив
       220 '''помещаем в массив вторую таблицу
       230 DIM SECONDPASS$(45)      '
       240 FOR N = 1 TO 2           '
       250 Y$ = ""                  '
       260 FOR M=1 TO 6: READ X: Y$ = Y$+CHR$(X): NEXT
       270 SECONDPASS$(N) = Y$: NEXT '
       280 '''печатаем текст следующей строки
       290 TEXT$ = "Here is one column"+CHR$(179)+"Here is the
                    second column"
       300 TEMP$ = STRING$(80,32)   'создаем строку для 2-го прохода
       310 GRAPH$ = CHR$(27)+CHR$(75)+CHR$(6)+CHR$(0)
       320 OPEN "LPT1:" AS #1       'открываем принтер
       330 FOR N = 1 TO LEN(TEXT$)  'для каждого символа текста
       340 C$ = MID$(TEXT$,N,1)     'берем символ и проверяем его
       350 IF C$ < CHR$(128) THEN PRINT #1,C$;: GOTO 400
       360 '''предполагаем, что все остальные символы - псевдографика
       370 PRINT #1,GRAPH$;         'входим в графический режим
       380 PRINT #1,FIRSTPASS$(ASC(C$) - 178);выводим 1-й проход
       390 MID$(TEMP$,N) = C$       'маркер в строке 2-го прохода
       400 NEXT
       410 '''смещаемся на 8 точек вниз и делаем второй проход
       420 PRINT #1,CHR$(27)+CHR$(65)+CHR$(4)+CHR$(141);
       430 Z$ = ""                  'Z$ содержит строку для 2-го прохода
       440 FOR N = 1 TO LEN(TEXT$)  'для каждого символа текста
       450 C$ = MID$(TEMP$,N,1)     'берем символ и обрабатываем его
       460 IF C$ = CHR$(32) THEN Z$ = Z$+" ": GOTO 480
       470 Z$ = Z$+GRAPH$+SECONDPASS$(ASC(C$) - 178)
       480 NEXT
       490 PRINT #1,Z$              'печатаем всю строку сразу
       500 PRINT #1,CHR$(10);       'добавляем в конце перевод строки

          Низкий уровень.
       


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

          6.3.5 Копирование экрана на принтер (дамп экрана).

          Дамп текстового  экрана  сделать  достаточно  просто, если все
       используемые  символы содержатся в ПЗУ принтера и ни один из  них
       не выводится со специальными атрибутами, такими как подчеркивание
       или  негативное изображение.  В этом простейшем случае  программе
       нужно лишь установить ширину принтера равной 80 символам, а затем
       считывать  символы поочередно из видеобуфера, посылая их как неп-
       рерывный поток данных на принтер. Если в ПЗУ принтера отсутствуют
       специальные символы, такие как символы псевдографики, то програм-
       ма должна подготовить  свою  таблицу  данных  для этих символов и
       выводить их на принтер в графическом режиме. Поскольку эти симво-
       лы могут заходить в межстрочные интервалы, то может потребоваться
       специальное программирование [6.3.4].
          Каждый  из специальных атрибутов символов создает свои пробле-
       мы.  Проверяйте атрибут  каждого  символа  при  считывании его из
       видеобуфера (в [4.1.3] обсуждается значение битов,  соответствую-
       щее различным атрибутам). Когда  символ выделен с помощью подчер-
       кивания  или повышенной интенсивности, то надо включать подчерки-
       вание или печать жирным шрифтом  на  принтере. Однако если символ
       выводится  в негативном изображении, то возникают те же проблемы,
       что и с некоторыми графическими  символами:  область  негативного
       изображения должна простираться до верхнего края следующей  стро-
       ки. В этом случае надо следуя  указаниям [6.3.4] заполнить черным
       всю  область при втором проходе.  В зависимости от принтера,  Вам
       может понадобиться создать специальную  таблицу данных для вывода
       символов  в  негативе, поскольку когда они будут  печататься,  то
       окружающие точки могут  находиться  слишком близко одна к другой,
       затемняя изображаемый символ. В  этом случае не может быть и речи
       о  печати в два прохода.  Простым решением проблемы с  негативным
       изображением является  использование  графического  режима экрана
       для вывода текста, а затем сделать дамп графического экрана.
          Графические дампы создают свои проблемы.  Байт данных принтера
       соответствует восьми вертикальным точкам, в то время как на экра-
       не  байт представляет 8 горизонтальных точек.  Поэтому  требуется
       процедура  преобразования,  показанная  на  рис.  6-4. Надо сразу
       получать по 8 байтов памяти экрана, выбирая такие, которые  соот-
       ветствуют области точек 8*8.  Затем  надо использовать логические
       операции для перестановки битов, как показано в примерах.
          Имейте  ввиду,  что большинство матричных  принтеров  искажают
       экранное изображение.  Это  происходит потому, что они используют
       масштабный коэффициент 1:1, в то время как экран использует коэф-
       фициент 5:6 (масштабный коэффициент сравнивает число горизонталь-
       ных  точек на дюйм с числом вертикальных точек на дюйм).   Точнее
       говоря, искажение изображения на самом деле возникает из-за масш-
       табного коэффициента экрана, поскольку программы должны специаль-
       но менять данные для  изображения,  чтобы  оно выглядело так, как
       нам хочется (например, изображение окружности на экране создается
       выводом на него  эллипса).   Когда  данные  с экрана выводятся на
       принтер, то эти искажение должны обращаться.  Некоторые графичес-
       кие принтеры имеют специальные  режимы,  в которых можно выводить
       копию  экрана без искажения, а цветной принтер IBM  может  менять
       масштабный коэффициент в любом из своих графических режимов.

          Высокий уровень.
       


                                     -259-
          Приводимая процедура на  Бейсике делает копию текстового экра-
       на, игнорируя специальные атрибуты:

        10 OPEN "LPT1:" AS #1          'открываем принтер
        20 DEF SEG = &HB000            'указываем на видеобуфер
        30 PRINT #1,CHR$(13)           'сдвигаем головку влево
        40 FOR G = 0 TO 3998 STEP 2    'для каждого байта буфера
        50 PRINT #1,CHR$(PEEK(G));     'читаем его и выводим на принтер
        60 NEXT                        'обрабатываем следующий байт

       Переброска цепочек битов для графического дампа требует в Бейсике
       слишком много времени.  Поместите  в массив (здесь, BYTE$) восемь
       байтов,  отвечающих  области экрана 8*8 точек.   Создайте  второй
       массив (VERTICAL$) и  обнулите  его  элементы, а затем поочередно
       перебрасывайте биты элементов этих массивов следующим образом:

       500 FOR M = 0 TO 7       'для каждого бита
       510 FOR N = 0 TO 7       'для каждого байта
       520 X = ASC(BYTES(N))    'получаем значение байта
       530 Y = 2*(7 - M)        'маска для одного включенного бита
       540 Z = X AND Y          'проверка этого бита в байте
       550 IF Z <> 0 THEN VERTICAL$(M) = CHR$(ASC(VERTICAL$(M) OR 2*N)
                                'если он включен, то устанавливаем бит
                                'в соответствующей позиции 2-го массива
       560 NEXT N               'следующий бит
       570 NEXT M               'следующий байт

          Низкий уровень.

          Язык ассемблера делает битовые преобразования намного быстрее.
       Вот процедура, которая делает  эти  преобразования ужасно быстро,
       поскольку она держит все в микропроцессоре (она немного великова-
       та, но Вы можете использовать взамен  алгоритм, показанный в Бей-
       сике).   Процедура работает, храня 8 результирующих байтов в  ре-
       гистрах CX, DX, BP и DI. Байт  экранных данных помещается в AL, а
       затем в AH передвигаются последовательно CL, CH, DL и DH.  Каждый
       раз из AL в AH сдвигается один  бит  и когда сделаны 4 сдвига, то
       CX  и  DX обмениваются с DX и BP, после чего все это  повторяется
       снова.  Этот  процесс  повторяется  для  каждого из 8-ми экранных
       байтов  и когда он завершен, то преобразованное изображение  хра-
       нится в регистрах микропроцессора, причем самый левый байт данных
       для  печати  в CL.  Содержимое регистров выводится на  принтер  и
       обнуляется, после чего процесс  повторяется  для следующих восьми
       байтов экрана.  Сначала получите 8 байтов из видеобуфера и помес-
       тите их в буфер с именем BUFFER.   Поместите 0 в AX, CX, DX, BP и
       DI. Затем:

          LEA  BX,BUFFER    ;указываем на буфер видеоданных
          MOV  SI,0         ;смещение в этом буфере
       GET_BYTE:  MOV  AL,[BX][SI]     ;берем байт
       DO_HALF:   XCNG AH,CL           ;получаем CL, CH, DL и DH
          SHL  AX,1         ;сдвигая бит из AL
          XCNG AH,CL        ;
          XCNG AH,CH        ;
          SHL  AX,1         ;
          XCNG AH,CH        ;
          XCNG AH,DL        ;
          SHL  AX,1         ;
          XCNG AH,DL        ;
          XCNG AH,DH        ;
          SHL  AX,1         ;
          XCNG AH,DH        ;

       


                                     -260-
       ;---начинаем вторую половину перемещения битов
          XCNG CX,BP        ;обмениваем содержимое CX и DX
          XCNG DX,DI        ;
          CMP  SI,7         ;если все байты преобразованы, то печатаем
          JE   PRINT_BYTES  ;
          INC  SI           ;иначе переходим к следующему байту
          JMP  SHORT GET_BYTE    ;
       ;---печатаем байты
       PRINT_BYTES:   PUSH DX    ;сохроаняем DX
          MOV  AH,5         ;функция вывода на принтер
          MOV  DL,27        ;код Esc
          INT  21H          ;посылаем его
          MOV  DL,75        ;код графического режима
          INT  21H          ;посылаем его
          MOV  DL,6         ;будет послано 6 байтов
          INT  21H          ;
          MOV  DL,0         ;
          INT  21H          ;
          CALL PRINT_2_BYTES  ;посылаем содержимое CX
          POP  CX           ;
          CALL PRINT_2_BYTES  ;посылаем содержимое DX
          MOV  CX,BP        ;
          CALL PRINT_2_BYTES  ;посылаем содержимое BP
          MOV  DX,DI        ;
          CALL PRINT_2_BYTES  ;посылаем содержимое DI
           .
          (идем к следующей группе из восьми байтов)
           .
       PRINT_2_BYTES:    PROC  NEAR
          MOV  AH,5         ;функция печати
          MOV  DL,CL        ;сначала CL
          INT  21H          ;печатаем
          MOV  DL,CH        ;затем CH
          INT  21H          ;печатаем
          RET
       PRINT_2_BYTES     ENDP

                               Глава 7. Ввод/вывод.

          Раздел 1. Доступ к последовательному порту.

          При  асинхронной  связи  машина посылает или  принимает  байты
       информации по одному биту.  Временные интервалы между байтами при
       этом  несущественны,  но  времена между отдельными  битами  байта
       очень важны.  Сигнал на  линии  может  быть  высокого или низкого
       уровня,  что соответствует логическим нулю и единице, и  говорят,
       что линия отмечена  (marking),  когда  уровень  высокий, и пустая
       (spacing), когда уровень низкий.
          Линия  поддерживается в отмеченном состоянии, когда по ней нет
       передачи данных. При начале передачи байта данных сигнал падает в
       0,  отмечая  стартовый  бит.  Затем следуют восемь  битов  данных
       (иногда меньше) в виде набора высоких и низких уровней. Последний
       бит данных может сопровождаться битом четности, используемым  для
       обнаружения ошибок, а затем в последовательность включаются 1 или
       более  стоп-битов,  которым соответствует высокий  уровень.   Эти
       стоп-биты начинают отмеченное состояние, которое будет сохранять-
       ся до тех пор, пока не начнется передача следующего байта данных;
       число используемых стоп-битов существенно, поскольку они устанав-
       ливают  минимальное время, которое должно пройти перед  следующим
       стартовым битом. На рис. 7-1 показана эта последовательность.
          Конечно, передающая  и  приемная  станции  должны использовать
       один  и тот же протокол для этих цепочек битов и они должны рабо-
       тать с одной и той  же  скоростью  обмена  (измеряемой  в битах в
       


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

          7.1.1 Программирование микросхемы UART 8250.

          Последовательная  связь настолько сложна, что были разработаны
       специальные  микросхемы,  выполняющие  работу  по  формированию и
       синхронизации строк битов, составляющих последовательные  данные.
       Такие  микросхемы  называют  универсальным  асинхронным  приемни-
       ком-передатчиком (universal asynchronous receiver transmitter или
       UART). IBM PC использует UART 8250 фирмы Intel.
          Операционная система поддерживает 2 порта коммуникации, поэто-
       му  в машине имеются 2 микросхемы.  Их базовые адреса хранятся  в
       ячейке 0040:0000 для COM1 и  0040:0002  для  COM2. (Базовый адрес
       это  2-хбайтовый адрес порта, который является младшим из  группы
       адресов портов, дающих доступ к UART.) На всех машинах кроме PCjr
       COM1  имеет  базовый адрес 3F8H, а COM2 - 2F8H; PCjr  имеет  свой
       внутренний модем по  адресу  3F8H,  а  COM1 - по адресу 3F8H. Для
       удобства,  мы в дальнейшем будем всегда нумеровать регистры 3FxH,
       но все сказанное в равной степени применимо и к регистрам 2FxH.
          Микросхема 8250 имеет 10  программируемых  однобайтных регист-
       ров, с помощью которых управляется и контролируется порт коммуни-
       кации. Большинство из них  занимаются  инициализацией порта, про-
       цессом,  который может быть очень сложным.  Доступ к этим 10  ре-
       гистрам осуществляется через  семь адресов портов с номерами 3F8H
       -  3FEH  (или 2F8H - 2FEH).  В пяти случаях  регистр, к  которому
       получаем доступ через данный порт,  зависит от того, как установ-
       лен бит 7 в регистре контроля линии, который является  единствен-
       ным регистром с адресом порта 3FBH. Вот эти регистры:

       3F8H (OUT, бит 7 = 0 в 3FBH)   Регистр хранения передатчика
       3F8H (IN, бит 7 = 0 в 3FBH)    Регистр данных приемника
       3F8H (OUT, бит 7 = 1 в 3FBH)   Делитель скорости обмена (младший)
       3F9H (IN, бит 7 = 1 в 3FBH)    Делитель скорости обмена (старший)
       3F9H (OUT, бит 7 = 0 в 3FBH)   Регистр разрешения прерывания
       3FAH (IN)                      Регистр идентификации прерывания
       3FBH (OUT)                     Регистр управления линии
       3FCH (OUT)                     Регистр управления модемом
       3FDH (IN)                      Регистр статуса линии
       3FEH (IN)                      Регистр статуса модема

          Из десяти регистров только шесть необходимы для простой после-
       довательной связи.  Регистр  хранения  передатчика  содержит байт
       данных, которые будут посланы [7.1.6], а регистр данных приемника
       - последний полученный байт данных [7.1.7]. Регистры управления и
       статуса линии инициализируют и управляют линией связи,  используя
       скорость обмена, содержащуюся в  двух регистрах делителя скорости
       обмена [7.1.2]. Из оставшихся четырех регистров регистры управле-
       ния и статуса  модема  используются  только для связи через модем
       [7.1.5],  а  два регистра, связанных с прерываниями  используются
       только в процедурах, управляемых прерываниями [7.1.8].
          Прерывания используются при связи в целях эффективности. Обыч-
       ная коммуникационная процедура непрерывно проверяет регистр  ста-
       туса линии, ожидая вводимого символа или указаниия, что все гото-
       во  для  передачи следующего байта данных.   Поскольку  процессор
       намного быстрее, чем обычные скорости  с которыми передаются пос-
       


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

          7.1.2 Инициализация последовательного порта.

          При  инициализации порта коммуникации ("открытии")  устанавли-
       ваются все  его  параметры.  Эти  параметры  длину  слова,  число
       стоп-битов, установку четности и скорость обмена. Длина слова это
       число битов, которое образует  основную  единицу данных.  Если мы
       работаем  с привычными порциями по 8 битов, то 7 битов достаточны
       для стандартных файлов ASCII (в  которых  все символы имеют коды,
       не  превышающие ASCII 128), в то время как для передачи численных
       данных достаточно порций по 4 бита.

          Высокий уровень.

          Бейсик открывает коммуникационный  канал как файл, и как тако-
       вому ему должен быть присвоен идентификационный номер:

          OPEN "COM1: .........." AS #1

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

       Скорость обмена   дается как целое число: 75, 100, 150, 300, 600,
                         1200, 1800, 2400, 4800 или 9600 бод. По умолча-
                         нию берется скорость обмена 300 бод.
       Четность          вводится как односимвольный код: O для нечетной
                         E - для четной и N - при отсутствии контроля по
                         четности.  Могут быть  также S - когда бит чет-
                         ности  всегда равен 0 и M - когда бит  четности
                         всегда равен 1. Если используются 8 бит данных,
                         то  надо указывать N; при использовании четырех
                         бит не надо использовать N. По умолчанию - E.
       Биты данных       дается  как  целое  число  4, 5, 6, 7 или 8. По
                         умолчанию берется 7.
       Стоп-биты         дается  как  целое  число  1  или 2, причем 2 -
                         значение  по умолчанию для 75 и 110 бод, а 1  -
                         для остальных. Когда число битов данных равно 4
                         или  5, то 2 обозначает 1 1/2 стоп-бита.  Такое
                         значение возможно при  коммуникации,  так как в
                         этом  случае  бит является единицей  времени  и
                         поэтому делим.

       Оператор OPEN "COM1:" AS #1 открывает COM1 для связи со скоростью
       300   бод  с  четной  четностью,  используя 7  битов  данных и  1
       стоп-бит. OPEN "COM1:1200,O,8,1" устанавливает скорость 1200 бод,
       нечетную четность, 8 бит на символ и 1 стоп-бит.  Отметим, что Вы
       можете завершить оператор OPEN  выражением LEN = число, где число
       устанавливает  максимальный размер блока, с которым операторы GET
       и PUT могут обрабатывать данные (по умолчанию 128 байтов). Имеет-
       ся также ряд команд управления модемом, которые также могут  быть
       включены в эту спецификацию. (В  [7.1.5]  объясняется специальная
       терминология, используемая при этом):

          RS     Подавляет сигнал "Запрос на посылку" (Request to send).
       


                                     -263-
                 Если эта команда опущена, то OPEN "COM" включает RTS.
          CS     Вызывает  проверку линии "Очистка посылки"  (Clear  to
                 send).  За этой командой может следовать значение (от 0
                 до  65535), дающее число миллисекунд которые будет ожи-
                 даться сигнал перед тем как будет выдана ошибка таймау-
                 та,  например, CS500.  Значение по умолчанию 1000, если
                 указан параметр RS, в этом случае 0.
          DS     Вызывает  проверку  линии  "Готовность  набора  данных"
                 (Data set ready).  Допускается необязательный параметр,
                 как и для CS. Значение по умолчанию 1000.
          CD     Вызывает проверку линии "Определение носителя" (Carrier
                 detect). Допускается необязательный временной параметр,
                 как и для CS. Значение по умолчанию 0.
          LF     Вызывает  автоматическую  подачу  кода перевода строки
                 (ASCII 10) после каждого символа возврата каретки  (AS-
                 CII 13). Используется для  последовательного  вывода на
                 принтер.
          PE     Разрешает проверку четности, вызывая  ошибку  таймаута
                 устройства при возникновении ошибки четности.

          Эти специальные команды могут  помещаться в любом месте опера-
       тора  OPEN "COM" и в любом порядке.  Отметим, что обычно  сигналы
       CTS и DSR должны быть установлены,  чтобы оператор OPEN выполнил-
       ся, а иначе будет выдана ошибка таймаута устройства. В заключение
       приводим оператор OPEN "COM", содержащий  все параметры, кроме RS
       и LF:

          OPEN "COM1:1200,O,7,1,CS2000,DS2000,CD,PE" AS #1 LEN = 256

          Средний уровень.

          Функция  0 прерывания 14H BIOS инициализирует порт  коммуника-
       ции.  В DX должен даваться номер коммуникационного канала (COM1 =
       0,  COM2  = 1).  В AL должен содержаться  байт  инициализационных
       данных, значение битов которого следующее:

          биты 1-0   длина слова. 10 = 7 битов, 11 = 8 битов.
                 2   число стоп-битов. 0 = 1, 1 = 2.
               4-3   четность. 00 или 10 = нет, 01 = нечет., 11 = чет.
               7-5   скорость обмена. 000 = 110 бод
                                      001 = 150 бод
                                      010 = 300 бод
                                      011 = 600 бод
                                      100 = 1200 бод
                                      101 = 2400 бод
                                      110 = 4800 бод
                                      111 = 9600 бод

          В данном примере порт  инициализируется  со  словом в 8 битов,
       одним стоп-битом и четной четностью. Скорость обмена 1200 бод.

       ;---присваиваем значения параметров переменным
          MOV  WORDLENGTH,00000011B   ;длина слова 8 битов
          MOV  STOPBITS,00000000B     ;1 стоп-бит
          MOV  PARITY,00011000B       ;четная четность
          MOV  BAUDRATE,10000000B     ;скорость 1200 бод
       ;---инициализируем COM1
          MOV  AL,0                   ;чистим AL
          OR   AL,WORDLENGTH          ;устанавливаем нужные биты
          OR   AL,STOPBITS            ;
          OR   AL,PARITY              ;
          OR   AL,BAUDRATE            ;
          MOV  AH,0                   ;функция инициализации порта
       


                                     -264-
          MOV  DX,0                   ;выбираем COM1
          INT  14H                    ;инициализируем порт

          Низкий уровень.

          Независимо от того, занимаемся  ли  мы вводом или выводом, как
       минимум  4 регистра микросхемы 8250 должны быть  инициализированы
       для операций обмена.  Это  регистры делителя скорости обмена, ре-
       гистр контроля линии и регистр разрешения прерывания.

       Инициализация скорости обмена.

          Делитель  скорости обмена это число, на которое надо разделить
       частоту системных часов  (1190000  герц), чтобы получить желаемую
       скорость обмена.  Например, для скорости обмена 1200 бод делитель
       скорости обмена должен быть равен  96, поскольку 1190000/96 приб-
       лиженно  равно  1200.  Чем больше делитель, тем  меньше  скорость
       обмена. Скорости обмена 300 и  меньше  требуют двухбайтного числа
       для делителя.  Старший байт посылается в 3F9H (или 2F9H), а млад-
       ший в 3F8H (2F8H).  В  обоих  случаях  бит  7 регистра управления
       линии  должен быть установлен в 1 перед засылкой значений; в про-
       тивном случае по этим двум  адресам  значения  будут адресованы в
       другие регистры (см. [7.1.0]).  Вот некоторые значения, требуемые
       для обычных скоростей обмена:

          Скорость обмена             3F9H            3F8H

               110                     04H             17H
               300                     01H             80H
               600                     00H             C0H
              1200                     00H             60H
              1800                     00H             40H
              2400                     00H             30H
              3600                     00H             20H
              4800                     00H             18H
              9600                     00H             0CH

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

       Инициализация регистра контроля линии.

          Значение  битов регистра контроля линии, адрес порта  которого
       равен 3FBH (или 2FBH), следующее:

          биты 1-0   Длина символа. 00 = 5 битов, 01 = 6 битов
                                    10 = 7 битов, 11 = 8 битов
                 2   Число стоп-битов. 0 = 1, 1 = 1.5, если длина
                                       пяти, иначе 2.
                 3   Четность. 1 = генерируется бит четности, 0 = нет.
                 4   Тип четности. 0 = нечетная, 1 = четная
                 5   Фиксация четности. Заставляет бит четности всегда
                     быть 0 или 1. 0 = отменена
                         1 = всегда 1, если бит 3 = 1 & бит 4 = 0
                     или 1 = всегда 0, если бит 3 = 1 & бит 4 = 1
                     или 1 = нет четности, если бит 3 = 0
       


                                     -265-
                 6   Установка перерыва. Вызывает вывод строки нулей
                     в качестве сигнала отдаленной станции.
                     0 = запрещено, 1 = перерыв
                 7   Меняет адреса портов других регистров

       Обычно биты 5-7 сброшены в 0. Остальные описывают значения, опре-
       деляемые протоколом обмена.

       Регистр разрешения прерывания.

          Даже  если  Вы не используете прерывания, все равно Вы  должны
       произвести запись в  регистр  разрешения  прерывания,  чтобы быть
       уверенным,  что  прерывания запрещены.  Просто  поместите в  этот
       регистр 0. Регистр идентификации прерывания можно игнорировать.
          Инициализация остальных  регистров  связана  с модемами. Ясно,
       что модемы нужны только для связи с удаленными устройствами, а не
       для управления близлежащими  устройствами,  такими как последова-
       тельный принтер. В [7.1.5] объяснено как инициализировать регистр
       контроля модема.
          В данном примере из области  данных BIOS берется базовый адрес
       COM1, после чего различные регистры инициализируются для скорости
       обмена 1200  бод,  семибитных  данных,  четной  четности и одного
       стоп-бита.

       ;---получаем базовый адрес COM1
          MOV  AX,40H          ;ES указывает на область данных BIOS
          MOV  ES,AX           ;
          MOV  DX,ES:[0]       ;получаем базовый адрес COM1
       ;---инициализируеи регистры делителя скорости обмена на 1200 бод
          ADD  DX,3            ;указываем на регистр контроля линии
          MOV  AL,10000000B    ;устанавливаем бит 7
          OUT  DX,AL           ;посылаем байт
          DEC  DX              ;указываем на старший байт делителя
          DEC  DX              ;скорости обмена
          MOV  AL,0            ;старший байт для 1200 бод
          OUT  DX,AL           ;посылаем старший байт для 1200 бод
          DEC  DX              ;указываем на младший байт делителя
          MOV  AL,60H          ;младший байт делителя для 1200 бод
          OUT  DX,AL           ;посылаем младший байт
       ;---инициализируем регистр контроля линии
          MOV  AL,0            ;обнуляем AL
          OR   AL,10B          ;длина данных 7 битов
          OR   AL,000B         ;1 стоп-бит
          OR   AL,1000B        ;генерируется бит четности
          OR   AL,10000B       ;четная четность
          ADD  DX,3            ;указывае на регистр контроля линии
          OUT  DX,AL           ;посылаем инициализационное значение
       ;---инициализируем регистр разрешения прерывания
          DEC  DX              ;указываем на регистр разрешения
          DEC  DX              ;прерывания
          MOV  AL,0            ;запрещаем прерывания
          OUT  DX,AL           ;посылаем байт

          7.1.3 Установка текущего коммуникационного порта.

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


                                     -266-
       каналов (MS DOS поддерживает только первые два из них).   Базовый
       адрес порта это младший из  группы  адресов портов, через которые
       можно получить доступ к данному коммуникационному каналу. Базовый
       адрес для COM1 хранится в ячейке 0040:0000, а для COM2 - в ячейке
       0040:0002. Для смены коммуникационных портов надо просто поменять
       эти два значения.  Повторная  смена  значений приведет к первона-
       чальному назначению портов.

          Высокий уровень.

          В Бейсике оператор OPEN "COM" может использоваться в виде OPEN
       C$+"1200,N,8" AS  #2,  где  C$  может  быть  либо  "COM1:",  либо
       "COM2:".  В качестве альтернативы можно использовать PEEK и  POKE
       для обмена базовых адресов:

       100 DEF SEG = &H40          'указываем на область данных BIOS
       110 X = PEEK(0): Y = PEEK(1)  'запоминаем первые 2 байта
       120 POKE 0,PEEK(2): POKE 1,PEEK(3)  'переносим 2-е два байта
       130 POKE 2,X: POKE 3,Y      'засылаем запомненные значения

          Средний уровень.

          Если программа  обращается  к  коммуникационному  порту  через
       прерывание  14H  BIOS,  то COM порт определяется  содержимым  DX,
       которое равно 0 или 1  (для  COM1  или  COM2). Вместо того, чтобы
       присваивать DX непосредственное значение, заполняйте его из пере-
       менной, которой может быть присвоено значение 0 или 1. Программы,
       использующие коммуникационные функции 3 и 4 прерывания 21H всегда
       адресуются к COM1. В этом случае надо поменять базовые адреса:

       ;---обмен базовых адресов для COM1 и COM2
          MOV  AX,40H        ;ES указывает на область данных BIOS
          MOV  ES,AX         ;
          MOV  DX,ES:[0]     ;помещаем 1-й базовый адрес в DX
          MOV  AX,ES:[2]     ;помещаем 2-й базовый адрес в AX
          MOV  ES:[0],AX     ;обмениваем адреса
          MOV  ES:[2],DX     ;

          7.1.4 Определение статуса коммуникационного порта.

          Регистр статуса линии микросхемы UART 8250 определяет протокол
       связи.   Этот регистр имеет адрес порта на 5 больше, чем  базовый
       адрес данного канала.  Обычно он постоянно просматривается в про-
       цессе коммуникационного обмена. При передаче данных регистр сооб-
       щает, что предыдущий символ уже  послан, позволяя программе запи-
       сать новый символ поверх его.  При приеме данных регистр информи-
       рует программу о  поступлении  следующего  символа,  с  тем чтобы
       программа могла прочитать его прежде чем он будет уничтожен  сле-
       дующим прибывшим. Значение битов этого регистра следующее:

          бит 0   1 = байт данных получен
              1   1 = полученные данные были перезаписаны (предыдущий
                      символ не был вовремя считан)
              2   1 = ошибка четности (вероятно, из-за шума в линии)
              3   1 = ошибка окружения (передача не синхронизована)
              4   1 = обнаружен перерыв (получена длинная строка единиц,
                      индицирующая, что другая станция запрашивает
                      конец передачи)
              5   1 = регистр хранения передатчика пуст (в этот регистр
                      должны помещаться передаваемые данные)
              6   1 = регистр сдвига передатчика пуст (этот регистр по-
                      лучает данные из регистра хранения и преобразует
                      их в последовательный вид)
       


                                     -267-
              7   1 = таймаут (устройство не связано с машиной)

          Высокий уровень.

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

       100 DEF SEG = &H40         'указываем на область данных BIOS
       110 ADDR = PEEK(4)+PEEK(5)*256   'вычисляем адрес COM2
       120 X = INP(ADDR+5)        'вычисляем адрес регистра статуса
       130 IF X AND 16 THEN 500   'переход на подпр-му, если бит 4 = 1
        .
        .
       500 '''начинаем процедуру обработки перерыва

          Средний уровень.

          Функция 3 прерывания 14H BIOS  возвращает в AH регистр статуса
       линии (AL получает регистр статуса модема [7.1.5]).  При входе DX
       должен содержать номер  коммуникационного  порта, к которому осу-
       ществляется  доступ, где COM1 = 0, а COM2 = 1.  Как и  предыдущий
       пример, этот проверяет наличие перерыва:

          MOV  AH,3          ;номер функции
          MOV  DX,1          ;выбираем COM2
          INT  14H           ;получаем байт статуса
          TEST AH,10000B     ;обнаружен перерыв?
          JNZ  BREAK_DETECT  ;если да, то на процедуру обработки

          Низкий уровень.

          Этот пример совершенно аналогичен  приведенному на Бейсике. Из
       области  данных BIOS считывается базовый адрес  коммуникационного
       канала, к нему добавляется 5, а затем из полученного адреса порта
       считывается байт статуса.

          MOV  AX,40H          ;ES указывает на область данных BIOS
          MOV  ES,AX           ;
          MOV  DX,ES:[2]       ;получаем базовый адрес COM2
          ADD  DX,5            ;добавляем 5 для регистра статуса
          IN   AL,DX           ;получаем байт статуса
          TEST AL,10000B       ;бит 5 установлен?
          JNZ  BREAK_DETECT    ;если да, то на обработку перерыва

          7.1.5 Инициализация и управление модемом.

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

       От компьютера к модему:

       Data Terminal Ready (DTR)        Информирует модем, что компьютер
       Готовность компьютера            включен и готов к связи.

       Request To Send (RTS)            Информирует модем, что компьютер
       Запрос на посылку                ожидает посылки данных.

       


                                     -268-
       От модема к компьютеру:

       Data Set Ready (DSR)             Информирует компьютер, что модем
       Готовность модема                включен и готов.

       Clear To Send (CTS)              Информирует компьютер, что модем
       Готовность к посылке             готов начать передачу данных.

       Data Carrier Detect (DCD)        Информирует компьютер, что модем
       Обнаружен носитель данных        связан с другим модемом.

       Ring Indicator (RI)              Информирует компьютер, что теле-
       Индикатор звонка                 фонная линия, по которой присое-
                                        динен модем имеет звонок.

          Сначала компьютер  устанавливает сигнал DTR, а затем инстукти-
       рует модем связаться с удаленной станцией.  После того, как модем
       установил связь он устанавливает сигнал DSR. Этот сигнал информи-
       рует компьютер, что модем готов к связи и в этот момент компьютер
       может установить сигнал RTS. Когда модем ответит сигналом CTS, то
       передача начинается.
          Две стандартные линии, по которым компьютер управляет модемом,
       доступны через регистр контроля модема микросхемы UART 8250. Этот
       регистр  имеет адрес порта на 4 больше, чем базовый адрес исполь-
       зуемого коммуникационного канала. Вот значение его битов:

       Регистр контроля модема:

          биты 7-5     (всегда 0)
                 4     1 = выход UART замкнут на вход
                 3     добавочный пользователь назначен на вывод #2
                 2     добавочный пользователь назначен на вывод #1
                 1     1 = "запрос на посылку" активен
                 0     1 = "готовность компьютера" активна

          Обычно установлены биты 0  и  1  регистра  контроля  модема, а
       остальные  равны 0.  Бит 2 равен 0, за исключением случаев, когда
       производитель модема предназначил  его для специального использо-
       вания.  Бит 3 установлен только в случае, когда используются пре-
       рывания [7.1.8]. Наконец, бит 4  предоставляет возможность тести-
       рования  коммуникационных программ без установления реальной свя-
       зи.  Выходной сигнал микросхемы  UART подается на вход, как будто
       UART  принимает последовательные данные.  Это свойство можно  ис-
       пользовать для тестирования правильности работы самой микросхемы.
       Оно недоступно при использовании коммуникационных процедур преры-
       вания 14H BIOS.
          Четыре линии, по которым модем посылают информацию компьютеру,
       управляются регистром статуса модема.  Этот регистр расположен по
       адресу порта на 6 больше, чем  базовый адрес используемого комму-
       никационного адаптера. Вот значение его битов:

       Регистр статуса модема:

          бит 7      1 = DCD
              6      1 = RI
              5      1 = DSR
              4      1 = CTS
              3      1 = изменение в DCD
              2      1 = изменение в RI
              1      1 = изменение в DSR
              0      1 = изменение в CTS

          Программа  непрерывно проверяет эти биты в ходе коммуникацион-
       


                                     -269-
       ных операций.  Отметим, что  4  младших  бита параллельны старшим
       четырем битам.  Эти биты устанавливаются в 1 только тогда,  когда
       происходит изменение в статусе  соответствующего  старшего бита с
       тех пор, когда регистр читался последний раз.  Все 4 младших бита
       автоматически сбрасываются при чтении регистра.  Программы любого
       уровня  могут прямо читать этот регистр.  Другой возможностью яв-
       ляется использование функции 3 прерывания 14H BIOS, которая возв-
       ращает  регистр статуса модема в AL (при этом в AH  будет  содер-
       жаться регистр  статуса  линии).  При  входе  DX должен содержать
       номер коммуникационного канала (0 или 1).
          Большинство  модемов  имеет  намного больше  возможностей,  по
       сравнению с теми, что отражены в двух связанных с модемом регист-
       рах.   Имеются возможности автоматической связи и автоматического
       ответа,  которые контролируются управляющей строкой.  Эта  строка
       посылается в модем, как будто  передаются  обычные данные.  Модем
       выделяет эту строку из данных по специальному символу, используе-
       мому только для указания начала управляющей  строки.  Этот символ
       может  быть предопределенным (часто используется код Esc -  ASCII
       27) или выбираемым пользователем.  Модем способен определить нас-
       колько  длинной должна быть каждая строка, поэтому  по  окончании
       строки он опять рассматривает  входящий поток информации как дан-
       ные.   Каждый модем имеет свой набор команд.  В качестве  примера
       рассмотрим команды, используемые внутренним модемом PCjr:

          Символ          Значение          Применение

           A            ответ        вход в режим ответа
           Bn           перерыв      посылает сигнал перерыва n*100 мс
           Cn           отсчет n     отсчитывает n звонков до ответа
           Dn...n       вызов        посылает строку чисел n...n
           Fn           формат       устанавливает протокол связи
           H            разрыв       прекращает связь с машиной
           I          инициализация  инициализирует модем
           LR         долгий ответ   меняет используемую кодовую систему
           M            режим        модем берет символы как данные
           Nn           новый        меняет командный символ на n
           O           originate     вход в режим originate
           P            pick-up      вход в режим голоса
           Q            запрос       запрос статуса модема
           R            повтор       повторить команду связи
           Sn          скорость      выбор скорости обмена
           Tn...n     прозрачность   игнорировать управляющие строки
                                     в следующих n...n байтах
           V            голос        перевести модем в режим голоса
           W            ожидание     ничего не делать до след. команды
           X            передать     передать тона вызова
           Z            тест         проводит диагностику оборудования

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

          Высокий уровень.

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

       100 OUT BASEADDRESS+4,1        'устанавливаем бит DTR
       


                                     -270-
       110 '''теперь посылаем управляющую строку для вызова и установле-
       120 '''ния связи - этот код меняется от модема к модему
        .
        .
       200 X = INP(BASEADDRESS+2)     'получаем регистр статуса модема
       210 IF X AND 2 <> 2 THEN 200   'ждем пока будет установлен бит 1
       220 OUT BASEADDRESS+4,3        'устанавливаем бит RTS
       230 X = INP(BASEADDRESS+2)     'получаем регистр статуса модема
       240 IF X AND 1 <> 1 THEN 230   'ждем пока будет установлен бит 0
       250 '''теперь посылаем данные

          Низкий уровень.

          Вот та же самая схема на языке ассемблера:

       ;---устанавливаем сигнал DTR
          MOV  DX,BASE_ADDRESS    ;начинаем с базового адреса
          ADD  DX,4               ;указываем на регистр контроля модема
          MOV  AL,1               ;устанавливаем бит 1
          OUT  DX,AL              ;посылаем в порт
       ;---посылаем управляющую строку модему для вызова
           .
          (этот код разный для разных модемов)
           .
       ;---ожидаем пока будет установлен сигнал DSR
          INC  DX                 ;указываем на регистр статуса модема
          INC  DX                 ;
       TRY_AGAIN:  IN   AL,DX     ;получаем содержимое
          TEST AL,10B             ;проверяем второй бит
          JZ   TRY_AGAIN          ;ждем пока он не будет равен 1
       ;---устанавливаем бит RTS
          DEC  DX                 ;возвращаемся к регистру управления
          DEC  DX                 ;
          MOV  AL,3               ;устанавливаем сигнал RTS
          OUT  DX,AL              ;посылаем в порт
       ;---ожидаем сигнала CTS
          INC  DX                 ;возвращаемся к регистру статуса
          INC  DX                 ;
       ONCE_MORE:  IN   AL,DX     ;получаем байт статуса
          TEST AL,1               ;проверяем бит CTS
          JZ   ONCE_MORE          ;не продолжаем пока он не установлен
       ;---теперь можно посылать данные

          7.1.6 Передача данных.

          Передача  данных  проще чем прием, поскольку  программа  имеет
       полный контроль над  составом  данных  и скоростью, с которой они
       должны  посылаться.  Тем не менее процедуры передачи  могут  быть
       достаточно сложными, если они  обрабатывают  данные по мере того,
       как  они посылаются.  Могут быть также проблемы с  синхронизацией
       при использовании протокола  XON/XOFF.   Этот протокол использует
       коды  ASCII  17(XON) и 19(XOFF), для того  чтобы  сигнализировать
       принимающей станции,  что  передатчик  хочет  продолжить передачу
       временно  прерванного потока данных.  Чтобы принять эти  сигналы,
       программа должна непрерывно анализировать принимаемые символы при
       передаче  (в  полнодуплексном режиме, в котором  обычно  работают
       модемы, сигналы  одновременно  идут  в обе стороны по телефонному
       каналу).   Кроме  того, чтобы обнаружить, что  удаленная  станция
       посылает строку нулей, в качестве сигнала перерыва, должен непре-
       рывно  анализироваться  статус бита перерыва (номер  4)  регистра
       статуса линии [7.1.4]. На рис.  7-2 (в [7.1.7]) показано как про-
       цедура  передачи данных взаимодействует с кодом, принимающим дан-
       ные.
       


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

          Высокий уровень.

          В Бейсике для того, чтобы послать данные в открытый коммуника-
       ционный порт надо использовать операторы PRINT#, PRINT# USING или
       WRITE#.  Последние два оператора имеют специальный формат, парал-
       лельный  тому,  который используется ими при выводе  на  дисплей.
       Обычно используется оператор PRINT#.  В данном примере посылаемые
       данные берутся непосредственно с клавиатуры.  Предполагается, что
       COM1 уже открыт, как показано в [7.1.2].   Процедура обрабатывает
       бит перерыва в регистре статуса линии.
        .
        .
       500 C$ = INKEY$: IF C$ <> "" THEN PRINT #1,C$
       510 X = INP(BASEADDRESS + 5)    'читаем регистр статуса линии
       520 IF X AND 32 = 32 THEN 1000  'проверяем бит перерыва
       530 IF EOF(1) THEN 500       'если буфер пуст, то ждем ввода

        .
        (здесь расположена процедура приема данных)
        .
       1000 '''здесь процедура обработки перерыва

          Средний уровень.

          Функция 1 прерывания 14H BIOS посылает символ, содержащийся  в
       AL в коммуникационный канал. При входе DX содержит номер порта (0
       или 1).  При возврате AH содержит байт статуса, в котором бит 7 =
       1, если операция неуспешна.  В этом случае имеют значение следую-
       щие биты:

       бит 4   обнаружен перерыв (сигнал "стоп" от принимающей станции)
           5   регистр сдвига передатчика пуст
           6   регистр хранения передатчика пуст

          MS DOS имеет функцию для  передачи по коммуникационному каналу
       символа, помещаемого в DL. Это функция номер 4 прерывания 21H, но
       она не  имеет  никаких  преимуществ  перед  функцией BIOS; она не
       возвращает статусной информации и не позволяет назначать какой из
       коммуникационных портов  надо  использовать  (всегда используется
       COM1).
          Чтобы вывести строку данныз используйте функцию 40H прерывания
       21H. Это обычная функция вывода  для  всех файлов и устройств при
       использовании метода доступа дескриптора файлов.  COM1 имеет пре-
       лопределенный номер #3. Поместите номер файла в BX, а число пере-
       даваемых  байтов в CX.  Пусть DS:DX указывают на буфер  выводимых
       данных и вызывайте функцию.

          MOV  AH,40H         ;номер функции
          MOV  BX,3           ;предопределенный номер файла для COM1
          MOV  CX,50          ;выводим 50 байтов
          LEA  DX,DATA_BUFFER ;DS:DX указывают на буфер данных
          INT  21H            ;посылаем данные
          JC   COM_ERROR      ;уход на обработку ошибки

       Отметим, что при использовании предопределенных номеров файлов их
       


                                     -272-
       не надо открывать. Если произошла ошибка, то устанавливается флаг
       переноса, а в AX  возвращается  5  если  коммуникационный порт не
       готов и 6 при указании неверного номера файла.

          Низкий уровень.

          Когда  байт данных помещается в регистр хранения  передатчика,
       то он  автоматически  выводится  в  последовательный  канал через
       регистр сдвига передатчика, который сериализует данные. Нет необ-
       ходимости в импульсе бита  строба,  как это делается в случае па-
       раллельного  адаптера.   Бит 5 регистра статуса линии  показывает
       свободен ли регистр хранения  передатчика  для приема данных. Ре-
       гистр  постоянно  проверяется  до тех пор, пока бит 5  не  станет
       равным 1.  После этого в регистр  хранения передатчика посылается
       очередной  байт  из того места, откуда они берутся.   В  процессе
       передачи бит 5 равен 0 и только  когда  он опять станет равным 1,
       то  в  регистр хранения передатчика может быть  послан  следующий
       символ. Этот процесс повторяется до тех пор, пока это нужно.
          В следующем примере даны  основные  понятия об этой процедуре.
       Конечно, она может быть сделана необычайно сложной (в  частности,
       программирование связи требует  особо тщательных процедур обнару-
       жения ошибок и восстановления при сбоях). В примере предполагает-
       ся, что коммуникационный порт и  модем  уже инициализированы, как
       показано  в  [7.1.2] и [7.1.5].  Первая часть это  цикл  проверки
       ошибок и приема символов.  В  [7.1.7]  приведен код для процедуры
       приема данных.

       ;---ждем пока все будет готово для посылки символа
       KEEP_TRYING:  MOV  DX,BASE_ADDRESS   ;базовый адрес
          ADD  DX,5              ;указываем на регистр статуса линии
          IN   AL,DX             ;получаем байт статуса
          TEST AL,00011110B      ;проверяем на ошибку
          JNZ  ERROR_ROUTINE     ;если есть, то на процедуру обработки
          TEST AL,00000001B      ;проверяем получены ли данные
          JNZ  RECEIVE           ;если да, то на процедуру приема
          TEST AL,00100000B      ;проверяем готовность к передаче
          JZ   KEEP_TRYING       ;если нет, то возвращаемся назад
       ;---передаем символ принимаемый с клавиатуры
          MOV  AH,1              ;функция проверки нажатия клавиши
          INT  16H               ;прерывание клавиатуры BIOS
          JZ   KEEP_TRYING       ;возврат, если не было нажатия
          MOV  AH,0              ;функция получения кода с клавиатуры
          INT  16H               ;теперь нужный символ в AL
          SUB  DX,5              ;адрес регистра хранения передатчика
          OUT  DX,AL             ;посылаем символ
          JMP  SHORT KEEP_TRYING ;возвращаемся к началу цикла

          7.1.7 Получение данных.

          Коммуникационная  программа готова принимать данные как только
       инициализирован коммуникационный порт [7.1.2] и установлена связь
       с  удаленной станцией [7.1.5].  Прием данных никогда полностью не
       отделен от передачи данных,  поскольку  программе  может потребо-
       ваться  послать  сигнал XOFF (ASCII 19), чтобы  остановить  поток
       данных, если они поступают  слишком  быстро  и она не успевает их
       обрабатывать.  Код XON (ASCII 17) сообщает удаленной станции, что
       можно продолжить передачу.  Отметим,  что PCjr не может принимать
       данные  во время дисковых операций; чтобы снять  это  ограничение
       можно использовать XON и XOFF.
          В зависимости от  сложности  используемого  протокола  обмена,
       принимаемые данные могут требовать простой или сложной обработки.
       Может быть получен один из набора  управляющих кодов, приведенных
       в [7.1.9]. Те из них, которые являются ограничителями данных чаще
       


                                     -273-
       обнаруживаются при синхронном обмене.  При выводе получаемых сим-
       волов на экран учитывайте влияние символов перевода строки (ASCII
       10), поскольку некоторые  языки  (включая  Бейсик)  автоматически
       вставляют  перевод  строки после возврата каретки; в этом  случае
       исключайте переводы строки из принимаемых  данных, чтобы избежать
       пустых строк при выводе.  На рис.  7-2 показана  коммуникационная
       процедура, включающая также код передачи, обсуждаемый в [7.1.6].

          Высокий уровень.

          Для коммуникационной процедуры, написанной на интерпретируемом
       Бейсике,  время очень существенно.  Обработка  медленна,  поэтому
       если процедура приема неверно  сконструирована,  то входной буфер
       может  заполниться (т.е.  произойдет переполнение) в то время как
       программа еще будет  анализировать  ранее полученные данные. Оче-
       видным  решением  этой  проблемы является  максимально  возможный
       размер буфера.  При загрузке Бейсика размер буфера ввода устанав-
       ливается добавлением к команде ключа /C:.  BASICA /C:1024 создает
       буфер размером в 1K и это  минимальное  число для скорости обмена
       1200  бод (сложным процедурам может понадобиться 4096 байт).   По
       умолчанию используется размер  буфера  равный  256 байтам и такой
       буфер имеет то преимущество, что он может быть целиком помещен  в
       одну символьную переменную.  Такой  размер буфера можно использо-
       вать только при скорости обмена 300 бод и ниже.
          Бейсик  читает  из буфера с помощью  оператора  INPUT$  (можно
       использовать также INPUT# и LINE  INPUT#, но INPUT$ более гибок).
       Этот оператор имеет форму INPUT$(числобайт,номерфайла). Например,
       INPUT$(10,#1) читает 10 байтов  из коммуникационного канала, отк-
       рытого как файл #1.  Если размер буфера не превышает 256  байтов,
       то очень удобно читать все  содержимое  буфера  за один раз.  LOC
       сообщает  сколько байтов данных находится в буфере в  данный  мо-
       мент. Поэтому напишите  оператор  INPUT$(LOC(1),#1)  и в S$ будут
       записаны все данные с момента последнего доступа к буферу. Конеч-
       но, если LOC(1) = 0, то  буфер  пуст  и  процедура должна ожидать
       пока  данные будут получены.  Отметим, что EOF(1) также можно ис-
       пользовать для  проверки  состояния  буфера,  так как эта функция
       возвращает  -1  если буфер пуст и 0, если там есть хотя  бы  один
       символ.
          После того как данные записаны в S$ программа должна проверить
       не  содержатся ли там управляющие коды.  Функция INSTR  выполняет
       эту задачу быстрее всего.   Напомним, что ее параметрами являются
       сначала  позиция, с которой надо вести поиск в строке, затем  имя
       строки и, наконец, символ  (или  строка)  который  ищется.  Чтобы
       найти   символ   XOFF  (ASCII  19)  оператор  должен  иметь   вид
       INSTR(1,S$,CHR$(19)). Чтобы найти второе появление нужного управ-
       ляющего  символа  повторите поиск в  строке,  начиная с  символа,
       следующего за позицией, в которой найден первый.
          Обычно процедура ввода  исключает большинство управляющих сим-
       волов  из принимаемых данных, с тем чтобы они нормально выглядели
       при выводе.  Затем данные выводятся на экран, пересылаются в дру-
       гое  место в памяти, а иногда записываются на диск или  выводятся
       на принтер. В процессе  всей  этой  деятельности программа должна
       постоянно  возвращаться к просмотру не поступили ли новые данные.
       Если оказалось, что буфер заполняется слишком быстро, то програм-
       ма  может послать сигнал XOFF, останавливая поток данных.  Затем,
       после того как полученные  данные  буду декодированы, можно снова
       разрешить  передачу данных.  Конечно, необходимо  чтобы  протокол
       обмена поддерживал XON и XOFF. Программы, написанные на интерпре-
       тируемом Бейсике, обычно могут использовать XON/XOFF для установ-
       ления соответствия скоростей  при  приеме данных, но при передаче
       данных такая программа часто не может достаточно быстро  отреаги-
       ровать на получение сигнала XOFF.
        .
       


                                     -274-
        .
       500 '''здесь находится процедура передачи (см. [7.1.6])
        .
        .
       600 IF LOC(1)>100 THEN XOFF = 1: PRINT #1,CHR$(19)
       610 C$ = INPUT$(LOC(1),#1)   'читаем содержимое буфера
       620 '''выделяем из данных управляющие символы
       630 IF INSTR(1,C$,CHR$(19))>0 THEN 800  'получен XOFF
       640 IF INSTR(1,C$,CHR$(17))>0 THEN 900  'получен XON
        .
        (здесь удаляются ненужные управляющие символы
        .
       700 PRINT C$                 'выводим данные на экран
       710 IF LOC(1) > 0 THEN 600   'если получены данные, то читаем их
       720 IF XOFF = 1 THEN XOFF = 0: PRINT #1,CHR$(17)
        .
        .
       800 'реакция на XOFF
        .
       900 'реакция на XON

          Если функция LOF применяется к коммуникационному порту, то она
       возвращает  количество свободного места, оставшееся в буфере вво-
       да.  Например, если COM1 открыт как #1, то LOF(1) сообщит свобод-
       ного  пространства.  Это может быть полезно для определения,  что
       буфер почти полон.  Отметим,  однако, что оператор LOC возвращает
       позицию указателя в буфере и это значение может быть использовано
       для той же  цели.  Например,  если  COM1  открыт как #3, а размер
       буфера  ввода  равен  256 байтам, то до тех пор, пока  LOC(3)  не
       будет равен 256, буфер не полон.

          Средний уровень.

          Функция 2 прерывания 14H BIOS ожидает символ из последователь-
       ного порта, помещает его в AL при получении и затем  возвращается
       в программу. При входе надо поместить номер порта (0-1) в DX. При
       возврате AX равен нулю, если не было ошибки.  Если AH не равен 0,
       то может быть возвращен байт  статуса,  в  котором имеют значение
       только 5 битов. Это следующие биты:

       бит  1   ошибка переполнения (новый символ поступил  раньше,  чем
                был удален старый)
            2   ошибка четности (вероятно, из-за проблем в линии)
            3   ошибка оформления (стартовый или стоп-биты неверны)
            4   обнаружен перерыв (получена длинная строка битов 0)
            5   ошибка таймаута (не получен сигнал DSR)

          MS DOS также предоставляет коммуникационную функцию для приема
       одного символа, это функция  3  прерывания  21H.  Функция ожидает
       символ  из COM1 и помещает его в AL.  Отметим, что при  этом  нет
       функции инициализации порта,  которую надо делать через процедуру
       BIOS  или непосредственно, как показано в [7.1.2].  По  умолчанию
       порт инициализируется со  значениями  2400 бод, нет контроля чет-
       ности,  один стоп-бит и 8 битов на символ.  Эта функция не  имеет
       никаких достоинств по сравнению  с  функцией BIOS и не возвращает
       информации о статусе.

          Низкий уровень.

          При  получении данных без использования коммуникационного пре-
       рывания [7.1.8]  программа  должна  постоянно  проверять  регистр
       статуса  линии, адрес порта которого на 5 больше базового  адреса
       используемого коммуникационного  адаптера.  Бит  0 этого регистра
       


                                     -275-
       будет  равен нулю, до тех пор пока не будет получен символ в  ре-
       гистр данных приемника.  Когда бит 0 становится равным 1, то надо
       немедленно  считать его из регистра, с тем чтобы на него не нало-
       жился следующий принимаемый символ. После того как символ считан,
       бит 0 опять становится равным 0 и остается таковым, пока не  при-
       будет новый символ.
          Хотя здесь об этом не говорилось, но коммуникационные процеду-
       ры  обычно создают циклический буфер для сбора поступающих симво-
       лов.  Циклические буфера обсуждались  в [3.1.1].  Вы должны также
       знать, что если поступающие данные подавать на экран со скоростью
       1200 бод, то процедура сдвига экрана  BIOS [4.5.1] не будет успе-
       вать  и  произойдет переполнение.  Простое решение  этих  проблем
       состоит в использовании  коммуникационного прерывания, как объяс-
       нено в [7.1.8].
          Следующий  пример  частично дублирует  содержимое  предыдущего
       раздела, относящегося к передаче символов. Как и в том случае код
       начинается  с бесконечного цикла.  Объедините эти 2  процедуры  с
       процедурами инициализации из  [7.1.2]  и [7.1.5] для создания за-
       конченной процедуры ввода/вывода через коммуникационный канал.

       KEEP_TRYING:   MOV  DX,BASE_ADDRESS   ;базовый адрес
          ADD  DX,5           ;указываем на регистр статуса линии
          IN   AL,DX          ;получаем байт статуса
          TEST AL,00011110B   ;проверяем на ошибку
          JNZ  ERROR_ROUTINE  ;если да, то на обработку ошибки
          TEST AL,00000001B   ;проверяем получены ли данные
          JNZ  RECEIVE        ;на процедуру приема данных
          TEST AL,00100000B   ;проверяем готовность к передаче
          JZ   KEEP_TRYING    ;если нет, то к началу цикла
           .
          (здесь расположена процедура передачи - см. [7.1.6])
           .
       ;---получаем данные и выводим их на экран
       RECEIVE:   MOV  DX,BASE_ADDRESS        ;базовый адрес
          IN   AL,DX          ;читаем полученный символ
          CMP  AL,19          ;проверка на XOFF
          JE   XOFF_ROUTINE   ;
           .
          (и т.д.)
           .
          MOV  DL,AL          ;готовим символ для вывода на экран
          MOV  AH,2           ;функция вывода символа
          INT  21H            ;выводим его
          JMP  SHORT KEEP_TRYING   ;возвращаемся на начало цикла

          7.1.8  Посылка/получение  данных с  помощью  коммуникационного
       прерывания.

          Хорошая коммуникационная программа имеет слишком много работы,
       чтобы посвятить себя целиком  вводу/выводу.   Поступающие  данные
       должны анализироваться, передаваемые данные должны собираться,  а
       большие блоки данных могут записываться на диск или считываться с
       него.  Коммуникационное прерывание позволяет программе не тратить
       на ввод/вывод больше  времени,  чем  он  того  требует. Например,
       после установки прерывания, управление передается процедуре пере-
       дачи данныз только в том случае, когда  регистр хранения передат-
       чика  пуст и возвращается программе, как только послан байт  дан-
       ных, позволяя ей продолжать свою работу  до тех пор, пока регистр
       хранения  передатчика не будет снова готов.  Не забудьте  ознако-
       миться с обсуждением прерываний  в [1.2.3], прежде чем продолжить
       чтение.
          IBM  PC отводит два аппаратных прерывания для коммуникационных
       каналов, номер 3 (COM1) и 4 (COM2). Отметим, что у PCjr, встроен-
       


                                     -276-
       ный  модем имеет номер 3, а COM1 - номер 4.  Микросхема UART 8250
       допускает 4 класса прерываний для  каждого канала, используя сле-
       дующие двоичные кодовые числа:

          00     изменение в регистре статуса модема
          01     регистр хранения передатчика пуст
          10     получены данные
          11     ошибка приема, или получено условие перерыва

       Эти  коды содержатся в битах 2-1 регистра идентификации  прерыва-
       ния, адрес порта которого на 2 больше,  чем базовый адрес исполь-
       зуемого коммуникационного адаптера. Бит 0 этого регистра устанав-
       ливается при возникновении  прерывания,  а  остальные биты не ис-
       пользуются и всегда равны 0.
          Чтобы  выбрать одно или более прерываний, надо  запрограммиро-
       вать регистр разрешения  прерывания,  адрес  которого на 1 больше
       базового адреса. Значение его битов такое:

       бит 0     1 = прерывание при получении данных
           1     1 = прерывание когда регистр хранения передатчика пуст
           2     1 = прерывание при ошибке приема данных
           3     1 = прерывание при изменении регистра статуса модема
         7-4     не используются, всегда 0

       Когда одно из этих событий происходит, то инициируется аппаратное
       прерывание, возникающее в микросхеме обработки прерываний 8259 по
       каналу  3  для COM1 и по каналу 4 для COM2.  Процедура  обработки
       прерываний передает управление  тому  коду,  на который указывает
       соответствующий вектор прерывания.  Поскольку это аппаратное пре-
       рывание, то оно может  быть  маскировано  [1.2.2].   Помните, что
       процедура  обработки  прерывания должна  завершаться  стандартным
       кодом выхода из аппаратного прерывания MOV AL,20H/OUT 20H,AL.  На
       рис. 7-3 показано коммуникационное прерывание.
          Любое число типов прерывания может быть разрешено  одновремен-
       но. Но если разрешен более чем  один  тип, то процедура обработки
       прерывания должна сама определять какой из типов прерывания прои-
       зошел, проверяя регистр  идентификации  прерывания.  Одновременно
       могут  происходить более чем одно прерывание, поэтому  бит 0  ре-
       гистра идентификации сообщает о  том, что поступило еще одно пре-
       рывание. Когда два или более прерываний поступило в один и тот же
       момент времени,  то  они  обрабатываются  в  порядке, указанном в
       нижеприведенной таблице.  Добавочные прерывания должны быть обра-
       ботаны  до  завершения процедуры обработки  прерывания.   Условия
       предшествующих прерываний "отменяются"   с помощью действий, при-
       веденных в правом столбце следующей таблицы:

       Код             Тип               Действия для "сброса"

        11       ошибка или перерыв     чтение регистра статуса линии
        10       получены данные        чтение регистра приемника данных
        01       передатчик готов       вывод символа в регистр хранения
                                        передатчика
        00    изменение статуса модема  чтение регистра статуса модема

          Низкий уровень.

          Вот  общая  форма программы,  обрабатывающей  коммуникационные
       прерывания:

       ;---установка вектора коммуникационного прерывания
          PUSH DS                  ;сохраняем DS
          MOV  DX,OFFSET IO_INT    ;DS:DX указывают на процедуру
          MOV  AX,SEG IO_INT       ;
       


                                     -277-
          MOV  DS,AX               ;
          MOV  AL,0BH              ;номер вектора для COM1
          MOV  AH,25H              ;функция изменения вектора
          INT  21H                 ;меняем вектор прерывания
       ;---инициализация регистра разрешения прерывания (COM1)
          MOV  AX,40H              ;DS указывает на данные BIOS
          MOV  DS,AX               ;
          MOV  DX,DS:[0]           ;получаем базовый адрес COM1
          INC  DX                  ;указываем на регистр разрешения
          MOV  AL,3                ;прерываний и разрешаем прерывания
          OUT  DX,AL               ;приема и передачи
          POP  DS                  ;восстанавливаем регистр
       ;---процедура обработки прерывания - сначала определяем его тип
       IO_INT      PROC FAR
       NEXT_INT:   MOV  DX,BASEADDRESS     ;базовый адрес
          INC  DX                  ;указываем на регистр идентификации
          INC  DX                  ;прерывания
          IN   AL,DX               ;читаем его значение
          TEST AL,10B              ;это прерывание передатчика?
          JNZ  TRANSMIT            ;если да, то на передачу
       RECEIVE:                    ;иначе на прием
          .
          .
          JMP SHORT ANOTHER        ;проверяем нет ли другого прерывания
       TRANSMIT:                   ;здесь код для передачи
          .
          .
       ;---перед выходом, проверяем нет ли другого прерывания
       ANOTHER:   MOV  DX,BASEADDRESS       ;базовый адрес
          INC  DX                  ;указываем на регистр идентификации
          INC  DX                  ;прерывания
          IN   AL,DX               ;читаем его значение
          TEST AL,1                ;проверяем бит 1
          JNZ  NEXT_INT            ;если он установлен, то на начало
          MOV  AL,20H              ;иначе код завершения аппаратного
          OUT  20H,AL              ;прерывания
          IRET
       IO_INT      ENDP

          7.1.9 Сводка управляющих кодов, используемых при коммуникации.

          Эта таблица содержит 32  управляющих  кода  ASCII, которые ис-
       пользуются при коммуникации, а также при работе принтера и других
       устройств.  Добавлен также код  ASCII  127 - DEL (Забой), который
       обычно используется как управляющий код, хотя его и нельзя выдать
       с клавиатуры с помощью сочетания Ctrl + клавиша. Применение неко-
       торых  кодов,  таких как возврат каретки, инвариантно.  Но  боль-
       шинство других управляющих кодов имеют широкий диапазон интерпре-
       тации, во многом из-за отсутствия совместимости оборудования.

                     Раздел 2. Создание драйвера устройства.

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

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


                                     -278-
       каждый раз когда он потребуется.  Этот подход тратит много памяти
       и может быть достаточно медленным.

          2.  Создать ряд драйверов устройств и потребовать, чтобы прог-
       рамма загружала необходимый в качестве оверлея (т.е. помещать его
       в   область  программы,  специально  оставленную  для  этой  цели
       [1.3.5]).

          3. Создать драйвер устройства как отдельную программу, которая
       указывается в командном файле, выполняемом при загрузке  системы.
       Программа  запускается  и  устанавливает  драйвер  устройства как
       программу обработки прерывания.  После этого программа завершает-
       ся, но остается резидентной в  памяти,  как  объяснено в [1.3.4].
       Впоследствии наша программа использует этот драйвер через  вектор
       прерывания.

          4. Создать полноценный драйвер  устройства, который будет заг-
       ружаться при старте с помощью файла CONFIG.SYS.  MS DOS поддержи-
       вает такой тип драйверов устройств и однажды загруженный он может
       использовать все возможности команд DOS, включая проверку ошибок.
       Специальная команда IOCTL (Контроль ввода/вывода) позволяет прог-
       рамме  узнать статус драйвера и послать ему  управляющую  строку,
       помимо обычного потока данных.
          Первые три стратегии легко  реализуются  с помощью информации,
       приведенной  в остальных частях данной книги.  Но устанавливаемые
       драйверы устройств очень сложны.  Зато когда он есть, то он очень
       мощен.   В этом случае система будет работать с устройством  нас-
       только же тесно, как  с  клавиатурой  или  дисковым  накопителем.
       Устройству может быть присвоено имя, например, SERIALPR для  пос-
       ледовательного принтера, и затем это устройство может быть откры-
       то  для  доступа из любого языка.  В Бейсике оператор  OPEN  "SE-
       RIALPR" FOR OUTPUT AS #2 подготовит  последовательный принтер для
       вывода.  В языке ассемблера Вы сможете получить доступ к принтеру
       как с помощью  метода  управляющего  блока файла, так и с помощью
       метода дескриптора файла, включая очень мощную функцию IOCTL. При
       этом пользователь имеет возможность доступа к устройству на уров-
       не  операционной системы и может просто ввести команду COPY A:MY-
       FILE SERIALPR:, чтобы скопировать содержимое файла на принтер.
          Устанавливаемые драйверы устройств  могут быть написаны только
       на  языке ассемблера.  Они могут обслуживать два типа  устройств:
       символьные и блочные. Эти имена  описывают единицы, которыми уст-
       ройство  обрабатывает данные.  Обычно драйверы блочных  устройств
       обслуживают  дисковые  накопители,  а  драйверы  символьных - все
       остальное, начиная от последовательных принтеров и кончая робота-
       ми.  Блочные устройства  обмениваются блоками данных, поэтому они
       занимаются накоплением данных. Символьные устройства обмениваются
       данными побайтно,  поэтому  они  лучше  подходят  для управляющих
       устройств,  а  также для устройств, которые не  могут  обеспечить
       высокую скорость обмена данными. Драйверы блочных устройств очень
       сложны  и здесь нет достаточно места, чтобы объяснить их структу-
       ру.  Очень редко кому требуется написать такой драйвер. Техничес-
       кое  руководство по MS DOS предоставляет всю необходимую информа-
       цию и содержит полный пример драйвера виртуального диска в опера-
       тивной  памяти.  Вы можете просмотреть эту информацию после  того
       как изучите обсуждение драйверов  символьных устройств, приведен-
       ное здесь.
          Устанавливаемые  драйверы устройств беспощадны к программистс-
       ким ошибкам.  Поскольку драйверы автоматически загружаются систе-
       мой  при загрузке, то невозможно использовать отладчики для выяв-
       ления причин неполадок.  Поэтому будьте предельно внимательны при
       их написании.
          Программа драйвера устройства разбивается на три части, каждая
       из которых обсуждается  отдельно  в  следующих разделах.  Это (1)
       


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

          7.2.1 Создание заголовка драйвера.

          Драйверы  устройств  должны  создаваться  в  виде  COM  файлов
       [1.3.6]. Однако они не являются настоящими программами, поскольку
       у  них отсутствует префикс программного сегмента.  Чтобы добиться
       этого не надо включать оператор ORG  100H в начале программы, как
       это  делается  для COM файлов.  Либо запишите ORG 0, либо  вообще
       ничего не пишите.  Драйвер  должен  быть описан как далекая (far)
       процедура,  как  и в любой программе.  В нижеприведенном  примере
       приведен начальный код для драйвера устройства с именем DEVICE12.
       Оно  заменяет  стандартное устройство AUX, используемое  MS  DOS,
       принимая вывод функции 4 прерывания  21H. Весь драйвер устройства
       состоит из кода этого раздела вместе с кодом, приведенном в  сле-
       дующих двух разделах; поместите  их  подряд один за другим, чтобы
       получить полную программу.
          Драйвер устройства должен начинаться с заголовка драйвера.  Он
       имеет длину 18 байтов, разделенных  на 5 полей.  Первое поле (DD)
       всегда содержит значение -1 (FFFFFFFFH), и когда MS DOS загружает
       драйвер, то оно заменяется на стартовый  адрес следующего драйве-
       ра.   Таким  образом, система может искать следующий  драйвер  по
       цепочке. У последнего  загруженного драйвера в этом поле остается
       значение -1.
          Второе поле это байт атрибутов драйвера. Имеют значение только
       7 битов этого слова:

       бит 15   1 = символьное устройство, 0 = блочное устройство
           14   1 = поддерживает IOCTL, 0 = не поддерживает IOCTL
           13   1 = формат блоков IBM, 0 = другой формат блоков
            3   1 = часы, 0 = не часы
            2   1 = нулевое устройство, 0 = не нулевое устройство
            1   1 = устройство стандартного вывода, 0 = нет
            0   1 = устройство стандартного ввода, 0 = нет

       Обычно установлен только бит 15, или биты 15 и 14, если устройст-
       во поддерживает IOCTL (как обсуждается в [7.2.4]).  Бит 13  уста-
       навливается только для блочных устройств.  Остальные биты исполь-
       зуются  для  замены устройств, используемых MS DOS  по  умолчанию
       (устройствами стандартного  ввода  и вывода являются клавиатура и
       видеодисплей;  устройство часов объединяет часы реального времени
       с часами времени суток BIOS; а  нулевое  устройство  (NULL) - это
       псевдоустройство, используемое для тестовых целей).
          Третье и четвертое поля содержат смещения для процедур страте-
       гии и обработки прерывания, которые будут рассмотрены в следующих
       разделах.  Наконец, последнее поле содержит имя устройства.   Имя
       может содержать до  8  символов  и  оно  должно быть выравнено по
       левому краю с завершающими пробелами.  Для замены существующих  в
       DOS устройств, таких как  LPT1  или  COM1,  используйте то же имя
       устройства, как в данном примере.

          Низкий уровень.

          В данном примере создается драйвер для последовательного  уст-
       ройства. "DEVICE12" -  имя  файла,  который  должен быть указан в
       файле  конфигурации сиситемы, чтобы этот драйвер был загружен.  В
       байте атрибутов установлен  только  бит 15, указывая что это сим-
       вольное устройство и что оно не поддерживает IOCTL.  DEV_STRATEGY
       и DEV_INTERRUPT - имена процедур,  обсуждаемых в следующих разде-
       


                                     -280-
       лах.   Устройство названо AUX, с тем чтобы заменить обычное  уст-
       ройство MS DOS с этим  именем.  Это  позволяет очень просто обра-
       щаться к этому устройству, поскольку система имеет предопределен-
       ный номер файла для обращения к  устройству AUX (последовательно-
       му).   В пример включен начальный код для драйвера,  определяющий
       его как COM программу.

       CSEG      SEGMENT PUBLIC 'CODE'   'устанавливаем кодовый сегмент
                 ORG 0                   'эта строка необязательна
                 ASSUME CS:CSEG,DS:CSEG,ES:CSEG
       DEVICE12  PROC FAR         'драйвер это далекая процедура
                 DD   0FFFFFFFFH  'адрес следующего драйвера
                 DW   8000H       'байт атрибутов
                 DW   DEV_STATEGY 'адрес процедуры стратегии
                 DW   DEV_INTERRUPT  'адрес процедуры прерывания
                 DB   'AUX     ' 'имя устройство (дополненное пробелами)

          7.2.2 Создание стратегии устройства.

          Процедура  стратегии  устройства  требует  только  пяти строк.
       Когда  система загружает устройство, то она создает блок  данных,
       называемый заголовком запроса. Он имеет две функции. Во-первых он
       служит  областью  данных для внутренних операций системы.   Более
       важно то, что заголовок запроса  служит  областью,  через которую
       происходит  обмен  информацией между драйвером и  вызывающей  его
       программой. Например, когда драйвер выводит данные, то ему дается
       адрес данных через заголовок запроса.  Когда же драйвер завершает
       свою работу, то он устанавливает  в заголовке запроса байт стату-
       са,  который доступен вызывающей программе, тем самым давая  воз-
       можность ей узнать об ошибке.
          MS DOS создает заголовок  запроса  при установке драйвера уст-
       ройства  (когда  система загружается).  Процедура стратегии  уст-
       ройства выполняется только один раз в этот момент. При этом ES:BX
       указывают на вновь созданный заголовок запроса и процедуре  нужно
       просто скопировать их, чтобы  впоследствии  он мог быть обнаружен
       при  обращении к драйверу.  Адреса смещения и сегмента  заголовка
       помещаются в две переменные. В  следующем разделе Вы увидите, что
       при  обращении к драйверу, первое что он делает - восстанавливает
       значения ES:BX, чтобы можно было получить информацию из заголовка
       запроса.
          Размер заголовка запроса может меняться, в зависимости от типа
       сделанного запроса к драйверу (напр.  инициализация, вывод данных
       или возврат статуса). Однако первые 13 байт заголовка всегда одни
       и те же. Их формат таков:
       


                                     -281-
       1. Длина заголовка запроса (DB).
       2. Код устройства (DB). Определяет номер для блочных устройств.
       3.   Код команды (DB).  Здесь хранится номер последней  посланной
       драйверу команды. Эти коды перечислены в [7.2.3].
       4. Статус (DW).   Статус  устанавливается  каждый  раз при вызове
       драйвера. Если установлен бит 15, то в младших восьми битах нахо-
       дится код ошибки. Коды ошибок перечислены в [7.2.3].
       5. Резервная область (8 байтов). Используется MS DOS.
       6. Данные необходимые для работы драйвера (переменной длины).

          Низкий уровень.

          Вот 5 строк процедуры стратегии  устройства. Отмечаем, что две
       словные  переменные, хранящие значения ES и BX, следуют за  инст-
       рукцией RET, как и положено в формате COM.

       DEV_STRATEGY:   MOV  CS:KEEP_ES,ES
                       MOV  CS:KEEP_BX,BX
                       RET
       KEEP_CS         DW ?
       KEEP_BX         DW ?

          7.2.3 Создание обработчика прерывания устройства.

          Драйвер устройства начинается с  двух порций кода, приведенных
       в  предыдущих разделах.  За ними должна следовать соответствующая
       процедура обработки прерывания. На самом деле, это неверно, назы-
       вать  эту процедуру процедурой обработки прерывания, так как  она
       вовсе не обслуживает прерывание и завершается обычной инструкцией
       RET.
          Имеется  13 типов функций, которые может выполнять  устанавли-
       ваемый драйвер устройства.  Когда драйвер вызывается функцией DOS
       (скажем функцией  3FH  прерывания  21H,  которая читает данные из
       файла или устройства), то функция помещает кодовый номер от 1  до
       13 в однобайтное  поле  по  смещению  2  в заголовке запроса (для
       ввода - кодовый номер 5).  Затем управление передается  процедуре
       обработки прерывания  драйвера,  адоес  которой  определяется при
       просмотре  заголовка  драйвера [7.2.1].  Эта  процедура в  первую
       очередь восстанавливает ES:BX, с тем чтобы они указывали на заго-
       ловок  запроса, а затем читает кодовый номер команды.   По  этому
       коду процедура  обработки  прерывания  вызывает нужную процедуру,
       которая  выполнит требуемую функцию.  Процедура ищется с  помощью
       13-словной таблицы,  содержащей  смещения  для  13 типов функций.
       Функции всегда перечисляются в следующем порядке:

          1. INITIALIZE (инициализация)
          2. CHECK_MEDIA (проверка носителя)
          3. MAKE_BPB
          4. IOCTL_IN
          5. INPUT_DATA (ввод данных)
          6. NONDESTRUCT_IN
          7. INPUT_STATUS (статус ввода)
          8. CLEAR_INPUT (очистка ввода)
          9. OUTPUT_DATA (вывод данных)
         10. OUTPUT_VERIFY (проверка вывода)
         11. OUTPUT_STATUS (статус вывода)
         12. CLEAR_OUTPUT (очистка вывода)
         13. IOCTL_OUT

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


                                     -282-
       буемой  степени контроля ошибок и управления устройством.  Номера
       функций, для которых не  написаны  процедуры,  должны завершаться
       выходом из драйвера без выполнения чего-либо.  В этом случае надо
       только перед  выходом  установить  биты  15, 8, 1 и 0 в заголовке
       запроса, чтобы информировать вызывающую задачу, что была затребо-
       вана несуществующая  функция  (бит  15  индицирует  ошибку, бит 8
       показывает, что драйвер работает нормально, а биты 0 и 1 дают код
       ошибки 3, что соответствует "неизвестной команде").
          Но одна функция должна  присутствовать  во всех драйверах уст-
       ройств, и это функция номер 1 - инициализация.  Эта функция авто-
       матически выполняется при загрузке драйвера, а затем нет. Одна из
       важных  задач,  выполняемая этой  процедурой,  состоит  установке
       адреса конца драйвера в четырех  байтах, начинающихся со смещения
       14 в заголовке запроса. В нижеприведенном примере конец программы
       отмечен меткой eop:. Кроме этой  задачи,  процедура инициализации
       должна  также  выполнить всю необходимую для  данного  устройства
       инициализацию. На рис.  7-4 показана структура драйвера устройст-
       ва.
          Какие  из  оставшихся 12-ти функций будут  включены в  драйвер
       устройства зависит от того, что драйвер должен делать. Некоторые,
       такие  как  CHECK_MEDIA  и MAKE_BPB, относятся  только к  блочным
       устройствам  (они  устанавливают   тип  диска,  размер секторов и
       т.д.).   Для  символьных устройств наиболее важными являются  две
       функции: INPUT_DATA и  OUTPUT_DATA  (отметим, что эти имена несу-
       щественны  - важна позиция в таблице функций, которая неизменна).
       В обоих случаях заголовок запроса имеет следующую структуру:

       13 байтов    стандартный формат заголовка запроса
        1 байт      байт описания среды (только для блочных устройств)
        4 байта     смещение/сегмент буфера обмена данных
        2 байта     число байтов, которое надо передать
        2 байта     стартовый номер сектора (только для блочных)

       В нижеприведенном примере используется функция вывода. Процедура,
       выполняющая  вывод получает из заголовка запроса адрес буфера,  в
       котором находятся выводимые данные  (смещение 14). Она также счи-
       тывает  число байтов, которое надо вывести (смещение 18).   Когда
       процедура завершит вывод данных, то она установит слово статуса в
       заголовке запроса (смещение 3) и возвратит управление.  Если опе-
       рация успешна, то  надо  установить  бит  8 слова статуса. Другие
       возможности будут обсуждены позднее.

          Низкий уровень.

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

       ;---инициализация обработчика прерывания устройства
       DEV_INTERRUPT:  PUSH ES     ;сохраняем регистры
                       PUSH DS
                       PUSH AX
                       PUSH BX
                       PUSH CX
                       PUSH DX
                       PUSH SI
                       PUSH DI
                       PUSH BP

          MOV  AX,CS:KEEP_ES    ;ES:BX указывают на заголовок запроса
          MOV  ES,AX            ;
          MOV  BX,CS:KEEP_BX    ;
          MOV  AL,ES:[BX]+2     ;получаем код команды из заголовка
          SHL  AL,1             ;умножаем на 2 (т.к. таблица словная)
       


                                     -283-
          SUB  AH,AH            ;обнуляем AH
          LEA  DI,FUNCTIONS     ;DI указывает на смещение до таблицы
          ADD  DI,AX            ;добавляем смещение в таблице
          JMP  WORD PTR [DI]    ;переходим на адрес из таблицы

       FUNCTIONS       LABEL  WORD  ;это таблица функций
          DW   INITIALIZE
          DW   CHECK_MEDIA
          DW   MAKE_BPB
          DW   IOCTL_IN
          DW   INPUT_DATA
          DW   NONDESTRUCT_IN
          DW   INPUT_STATUS
          DW   CLEAR_INPUT
          DW   OUTPUT_DATA
          DW   OUTPUT_VERIFY
          DW   OUTPUT_STATUS
          DW   CLEAR_OUTPUT
          DW   IOCTL_OUT
       ;---выход из драйвера, если функция не поддерживается
       CHECK_MEDIA:
       MAKE_BPB:
       IOCTL_IN:
       INPUT_DATA:
       NONDESTRUCT_IN:
       INPUT_STATUS:
       CLEAR_INPUT:
       OUTPUT_VERIFY:
       OUTPUT_STATUS:
       CLEAR_OUTPUT:
       IOCTL_OUT:
          OR   ES:WORD PTR [BX]+3,8103H   ;модифицируем статус
          JMP  QUIT
       ;---процедуры для двух поддерживаемых кодов
       INITIALIZE:   LEA  AX,E_O_P      ;смещение конца программы в AX
          MOV  ES:WORD PTR [BX]+14,AX   ;помещаем его в заголовок
          MOV  ES:WORD PTR [BX]+16,CS   ;
           .
          (здесь идет инициализация устройства)
           .
          JMP  QUIT
       OUTPUT_DATA:  MOV  CL,ES:[BX]+18 ;получаем число символов
          CBW  CX                       ;CX используем как счетчик
          MOV  AX,ES:[BX]+16            ;получаем адрес буфера данных
          MOV  DS,AX                    ;
          MOV  DX,ES:[BX]+14            ;
           .
          (здесь идут операции по выводу)
           .
          JMP  QUIT
       ;---выходим, модифицируя байт статуса в заголовке запроса
       QUIT:   OR   ES:WORD PTR [BX]+3,100H  ;устанавливаем бит 8
          POP BP                    ;восстанавливаем регистры
          POP DI                    ;
          POP SI                    ;
          POP DX                    ;
          POP CX                    ;
          POP BX                    ;
          POP AX                    ;
          POP DS                    ;
          POP ES                    ;
          RET
       E_O_P:              ;метка конца программы
       


                                     -284-
       DEVICE12     ENDP
       CSEG         ENDS
                    END    DEVICE12

          Перед возвратом драйвер устанавливает слово статуса в заголов-
       ке запроса.  В данном примере это делается в двух местах, в зави-
       симости от того вызывалась  функция  обеспечиваемая драйвером или
       нет. Эти строки выглядят так: OR ES:WORD PTR [BX]+3,XXXXH. Значе-
       ние битов XXXX следующее:

          биты 0-7   код ошибки (если бит 15 = 1)
          бит    8   устанавливается в 1, когда функция завершена
          бит    9   устанавливается в 1, когда драйвер занят
        биты 10-14   зарезервированы MS DOS
          бит   15   устанавливается при возникновении ошибки

       Младший байт этого  слова  содержит  следующие  коды ошибок, если
       установлен бит 15, индицирующий ошибку:

          0    попытка записи на защищенное от записи устройство
          1    неизвестное устройство
          2    устройство не готово
          3    неизвестная команда
          4    ошибка проверки по контрольной сумме
          5    неверная длина запроса к устройству
          6    ошибка поиска
          7    неизвестный носитель
          8    сектор не найден
          9    нет бумаги в принтере
          A    ошибка записи
          B    ошибка чтения
          C    общая ошибка

          7.2.4 Доступ к драйверу устройства.

          Драйвер устройства устанавливается путем включения имени гото-
       вой программы в файл конфигурации системы.  Для установки пробной
       программы поместите  в  файл  CONFIG.SYS  строку  DEVICE  = DEVI-
       CE12.COM.   Затем перезагрузите систему для  установки  драйвера.
       Если машина не будет  загружаться, то скорее всего имеется ошибка
       в коде инициализации драйвера.
          После того как драйвер установлен, для доступа к нему пользуй-
       тесь обычными функциями MS  DOS  прерывания  21H.   Какие функции
       можно  использовать зависит от того, заменяет ли устройство стан-
       дартное устройство DOS (как в приведенном примере) или оно добав-
       ляется  как совершенно новое устройство.  Для замены стандартного
       последовательного устройства,  назовите  драйвер  AUX, после чего
       функции  3 [7.1.7] и 4 [7.1.6] прерывания 21H будут  осуществлять
       соответственно ввод и  вывод.   Если  устройство параллельное, то
       назовите  его  PRN, после чего функция 5 [6.3.1]  будет  выводить
       данные на принтер.  Другой  возможностью  является  использование
       функции 3FH [5.4.4] для ввода и [5.4.3] для вывода. В этом случае
       используйте номер файла 3 - для последовательного  устройства и 4
       - для параллельного.  Напоминаем, что при использовании предопре-
       деленных номеров файла нет необходимости открывать устройство.
          Если устройство не заменяет  одно  из стандартных устройств MS
       DOS (т.е.  если оно не названо одним из резервных слов, таким как
       PRN, AUX и т.д.), то Вы можете открыть устройство с помощью одной
       из функций для открытия файла.  Вы можете использовать как  метод
       доступа с помощью управляющего  блока файла, так и метод дескрип-
       тора файла, хотя последний предпочтительнее.  Чтобы быть  уверен-
       ным, что Вы по ошибке не  откроете дисковый файл, поместите номер
       файла в BX, 0 - в AL, посде чего выполните функцию 44H прерывания
       


                                     -285-
       21H. Это функция IOCTL и если  бит 7 значения, возвращаемого в DL
       установлен, то драйвер устройства загружен.
          IOCTL  требует,  чтобы в байте атрибутов драйвера  была  соот-
       ветствующая установка битов и чтобы по крайней мере основы проце-
       дуры  обработки IOCTL имелись в процедуре обработчика  прерывания
       драйвера. Функция IOCTL имеет 8  подфункций, пронумерованных от 0
       до  7, при этом соответствующий кодовый номер помещается в AL при
       вызове функции:

          0    Возвратить информацию об устройстве в DX
          1    Установить информацию об устройстве, используя DL (DH=0)
          2    Считать CX байтов от драйвера устройства через управля-
               щий канал и поместить их начиная с DS:DX
          3    Записать CX байтов в драйвер устройства через управляющий
               канал, взяв их начиная с DS:DX
          4    То же, что и 2, но использовать номер накопителя в BL,
               где 0 = накопитель по умолчанию, 1 = A и т.д.
          5    То же, что и 3, но использовать номер накопителя как в 5
          6    Получить статус ввода
          7    Получить статус вывода

          В  ответ возвращается различная информация, в  зависимости  от
       того, какая функция вызвана. Для  подфункций 0 и 1 значение битов
       регистра  DX следующее (при условии, что бит 7 = 1, что означает,
       что доступ получен к устройству, а не к файлу):

          0    1 = устройство консольного ввода
          1    1 = устройство консольного вывода
          2    1 = нулевое устройство
          3    1 = устройство часы
          4    резерв
          5    1 = нет проверки на Ctrl-Z, 0 = есть проверка на Ctrl-Z
          6    1 = не конец файла, 0 = конец файла
          7    1 = устройство, 0 = дисковый файл
       8-13    резерв
         14    1 = если можно использовать подфункции 2 и 3, 0 = нельзя
         15    резерв

          Подфункции 2-5 позволяют  программе  и устройству обмениваться
       произвольными  управляющими строками.  Это  позволяет  передавать
       управляющие сообщения  отдельно  от  основного потока данных, что
       существенно упрощает дело.  При возврате AX будет содержать число
       переданных байтов.  Подфункции 6-7 позволяют программе проверить,
       готово  ли устройство для ввода или вывода.  Для  устройств в  AL
       возвращается FF, если  устройство  готово и 0, если нет.  При ис-
       пользовании с открытым файлом (бит 7 = 0) в AL возвращается FF до
       тех пор, пока не будет доститгнут конец файла.
          Отметим, что в Бейсике 3.0 добавлены операторы IOCTL и IOCTL$.
       Они позволяют бейсиковской программе, соответственно, посылать  и
       принимать управляющие строки от драйвера устройства, которое было
       предварительно  открыто оператором OPEN.  Выходная строка  должна
       быть заключена в кавычки, как в IOCTL #3,"...". Подобным образом,
       A$ = IOCTL$(3) принимает информацию о статусе через IOCTL.

          7.2.5 Обнаружение и анализ ошибок устройства.

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


                                     -286-
       восстановления.

          Высокий уровень.

          Интерпретатор  Бейсика  обнаруживает   многие  ошибки, включая
       ошибки  драйверов устройств.  При обнаружении ошибки возвращается
       код ошибки и если не  предусмотрена  программа восстановления при
       ошибках,  то программа останавливается.  Однако можно  установить
       обработку ошибок, с тем чтобы когда происходит критическая ошибка
       Бейсик  автоматически переходил на процедуру  восстановления  при
       сбоях, которую Вы создали. Процедура может проанализировать код и
       определить в какой строке программы произошла ошибка.  После того
       как это сделано, программа может принять меры по устранению ошиб-
       ки, либо с помощью пользователя, либо выполняя другую часть прог-
       раммы.  После того, как эта  процедура завершена, программа может
       продолжить  выполнение с любого места, с которого Вы захотите  (с
       некоторыми ограничениями). Код  для тщательного анализа ошибочных
       ситуаций может существенно увеличить размер программы.   Отметим,
       что компилятора Бейсика даже  минимальные проверки на ошибки пот-
       ребуют  дополнительно  по не менее чем 4 байта на  каждую  строку
       программы.
          Чтобы разрешить обработку  ошибок в Бейсике поместите в начале
       программы  строку ON ERROR GOSUB n, где n это номер строки  прог-
       раммы, в которой начинается процедура обработки ошибок.  При воз-
       никновении  критической ошибки управление будет передано  на  эту
       строку. В начале  процедуры  поместите  ряд строк вида IF ERR = n
       THEN  номерстроки,  где n - номер ошибки, взятый из приложения  к
       руководству по Бейсику, содержащему  сообщения об ошибках. Номера
       строк в этих операторах соответствуют началу кода, обрабатывающе-
       го данную конкретную ошибку.  Эти части могут быть в свою очередь
       разбиты  на  куски рядом операторов IF ERL = n THEN  номерстроки.
       ERL возвращает номер строки, в которой произошла ошибка, позволяя
       процедуре восстановления точно определить ошибочное место.
          После того как процедура восстановления завершила свою  работу
       надо использовать  оператор  RESUME  для возврата управления в ту
       строку, где произошла ошибка.  За этим оператором может следовать
       номер, в этом случае управление  будет  передано на строку с ука-
       занным  номером.  Однако, имейте ввиду, что  нельзя  использовать
       RESUME для перехода в точку  программы, которая находится за пре-
       делами процедуры, в которой произошла ошибка. Если восстановление
       после ошибки невозможно, но необходимо, чтобы программа продолжи-
       ла  свою работу, то напишите RESUME NEXT и управление будет пере-
       дано на строку, следующую за той, в которой произошла ошибка. Вот
       общая структура процедуры восстановления в Бейсике:

       100 ON ERROR GOSUB 5000   'разрешаем обработку ошибок
        .
        .
       5000 IF ERR = 61 THEN 5100  'диск полон
       5010 IF ERR = 71 THEN 5200  'диск не готов
        .
        .
       5100 IF ERL = 2080 THEN 5120   'где произошла ошибка?
       5110 BEEP: PRINT "Disk in drive B: is full": RESUME
       5120 BEEP: PRINT "Disk in drive A: is full": RESUME
        .
       5200 BEEP: PRINT "A disk drive is not ready"
       5210 PRINT "Strike any key when corrected"
       5220 IF INKEY$ = "" THEN 5220    'ожидаем нажатия клавиши
       5230 RESUME ERL - 10             'пытаемся повторить операцию

          В Бейсике 3.0 введены инструкции ERDEV и ERDEV$.  Обе они поз-
       воляют получить переменные  только  для чтения от прерывания 24H,
       


                                     -287-
       обрабатывающего  критичекие  ошибки.  Z% = ERDEV возвращает в  Z%
       слово статуса, в котором старший байт содержит 13-15 биты атрибу-
       та  заголовка устройства, а младший байт - код ошибки  прерывания
       24H.  Z$ = ERDEV$ помещает в Z$ 8-байтное имя устройства для сим-
       вольных  устройств  и 2-байтный указатель накопителя для  блочных
       устройств.

          Низкий уровень.

          Иногда драйверы устройств содержат такие серьезные ошибки, что
       программа просто не может продолжаться, пока они не будут исправ-
       лены. Когда такие ошибки происходят, то система вызывает обработ-
       чик  критических  ошибок.  Он может вступать в действие  как  для
       стандартных устройств, так и для установленных драйверов. Пользо-
       ватель  наиболее часто сталкивается с ним, когда пытается  произ-
       вести дисковую операцию с  дисководом, у которого открыта дверца.
       В  этом  случае  появляется сообщение: "Not ready  error  reading
       drive A - Abort, Retry, Ignore?"
          Обработчик критических ошибок  может  быть переписан, чтобы он
       лучше  обрабатывал устройства, для которых Вы создали  устанавли-
       ваемые драйверы. Вектор  прерывания  24H указывает на стандартную
       процедуру MS DOS, но Вы можете перенаправить вектор на свою  про-
       цедуру. При вызове этой  процедуры старший бит AH содержит 0 если
       ошибка  произошла на блочном устройстве и 1, если на  символьном.
       BP:SI указывают на заголовок драйвера виновного устройства, кото-
       рый может дать дополнительную информацию.  Восемь байтов, начиная
       со смещения AH в заголовке содержат  имя устройства, а обработчик
       критичеких  ошибок помещает код ошибки длиной в слово в DI.   Вот
       кодовые номера (они не представляют битовых позиций):

          Код           Проблема

           0      попытка писать на диск, защищенный от записи
           1      неизвестное устройство
           2      накопитель не готов
           3      неизвестная команда
           4      ошибка обмена данными
           5      неверная длина запроса
           6      ошибка поиска
           7      неизвестный тип носителя
           8      сектор не найден
           9      нет бумаги в принтере
           A      ошибка при записи
           B      ошибка при чтении
           C      общая ошибка

       В случае дисковой ошибки AL содержит номер накопителя, на котором
       произошла  ошибка (0 = A, 1 = B и т.д.), а биты 2-0 AH индицируют
       тип ошибки. Бит 0 устанавливается, если ошибка произошла во время
       операции  записи, и сбрасывается - если при чтении.  Биты 2-1 со-
       держат информацию о том, в  каком  месте  диска произошла ошибка,
       давая  00  - для начальных секторов DOS, 01 - для FAT,  10 -  для
       каталога и 11 - для всего остального диска.
          Имеется три способа, которыми  программа  может восстановиться
       после критической ошибки:

       1.  Можно попросить пользователя устранить причину ошибки (напри-
       мер, закрыть дверцу  накопителя),  после чего система предоставит
       устройству возможность повторить операцию.
       2.  Управление может быть возвращено инструкции, следующей за INT
       21H, которая сделала попытку обратиться к драйверу.
       3. Программа может завершиться и вернуть управление системе.

       


                                     -288-
          Ваша процедура обработки  ошибок  может восстановить ситуацию,
       выдав  инструкцию  IRET,  после того, как она поместила  0 в  AL,
       чтобы игнорировать ошибку, 1  -  чтобы  повторить  операцию и 2 -
       чтобы завершить программу.  Если Вы хотите, чтобы Ваша  процедура
       провела восстановление сама, то  она должна восстановить регистры
       выполняемой  программы  из стека, а затем удалить со  стека  все,
       кроме последних трех слов. После  этого инструкция IRET возвратит
       управление  программе, хотя сама система останется в нестабильном
       состоянии до тех пор, пока она не сделает вызов функции с номером
       большим, чем 12.  Вот конфигурация стека (начиная сверху до низа)
       когда вызывается обработчик критических ошибок:

       Адрес возврата обработчика ошибок:  IP, CS, флаги

       Пользовательские регистры задачи,   AX, BX, CX, DX, SI, DI, BP,
       из которой был вызван драйвер:      DS, ES, IP, CS, флаги

          MS DOS обрабатывает также многие  некритические  ошибки.  Сюда
       включаются  коды ошибок, которые могут возвращаться в  регистрах,
       когда вызывалась функция DOS. Эти коды обсуждаются в данной книге
       в  тех  местах, в которых  описываются  соответствующие  функции.
       Однако имейте ввиду, что  начиная  с версии 3.0 MS DOS возвращает
       расширенные  коды ошибок для функций, использующих FCB или  деск-
       рипторы файлов.  Когда при выполнении одной из этих функций уста-
       навливается флаг переноса, то в AX возвращается обычный код ошиб-
       ки. Дополнительный расширенный код доступен через прерывание 59H,
       если в BX поместить 0.  Эта функция сообщает также о  критических
       ошибках и она может быть  использована из обработчика критических
       ошибок, вызываемого через прерывание 24H.
          Функция  помещает  в AX код ошибки, взятый из обычного  списка
       знакомых кодов ошибок (например, "недостаточно  памяти") или один
       из новых кодов (например, "ограничение доступа" для  многопользо-
       вательской системы).  BH  возвращает  код класса ошибки, указывая
       какого  типа  ошибка произошла.  Например, код 1  указывает,  что
       исчерпаны ресурсы, т.е.  что  память,  файловые буфера или что-то
       еще  израсходовано.  Другие классы могут указывать на программные
       ошибки, проблемы с носителями, форматированием и т.д. BL содержит
       код, предполагающий действие для восстановления, такое как  "пов-
       торить", "прекратить" или "запросить у пользователя". Наконец, CH
       возвращает число,  определяющее  место  где возникли проблемы: на
       блочном устройстве, на символьном, в памяти?
          Данные для этих кодов ошибок весьма обширны. Полную информацию
       о них см.  в  Техническом  руководстве  по MS DOS 3.0.  Поскольку
       предполагается,  что MS DOS 3.0 не будет использоваться на  маши-
       нах, более ранних, чем AT, то  использование этих кодов ограничи-
       вает совместимость Ваших программ.  Тем не менее, набор процедур,
       предназначенный только для MS  DOS  3.0  может дополняться поверх
       обычных  процедур обработки ошибок.  В [1.1.3] показано как прог-
       рамма может определить версию MS DOS, в которой она работает.
          Наконец, имейте ввиду, что процесс может передавать код завер-
       шения вызвавшему его процессу. Термин процесс относится к взаимо-
       действующим программам.  Например, когда одна программа загружает
       и  запускает другую с помощью функции EXEC, то запускаемая  прог-
       рамма называется потомком, а  запускающая  программа - родителем.
       Родителю может  потребоваться  информация  о  том, как завершился
       потомок.  Чтобы использовать эту возможность, поместите  желаемый
       код завершения в AL и  выполните  функцию  4CH прерывания 21H для
       завершения программы. Когда управление будет возвращено родителю,
       то он выполнит функцию 4DH прерывания 21H (без входных регистров)
       и  в  AL будет получен код завершения, который может  затем  быть
       проанализирован. Кроме того, AH будет содержать информацию о том,
       как  завершился  потомок: 0 - для нормального завершения, 1 -  по
       Ctrl-Break, 2 - по критической ошибке  устройства и 3 - с помощью
       


                                     -289-
       функции 31H, оставляющей задачу резидентной.
          Если программа завершилась с помощью этой функции (а не 20H  -
       см. обсуждение в [1.3.4]),  то  MS  DOS  получает код выхода и он
       может быть включен в обработку командным файлом с помощью  подко-
       манды IF. Эта  подкоманда  позволяет  условное  исключение других
       команд из командного файла.  Код выхода рассматривается как номер
       ERRORLEVEL и условные операции выполняются в зависимости от того,
       больше он или нет определенного числа. С помощью этой возможности
       командные файлы могут прекращать  обработку и выводить сообющение
       о  возникновении  ошибки в одной из запущенных  программ.   Более
       подробная информация приведена в разделе  "Команды пакетной обра-
       ботки" руководства по операционной системе.

          Раздел 3. Использование специальных устройств ввода/вывода.

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

       Адрес порта        Функция

         00-0F      микросхема DMA 8237 (не для PCjr)
         20-2F      микросхема прерываний 8259 (AT контроллер #1: 20-3F)
         40-4F      микросхема таймера 8253/8254
         60-6F      микросхема PPI 8255 (AT использует только адреса
                    клавиатуры
         70-7F      часы реального времени (только AT)
         80-83      регистры страниц DMA (не для PCjr)
         A0-BF      микросхема прерываний #2 (только AT)
         C0-C7      микросхема звука SN76496 (только PCjr)
         F0-FF      PCjr - контроллер НГМД, AT - управление математиче-
                    ским сопроцессором
       1F0-1F8      фиксированный диск AT
       200-20F      игровой адаптер
       278-27F      AT коммуникационный порт #2
       2F8-2FF      коммуникационный порт COM2 (COM1 для PCjr)
       320-32F      фиксированный диск XT
       378-37F      адаптер параллельного принтера для PC, XT, AT
       3B0-3BF      монохромный/параллельный адаптеры (не для PCjr)
       3D0-3DF      цветной графический адаптер
       3F0-3F7      контроллер НГМД
       3F8-3FF      коммуникационный адаптер COM1 (модем PCjr)

          7.3.1 Чтение/запись с кассетного магнитофона.

          Только очень немногие IBM PC и PCjr используют кассетный  маг-
       нитофон, а XT и AT не поддерживают  его вообще.  Помимо того, что
       он очень ненадежен, обмен с кассетным магнитофоном возможен толь-
       ко последовательный, но не с прямым доступом. Тем не менее, могут
       быть причины для программирования кассетного магнитофона на PCjr.
       Имейте ввиду, что кассетные операции используют канал 2 микросхе-
       мы  таймера 8253 [2.1.1], поэтому не пытайтесь  одновременно  ис-
       пользовать этот канал для  других  целей.  Отметим также, что при
       операции  чтения  с кассеты, запрещено прерывание времени  суток,
       поэтому счетчик времени суток  BIOS  будет давать неверное значе-
       ние.
       


                                     -290-
          Высокий уровень.

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

          .B      программа на Бейсике
          .P      защищенная программа на Бейсике
          .A      программа на Бейсике в формате ASCII
          .M      файл изображения памяти
          .D      последовательный файл данных

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

          Средний уровень.

          BIOS работает с кассетной лентой порциями в 256-байтные блоки.
       Набору блоков предшествует "лидер", который состоит из 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 Чтение позиции светового пера.

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

          Высокий уровень.
       


                                     -291-
          Бейсик может определять  позицию светового пера двумя способа-
       ми.  При первом программа непрерывно определяет статус пера.  При
       втором, когда перо используется, то управление временно передает-
       ся процедуре, обеспечиваемой Вашей программой.  Для  непрерывного
       контроля за пером  надо  использовать  оператор 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.
          Данный пример вызывает остановку программы, когда нажата кноп-
       ка на световом пере. Точка  в  позиции  светового пера включается
       процедурой, обрабатывающей включение светового пера.

       100 ON PEN GOSUB 5000   'устанавливаем процедуру для светового
       110 PEN ON              'пера и включаем режим отслеживания его
        .
        .
       5000 '''процедура обработки светового пера
       5010 X = PEN(4)         'получаем координату X
       5020 Y = PEN(5)         'получаем координату Y
       5030 PSET(X,Y)          'включить эту точку
       


                                     -292-
       5040 RETURN             '

          Средний уровень.

          Функция  4 прерывания 10H BIOS сообщает текущую позицию свето-
       вого пера.  У нее нет входных регистров. При возврате AX содержит
       0,  если перо не включено и 1 - если получены новые значения  для
       его позиции. Возвращается два  набора  координат, позиции точки и
       позиции строки и столбца. Позиции символа содержатся в DX, причем
       DH содержит строку (0-24), а DL  -  столбец (0-79). Позиция точки
       содержится  в CH и BX, причем CH содержит вертикальную координату
       (0-199), а BX - горизонтальную (0-319 или 0-639, в зависимости от
       режима терминала).

       ;---читаем и запоминаем положение светового пера
          MOV  AH,4            ;номер функции
          INT  10H             ;прерывание BIOS
          CMP  AH,1            ;новая позиция?
          JE   NO_READING      ;если нет, то уходим
          MOV  COL,BX          ;сохраняем горизонтальную координату
          MOV  CL,CH           ;помещаем вертикальную координату
          MOV  CH,0            ;в CX
          MOV  ROW,CX          ;сохраняем вертикальную координату

          Низкий уровень.

          По своей сути световое перо является расширением  видеосистемы
       и как таковое использует микросхему контроллера CRT 6845. Позиция
       светового  пера  дается одним 2-хбайтным значением, хранящимся  в
       регистрах 10H (старший байт) и  11H  (младший байт) микросхемы. В
       [4.1.1]  объясняется как читать регистры микросхемы.   Посмотрите
       пример.  Порт с адресом  3DCH  устанавливает  задвижку  светового
       пера, а с номером 3DBH - сбрасывает ее.

       ;---проверка светового пера и чтение его позиции
          MOV  DX,3DAH       ;указываем на регистр статуса
          IN   AL,DX         ;получаем информацию
          TEST AL,4          ;проверяем выключатель
          JNZ  NOT_SET       ;на выход
          TEST AL,2          ;проверяем триггер
          JZ   NOT_SET       ;на выход
          SUB  DX,7          ;указываем на регистр адреса 6845
          MOV  AL,10H        ;запрос на старший байт позиции пера
          OUT  DX,AL         ;посылаем запрос
          INC  DX            ;указываем на регистр данных 6845
          IN   AL,DX         ;получаем значение
          XCNG AH,AL         ;запоминаем его в AH
          DEC  DX            ;возвращаемся к адресному регистру
          MOV  AL,11H        ;теперь хотим получить младший байт
          OUT  DX,AL         ;посылаем запрос
          INC  DX            ;назад к регистру данных
          IN   AL,DX         ;теперь это значение в AX

          7.3.3 Получение аналогового ввода через игровой порт.

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


                                     -293-
          Высокий уровень.

          Функция STICK возвращает позиции  по осям, указываемую следую-
       щими кодовыми номерами:

          0    ось X джойстика A
          1    ось Y джойстика A
          2    ось X джойстика B
          3    ось Y джойстика B

       Вам нужно написать, например, X = STICK(0) и в X будет содержать-
       ся значение координаты X для  джойстика  A.  Но эта функция имеет
       ловушку, о которой Вам необходимо знать. Только при использовании
       кода  0  действительно читаются координаты  джойстика,  при  этом
       читаются все 4 значения. Кодовые номера 1-3 просто сообщают пока-
       зания, прочитанные кодом 0. Чтобы получить последние 3 координаты
       Вам необходимо перед этим использовать функцию X = STICK(0), даже
       если Вам не нужно знать значение, возвращаемое кодом 0.
          Джойстики  отличаются  по  своим  физическим  характеристикам,
       поэтому необходимо настраивать их,  чтобы их предельные положения
       совпадали  с границами экрана.  В следующем примере показано  как
       это делается. В  примере  непрерывно  рисуется  точка, в позиции,
       указываемой джойстиком, действие, которое требуется, чтобы диапа-
       зон значений,  принимаемых  игровым  портом,  преобразовывался  в
       диапазон позиций экрана.

       100 '''получаем предельные показания джойстика
       110 STRIG ON            'разрешаем кнопки
       120 V= STRIG(0)         'чистим старые показания
       130 PRINT "Briefly push button 1 when stick is farthest to left"
       140 XLEFT = STICK(0)    'получаем самое левое значение
       150 IF STRIG(0) = 0 THEN 140  'ждем нажатия кнопки
       160 STRIG OFF: FOR N = 1 TO 1000: NEXT: STRIG ON
       170 PRINT "Briefly push button 1 when stick is farthest to right"
       180 XRIGHT = STICK(0)   'получаем самое правое значение
       190 IF STRIG(0) = 0 THEN 180  'ждем нажатия кнопки
       200 STRIG OFF: FOR N = 1 TO 1000: NEXT: STRIG ON
       210 PRINT "Briefly push button 1 when stick is farthest to top"
       220 V = STICK(0): YTOP = STICK(1)  'самое верхнее значение
       230 IF STRIG(0) = 0 THEN 220  'ждем нажатия кнопки
       240 STRIG OFF: FOR N = 1 TO 1000: NEXT: STRIG ON
       250 PRINT "Briefly push button 1 when stick farthest to bottom"
       260 V = STICK(0): YBOTTOM = STICK(1)  'самое нижнее значение
       270 IF STRIG(0) = 0 THEN 260  'ждем нажатия кнопки
       280 STRIG OFF           'закончили
       290 '''получаем множители для установки на размер экрана
       300 XRIGHT = XRIGHT - XLEFT   'горизонтальный размер
       310 XMULTIPLIER = 320/XRIGHT  'вычисляем число точек на деление
       320 YBOTTOM = YBOTTOM - YTOP  'вертикальный размер
       330 YMULTIPLIER = 200/B/YBOTTOM  'число точек на деление
       340 '''теперь вычисляем координаты в режиме умеренного разрешения
       350 X = STICK(0)        'получаем горизонтальную позицию
       360 Y = STICK(1)        'получаем вертикальную позицию
       370 X = (X - XLEFT)*XMULTIPRIER  'приводим к экрану
       380 Y = (Y - YTOP)*YMULTIPRIER   '
       390 PSET(X,Y)           'выводим точку в нужной позиции
       400 GOTO 350            'повторяем

          Средний уровень.

          Только  AT предоставляет поддержку джойстика на уровне  опера-
       ционной системы.  Это функция 84H прерывания 15H, которая возвра-
       щает координаты, причем:
       


                                     -294-
          AX  =  координата X джойстика A
          BX  =  координата Y джойстика A
          CX  =  координата X джойстика B
          DX  =  координата Y джойстика B

       При входе поместите в DX 1. Когда в DX содержится 0, то эта функ-
       ция возвращает состояние кнопок джойстика  [7.3.4].  При возврате
       флаг переноса установлен, когда у машины нет игрового порта.

          Низкий уровень.

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

       Бит             Джойстик                  Весло

        3      координата Y джойстика B      координата весла D
        2      координата X джойстика B      координата весла C
        1      координата Y джойстика A      координата весла B
        0      координата X джойстика A      координата весла A

          Координата  может  описываться одним битом за  счет  измерения
       временных интервалов.  Пошлите в  порт байт с произвольным значе-
       нием.   Это приведет к тому, что младшие 4 бита обнулятся.  Затем
       постоянно считывайте значение из порта, замеряя интервал времени,
       через который интересующий Вас бит станет равным 1. Этот интервал
       пропорционален позиции джойстика по интересующей Вас оси.  Макси-
       мальны еинтервалы соответствуют  нижней позиции по оси Y и правой
       позиции по оси X.  Независимо от позиции, биты меняются от 0 к  1
       очень быстро, по сравнению с  механической  скоростью перемещения
       джойстика или весла.  Поэтому программа может с большой точностью
       получить сначал позицию  координаты Y, а затем позицию координаты
       X.   Нет необходимости тестировать каждую координату отдельно.  В
       данном примере определяется координата X джойстика A.

       ;---получаем координату X джойстика A
          MOV  DX,201H         ;адрес игрового порта
          OUT  DX,AL           ;посылаем в порт произвольное значение
          MOV  AH,1            ;проверяем бит 1
          MOV  SI,0            ;инициализируем счетчик
       NEXT:   IN   AL,DX      ;читаем значение из порта
          TEST AL,AH           ;проверяем бит 1
          JE   FINISHED        ;выход, когда бит установлен
          INC  SI              ;иначе, увеличиваем счетчик
          LOOP NEXT            ;на начало цикла
       FINISHED:

          7.3.4 Получение цифрового ввода из игрового порта.

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

          Высокий уровень.
       


                                     -295-
          Бейсик использует оператор  STRIG  для  чтения статуса кнопок.
       Оператор STRIG достаточно хитрый, чтобы обнаружить нажатие  кноп-
       ки, даже если программа  в  данный  момент не занимается статусом
       кнопки, т.е.  программа может запросить: "Было ли нажатие кнопки,
       со времени моего последнего запроса?"  Это свойство очень полезно
       для видеоигр, поскольку программа может заниматься  манипуляциями
       с экраном, не  заботясь  о  постоянной  проверке  статуса кнопок.
       Однако  это существенно замедляет скорость выполнения  программы,
       поскольку проверка статуса кнопок осуществляется после выполнения
       каждого оператора.  По этой причине, 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 возвращает установку
       


                                     -296-
       кнопок в битах 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,
       


                                     -297-
       а за шестнадцатиричными числами  -  буква  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
       65,  просмотрите вышеприведенную таблицу на значения битов, кото-
       рые она содержит: 64 и 1, что соответствует 01000001B.
          Зачем связываться с двоичными числами?  Одна из причин состоит
       в том, что компьютер хранит информацию в статусных байтах  памяти
       и статусных регистрах микросхем.  Отдельные части этой информации
       распределены по одному или двум байтам.  Это достигается назначе-
       нием определенных  битов  определенным  данным.  Например, помимо
       других  вещей,  байт статуса может сообщить сколько  принтеров  и
       дисковых  накопителей  присоединено  к  Вашей машине.  Скажем два
       старших  байта  содержат число принтеров, а два  младших -  число
       дисковых  накопителей.  Байт  статуса  расположен  в определенной
       ячейке  памяти  и как и любой байт может иметь значения  от 0  до
       255. Если значение этого байта  равно  66 или 01000010 в двоичной
       


                                     -298-
       форме,  то два старших байта равны 01, а два младших байта -  10.
       Первая пара говорит о том,  что  у  нас  имеется  один принтер, а
       вторая - что 2 дисковых накопителя. Группа битов, рассматриваемая
       совместно таким образом называется  полем. Часто Вашим программам
       приходится  читать  статусные байты или  регистры, а  иногда  Вам
       нужно изменить установку битов.  Эти  операции тривиальны в языке
       ассемблера,  но не в Бейсике.  В приложении Б объясняется как они
       производятся в Бейсике.

       Шестнадцатиричные числа:

          В то время как в  двоичных  числах  каждая  последующая  цифра
       вдвое больше предыдущей, в шестнадцатиричных числах каждая после-
       дующая цифра больше в 16 раз.  У  десятичных чисел первая позиция
       соответствует  единицам, вторая - десяткам, третья -  сотням.   У
       двоичных чисел первая  позиция  соответствует  единицам, вторая -
       двойкам,  третья - четверкам.  У шестнадцатиричных  чисел  первая
       позиция соответствует единицам,  вторая - 16, третья - 256 и т.д.
       Это  означает, что когда в позиции единиц расположена цифра 9, то
       прибавление единицы не приводит  к  переносу  в следующий разряд,
       как это было бы в случае десятичных чисел.  Но как записать деся-
       тичное число 10 одной цифрой? Ответ состоит в том, что шестнадца-
       тиричные  числа  используют первые 6 букв латинского  алфавита  в
       качестве дополнительных цифровых символов:

          шестнадцатиричный символ      десятичный эквивалент

                   A                             10
                   B                             11
                   C                             12
                   D                             13
                   E                             14
                   F                             15

       Перечисление шестнадцатиричных чисел продолжается так: ...  8, 9,
       A, B, C, D, E, F, 10, 11 ... 19, 1A, 1B и т.д.
          Полезность шестнадцатиричных чисел опирается на тот факт,  что
       одна шестнадцатиричная цифра  описывает содержимое ровно 1/2 бай-
       та.   Например, в числе F6 F соответствует старшим четырем  битам
       байта, а 6 - младшим  четырем  битам  (четыре  бита взятые вместе
       называются ниблом, или по-русски "огрызком").  Несложно вычислить
       двоичный эквивалент  четверки  битов.  FH  =  1111B, а 6H = 0110B
       (напоминаем,  что  H и B - суффиксы, помогающие Вам  отличить  11
       двоичное от 11 десятичного и 11 шестнадцатиричного).  Таким обра-
       зом  число F6H представляет цепочку битов 11110110.   Двухбайтное
       число (целое) может равняться 6FF6H.  В этом случае цепочка битов
       для него имеет вид 0110111111110110. Если число состоит только из
       трех цифр, то верхняя половина старшего  байта равна нулю, напри-
       мер, числу F6FH соответствует цепочка битов 0000111101101111.
          Шестнадцатиричные  числа намного легче читать,  чем  двоичные.
       После небольшой практики оказывается, что работать с ними намного
       удобнее, чем с десятичными.

       Адреса памяти и портов:

          Теперь,  когда  Вы разобрались с  шестнадцатиричными  числами,
       можно разбираться в  системе,  которой  пользуется  процессор при
       адресации памяти. Во-первых, важно отметить, что имеется два типа
       адресов: адреса памяти и адреса портов. Номера адресов используе-
       мые  теми  и другими совершенно не связаны;  засылка  значения  в
       ячейку памяти с алресом 2000  не  имеет  ничего общего с засылкой
       значения в порт с адресом 2000.  Доступ к портам осуществляется с
       помощью инструкций INP и OUT в Бейсике и IN в OUT языке ассембле-
       


                                     -299-
       ра. Доступ к адресам памяти осуществляется в Бейсике инструкциями
       PEEK и POKE, а в языке  ассемблера  инструкцией  MOV. Имеется 65K
       доступных адресов портов и 1024K доступных адресов памяти.
          Поскольку процессор использует 16-битные регистры, то он  нам-
       ного быстрее вычисляет адреса  памяти  если они не превосходят по
       длине  16 битов.  Однако максимальное число, которое может содер-
       жаться в 16 битах равно  65535.   Мы  будем  представлять его как
       четырехзначное  шестнадцатиричное  число FFFFH.  Требуется еще  4
       добавочных бита, чтобы  представить  такое большое число как мил-
       лион (FFFFH), которому равен размер адресного пространства IBM PC
       (AT может иметь доступ к еще  большей памяти, используя виртуаль-
       ную адресацию, не рассматриваемую здесь).
          Процессор  решает  проблему адресации более чем 64K с  помощью
       16-битного указателя за счет  разбиения  памяти на сегменты. Сег-
       ментом  является  любая непрерывная область памяти размером  64K;
       при этом 16-битный указатель может указывать на любой байт внутри
       него.   Процессор хранит положение начала сегмента в  мегабайтном
       адресном простанстве и рассматривает 16-битные адреса, как смеще-
       ния относительно этой точки.  Но как определить эту точку?  Ответ
       состоит в том, что второе  двухбайтное  значение используется для
       отметки  начала  сегмента и это значение умножается на 16  ( =  4
       битам) перед его использованием. Таким образом, если это сегмент-
       ное значение равно 2, то, умножив его на 16, получим 32, и адреса
       будут затем вычисляться как  смещение  относительно 32-го байта в
       памяти.  Если адрес в сегменте равен 7, то суммируя 32 и 7  полу-
       чаем, что нам нужно обратиться к 39-му байту памяти, а не к 7-му.
       Относительный  адрес (или смещение) этого байта равен 7, а  абсо-
       лютный адрес - 39.
          В Бейсике  Вы  можете  установить  сегментный  адрес с помощью
       оператора  DEF  SEG.  Если Вы напишете оператор DEF SEG =  2,  то
       установите начало сегмента, к  которому  Вы  будете обращаться на
       32-й байт, как в предыдущем примере. Затем Вы можете использовать
       операторы PEEK и POKE для чтения и  записи отдельных байтов памя-
       ти.   Например, PEEK(7) прочитает седьмой байт с начала сегмента,
       т.е. 39-й байт памяти.
          Во многих местах этой книги мы ссылаемся  на абсолютные адреса
       памяти.   Это  необходимо, поскольку операционная система  хранит
       важную информацию в определенных местах. Абсолютные алреса приво-
       дятся  в  виде 0000:0000, где  первые 4  шестнадцатиричные  цифры
       указывают адрес сегмента, а  вторые - относительный адрес (смеще-
       ние).  Вспоминая предыдущий пример, мы можем адресовать 39-й байт
       памяти записав 0002:0007.   Отметим, что тот же самый адрес может
       быть  записан  в другом виде, если изменить значение  сегментного
       регистра, например, 0001:0017. Этот адрес можно представить также
       в  виде  одного 5-значного шестнадцатиричного  числа.   Например,
       видеобуфер начинается с адреса  B000:0000, который можно записать
       как B0000H.  Отметим, что суффикс H опускается в специальной  ад-
       ресной нотации.
          И последнее замечание относительно использования памяти. Когда
       число  занимает два или более байтов, то младший байт этого числа
       хранится в ячейке с меньшим адресом.  Если целое число A48BH хра-
       нится, начиная с ячейки 1000:0007, то ячейка 0007 содержит 8B,  а
       0008 - A4. Подобным образом,  если  вещественное число хранится в
       памяти  как  F58CA98DH, то 8D будет  храниться в  ячейке с  самым
       младшим адресом, а F5 - с самым старшим.

                    Приложение Б. Битовые операции в Бейсике.

          В Бейсике нельзя использовать числа в двоичной форме. Он расс-
       матривает цепочку битов 11000000  как 11 миллионов, а не как 192.
       Однако  манипуляции с цепочками битов часто необходимы при  прог-
       раммировании,  поскольку  требуется  читать и изменять содержимое
       статусных байтов и статусных регистров.
       


                                     -300-
          В  большинстве случаев к цепочкам битов применяются две  логи-
       ческие  операции.  Это операции ИЛИ (OR) и И (AND) и обе они дос-
       тупны в Бейсике.  Используемые  по  отдельности  или в комбинации
       они  позволяют  программе читать и  устанавливать  индивидуальные
       биты байта.  Обе эти операции  бинарные,  т.е.  они применяются к
       паре значений, давая в качестве результата третье, в точности как
       обычные арифметические операции: Z = X OR Y. При использовании со
       значениями байтной длины эти операции выполняются 8 раз, по  разу
       для каждого бита. ИЛИ проверяет бит 0 двух байтов и если этот бит
       установлен  хотя бы в одном байте, то бит 0 будет установлен и  в
       результирующем байте.  Этот  процесс  выполняется и для остальных
       семи пар битов.  Операция И устанавливает бит результата только в
       том случае, если оба бита были  установлены,  в  противном случае
       этот бит будет равен 0.  Изучите эти две операции, используя при-
       веденную диаграмму:

             операнд1  операнд2  результат  операнд1 операнд2 результат

       бит 7    1         0          1         1        0         0
           6    1         0          1         1        0         0
           5    1         1          1         1        1         1
           4    1   OR    1    =     1         1  AND   1    =    1
           3    0         0          0         0        0         0
           2    0         0          0         0        0         0
           1    0         1          1         0        1         0
           0    0         1          1         0        1         0

          При программировании ИЛИ используется для установки одного или
       более  битов в ячейке памяти или статусном  регистре.   Например,
       может возникнуть необходимость  установить атрибут мерцания опре-
       деленному символу на экране терминала. Эта операция требует уста-
       новки седьмого бита.  Программа  может  просто записать весь байт
       атрибутов  по  нужному адресу, но состояние остальных семи  битов
       может быть неизвестным.  Поэтому  надо  прочитать байт из нужного
       места  видеобуфера и поместить его в целую переменную,  например,
       X.   Затем  готовится байт, у которого установлен только  седьмой
       бит. Как Вы знаете (или можете узнать из приложения А) такой байт
       равен 128.  Теперь просто запишите Y = X OR 128 и в Y будет то же
       значение, что и в X, но с установленным седьмым битом. Нижеприве-
       денная диаграмма иллюстрирует эту операцию:

          бит     атрибут           128     результат

           7         0               1          1
           6         1               0          1
           5         0               0          0
           4         1      OR       0     =    1
           3         0               0          0
           2         0               0          0
           1         0               0          0
           0         1               0          1

       В  числе 128 только седьмой бит установлен.  Независимо от  того,
       был ли он установлен или нет у байта атрибутов, он будет установ-
       лен в результирующем байте. Что касается остальных семи битов, то
       они будут установлены в результирующем  байте только если они уже
       были  установлены в байте атрибутов.  Операция ИЛИ может быть ис-
       пользована для установки более чем  одного бита за один прием (но
       смотрите предостерегающие замечания ниже).  Чтобы установить биты
       2 и 3 надо использовать сумму  значений  этих двух битов: 4 + 8 =
       12.

          бит     атрибут           12      результат
       


                                     -301-
           7         0               0          0
           6         1               0          1
           5         0               0          0
           4         1      OR       0     =    1
           3         0               1          1
           2         0               1          1
           1         0               0          0
           0         1               0          1

          Для сброса одного или более битов используется операция И. Для
       этого надо вычислить значение  байта,  у которого установлены все
       биты, за исключением того, который Вы хотите сбросить.   Помните,
       что все соответствующие биты  должны  быть установлены, чтобы ре-
       зультирующий  бит тоже был установлен.  Чтобы сбросить бит 7  ис-
       пользуйте значение 255 - 128 = 127:

          бит     атрибут           127     результат

           7         1               0          0
           6         1               1          1
           5         0               1          0
           4         1      AND      1     =    1
           3         0               1          0
           2         0               1          0
           1         0               1          0
           0         1               1          1

       Отметим, что каждый бит,  установленный  в байте атрибутов (кроме
       бита  7),  комбинируется с 1 в байтовом  значении  127 и  поэтому
       равен 1 и в результирующем  байте.   Биты, которые были равны 0 в
       байте атрибутов, остаются равными 0.
          Иногда программе нужно установить группу битов (поле).  Напри-
       мер, Вы хотите изменить три  младших  бита  байта видеоатрибутов,
       изменяя  тем самым цвет символа.  Пусть новая цепочка битов будет
       101.  Ей соответствует значение 5, но выполнение операции ИЛИ с 5
       может  не привести к желаемому результату, поскольку ИЛИ устанав-
       ливает бит в результирующем байте если хотя бы один из соответст-
       вующих битов был равен 1. Если средний бит был установлен в байте
       атрибутов, то он останется установленным  и в результирующем бай-
       те:

          бит     атрибут            5      результат

           2         0               1          1
           1         1               0          1
           0         0               1          1

       В  таком случае программа должна сначала сбросить все три бита  с
       помощью И, а затем установить нужные биты с помощью ИЛИ. В данном
       случае 255 - 4 - 2 - 1 = 248, поэтому сначала надо вычислить Y  =
       X AND 248, а затем Z = Y OR 5.
          Не слишком сложно  для  программы  и определить установлен или
       сброшен  определенный бит.  Для этого надо произвести операцию  И
       с байтом, у которого сброшены все  биты, кроме тестируемого (ска-
       жем,  бита 5, который равен 32).  Если результат отличен от нуля,
       то тестируемый бит был установлен:

          бит     атрибут           32      результат

           7         1               0          0
           6         0               0          0
           5         1               1          1
           4         1      AND      0     =    0
       


                                     -302-
           3         0               0          0
           2         0               0          0
           1         0               0          0
           0         1               0          0

          Ну а что делать,  когда  программе  требуется  знать установку
       двух или более битов? Например, биты 6 и 7 могут хранить номер от
       0 до 3, но если изолировать эти  биты,  то они дадут в результате
       одно из четырех (десятичных) значений: 0, 64, 128 и 192. Посколь-
       ку Бейсик вынуждает Вас работать не  с двоичными числами, то тре-
       буется  хитрая обработка, чтобы определить какому значению  соот-
       ветствует данная цепочка битов. Чтобы позволить Вам избежать этих
       махинаций  приводятся  две процедуры.  Первая из них  преобразует
       десятичное число, хранящееся в байте, в строку из восьми 1 или 0.
       Отметим,  что это символьная строка, а не двоичное число.  Вторая
       процедура берет такую  строку  (любой  длины)  и преобразует ее в
       десятичное число.  Используя эти процедуры Вы можете легко анали-
       зировать статусные байты  в  памяти.  С  помощью  функции MID$ Вы
       можете  выделить битовое поле и преобразовать содержащееся в  нем
       значение в десятичное число.   Вот процедура преобразования деся-
       тичного числа в двоичную строку:

       100 STATUSBYTE = PEEK(13): GOSUB 1000
       110 PRINT BITPATTERN$   'печатаем получившуюся строку
        .
        .
       1000 '''преобразуем 10-ное число в строку из 8 единиц и нулей
       1010 BITPATTERN$ = ""    'чистим переменную
       1020 FOR N = 7 TO 0 STEP -1  'идем назад, начиная с бита 7
       1030 IF STATUSBYTE - 2^N < 0 THEN 1060  'переход если бит равен 0
       1040 BITPATTERN$ = "1" + BITPATTERN$    'добавляем к строке 1
       1050 STATUSBYTE = STATUSBYTE - 2^N: GOTO 1070
       1060 BITPATTERN$ = "0" + BITPATTERN$    'добавляем к строке 0
       1070 NEXT
       1080 RETURN

          Важно  отметить, что порядок битов в двоичной строке обратный.
       Вместо того, чтобы двигаться  слева  направо  от бита 7 к биту 0,
       бит  0 расположен самым левым в строке.  Причиной этого  является
       тот факт, что функция  MID$  может  легко  выделить требуемые Вам
       биты.  Поскольку MID$ начинает  отсчет с 1, то Вы должны считать,
       что  биты  пронумерованы от 1 до 8.  Чтобы выделить  четвертый  и
       пятый биты, напишите BITFIELD$ = MID$(BITSTRING,4,2). Затем опре-
       делите десятичное значение (от 0 до 3) хранящееся в поле, исполь-
       зуя процедуру обратного преобразования:

       100 BITFIELD$ = MID$(BITPATTERN,4,2): GOSUB 2000
       110 PRINT DECVALUE
        .
        .
       2000 '''преобразуем строку 1 и 0 в десятичное число
       2010 DECVALUE = 0   'чистим переменную
       2020 FOR N = 1 TO LEN(BITFIELD$)  'повторяем до конца поля
       2030 DECVALUE = DECVALUE + VAL(MID$(BITFIELD$,N,1)*2^(N-1)
       2040 NEXT
       2050 RETURN

               Приложение В. Основные сведения об языке ассемблера.

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


                                     -303-
       которые  помогут новичкам разобраться в примерах на  этом  языке.
       Внимательный просмотр  разделов,  посвященных  среднему и низкому
       уровням,  даст Вам возможность получить представление о том,  как
       работает ассемблер, после чего намного легче изучить разные част-
       ные  вопросы.  Здесь обсуждаются не все ассемблерные  инструкции,
       встречающиеся в программах,  но  Вы  обнаружите,  что  около 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.  Но от чего отсчитывать смеще-
       ние? Ответ заключается в том, что все данные собраны в одну часть
       программы,  а весь исполняемый код - в другую.  Часть, отведенная
       под данные, называется сегментом данных,  а под программу - кодо-
       вым  сегментом.  Все переменные, отведенные для хранения  данных,
       адресуются через смещение относительно начала сегмента данных.
          Позиция в памяти, с которой начинается сегмент данных, хранит-
       


                                     -304-
       ся в регистре 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 хранится указатель стека, который всегда указы-
       вает на вершину стека и  изменяется при засылке на стек и выборке
       из стека.
          На  первый  взгляд стек кажется достаточно неуклюжим  способом
       хранения информации, но у него есть  два преимущества. Во-первых,
       доступ к его содержимому намного быстрее, чем к переменным,  хра-
       нящимся в памяти,  а,  во-вторых,  стек  может использоваться для
       многих  целей.   Он может хранить адреса возврата  из  процедуры,
       вложенной в другую  процедуру.  Впоследствии,  то же самое прост-
       ранство  может использоваться программистом для хранения  данных,
       которые должны сейчас  обрабатываться,  но для которых не хватает
       места в регистрах микропроцессора. Программа выталкивает содержи-
       


                                     -305-
       мое регистра на стек командой 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. Имеется еще  несколько  других инструк-
                   ций, связанных с пересылкой и поиском строк.

       


                                     -306-
       Язык ассемблера обеспечивает несколько вариантов этих инструкций,
       а  также ряд других специальных инструкций.  Имеется также  целый
       класс инструкций, называемых псевдооператорами, которые помещают-
       ся в текст программы с целью указания ассемблеру как обрабатывать
       данную программу. Например, один  из типов псевдооператоров авто-
       матически  вставляет часто используемый кусок кода по всей  прог-
       рамме. Такая порция кода называется макросом и именно это свойст-
       во ассемблера дало ему название "макроассемблер".
          И,  наконец,  ассемблер  имеет возможность,  которой  завидуют
       (или, по крайней мере, должны  завидовать)  все кто программирует
       только на языках высокого уровня. Имеется ввиду возможность опти-
       мальным образом  использовать  прерывания  операционной  системы.
       Ведь  это ничто иное, как готовые процедуры.  Однако вместо того,
       чтобы вызывать их по CALL, они вызываются инструкцией INT. INT21H
       вызывает  прерывание с шестнадцатиричным номером 21.  Имеется ряд
       таких прерываний, как в базовой системе ввода/вывода ПЗУ, так и в
       операционной системе, причем некоторые из этих процедур необычай-
       но мощны.  На самом деле некоторые из них настолько тесно связаны
       с  системой,  что Вы практически не можете сами написать  эквива-
       лентную процедуру.  Языки  высокого уровня позволяют использовать
       многие из этих прерываний. Они используют их для вывода на экран,
       приема ввода с клавиатуры и доступа к дискам.  Но многие действи-
       тельно полезные прерывания игнорируются языками высокого  уровня,
       например такие,  которые  позволяют  запустить из одной программы
       другую.   Некоторые  трансляторы (такие как Lattice C  или  Turbo
       Pascal) позволяют доступ к этим  прерываниям,  если Вы знаете как
       их готовить и Вы можете использовать разделы среднего уровня этой
       книги для этой цели.
          Перед  вызовом  прерывания  некоторая  информация  должна быть
       помещена в регистры процессора.  Например, прерывание, верикально
       сдвигающее экран, должно знать  размеры  сдвигаемого  окна, число
       строк  на  которое его надо сдвинуть и т.д.  Эти  значения  часто
       называют входными регистрами.  Снова  и снова Вы будете встречать
       слова "при входе BX должен содержать ...", описывающие специфика-
       цию входных  регистров.   Аналогично,  при возврате из прерывания
       некоторые  регистры возвращают значения или статусную информацию.
       Они называются  выходными  регистрами  и  мы описываем их словами
       "при выходе AX содержит ...".  Зачастую одно прерывание  содержит
       много функций. В частности, операционная система впихнула практи-
       чески все свои возможности в прерывание 21H.  Поэтому при  вызове
       прерывания необходимо  указывать  номер  функции.  Все прерывания
       (как  BIOS  так и DOS) передают номер функции в AH  (иногда в  AL
       содержится номер подфункции).
          Все сказанное  в  основном  служит  только  чтобы  дать первое
       представление о предмете.  Но если Вы будете внимательно просмат-
       ривать простейшие примеры, содержащиеся  в этой книге, то Вы пой-
       мете  стоящую  за ними логику.  Язык ассемблера  имеет  репутацию
       трудного языка. Но то, что  Вы  только  что прочитали - настоящая
       чепуха. Имеется достаточно сложностей и в языках высокого уровня.
       И если ошибки в ассемблерной  программе бывает очень сложно обна-
       ружить,  то в основном это связано с тем, что сам текст программы
       намного длиннее, чем эквивалентный  текст на языке высокого уров-
       ня  (однако ассемблерный код намного плотнее).  В настоящее время
       многие профессионалы пишут  программы  на языке C, затем анализи-
       руют  эффективность и переписывают критические кусочки программы,
       которые расходуют много  времени,  на языке ассемблера. Невозмож-
       ность  написания таких ассемблерных процедур может иногда  свести
       усилия программиста к  нулю.  Поэтому  найдите хороший букварь по
       ассемблеру и приступайте! Возможно самой большой наградой для Вас
       станет момент, когда Вы  наконец  действительно  станете понимать
       как же работает компьютер.

          Приложение Г.  Включение ассемблерных процедур в программы  на
       


                                     -307-
       Бейсике.

          Процедуры на языке ассемблера состоят из строк байтов машинно-
       го кода. При выполнении этой процедуры Бейсик передает управление
       из  последовательности инструкций, составляющих программу на Бей-
       сике, в то место, где  хранятся  инструкции,  которые  могут быть
       декодированы  в  последовательность инструкций языка  ассемблера.
       При завершении ассемблерной  процедуры  управление возвращается в
       то место бейсиковской программы, откуда была вызвана процедура.
          В этой книге ассемблерные процедуры, используемые в программах
       на Бейсике, приведены в двух видах. В обоих видах процедуры вклю-
       чены  в программу, а не хранятся в виде отдельного дискового фай-
       ла. При первом способе требуется, чтобы коды процедуры находились
       в  отдельном месте в памяти, а при втором, менее принятом,  этого
       не требуется.
          В первом способе процедура помещается в операторы 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)  'выполняем процедуру

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


                                     -308-
       внутри программы.  Каждый байт  может  быть закодирован с помощью
       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.

          Число тактов, которое надо добавить для вычисления эффективно-
       го адреса следующее:

            компоненты адреса          операнды                  такты
       


                                     -309-
       (а)  база или индекс            [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       2-3
       CALL близкая процедура                             23        3
       CALL далекая процедура                             36        5
       CALL словный указатель в памяти                  29 + EA    2-4
       CALL словный регистр указатель                     24        2
       CALL двухсловный указатель в памяти              57 + EA    2-4
       CBW                                                 2        1
       CLC                                                 2        1
       CLD                                                 2        1
       CLI                                                 2        1
       CMC                                                 2        1
       CMP  регистр, регистр                               3        2
       CMP  регистр, память                           9(13) + EA   2-4
       CMP  память, регистр                           9(13) + EA   2-4
       CMP  регистр, значение                              4       3-4
       CMP  память, значение                         10(14) + EA   3-6
       CMP  аккумулятор, значение                          4       2-3
       CMPS приемник, источник                           22(30)     1
       CMPS (REP) приемник, источник              9 + 22(30)/повтор 1
       CWD                                                 5        1
       DAA                                                 4        1
       DAS                                                 4        1
       DEC  словный регистр                                2        1
       DEC  байтный регистр                                3        2
       DEC  память                                    15(23) + EA  2-4
       DIV  байтный регистр                              80-90      2
       DIV  словный регистр                            144-162      2
       DIV  байт памяти                              (86-96) + EA  2-4
       DIV  слово памяти                           (154-172) + EA  2-4
       


                                     -310-
       ESC  значение, память                           8(12) + EA  2-4
       ESC  значение, регистр                              2        2
       HLT                                                 2        1
       IDIV байтный регистр                            101-112      2
       IDIV словный регистр                            165-185      2
       IDIV байт памяти                            (107-118) + EA  2-4
       IDIV слово памяти                           (175-194) + EA  2-4
       IMUL байтный регистр                              80-98      2
       IMUL словный регистр                            128-154      2
       IMUL байт памяти                             (86-104) + EA  2-4
       IMUL слово памяти                           (138-164) + EA  2-4
       IN   аккумулятор, байт значения                   10(14)     2
       IN   аккумулятор, DX                               8(12)     1
       INC  словный регистр                                2        1
       INC  байтный регистр                                3        2
       INC  память                                    15(23) + EA  2-4
       INT  3                                             52        1
       INT  значение байта, отличное от 3                 51        2
       INTO                                           53 или 4      1
       IRET                                               32        1
       JCXZ короткая метка                            18 или 6      2
       JMP  короткая метка                                15        2
       JMP  близкая метка                                 15        3
       JMP  далекая метка                                 15        5
       Jxxx короткая метка                            16 или 4      2
       LAHF                                                4        1
       LDS  словный регистр, двойное слово памяти       24 + EA    2-4
       LEA  словный регистр, слово памяти                2 + EA    2-4
       LES  словный регистр, двойное слово памяти       24 + EA    2-4
       LOCK                                                2        1
       LODS строка-источник                             12(16)      1
       LODS (REP) строка-источник                 9+13(17)/повтор   1
       LOOP    короткая метка                          17 или 5     2
       LOOPE   короткая метка                          18 или 6     2
       LOOPNE  короткая метка                          19 или 5     2
       LOOPNZ  короткая метка                          19 или 5     2
       LOOPZ   короткая метка                          18 или 6     2
       MOV  память, аккумулятор                         10(14)      3
       MOV  аккумулятор, память                         10(14)      3
       MOV  регистр, регистр                               2        2
       MOV  регистр, память                           8(12) + EA   2-4
       MOV  память, регистр                           9(13) + EA   2-4
       MOV  регистр, значение                              4       2-3
       MOV  значение, регистр                        10(14) + EA    3
       MOV  сегментный регистр, словный регистр            2        2
       MOV  сегментный регистр, слово памяти          8(12) + EA   2-4
       MOV  словный регистр, сегментный регистр            2        2
       MOV  слово памяти, сегментный регистр          9(13) + EA   2-4
       MOVS приемник, источник                          18(26)      1
       MOVS (REP) приемник, источник              9+17(25)/повтор   1
       MUL  байтный регистр                              70-77      2
       MUL  словный регистр                            118-133      2
       MUL  байт памяти                              (76-83) + EA  2-4
       MUL  слово памяти                           (128-143) + EA  2-4
       NEG  регистр                                        3        2
       NEG  память                                    16(24) + EA  2-4
       NOP                                                 3        1
       NOT  регистр                                        3        2
       NOT  память                                    16(24) + EA  2-4
       OR   регистр, регистр                               3        2
       OR   регистр, память                           9(13) + EA   2-4
       OR   память, регистр                          16(24) + EA   2-4
       OR   регистр, значение                              4       3-4
       


                                     -311-
       OR   память, значение                         17(25) + EA   3-6
       OR   аккумулятор, значение                          4       2-3
       OUT  байт значения, аккумулятор                   10(14)     2
       OUT  DX, аккумулятор                               8(12)     1
       POP  регистр                                       12        1
       POP  сегментный регистр                            12        1
       POP  память                                      25 + EA    2-4
       POPF                                               12        1
       PUSH регистр                                       15        1
       PUSH сегментный регистр                            14        1
       PUSH память                                      24 + EA    2-4
       PUSHF                                              14        1
       RCL  регистр, 1                                     2        2
       RCL  регистр, CL                                 8+4/бит     2
       RCL  память, 1                                  15(23) + EA  2
       RCL  память, 1                               20(28)+EA+4/бит 2
       RCR  регистр, 1                                     2        2
       RCR  регистр, CL                                 8+4/бит     2
       RCR  память, 1                                  15(23) + EA  2
       RCR  память, 1                               20(28)+EA+4/бит 2
       REP                                                 2        1
       REPE                                                2        1
       REPNE                                               2        1
       REPZ                                                2        1
       REPNZ                                               2        1
       RET  (внутрисегментный, без POP)                   20        1
       RET  (внутрисегментный, с POP)                     24        3
       RET  (межсегментный, без POP)                      32        1
       RET  (межсегментный, с POP)                        31        3
       ROL  регистр, 1                                     2        2
       ROL  регистр, CL                                 8+4/бит     2
       ROL  память, 1                                  15(23) + EA  2
       ROL  память, 1                               20(28)+EA+4/бит 2
       ROR  регистр, 1                                     2        2
       ROR  регистр, CL                                 8+4/бит     2
       ROR  память, 1                                  15(23) + EA  2
       ROR  память, 1                               20(28)+EA+4/бит 2
       SAHF                                                4        1
       SAL  регистр, 1                                     2        2
       SAL  регистр, CL                                 8+4/бит     2
       SAL  память, 1                                  15(23) + EA  2
       SAL  память, 1                               20(28)+EA+4/бит 2
       SAR  регистр, 1                                     2        2
       SAR  регистр, CL                                 8+4/бит     2
       SAR  память, 1                                  15(23) + EA  2
       SAR  память, 1                               20(28)+EA+4/бит 2
       SBB  регистр, регистр                               3        2
       SBB  регистр, память                           9(13) + EA   2-4
       SBB  память, регистр                          16(24) + EA   2-4
       SBB  регистр, значение                              4       3-4
       SBB  память, значение                         17(25) + EA   3-6
       SBB  аккумулятор, значение                          4       2-3
       SCAS приемник                                     15(19)     1
       SCAS (REP) приемник                          9+15(19)/повтор 1
       SHL  регистр, 1                                     2        2
       SHL  регистр, CL                                 8+4/бит     2
       SHL  память, 1                                  15(23) + EA  2
       SHL  память, 1                               20(28)+EA+4/бит 2
       SHR  регистр, 1                                     2        2
       SHR  регистр, CL                                 8+4/бит     2
       SHR  память, 1                                  15(23) + EA  2
       SHR  память, 1                               20(28)+EA+4/бит 2
       STC                                                 2        1
       


                                     -312-
       STD                                                 2        1
       STI                                                 2        1
       STOS приемник                                     11(15)     1
       STOS (REP) приемник                          9+10(14)/повтор 1
       SUB  регистр, регистр                               3        2
       SUB  регистр, память                           9(13) + EA   2-4
       SUB  память, регистр                          16(24) + EA   2-4
       SUB  регистр, значение                              4       3-4
       SUB  память, значение                         17(25) + EA   3-6
       SUB  AL, значение                                   4       2-3
       TEST регистр, регистр                               3        2
       TEST регистр, память                           9(13) + EA   2-4
       TEST регистр, значение                              5       3-4
       TEST память, значение                             11 + EA   3-6
       TEST AL, значение                                   4       2-3
       WAIT                                             3 + 5n      1
       XCNG AL, словный регистр                            3        1
       XCNG память, регистр                           17(25) + EA  2-4
       XCNG регистр, регистр                               4        2
       XLAT таблица-источник                              11        1
       XOR  регистр, регистр                               3        2
       XOR  регистр, память                           9(13) + EA   2-4
       XOR  память, регистр                          16(24) + EA   2-4
       XOR  регистр, значение                              4       3-4
       XOR  память, значение                         17(25) + EA   3-6
       XOR  AL, значение                                   4       2-3

                                   Приложения.

          Приложение А.  Двоичные и шестнадцатиричные числа и  адресация
       памяти.

          Основной единицей хранения данных в компьютере является бит. В
       большинстве  микрокомпьютеров восемь битов объединены в байт, при
       этом каждый бит байта может  быть  установлен или "включен" (= 1)
       или  сброшен или "выключен" (= 0), допуская 256 разных вариантов.
       Таким образом, в одном байте можно  представить 256 разных симво-
       лов  (расширенный набор кодов ASCII) или целое число в  диапазоне
       от 0 до 255.  Хотя мы привыкли  записывать эти числа в десятичной
       форме,  они могут записываться также в двоичной или  шестнадцати-
       ричной форме - их значения  при  этом  не изменяются, а программы
       могут с одинаковой легкостью читать эти значения как в той, так и
       в другой форме. Вместо  того,  чтобы  говорить, что в одном байте
       могут  храниться числа от 0 до 255, можно сказать, что могут хра-
       ниться двоичные числа от 00000000 до 11111111 или шестнадцатирич-
       ные  числа от 00 до FF.  Поскольку можно перепутать разные формы,
       то двоичные  и  шестнадцатиричные  числа  отмечаются  специальным
       образом. В языке ассемблера за двоичными числами следует буква B,
       а за шестнадцатиричными числами  -  буква  H, например, 11111111B
       или  FFH.   Бейсик фирмы Microsoft  предваряет  шестнадцатиричные
       числа символами &H, например  &FFH; к сожалению, он не распознает
       числа в двоичной форме.

       Двоичные числа:

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


                                     -313-
       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
       65,  просмотрите вышеприведенную таблицу на значения битов, кото-
       рые она содержит: 64 и 1, что соответствует 01000001B.
          Зачем связываться с двоичными числами?  Одна из причин состоит
       в том, что компьютер хранит информацию в статусных байтах  памяти
       и статусных регистрах микросхем.  Отдельные части этой информации
       распределены по одному или двум байтам.  Это достигается назначе-
       нием определенных  битов  определенным  данным.  Например, помимо
       других  вещей,  байт статуса может сообщить сколько  принтеров  и
       дисковых  накопителей  присоединено  к  Вашей машине.  Скажем два
       старших  байта  содержат число принтеров, а два  младших -  число
       дисковых  накопителей.  Байт  статуса  расположен  в определенной
       ячейке  памяти  и как и любой байт может иметь значения  от 0  до
       255. Если значение этого байта  равно  66 или 01000010 в двоичной
       форме,  то два старших байта равны 01, а два младших байта -  10.
       Первая пара говорит о том,  что  у  нас  имеется  один принтер, а
       вторая - что 2 дисковых накопителя. Группа битов, рассматриваемая
       совместно таким образом называется  полем. Часто Вашим программам
       приходится  читать  статусные байты или  регистры, а  иногда  Вам
       нужно изменить установку битов.  Эти  операции тривиальны в языке
       ассемблера,  но не в Бейсике.  В приложении Б объясняется как они
       производятся в Бейсике.

       Шестнадцатиричные числа:

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


                                     -314-
       двоичных чисел первая  позиция  соответствует  единицам, вторая -
       двойкам,  третья - четверкам.  У шестнадцатиричных  чисел  первая
       позиция соответствует единицам,  вторая - 16, третья - 256 и т.д.
       Это  означает, что когда в позиции единиц расположена цифра 9, то
       прибавление единицы не приводит  к  переносу  в следующий разряд,
       как это было бы в случае десятичных чисел.  Но как записать деся-
       тичное число 10 одной цифрой? Ответ состоит в том, что шестнадца-
       тиричные  числа  используют первые 6 букв латинского  алфавита  в
       качестве дополнительных цифровых символов:

          шестнадцатиричный символ      десятичный эквивалент

                   A                             10
                   B                             11
                   C                             12
                   D                             13
                   E                             14
                   F                             15

       Перечисление шестнадцатиричных чисел продолжается так: ...  8, 9,
       A, B, C, D, E, F, 10, 11 ... 19, 1A, 1B и т.д.
          Полезность шестнадцатиричных чисел опирается на тот факт,  что
       одна шестнадцатиричная цифра  описывает содержимое ровно 1/2 бай-
       та.   Например, в числе F6 F соответствует старшим четырем  битам
       байта, а 6 - младшим  четырем  битам  (четыре  бита взятые вместе
       называются ниблом, или по-русски "огрызком").  Несложно вычислить
       двоичный эквивалент  четверки  битов.  FH  =  1111B, а 6H = 0110B
       (напоминаем,  что  H и B - суффиксы, помогающие Вам  отличить  11
       двоичное от 11 десятичного и 11 шестнадцатиричного).  Таким обра-
       зом  число F6H представляет цепочку битов 11110110.   Двухбайтное
       число (целое) может равняться 6FF6H.  В этом случае цепочка битов
       для него имеет вид 0110111111110110. Если число состоит только из
       трех цифр, то верхняя половина старшего  байта равна нулю, напри-
       мер, числу F6FH соответствует цепочка битов 0000111101101111.
          Шестнадцатиричные  числа намного легче читать,  чем  двоичные.
       После небольшой практики оказывается, что работать с ними намного
       удобнее, чем с десятичными.

       Адреса памяти и портов:

          Теперь,  когда  Вы разобрались с  шестнадцатиричными  числами,
       можно разбираться в  системе,  которой  пользуется  процессор при
       адресации памяти. Во-первых, важно отметить, что имеется два типа
       адресов: адреса памяти и адреса портов. Номера адресов используе-
       мые  теми  и другими совершенно не связаны;  засылка  значения  в
       ячейку памяти с алресом 2000  не  имеет  ничего общего с засылкой
       значения в порт с адресом 2000.  Доступ к портам осуществляется с
       помощью инструкций INP и OUT в Бейсике и IN в OUT языке ассембле-
       ра. Доступ к адресам памяти осуществляется в Бейсике инструкциями
       PEEK и POKE, а в языке  ассемблера  инструкцией  MOV. Имеется 65K
       доступных адресов портов и 1024K доступных адресов памяти.
          Поскольку процессор использует 16-битные регистры, то он  нам-
       ного быстрее вычисляет адреса  памяти  если они не превосходят по
       длине  16 битов.  Однако максимальное число, которое может содер-
       жаться в 16 битах равно  65535.   Мы  будем  представлять его как
       четырехзначное  шестнадцатиричное  число FFFFH.  Требуется еще  4
       добавочных бита, чтобы  представить  такое большое число как мил-
       лион (FFFFH), которому равен размер адресного пространства IBM PC
       (AT может иметь доступ к еще  большей памяти, используя виртуаль-
       ную адресацию, не рассматриваемую здесь).
          Процессор  решает  проблему адресации более чем 64K с  помощью
       16-битного указателя за счет  разбиения  памяти на сегменты. Сег-
       ментом  является  любая непрерывная область памяти размером  64K;
       


                                     -315-
       при этом 16-битный указатель может указывать на любой байт внутри
       него.   Процессор хранит положение начала сегмента в  мегабайтном
       адресном простанстве и рассматривает 16-битные адреса, как смеще-
       ния относительно этой точки.  Но как определить эту точку?  Ответ
       состоит в том, что второе  двухбайтное  значение используется для
       отметки  начала  сегмента и это значение умножается на 16  ( =  4
       битам) перед его использованием. Таким образом, если это сегмент-
       ное значение равно 2, то, умножив его на 16, получим 32, и адреса
       будут затем вычисляться как  смещение  относительно 32-го байта в
       памяти.  Если адрес в сегменте равен 7, то суммируя 32 и 7  полу-
       чаем, что нам нужно обратиться к 39-му байту памяти, а не к 7-му.
       Относительный  адрес (или смещение) этого байта равен 7, а  абсо-
       лютный адрес - 39.
          В Бейсике  Вы  можете  установить  сегментный  адрес с помощью
       оператора  DEF  SEG.  Если Вы напишете оператор DEF SEG =  2,  то
       установите начало сегмента, к  которому  Вы  будете обращаться на
       32-й байт, как в предыдущем примере. Затем Вы можете использовать
       операторы PEEK и POKE для чтения и  записи отдельных байтов памя-
       ти.   Например, PEEK(7) прочитает седьмой байт с начала сегмента,
       т.е. 39-й байт памяти.
          Во многих местах этой книги мы ссылаемся  на абсолютные адреса
       памяти.   Это  необходимо, поскольку операционная система  хранит
       важную информацию в определенных местах. Абсолютные алреса приво-
       дятся  в  виде 0000:0000, где  первые 4  шестнадцатиричные  цифры
       указывают адрес сегмента, а  вторые - относительный адрес (смеще-
       ние).  Вспоминая предыдущий пример, мы можем адресовать 39-й байт
       памяти записав 0002:0007.   Отметим, что тот же самый адрес может
       быть  записан  в другом виде, если изменить значение  сегментного
       регистра, например, 0001:0017. Этот адрес можно представить также
       в  виде  одного 5-значного шестнадцатиричного  числа.   Например,
       видеобуфер начинается с адреса  B000:0000, который можно записать
       как B0000H.  Отметим, что суффикс H опускается в специальной  ад-
       ресной нотации.
          И последнее замечание относительно использования памяти. Когда
       число  занимает два или более байтов, то младший байт этого числа
       хранится в ячейке с меньшим адресом.  Если целое число A48BH хра-
       нится, начиная с ячейки 1000:0007, то ячейка 0007 содержит 8B,  а
       0008 - A4. Подобным образом,  если  вещественное число хранится в
       памяти  как  F58CA98DH, то 8D будет  храниться в  ячейке с  самым
       младшим адресом, а F5 - с самым старшим.

                    Приложение Б. Битовые операции в Бейсике.

          В Бейсике нельзя использовать числа в двоичной форме. Он расс-
       матривает цепочку битов 11000000  как 11 миллионов, а не как 192.
       Однако  манипуляции с цепочками битов часто необходимы при  прог-
       раммировании,  поскольку  требуется  читать и изменять содержимое
       статусных байтов и статусных регистров.
          В  большинстве случаев к цепочкам битов применяются две  логи-
       ческие  операции.  Это операции ИЛИ (OR) и И (AND) и обе они дос-
       тупны в Бейсике.  Используемые  по  отдельности  или в комбинации
       они  позволяют  программе читать и  устанавливать  индивидуальные
       биты байта.  Обе эти операции  бинарные,  т.е.  они применяются к
       паре значений, давая в качестве результата третье, в точности как
       обычные арифметические операции: Z = X OR Y. При использовании со
       значениями байтной длины эти операции выполняются 8 раз, по  разу
       для каждого бита. ИЛИ проверяет бит 0 двух байтов и если этот бит
       установлен  хотя бы в одном байте, то бит 0 будет установлен и  в
       результирующем байте.  Этот  процесс  выполняется и для остальных
       семи пар битов.  Операция И устанавливает бит результата только в
       том случае, если оба бита были  установлены,  в  противном случае
       этот бит будет равен 0.  Изучите эти две операции, используя при-
       веденную диаграмму:
       


                                     -316-
             операнд1  операнд2  результат  операнд1 операнд2 результат

       бит 7    1         0          1         1        0         0
           6    1         0          1         1        0         0
           5    1         1          1         1        1         1
           4    1   OR    1    =     1         1  AND   1    =    1
           3    0         0          0         0        0         0
           2    0         0          0         0        0         0
           1    0         1          1         0        1         0
           0    0         1          1         0        1         0

          При программировании ИЛИ используется для установки одного или
       более  битов в ячейке памяти или статусном  регистре.   Например,
       может возникнуть необходимость  установить атрибут мерцания опре-
       деленному символу на экране терминала. Эта операция требует уста-
       новки седьмого бита.  Программа  может  просто записать весь байт
       атрибутов  по  нужному адресу, но состояние остальных семи  битов
       может быть неизвестным.  Поэтому  надо  прочитать байт из нужного
       места  видеобуфера и поместить его в целую переменную,  например,
       X.   Затем  готовится байт, у которого установлен только  седьмой
       бит. Как Вы знаете (или можете узнать из приложения А) такой байт
       равен 128.  Теперь просто запишите Y = X OR 128 и в Y будет то же
       значение, что и в X, но с установленным седьмым битом. Нижеприве-
       денная диаграмма иллюстрирует эту операцию:

          бит     атрибут           128     результат

           7         0               1          1
           6         1               0          1
           5         0               0          0
           4         1      OR       0     =    1
           3         0               0          0
           2         0               0          0
           1         0               0          0
           0         1               0          1

       В  числе 128 только седьмой бит установлен.  Независимо от  того,
       был ли он установлен или нет у байта атрибутов, он будет установ-
       лен в результирующем байте. Что касается остальных семи битов, то
       они будут установлены в результирующем  байте только если они уже
       были  установлены в байте атрибутов.  Операция ИЛИ может быть ис-
       пользована для установки более чем  одного бита за один прием (но
       смотрите предостерегающие замечания ниже).  Чтобы установить биты
       2 и 3 надо использовать сумму  значений  этих двух битов: 4 + 8 =
       12.

          бит     атрибут           12      результат

           7         0               0          0
           6         1               0          1
           5         0               0          0
           4         1      OR       0     =    1
           3         0               1          1
           2         0               1          1
           1         0               0          0
           0         1               0          1

          Для сброса одного или более битов используется операция И. Для
       этого надо вычислить значение  байта,  у которого установлены все
       биты, за исключением того, который Вы хотите сбросить.   Помните,
       что все соответствующие биты  должны  быть установлены, чтобы ре-
       зультирующий  бит тоже был установлен.  Чтобы сбросить бит 7  ис-
       пользуйте значение 255 - 128 = 127:
       


                                     -317-
          бит     атрибут           127     результат

           7         1               0          0
           6         1               1          1
           5         0               1          0
           4         1      AND      1     =    1
           3         0               1          0
           2         0               1          0
           1         0               1          0
           0         1               1          1

       Отметим, что каждый бит,  установленный  в байте атрибутов (кроме
       бита  7),  комбинируется с 1 в байтовом  значении  127 и  поэтому
       равен 1 и в результирующем  байте.   Биты, которые были равны 0 в
       байте атрибутов, остаются равными 0.
          Иногда программе нужно установить группу битов (поле).  Напри-
       мер, Вы хотите изменить три  младших  бита  байта видеоатрибутов,
       изменяя  тем самым цвет символа.  Пусть новая цепочка битов будет
       101.  Ей соответствует значение 5, но выполнение операции ИЛИ с 5
       может  не привести к желаемому результату, поскольку ИЛИ устанав-
       ливает бит в результирующем байте если хотя бы один из соответст-
       вующих битов был равен 1. Если средний бит был установлен в байте
       атрибутов, то он останется установленным  и в результирующем бай-
       те:

          бит     атрибут            5      результат

           2         0               1          1
           1         1               0          1
           0         0               1          1

       В  таком случае программа должна сначала сбросить все три бита  с
       помощью И, а затем установить нужные биты с помощью ИЛИ. В данном
       случае 255 - 4 - 2 - 1 = 248, поэтому сначала надо вычислить Y  =
       X AND 248, а затем Z = Y OR 5.
          Не слишком сложно  для  программы  и определить установлен или
       сброшен  определенный бит.  Для этого надо произвести операцию  И
       с байтом, у которого сброшены все  биты, кроме тестируемого (ска-
       жем,  бита 5, который равен 32).  Если результат отличен от нуля,
       то тестируемый бит был установлен:

          бит     атрибут           32      результат

           7         1               0          0
           6         0               0          0
           5         1               1          1
           4         1      AND      0     =    0
           3         0               0          0
           2         0               0          0
           1         0               0          0
           0         1               0          0

          Ну а что делать,  когда  программе  требуется  знать установку
       двух или более битов? Например, биты 6 и 7 могут хранить номер от
       0 до 3, но если изолировать эти  биты,  то они дадут в результате
       одно из четырех (десятичных) значений: 0, 64, 128 и 192. Посколь-
       ку Бейсик вынуждает Вас работать не  с двоичными числами, то тре-
       буется  хитрая обработка, чтобы определить какому значению  соот-
       ветствует данная цепочка битов. Чтобы позволить Вам избежать этих
       махинаций  приводятся  две процедуры.  Первая из них  преобразует
       десятичное число, хранящееся в байте, в строку из восьми 1 или 0.
       Отметим,  что это символьная строка, а не двоичное число.  Вторая
       процедура берет такую  строку  (любой  длины)  и преобразует ее в
       


                                     -318-
       десятичное число.  Используя эти процедуры Вы можете легко анали-
       зировать статусные байты  в  памяти.  С  помощью  функции MID$ Вы
       можете  выделить битовое поле и преобразовать содержащееся в  нем
       значение в десятичное число.   Вот процедура преобразования деся-
       тичного числа в двоичную строку:

       100 STATUSBYTE = PEEK(13): GOSUB 1000
       110 PRINT BITPATTERN$   'печатаем получившуюся строку
        .
        .
       1000 '''преобразуем 10-ное число в строку из 8 единиц и нулей
       1010 BITPATTERN$ = ""    'чистим переменную
       1020 FOR N = 7 TO 0 STEP -1  'идем назад, начиная с бита 7
       1030 IF STATUSBYTE - 2^N < 0 THEN 1060  'переход если бит равен 0
       1040 BITPATTERN$ = "1" + BITPATTERN$    'добавляем к строке 1
       1050 STATUSBYTE = STATUSBYTE - 2^N: GOTO 1070
       1060 BITPATTERN$ = "0" + BITPATTERN$    'добавляем к строке 0
       1070 NEXT
       1080 RETURN

          Важно  отметить, что порядок битов в двоичной строке обратный.
       Вместо того, чтобы двигаться  слева  направо  от бита 7 к биту 0,
       бит  0 расположен самым левым в строке.  Причиной этого  является
       тот факт, что функция  MID$  может  легко  выделить требуемые Вам
       биты.  Поскольку MID$ начинает  отсчет с 1, то Вы должны считать,
       что  биты  пронумерованы от 1 до 8.  Чтобы выделить  четвертый  и
       пятый биты, напишите BITFIELD$ = MID$(BITSTRING,4,2). Затем опре-
       делите десятичное значение (от 0 до 3) хранящееся в поле, исполь-
       зуя процедуру обратного преобразования:

       100 BITFIELD$ = MID$(BITPATTERN,4,2): GOSUB 2000
       110 PRINT DECVALUE
        .
        .
       2000 '''преобразуем строку 1 и 0 в десятичное число
       2010 DECVALUE = 0   'чистим переменную
       2020 FOR N = 1 TO LEN(BITFIELD$)  'повторяем до конца поля
       2030 DECVALUE = DECVALUE + VAL(MID$(BITFIELD$,N,1)*2^(N-1)
       2040 NEXT
       2050 RETURN

               Приложение В. Основные сведения об языке ассемблера.

          Читатель  этой  книги, не знакомый с языком ассемблера,  скоро
       поймет, что многие программистские трюки не могут быть достигнуты
       другими  средствами.  Хотя изучение языка ассемблера требует  от-
       дельной книги, в  этом  приложении  приводятся  основные понятия,
       которые  помогут новичкам разобраться в примерах на  этом  языке.
       Внимательный просмотр  разделов,  посвященных  среднему и низкому
       уровням,  даст Вам возможность получить представление о том,  как
       работает ассемблер, после чего намного легче изучить разные част-
       ные  вопросы.  Здесь обсуждаются не все ассемблерные  инструкции,
       встречающиеся в программах,  но  Вы  обнаружите,  что  около 95 %
       инструкций, встреченных Вами в программах, описаны здесь, а  зна-
       чение остальных может быть  понято благодаря комментариям к прог-
       раммам.
          Микропроцессор 8088 имеет 13 16-разрядных регистров, каждый из
       которых имеет свои  функции.  В  то  время  как в языках высокого
       уровня  Вы можете поместить два числа в переменные, а затем  сло-
       жить эти переменные, то в языке ассемблера эти числа помещаются в
       регистры микропроцессора, а затем складываются значения, содержа-
       щиеся в  регистрах.  Все  операции  в  языке ассемблера состоят в
       обмене  данных  с регистрами, а затем выполнении операций на  ре-
       


                                     -319-
       гистрах, таких как изменение отдельных  битов, выполнение арифме-
       тических  операций и т.д.  Одной из причин высокой  эффективности
       языка ассемблера является  хранение  данных в регистрах микропро-
       цессора;  компиляторы имеют тенденцию возвращать все  значения  в
       память после выполнения операции, а доступ к памяти требует боль-
       шого времени.  На рис. В-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 в
       


                                     -320-
       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.  Имеется также инструкция умень-
       


                                     -321-
                   шения (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
       


                                     -322-
       Pascal) позволяют доступ к этим  прерываниям,  если Вы знаете как
       их готовить и Вы можете использовать разделы среднего уровня этой
       книги для этой цели.
          Перед  вызовом  прерывания  некоторая  информация  должна быть
       помещена в регистры процессора.  Например, прерывание, верикально
       сдвигающее экран, должно знать  размеры  сдвигаемого  окна, число
       строк  на  которое его надо сдвинуть и т.д.  Эти  значения  часто
       называют входными регистрами.  Снова  и снова Вы будете встречать
       слова "при входе BX должен содержать ...", описывающие специфика-
       цию входных  регистров.   Аналогично,  при возврате из прерывания
       некоторые  регистры возвращают значения или статусную информацию.
       Они называются  выходными  регистрами  и  мы описываем их словами
       "при выходе AX содержит ...".  Зачастую одно прерывание  содержит
       много функций. В частности, операционная система впихнула практи-
       чески все свои возможности в прерывание 21H.  Поэтому при  вызове
       прерывания необходимо  указывать  номер  функции.  Все прерывания
       (как  BIOS  так и DOS) передают номер функции в AH  (иногда в  AL
       содержится номер подфункции).
          Все сказанное  в  основном  служит  только  чтобы  дать первое
       представление о предмете.  Но если Вы будете внимательно просмат-
       ривать простейшие примеры, содержащиеся  в этой книге, то Вы пой-
       мете  стоящую  за ними логику.  Язык ассемблера  имеет  репутацию
       трудного языка. Но то, что  Вы  только  что прочитали - настоящая
       чепуха. Имеется достаточно сложностей и в языках высокого уровня.
       И если ошибки в ассемблерной  программе бывает очень сложно обна-
       ружить,  то в основном это связано с тем, что сам текст программы
       намного длиннее, чем эквивалентный  текст на языке высокого уров-
       ня  (однако ассемблерный код намного плотнее).  В настоящее время
       многие профессионалы пишут  программы  на языке C, затем анализи-
       руют  эффективность и переписывают критические кусочки программы,
       которые расходуют много  времени,  на языке ассемблера. Невозмож-
       ность  написания таких ассемблерных процедур может иногда  свести
       усилия программиста к  нулю.  Поэтому  найдите хороший букварь по
       ассемблеру и приступайте! Возможно самой большой наградой для Вас
       станет момент, когда Вы  наконец  действительно  станете понимать
       как же работает компьютер.

          Приложение Г.  Включение ассемблерных процедур в программы  на
       Бейсике.

          Процедуры на языке ассемблера состоят из строк байтов машинно-
       го кода. При выполнении этой процедуры Бейсик передает управление
       из  последовательности инструкций, составляющих программу на Бей-
       сике, в то место, где  хранятся  инструкции,  которые  могут быть
       декодированы  в  последовательность инструкций языка  ассемблера.
       При завершении ассемблерной  процедуры  управление возвращается в
       то место бейсиковской программы, откуда была вызвана процедура.
          В этой книге ассемблерные процедуры, используемые в программах
       на Бейсике, приведены в двух видах. В обоих видах процедуры вклю-
       чены  в программу, а не хранятся в виде отдельного дискового фай-
       ла. При первом способе требуется, чтобы коды процедуры находились
       в  отдельном месте в памяти, а при втором, менее принятом,  этого
       не требуется.
          В первом способе процедура помещается в операторы DATA и прог-
       рамма  пересылается в неиспользуемую часть памяти, а затем  вызы-
       вается оператором CALL. Надо позаботиться о том, чтобы код проце-
       дуры  не  накладывался на какие-либо данные и наоборот.   Обычное
       решение этой проблемы состоит в  том,  что процедура помещается в
       те  адреса  памяти,  к которым Бейсик не может  получить  доступ.
       Поскольку интерпретатор Бейсика не  может иметь доступ за пределы
       64K,  то  для системы, скажем, с памятью  256K,  нужно  поместить
       процедуру в старшие  64K.  Для  систем  с  памятью 128K Вы должны
       вычислить  сколько памяти требуется операционной системе, Бейсику
       


                                     -323-
       и драйверам  устройств.   Допустимо,  чтобы они занимали 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.

       


                                     -324-
          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
       


                                     -325-
       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       2-3
       CALL близкая процедура                             23        3
       CALL далекая процедура                             36        5
       CALL словный указатель в памяти                  29 + EA    2-4
       CALL словный регистр указатель                     24        2
       CALL двухсловный указатель в памяти              57 + EA    2-4
       CBW                                                 2        1
       CLC                                                 2        1
       CLD                                                 2        1
       CLI                                                 2        1
       CMC                                                 2        1
       CMP  регистр, регистр                               3        2
       CMP  регистр, память                           9(13) + EA   2-4
       CMP  память, регистр                           9(13) + EA   2-4
       CMP  регистр, значение                              4       3-4
       CMP  память, значение                         10(14) + EA   3-6
       CMP  аккумулятор, значение                          4       2-3
       CMPS приемник, источник                           22(30)     1
       CMPS (REP) приемник, источник              9 + 22(30)/повтор 1
       CWD                                                 5        1
       DAA                                                 4        1
       DAS                                                 4        1
       DEC  словный регистр                                2        1
       DEC  байтный регистр                                3        2
       DEC  память                                    15(23) + EA  2-4
       DIV  байтный регистр                              80-90      2
       DIV  словный регистр                            144-162      2
       DIV  байт памяти                              (86-96) + EA  2-4
       DIV  слово памяти                           (154-172) + EA  2-4
       ESC  значение, память                           8(12) + EA  2-4
       ESC  значение, регистр                              2        2
       HLT                                                 2        1
       IDIV байтный регистр                            101-112      2
       IDIV словный регистр                            165-185      2
       IDIV байт памяти                            (107-118) + EA  2-4
       IDIV слово памяти                           (175-194) + EA  2-4
       IMUL байтный регистр                              80-98      2
       IMUL словный регистр                            128-154      2
       IMUL байт памяти                             (86-104) + EA  2-4
       IMUL слово памяти                           (138-164) + EA  2-4
       IN   аккумулятор, байт значения                   10(14)     2
       IN   аккумулятор, DX                               8(12)     1
       INC  словный регистр                                2        1
       INC  байтный регистр                                3        2
       INC  память                                    15(23) + EA  2-4
       INT  3                                             52        1
       INT  значение байта, отличное от 3                 51        2
       INTO                                           53 или 4      1
       IRET                                               32        1
       JCXZ короткая метка                            18 или 6      2
       JMP  короткая метка                                15        2
       JMP  близкая метка                                 15        3
       JMP  далекая метка                                 15        5
       Jxxx короткая метка                            16 или 4      2
       


                                     -326-
       LAHF                                                4        1
       LDS  словный регистр, двойное слово памяти       24 + EA    2-4
       LEA  словный регистр, слово памяти                2 + EA    2-4
       LES  словный регистр, двойное слово памяти       24 + EA    2-4
       LOCK                                                2        1
       LODS строка-источник                             12(16)      1
       LODS (REP) строка-источник                 9+13(17)/повтор   1
       LOOP    короткая метка                          17 или 5     2
       LOOPE   короткая метка                          18 или 6     2
       LOOPNE  короткая метка                          19 или 5     2
       LOOPNZ  короткая метка                          19 или 5     2
       LOOPZ   короткая метка                          18 или 6     2
       MOV  память, аккумулятор                         10(14)      3
       MOV  аккумулятор, память                         10(14)      3
       MOV  регистр, регистр                               2        2
       MOV  регистр, память                           8(12) + EA   2-4
       MOV  память, регистр                           9(13) + EA   2-4
       MOV  регистр, значение                              4       2-3
       MOV  значение, регистр                        10(14) + EA    3
       MOV  сегментный регистр, словный регистр            2        2
       MOV  сегментный регистр, слово памяти          8(12) + EA   2-4
       MOV  словный регистр, сегментный регистр            2        2
       MOV  слово памяти, сегментный регистр          9(13) + EA   2-4
       MOVS приемник, источник                          18(26)      1
       MOVS (REP) приемник, источник              9+17(25)/повтор   1
       MUL  байтный регистр                              70-77      2
       MUL  словный регистр                            118-133      2
       MUL  байт памяти                              (76-83) + EA  2-4
       MUL  слово памяти                           (128-143) + EA  2-4
       NEG  регистр                                        3        2
       NEG  память                                    16(24) + EA  2-4
       NOP                                                 3        1
       NOT  регистр                                        3        2
       NOT  память                                    16(24) + EA  2-4
       OR   регистр, регистр                               3        2
       OR   регистр, память                           9(13) + EA   2-4
       OR   память, регистр                          16(24) + EA   2-4
       OR   регистр, значение                              4       3-4
       OR   память, значение                         17(25) + EA   3-6
       OR   аккумулятор, значение                          4       2-3
       OUT  байт значения, аккумулятор                   10(14)     2
       OUT  DX, аккумулятор                               8(12)     1
       POP  регистр                                       12        1
       POP  сегментный регистр                            12        1
       POP  память                                      25 + EA    2-4
       POPF                                               12        1
       PUSH регистр                                       15        1
       PUSH сегментный регистр                            14        1
       PUSH память                                      24 + EA    2-4
       PUSHF                                              14        1
       RCL  регистр, 1                                     2        2
       RCL  регистр, CL                                 8+4/бит     2
       RCL  память, 1                                  15(23) + EA  2
       RCL  память, 1                               20(28)+EA+4/бит 2
       RCR  регистр, 1                                     2        2
       RCR  регистр, CL                                 8+4/бит     2
       RCR  память, 1                                  15(23) + EA  2
       RCR  память, 1                               20(28)+EA+4/бит 2
       REP                                                 2        1
       REPE                                                2        1
       REPNE                                               2        1
       REPZ                                                2        1
       REPNZ                                               2        1
       


                                     -327-
       RET  (внутрисегментный, без POP)                   20        1
       RET  (внутрисегментный, с POP)                     24        3
       RET  (межсегментный, без POP)                      32        1
       RET  (межсегментный, с POP)                        31        3
       ROL  регистр, 1                                     2        2
       ROL  регистр, CL                                 8+4/бит     2
       ROL  память, 1                                  15(23) + EA  2
       ROL  память, 1                               20(28)+EA+4/бит 2
       ROR  регистр, 1                                     2        2
       ROR  регистр, CL                                 8+4/бит     2
       ROR  память, 1                                  15(23) + EA  2
       ROR  память, 1                               20(28)+EA+4/бит 2
       SAHF                                                4        1
       SAL  регистр, 1                                     2        2
       SAL  регистр, CL                                 8+4/бит     2
       SAL  память, 1                                  15(23) + EA  2
       SAL  память, 1                               20(28)+EA+4/бит 2
       SAR  регистр, 1                                     2        2
       SAR  регистр, CL                                 8+4/бит     2
       SAR  память, 1                                  15(23) + EA  2
       SAR  память, 1                               20(28)+EA+4/бит 2
       SBB  регистр, регистр                               3        2
       SBB  регистр, память                           9(13) + EA   2-4
       SBB  память, регистр                          16(24) + EA   2-4
       SBB  регистр, значение                              4       3-4
       SBB  память, значение                         17(25) + EA   3-6
       SBB  аккумулятор, значение                          4       2-3
       SCAS приемник                                     15(19)     1
       SCAS (REP) приемник                          9+15(19)/повтор 1
       SHL  регистр, 1                                     2        2
       SHL  регистр, CL                                 8+4/бит     2
       SHL  память, 1                                  15(23) + EA  2
       SHL  память, 1                               20(28)+EA+4/бит 2
       SHR  регистр, 1                                     2        2
       SHR  регистр, CL                                 8+4/бит     2
       SHR  память, 1                                  15(23) + EA  2
       SHR  память, 1                               20(28)+EA+4/бит 2
       STC                                                 2        1
       STD                                                 2        1
       STI                                                 2        1
       STOS приемник                                     11(15)     1
       STOS (REP) приемник                          9+10(14)/повтор 1
       SUB  регистр, регистр                               3        2
       SUB  регистр, память                           9(13) + EA   2-4
       SUB  память, регистр                          16(24) + EA   2-4
       SUB  регистр, значение                              4       3-4
       SUB  память, значение                         17(25) + EA   3-6
       SUB  AL, значение                                   4       2-3
       TEST регистр, регистр                               3        2
       TEST регистр, память                           9(13) + EA   2-4
       TEST регистр, значение                              5       3-4
       TEST память, значение                             11 + EA   3-6
       TEST AL, значение                                   4       2-3
       WAIT                                             3 + 5n      1
       XCNG AL, словный регистр                            3        1
       XCNG память, регистр                           17(25) + EA  2-4
       XCNG регистр, регистр                               4        2
       XLAT таблица-источник                              11        1
       XOR  регистр, регистр                               3        2
       XOR  регистр, память                           9(13) + EA   2-4
       XOR  память, регистр                          16(24) + EA   2-4
       XOR  регистр, значение                              4       3-4
       XOR  память, значение                         17(25) + EA   3-6
       


                                     -328-
       XOR  AL, значение                                   4       2-3

          Приложение Ж. Набор инструкций микропроцессора 80286.

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

                                                         такты   байты

       AAA                                                 3       1
       AAD                                                14       2
       AAM                                                16       2
       AAS                                                 3       1
       ADC  регистр/память с регистром                   2,7*      2
       ADC  значение с регистром/памятью                 3,7*     3-4
       ADC  значение с аккумулятором                       3      2-3
       ADD  регистр/память с регистром                   2,7*      2
       ADD  значение с регистром/памятью                 3,7*     3-4
       ADD  значение с аккумулятором                       3      2-3
       AND  регистр/память с регистром                   2,7*      2
       AND  значение с регистром/памятью                 3,7*     3-4
       AND  значение с аккумулятором                       3      2-3
       CALL прямой внутри сегмента                        7+m      3
       CALL косвенный через регистр/память внутри сег-та 7+m,11+m* 2
       CALL прямой между сегмента                        13+m      5
       CBW                                                 2       1
       CLC                                                 2       1
       CLD                                                 2       1
       CLI                                                 3       1
       CMC                                                 2       1
       CMP  регистр/память с регистром                   2,6*      2
       CMP  регистр с регистром/памятью                  2,7*      2
       CMP  значение с регистром/памятью                 3,6*     3-4
       CMP  значение с аккумулятором                       3      2-3
       CMPS повторенный CX раз                          5 + 9n     2
       CMPS байт или слово                                 8       1
       CWD                                                 2       1
       DAA                                                 3       1
       DAS                                                 3       1
       DEC  регистр/память                               2,7*      2
       DEC  регистр                                        2       1
       DIV  байтный регистр                               14       2
       DIV  словный регистр                               22       2
       DIV  байт памяти                                   17*      2
       DIV  слово памяти                                  25*      2
       ESC                                              9-20*      2
       HLT                                                 2       1
       IDIV байтный регистр                               17       2
       IDIV словный регистр                               25       2
       IDIV байт памяти                                   20*      2
       IDIV слово памяти                                  28*      2
       IMUL байтный регистр                               13       2
       IMUL словный регистр                               21       2
       IMUL байт памяти                                   16*      2
       IMUL слово памяти                                  24*      2
       IMUL умножение на целое значение                21,24*     3-4
       IN   фиксированный порт                             5       2
       


                                     -329-
       IN   переменный порт                                5       1
       INC  регистр/память                               2,7*      2
       INC  регистр                                        2       1
       INS  строка                                      5 + 4m     2
       INS  байт или слово                                 5       1
       INT  указанный тип                               23 + m     2
       INT  тип 3                                       23 + m     1
       INTO                                           24 + m или 3 1
       IRET                                             17 + m     1
       JCXZ                                           8 + m или 4  2
       JMP  короткий/длинный                             7 + m     2
       JMP  прямой внутри сегмента                       7 + m     2
       JMP  косвенный через регистр/память         7 + m,11 + m*   2
       JMP  прямой между сегментами                      7 + m     2
       Jxxx                                          7 + m или 3   2
       LAHF                                                2       1
       LDS                                                 7*      2
       LEA                                                 3*      2
       LES                                                 7*      2
       LOCK                                                0       1
       LODS                                                5       1
       LODS повторенный CX раз                         5 + 4n      1
       LOOP                                           8 + 4n или 4 2
       LOOPZ/LOOPE                                    8 + 4n или 4 2
       LOOPNZ/LOOPNE                                  8 + 4n или 4 2
       MOV  регистр в регистр/память                      2,3*     2
       MOV  регистр/память в регистр                      2,5*     2
       MOV  значение в регистр/память                     2,3*    3-4
       MOV  значение в регистр                             2      2-3
       MOV  память в аккумулятор                           5       3
       MOV  аккумулятор в память                           3       3
       MOV  регистр/память в сегментный регистр           2,5*     2
       MOV  сегментный регистр в регистр/память           2,3*     2
       MOVS байт или слово                                 5       1
       MOVS повторенное CX раз                           5 + 4n    2
       MUL  байтный регистр                               13       2
       MUL  словный регистр                               21       2
       MUL  байт памяти                                   16*      2
       MUL  слово памяти                                  24*      2
       NEG                                                 2       2
       NOT  регистр/память                               2,7*      2
       OR   регистр/память с регистром                   2,7*      2
       OR   значение с регистром/памятью                 3,7*     3-4
       OR   значение с аккумулятором                       3      2-3
       OUT  фиксированный порт                             3       2
       OUT  переменный порт                                3       1
       OUTS строка                                      5 + 4m     2
       OUTS байт или слово                                 5       1
       POP  память                                         5*      2
       POP  регистр                                        5       1
       POP  сегментный регистр                             5       1
       POPA                                               19       1
       POPF                                                5       1
       PUSH память                                         5*      2
       PUSH регистр                                        3       1
       PUSH сегментный регистр                             3       1
       PUSH значение                                       3      2-3
       PUSHA                                              17       1
       PUSHF                                               3       1
       RCA  регистр/память на 1                          2,7*      2
       RCA  регистр/память на CX                      5+n, 8+n*    2
       RCA  регистр/память на число                   5+n, 8+n*    3
       RCR  регистр/память на 1                          2,7*      2
       


                                     -330-
       RCR  регистр/память на CX                      5+n, 8+n*    2
       RCR  регистр/память на число                   5+n, 8+n*    3
       RET  внутри сегмента                             11 + m     1
       RET  внутри сегмента, добавляя значение к SP     11 + m     3
       RET  между сегментами                            15 + m     1
       RET  между сегментами, добавляя значенме к SP    15 + m     3
       ROL  регистр/память на 1                          2,7*      2
       ROL  регистр/память на CX                      5+n, 8+n*    2
       ROL  регистр/память на число                   5+n, 8+n*    3
       ROR  регистр/память на 1                          2,7*      2
       ROR  регистр/память на CX                      5+n, 8+n*    2
       ROR  регистр/память на число                   5+n, 8+n*    3
       SAHF                                               2        1
       SAL  регистр/память на 1                          2,7*      2
       SAL  регистр/память на CX                      5+n, 8+n*    2
       SAL  регистр/память на число                   5+n, 8+n*    3
       SAR  регистр/память на 1                          2,7*      2
       SAR  регистр/память на CX                      5+n, 8+n*    2
       SAR  регистр/память на число                   5+n, 8+n*    3
       SBB  регистр/память с регистром                   2,7*      2
       SBB  значение с регистром/памятью                 3,7*     3-4
       SBB  значение с аккумулятором                       3      2-3
       SCAS повторенное CX раз                          5+8n       2
       SCAS байт или слово                                 7       1
       SEG  (переопределение сегмента)                     0       1
       SHL  регистр/память на 1                          2,7*      2
       SHL  регистр/память на CX                      5+n, 8+n*    2
       SHL  регистр/память на число                   5+n, 8+n*    3
       STC                                                 2       1
       STD                                                 2       1
       STI                                                 2       1
       STOS повторенное CX раз                          5+3n       2
       STOS                                                3       1
       SAL  регистр/память на 1                          2,7*      2
       SAL  регистр/память на CX                      5+n, 8+n*    2
       SAL  регистр/память на число                   5+n, 8+n*    3
       SUB  регистр/память с регистром                   2,7*      2
       SUB  значение с регистром/памятью                 3,7*     3-4
       SUB  значение с аккумулятором                       3      2-3
       TEST регистр/память с регистром                   2,6*      2
       TEST значение с регистром/памятью                 3,6*     3-4
       TEST значение с аккумулятором                       3      2-3
       WAIT                                                3       1
       XCNG регистр/память с регистром                   3,5*      2
       XCNG регистр с аккумулятором                        3       1
       XLAT                                                5       1
       XOR  регистр/память с регистром                   2,7*      2
       XOR  значение с регистром/памятью                 3,7*     3-4
       XOR  значение с аккумулятором                       3      2-3

          Приложение З. Толковый словарь IBM PC.

       146818:   Микросхема  в AT, содержащая часы реального  времени  и
       информацию о конфигурации.

       6845:  Микросхема контроллера дисплея.

       76496:  Микросхема синтезатора звука PCjr.

       765 (PD765):  Микросхема контроллера НГМД.

       8048:  Микропроцессор клавиатуры.

       


                                     -331-
       8237:  Микросхема прямого доступа к памяти (DMA).

       8250:  Микросхема коммуникационного адаптера.

       8253:  Микросхема программируемого таймера.

       8255:  Микросхема адаптера интерфейса с периферией.

       8259:  Микросхема контроллера прерываний.

       8087:  Микросхема математического сопроцессора на PC, XT и PCjr.

       8088:  Центральный процессор у PC, XT и PCjr.

       80286:  Центральный процессор у AT.

       80287:  Микросхема математического сопроцессора на AT.

       Абсолютный адрес:  Адрес памяти, выраженный в виде смещения отно-
       сительно  младшего  адреса (0000:0000), а не  относительно  како-
       го-либо определенного смещения в памяти (относительный адрес).

       Абсолютные координаты:   Координаты, указанные относительно цент-
       ральной  оси, а не относительно предыдущих используемых координат
       (относительные координаты).

       Абсолютные сектора диска:  Под  "доступом  к  абсолютному сектору
       диска" понимается чтение сектора, занимающего определенное  поло-
       жение на диске.

       Код доступа:  Этот термин  используется в Техническом руководстве
       по MS DOS для номера подфункции - т.е. для кода одной из несколь-
       ких функций, которые могут выполняться данным прерыванием.

       Подтверждение:   Сигнал  ввода/вывода,  индицирующий,  что задача
       выполнена и оборудование снова готово начать выполнение задачи.

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

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

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

       ANSI.SYS:  Драйвер устройства, поставляемый вместе с операционной
       системой,  который  способен выполнять многие функции  BIOS.   Он
       используется для достижения программной совместимости с машинами,
       использующими MS DOS, отличными от IBM PC.

       Коды  ASCII:  Набор кодов от 0 до 127, соответствующих одному  из
       128 символов ASCII. IBM  PC  использует  расширенный  набор кодов
       ASCII, состоящий из 256 символов.

       Текстовый файл ASCII:  Последовательный текстовый файл, в котором
       все числа представлены в виде  символов  ASCII, а элементы данных
       


                                     -332-
       разделены  парой  возврат  каретки/перевод строки и  конец  файла
       отмечен символом ^Z (ASCII 26).

       Строка ASCIIZ:  То же, что и строка пути.

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

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

       Яэык  ассемблера:  Язык программирования самого низкого уровня, в
       котором программист пишет инструкции  непосредственно управляющие
       работой процессора.

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

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

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

       AUTOEXEC.BAT:  Имя командного файла, который автоматически выпол-
       няется при загрузке системы.

       B:   Суффикс, обозначающий число, представленное в двоичном виде,
       например, 10111011B. См. приложение А.

       Фоновый цвет:  Фоновый цвет используется  дисплеем. Это тот цвет,
       который принимает весь экран, когда он очищен.

       Фоновые операции:  Вторичный процесс, выполняемый при  выполнении
       программы. Например, текстовый  редактор может посылать данные на
       принтер в то время, когда программа используется для редактирова-
       ния. Фоновые операции могут работать за счет использования преры-
       ваний.

       Базовый  адрес:  Младший из группы смежных адресов портов,  через
       которые осуществляется доступ к периферийному устройству.

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

       Скорость  обмена:  Число битов в секунду, которое передается  при
       обмене.

       BIOS:  Базовая система ввода/вывода, которая является частью опе-
       рационной системы, постоянно хранящейся в ПЗУ машины.

       Область  данных  BIOS:   Область  данных,  начинающаяся с  адреса
       


                                     -333-
       0040:0000, в которой  BIOS  хранит  статусную  информацию и буфер
       клавиатуры.

       Битовое  поле:  Когда байт или слово рассматриваются как  цепочка
       битов, то некоторые биты, взятые вместе, могут хранить определен-
       ный элемент информации. Например, биты 0-3 байта атрибутов симво-
       ла на дисплее образуют битовое поле,  которое определяет основной
       цвет символа.

       Битовые операции:  Программные операции, читающие или  изменяющие
       определенные биты данных.

       Битовая плоскость:  В EGA  видеобуфер разделен на четыре области,
       которые называются битовыми плоскостями 0-3.  В режиме 16-ти цве-
       тов  четыре плоскости параллельны, при этом 4 байта, относятся  к
       определенному адресу памяти  (регистры  задвижки определяют обмен
       данными между процессором и памятью дисплея). В некоторых случаях
       плоскости могут быть связаны в цепь, образуя одну или две большие
       плоскости.

       Блочные  устройства:   Устройства, которые  посылают и  принимают
       данные порциями в  блок.  Дисковые  накопители  являются наиболее
       обычными блочными устройствами.

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

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

       Код отпускания:  Тип скан-кода, который генерируется при отпуска-
       нии клавиши (код нажатия генерируется при нажатии клавиши).

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

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

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

       CD:  "Носитель обнаружен". См. DCD.

       Связь в цепочку:  У EGA видеопамять  разделена на 4 битовые плос-
       кости.   Когда они объединяются в одну или две большие плоскости,
       то это называется связью в цепочку.

       Символьное устройство:   Устройство,  которое посылает или прини-
       мает  данные  по одному символу, такие как принтер.   Сравните  с
       блочными устройствами, которые обмениваются данными блоками.

       Процесс потомок:  Программа,  запускаемая  когда другая программа
       (родитель) имеет управление.

       Циклическая  очередь:  Тип буфера данных, в котором данные встав-
       


                                     -334-
       ляются с одного конца,  а  берутся  с  другого. Текущие положения
       этих  двух концов постоянно меняются и два указателя хранят теку-
       щие положения "головы" и "хвоста".

       Кластер:  Группа дисковых секторов,  образующая основную единицу,
       которая используется при распределении дискового пространства.

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

       Кодовый сегмент:  Область  памяти, хранящая программный код (дру-
       гие сегменты хранят данные и стек).

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

       Код цвета:  Число от 0 до 15, которое относится к одному из шест-
       надцати цветов дисплея.  Для дисплея EGA, присоединенному к улуч-
       шенному графичекому адаптеру, могут быть 64 кода цвета (0-63).

       COM:  Тип исполняемого  файла, в котором привязка уже выполнена и
       поэтому все адреса уже правильно  записаны в файле перед его заг-
       рузкой.

       Командная  строка:  Строка на экране дисплея, принимающая  управ-
       ляющую информацию, такая как  строка, начинающаяся с запроса опе-
       рационной системы.

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

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

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

       Управляющий блок:  См. блок параметров.

       Управляющий код:  Один из первых  32 символов набора кодов ASCII.
       Они  обычно используются управления оборудованием, а не кодирова-
       ния данных.  Наиболее  часто  употреблямыми  управляющими  кодами
       являются возврат каретки и перевод строки.

       Управляющая строка:  Строка символов, управляющая  оборудованием.
       Управляющие строки  часто  включаются  в поток данных, посылаемых
       на  принтер или модем.  Они начинаются со  специального  символа,
       указывающего их  специальный  статус  (обычно,  символ ESC, ASCII
       27).

       CPU:   Центральный процессор, который выполняет инструкции,  сос-
       


                                     -335-
       тавляющие компьютерную программу.  У всех IBM PC центральным про-
       цессором является микросхема 8088, за исключением PC AT, у  кото-
       рого процессором служит микросхема 80286.

       CRC:  См. циклический контроль четности.

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

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

       CR/LF:  Возврат каретки/перевод строки. Эта пара символов исполь-
       зуется,  чтобы вызвать перевод курсора или печатающей  головки  к
       началу следующей строки.

       CRT:  Электронно-лучевая трубка, т.е. видеодисплей.

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

       Текущий блок:  Блок  данных  файла,  состоящий из 128 записей, на
       который  ссылаеися при доступе к файлу методом управляющего блока
       файла. См. текущий номер записи.

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

       Текущий номер записи:  При доступе  к файлам методом управляющего
       блока файла, данные организованы в блоки по 128 записей.  Текущий
       номер записи это номер записи в текущем блоке.  Например, текущий
       номер записи для записи прямого доступа номер 128 будет равен  0,
       поскольку она будет первой  записью  в блоке 1 (весь отсчет начи-
       нается  с нуля, поэтому запись с номером 128 будет 129-й  записью
       файла, блок 1 - вторым блоком,  а  последняя запись блока 0 имеет
       номер 127).

       Циклический  контроль четности:  Метод проверки ошибок, в котором
       за переданным блоком  данных  следует  вычисленный  математически
       результат;  после приема вычисление повторяется и сравнивается  с
       переданным, чтобы быть уверенным, что данные переданы без искаже-
       ния.

       Цилиндр:   У  дисковых  накопителей цилиндром  называется  группа
       дорожек, находящихся на одинаковом расстоянии от центра диска или
       дисков, помещенных в накопитель.

       Сегмент  данных:  Область памяти, содержащая данные программы.  В
       языке ассемблера на эту область указывает регистр DS.

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

       DB:  Термин языка  ассемблера,  указывающий,  что  объект  данных
       имеет размер 1 байт, или что это строка состаящая из однобайтовых
       кодов.

       DCD:  Обнаружен носитель данных.  Сигнал от модема порту коммуни-
       


                                     -336-
       кации, индицирующий, что установлена связь с другим модемом.

       DD:   Термин  языка ассемблера, индицирующий, что  объект  данных
       имеет длину 4 байта.

       DTA по умолчанию:  Область  переноса данных, размером 128 байтов,
       которая  выделяется каждой программе и начинается со смещения 80H
       в префиксе программного сегмента.

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

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

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

       Заголовок  устройства:  Начальная часть процедуры  драйвера  уст-
       ройства, которая идентифицирует устройство.

       Обработчик прерывания устройства:  Основная часть процедуры драй-
       вера  устройства; она содержит код, выполняющий основные  функции
       драйвера.

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

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

       Прямое отображение в память:  См. отображение в память.

       DMA:  См. прямой доступ к памяти.

       Запрос системы:  Символы, появляющиеся в начале командной строки,
       например, A> или B>.

       Указатель  накопителя:   Двухбайтная  строка,  именующая дисковый
       накопитель, в виде A:, B: и т.д.

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

       DTA:  Область обмена с диском.   Буфер, используемый при обмене с
       диском,  при использовании метода доступа управляющего блока фай-
       ла.

       DTR: Приемник данных готов.  Сигнал  от коммуникационного порта к
       модему, индицирующий, что компьютер готов.

       DW:   Термин  языка ассемблера, указывающий,  что  объект  данных
       имеет длину 2 байта.

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

       Вход:  Слова "при входе" обычно  относятся  к установке регистров
       процессора,  которая должна быть сделана при  выполнении  функции
       


                                     -337-
       операционной системы.

       Строка окружения:  Строка, состоящая из одной или более спрецифи-
       каций,  которым  система следует при выполнении  программы.   Она
       может содержать конфигурационные команды, вводимые пользователем,
       такие как BUFFERS или BREAK.

       EOF:  Сокращение для "конца файла".

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

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

       Esc-последовательность:  Управляющая  строка, начинающаяся с сим-
       вола  Esc  (ASCII 27).  Например, большинство управляющих  команд
       принтера выполняется с помощью Esc-последовательностей.

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

       EXEC:  Функция операционной системы, позволяющая программе запус-
       тить другую программу. Она может также загружать оверлеи.

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

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

       Расширенный  код ошибки:  Начиная с версии 3.0 MS DOS более  под-
       робные расширенные коды  ошибки  возвращаются  при  возникновении
       ошибки.  Эти коды сообщают не только об ошибке, но и об ее  типе,
       ее месте в оборудовании и возможных способах восстановления.

       Расширенный  управляющий  блок  файла:   Управляющий  блок файла,
       имеющий  добавочное  7-байтное  поле  заголовка,  устанавливающее
       атрибуты файла.

       Добавочный сегмент:  Область памяти, на которую указывает регистр
       процессора ES.  Установка ES и DS (регистр сегмента данных) часто
       используется совместно для  переноса данных из одной части памяти
       в другую.

       FAT: См. таблица размещения файлов.

       FCB: См. управляющий блок файла.

       Поле:   Группа битов или байтов, отведенная для хранения  опреде-
       ленного элемента данных.
       


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

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

       Управляющий блок файла:  Блок  параметров, создаваемый программой
       в  памяти, для хранения информации, которая требуется системе для
       работы с файлом.

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

       Дескриптор файла:  В Бейсике или другом языке высокого уровня под
       дескриптором файла понимается номер буфера, с которым данный файл
       открывается, т.е. как #1 или #3.

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

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

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

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

       Основной  цвет:   Цвет, которым символы или  графические  объекты
       выводятся на экран.

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

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

       Глобальный символ:  Один из  символов  ? или *, когда они исполь-
       зуются  в системе для указания неопределенных  символов в  именах
       файлов.

       H:  Суффикс, обозначающий число, представленное в шестнадцатирич-
       


                                     -339-
       ном виде, например, 0D3H. См. приложение А.

       Рукопожатие:   Обмен предопределенными сигналами между двумя уст-
       ройствами для установления связи между ними.

       Аппаратное  прерывание:   Прерывание,  вызываеиое  оборудованием,
       т.е.  одним из периферийных устройств, микросхемой поддержки  или
       самим процессором.

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

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

       Скрытый файл:  Статус, который может быть присвоен файлу установ-
       кой  его байта атрибутов.  Скрытые файлы не выводятся при  выводе
       каталога файлов.

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

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

       Интерпретатор:  Программа,  которая  переводит текст программы по
       одной инструкции за раз, немедленно исполняя ее. Интерпретаторами
       являются программы BASIC.COM и BASICA.COM.

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

       Обработчик прерывания:   Процедура  прерывания. Этот термин более
       часто используется для аппаратных прерываний.

       Вектор прерывания:  См. вектор.

       IOCTL:  Управление вводом/выводом. Этот механизм, предоставляемый
       системой, позволяет программе  взаимодействовать с драйвером уст-
       ройства, прямо посылая и получая управляющие строки, а не включая
       их в поток данных, посылаемых драйверу устройства.

       IRQ:  Сокращение для "запроса  на  прерывание".  Используется при
       ссылке на маскируемые аппаратные прерывания.

       Буфер  клавиатуры:  15-символьная циклическая очередь, в  которую
       прерывание клавиатуры помещает вводимые символы.

       


                                     -340-
       Прерывание клавиатуры:   Аппаратное прерывание, вызываемое, когда
       клавиша на клавиатуре нажимается или отпускается. Оно преобразует
       скан-коды,  выдаваемые  микропроцессором  клавиатуры, в коды, ис-
       пользуемые программами и вставляет эти коды в буфер клавиатуры.

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

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

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

       LSB:  Младший бит или младший байт.

       Машинная  инструкция:  Числовые коды,  используемые  процессором.
       Например, инструкция INT  кодируется как CD, а последовательность
       CD 21 приводит к тому, что процессор выполняет прерывание 21H.

       Машинный  язык:   Самый  низкий уровень  программирования,  когда
       программист пишет инструкции  непосредственно  в  двоичных кодах,
       используемых  процессором.  Программирование на языке  ассемблера
       приводит к тем же результатам с  большими удобствами за счет соз-
       дания двоичных кодов из мнемоники типа MOV или TEST.

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

       Код нажатия:  Тип  скан-кода,  который  генерируется  при нажатии
       клавиши (код освобождения выдается, когда соответствующая клавиша
       отпускается).

       Маркировка:  Термин,  используемый для последовательного сигнала,
       когда он имеет высокий уровень, т.е.  равен логической 1. В част-
       ности, говорят, что сигнал асинхронной  связи маркирован в проме-
       жутки времени между передачей элементов данных.

       Маска:  Цепочка битов, определяющая какие из битов второй цепочки
       являются активными.  Например, определенные аппаратные прерывания
       запрещаются  за счет установки битов в регистре маски  микросхемы
       контроллера прерываний. При этом  прерывание 4 маскируется цепоч-
       кой битов 00001000B.

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


                                     -341-
       ной загрузки, которая инициирует загрузку  соответствующей опера-
       ционной системы.

       Отведение памяти:  Отведение системой блока памяти для  использо-
       вания программой.

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

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

       Пространство  памяти:  Область адресуемой памяти, к которой  про-
       цессор может иметь доступ.  Для  микропроцессора  8088 адресуемое
       простанство равно приближенно одному миллиону байтов.

       MSB: Старший бит или старший байт.

       Объектный  модуль:  Файл, содержащий машинный код, в котором  еще
       не установлены относительные адреса.   Компоновщик обрабатывает и
       объединяет объектные модкли, создавая исполняемые файлы типа  EXE
       или COM.

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

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

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

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

       Палетта:  Набор цветов, доступных в определеном режиме дисплея.

       Код палетты:  Номер, соответствующий определенному цвету из  дос-
       тупного набора.

       Регистр палетты:  Один из 16-ти регистров EGA и PCjr, указывающий
       цвет,  который  будет выводиться на экран, когда  соответствующий
       код цвета указан в видеобуфере.

       Параграф:  16-байтная единица памяти,  которая начинается на гра-
       нице точно делящейся на 16.

       Номер параграфа:  Номер, определяющий положение в памяти, основы-
       ваясь на 16-байтных единицах. Например, параграф номер 2 относит-
       ся ко вторым 16 байтам памяти и когда указатель указывает на этот
       параграф, то он указывает на 17-й байт памяти.

       Параметр: Число, используемое для спецификации работы устройства,
       


                                     -342-
       функции  операционной системы или оператора языка  программирова-
       ния.

       Блок параметров:   Группа  переменных,  создаваемая  в памяти для
       хранения информации, используемой устройством или функцией опера-
       ционной системы.

       Процесс родитель:  Программа, использующая другую программу (про-
       цесс потомок).

       Бит четности:   Дополнительный  (9-й)  бит, добавляемый к каждому
       байту памяти, чтобы проверять возможные ошибки при передаче. Биты
       четности  присоединяются  также  к  данным  при последжовательной
       коммуникации.

       Синтаксический  анализ:  Разбиение текстовой строки на составляю-
       щие части.  MS DOS может, проанализировав  информацию в командной
       строке, переформатировать ее для использования функциями  доступа
       к файлу.

       Раздел:  Область жесткого диска.   Жесткий диск может быть разбит
       на роазделы, с тем чтобы он использовался несколькими  операцион-
       ными системами.

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

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

       Физические  координаты:  Координаты точки на экране дисплея, отс-
       читываемые от левого верхнего угла, который имеет координаты 0,0.
       См. также мировые координаты.

       Точка:  Точка, выводимая в графическом режиме. В документации IBM
       ее называют также "pel".

       Указатель:  Переменная, которая содержит адрес другой переменной.

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

       Порт:  Путь, по которому происходит обмен данными между процессо-
       ром и микросхемами поддержки.

       Порт  A (порт B, порт C):  Один из трех регистров, через  которые
       программа получает доступ к  микросхеме  интерфейса  с периферией
       8255.

       Адрес  порта:  Число в диапазоне от 0 до 65535, которое  адресует
       порт.  Адреса портов отделены от адресов памяти.  Доступ к портам
       осуществляется с помощью инструкций IN и OUT в языке ассемблера и
       INP и OUT в Бейсике.

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


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

       Префикс программного сегмента:   256-байтный  заголовок,  который
       система  помещает  перед исполняемыми файлами при их  загрузке  в
       память.  Он содержит переменные, используемые MS DOS для управле-
       ния  программой,  а  также место для управляющего блока  файла  и
       область переноса данных.

       Протокол:  Система  параметров  и  форматов  данных, используемых
       устройством.

       PSP:  См. префикс программного сегмента.

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

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

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

       Запись:   Блок  данных, указанного размера,  являющийся  единицей
       обмена данными при обмене с файлами.

       Номер записи:  Число, определяющее позицию записи в файле, отсчи-
       тываемое от 0.  В файле, содержащем записи длиной 10 байтов,  за-
       пись номер 5 относится к 50-59  байтам  файла, даже если записи с
       меньшими номерами не вводились.

       Регистр:  Часть микросхемы, в которой данные хранятся и над  ними
       производятся операции. В IBM PC  большинство регистров имеет раз-
       мер  8  или 16 битов.  Регистры процессора получают  значения  из
       памяти и хранят их, пока  они  складываются,  умножаются  и  т.д.
       Регистры микросхемы управления дисплеем инициализируются данными,
       определяющими характеристики дисплея.

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

       Относительные координаты:  Координаты, определяемые  относительно
       последних используемых координат.  В этом случае 3,5 указывает "3
       вправо и 5 вверх", а -3,-5 - "3 влево и 5 вниз".

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

       Заголовок  запроса:   Блок параметров, создаваемый  системой  для
       управления драйвером устройства.

       Резидентная  программа:   Программа,   остающаяся  в памяти после
       


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

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

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

       RI:   Индикатор звонка.  Сигнал от модема с автоответчиком  порту
       коммуникации, который сообщает,  что  телефон, с которым связался
       модем, звонит.

       ROM-BIOS:  См. BIOS.

       Корневой  каталог:  Центральный каталог диска.  Он  расположен  в
       фиксированном месте на диске.  Он  может содержать список файлов,
       метку тома и указатели на подкаталоги.

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

       Сегмент:   Область  памяти, размером 64K, созданная для  хранения
       кода, данных или  стека.   Сегменты  всегда  выравнены на границу
       16-ти байт, поскольку их адрес получается умножением  содержимого
       сегментного регистра на 16.

       Сегментный адрес:  То же,  что  и  сегментное  значение или номер
       параграфа.

       Сегментный регистр:  Один из четырех регистров процессора, указы-
       вающий на начальную позицию  сегмента  памяти. Значение этого ре-
       гистра автоматиечски умножается на 16, с тем чтобы он указывал на
       одну из 16-байтных  границ  мегабайтного  адресного  пространства
       процессора.   Имена сегментных регистров CS (кодовый сегмент), DS
       (сегмент данных), SS (сегмент стека) и ES (добавочный сегмент).

       Сегментное значение:  Число,  определяющее  положение  в памяти в
       16-байтных единицах. То же, что и номер параграфа.

       SETBLOCK:   Функция операционной системы, которая  сокращает  или
       увеличивает область памяти, отведенной данной программе.

       Программное прерывание:  Прерывание, вызываемое инструкцией INT.

       Текст программы:  Исходный  вариант программы, в том виде как она
       выглядит  до  того, как она была оттранслирована,  ассемблирована
       или интерпретирована.

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

       Сегмент стека:  Область памяти, отводимая для хранения стека.

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


                                     -345-
       чающего конец  маркированного  состояния  (серии единиц), которое
       заполняет все время в промежутках между передачей символов.

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

       Начальная  строка: Строка матрицы символов, на которой начинается
       изображение курсора.  Например,  для  монохромного дисплея строка
       текста состоит из матрицы высотой в 14 строк, которые  пронумеро-
       ваны от 0 до 13. Для  обычного  курсора  номер начальной строки -
       12, а конечной - 13.

       Байт статуса:  Ячейка памяти, содержащая цепочку битов, описываю-
       щую текущий статус устройства.

       Регистр статуса:  Регистр ввода/вывода, содержащий цепочку битов,
       описывающую текущий статус устройства.

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

       Конечная  строка:  Строка матрицы символов, на которой  кончается
       изображение курсора. См. начальная строка.

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

       Подфункция:  Одна из  нескольких  процедур,  которые могут выпол-
       няться данной функцией операционной системы. В то время как номер
       функции всегда помещается в AH, номер подфункции надо поместить в
       AL перед выполнением прерывания.

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

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

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

       Системный файл:   Специальный  статус,  присваиваемый  файлу пос-
       редством  байта атрибутов.  Он отмечает файлы, являющиеся  частью
       операционной системы.

       Орнамент:  Заполнение области  графического  дисплея определенной
       картинкой, а не одним цветом.

       Счетчик времени суток:  Переменная в области данных BIOS, которая
       постоянно увеличивается прерыванием  таймера. Ее значение исполь-
       зуется операционной системой для вычисления времени суток.
       


                                     -346-
       Таймаут:   Выражение,  используемое при  операциях  ввода/вывода,
       указывающее, что периферийное устройство не действует.

       Прерывание таймера:  Прерывание, инициируемое микросхемой таймера
       8253  18.2 раз в секунду.  Каждый раз это прерывание  увеличивает
       счетчик времени суток BIOS.

       Дорожка:  Кольцо на диске.  Дискета емкостью 360K разделена на 40
       дорожек, каждая из которых делитсяч радиально на 9 секторов.

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

       Частота автоповтора:  Скорость с которой  клавиша клавиатуры пов-
       торяет посылку кода, когда она держится нажатой.

       Вектор:   Четырехбайтный  адрес  процедуры  прерывания в  памяти.
       Старшие два байта дают сегмент, а младшие два байта - смещение.

       Таблица векторов:  Таблица указателей.  Вектора прерывания содер-
       жатся  в 256 четырехбайтных полях, занимающих младшие 1024  байта
       памяти.  Каждое поле содержит адрес  процедуры прерывания.  INT 0
       указывается первым вектором, INT 1 - вторым и т.д.

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

       Массив  ворот дисплея:  Микросхема видеосистемы PCjr,  содержащая
       ряд управляющих и статусных регистров, включая регистры палетты.

       Метка тома:  Специальный элемент корневого каталога, обеспечиваю-
       щий возможность идентифицировать диск 11-байтным именем.

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

       Мировые координаты:  Система координат экрана, определяемая прог-
       раммой, которая устанавливает  диапазон значений координат x и y,
       которые  могут  включать или не включать отрицательные  значения.
       Например, левому и правому краю экрана могут быть присвоены коор-
       динаты -100 и 100. Эти коордтинаты накладываются на систему физи-
       ческих координат экрана,  в  которой  левый  верхний  угол всегда
       определяется  значениями x = 0 и y = 0 и в  которой  используются
       только положительные значения.

       XON/XOFF:  Метод установления  связи  при последовательной связи,
       который использует коды ASCII 17 и 19, соответственно, для сигна-
       ла передающей станции, что она должна  возобновить или приостано-
       вить  передачу.  Они используются, когда данные поступают слишком
       быстро и программа не успевает их обработать.

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