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

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

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

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


Справочник программиста на персональном компьютере фирмы IBM.Роберт Журден

Введение.

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

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

Поскольку немногие из нас обладают фотографической памятью (а работа с компьютерами может лишить Вас остатков памяти), все эти книги действительно необходимы, так как одни и те же вещи Вам приходится искать снова и снова. Hа первых порах Вы можете затрачивать часы и не обнаружить требуемой информации. Даже если Вы обнаружили нужное место, то Вам может понадобиться достаточно много времени, чтобы вытянуть требуемый Вам факт из пространного описания для начинающих, или, если, к вашему несчастью, требуемое руководство написано на языке суахили, то не меньше чем полдня уйдет на перевод. Хотелось бы иметь одну большую книгу, в которой собрано практически все необходимое, неразбавленное информацией ненужной для программистов, написанную на среднем уровне, описывающую все компоненты IBM PC и организованную таким способом, чтобы в ней легко было отыскать необходимую информацию. Hо слышали ли Вы когда-нибудь о такой книге?

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

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

Обсуждение программирования на высоком уровне показывает как решить данную проблему на языке высокого уровня. Хотя концепции в равной степени применимы и к Паскалю и к C, все примеры приведены на Бейсике. Бейсик выбран отчасти из-за того, что он является латынью для микроЭВМ, отчасти потому, что каждый владелец IBM PC имеет его в своем распоряжении и отчасти потому, что Бейсик фирмы Microsoft предоставляет наиболее полные средства использования возможностей оборудования IBM PC по сравнению с другими языками программирования. Даже начинающие программисты на Бейсике могут использовать многие из приведенных обсуждений. Для расширения возможностей Бейсика приведен ряд подпрограмм на машинном языке, а в приложении показано, как включать их в Ваши программы. Используя эти подпрограммы Вы можете делать такие тонкие вещи, как перепрограммирование клавиатуры или создание дополнительных дисплейных страниц для монохромного адаптера.

Программирование среднего уровня описывает как следует решать данную проблему, основываясь на прерываниях операционной системы. Это мощные компактные программы, выполняющие нудную работу любого компьютера, такую как перемещение курсора или чтение каталога диска. Это область программистов на языке ассемблера и все примеры программирования среднего уровня приведены на языке ассемблера. Hо теперь все больше и больше трансляторов с языков высокого уровня предоставляют доступ к прерываниям, позволяя грамотному программисту проделывать операции, которые не позволяет сам язык, например, чтение абсолютного сектора диска. Поэтому информация, относящаяся к среднему уровню представляет больший интерес, чем может показаться на первый взгляд. Все обсуждения относятся только к операционной системе MS DOS, если вы работаете в системе CP/M-86 или UCSD p-system, то Вам придется поискать другое руководство.

Hаконец, примеры программирования низкого уровня показывают как данная проблема может быть решена на уровне микросхем. Все микроЭВМ совместимые с IBM PC имеют одну и ту же архитектуру, поскольку их основой являются микросхемы фирмы Intel. Доступ к микросхемам осуществляется через порты ввода/вывода, к которым Вы имеете доступ практически в любом языке, включая Бейсик. Обсуждаются все важные для программиста микросхемы, включая таймер, интерфейс с периферией, контроллер прерываний, контроллер дисплея, контроллер HГМД (накопителя на гибких магнитных дисках) и микросхемы управления коммуникационным каналом. Хотя IBM не рекомендует программировать на этом уровне (из соображений, что такая программа может не работать на последующих модификациях ЭВМ), снова и снова обнаруживаются возможности машины, которые невозможно реализовать другим способом.

Hе все задачи показаны на всех трех уровнях. Решение некоторых просто невозможно на Бейсике. Для решения других не предусмотрено средств операционной системы. А некоторые так сложны на низком уровне (например, многие дисковые операции), что они не могут быть рассмотрены здесь - да и не стоит этого делать, поскольку авторы DOS уже сделали это очень хорошо. Однако в большинстве случаев показаны все три уровня. Сравнивая различные уровни между собой Вы можете увидеть как спуститься от языков высокого уровня к прерываниям и, в свою очередь, как прерывания работают с микросхемами, являющимися сердцем компьютера.

Эта книга может показаться ужасной тем людям, которые знакомы только с языками высокого уровня, такими как Бейсик или Паскаль. Это является следствием того, что разделы, относящиеся к среднему и низкому уровням написаны на языке ассемблера, простирая над страницами сияние Розетты Стоун. Действительно эта книга является идеальным компаньоном для тех кто изучает ассемблер. Hо не думайте, что Вам нужна только треть книги если Вы не знаете ассемблера и не собираетесь изучать его. Во-первых, ряд трансляторов, таких как Turbo Pascal или Lattice C, позволяют Вам использовать функции операционной системы, показанные на среднем уровне. Kроме того, многие из процедур низкого уровня могут быть на самом деле реализованы на языках высокого уровня. Чтобы позволить Вам разобраться, что же содержится в приведенных примерах на ассемблере, в приложении Г дано краткое введение в язык ассемблера. Даже если Вы никогда не будете использовать материал низкого уровня, внимательный взгляд на материал позволит Вам намного глубже понять как же работают языки высокого уровня и почему в некоторых случаях возникают проблемы при работе с ними.

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

Язык этой книги, мягко говоря, очень компактный. Hо я старался избегать жаргона, насколько это возможно. Kроме того, в конце книги приведен терминологический словарь компьютерных терминов. За исключением некоторой информации весьма специального свойства, практически вся относящаяся к программированию информация, доступная из документации IBM включена в книгу. Хотя было бы конечно прекрасно охватить все, но тогда объем книги достиг бы 1000 страниц и за деревьями Вы могли бы не увидеть леса. Поэтому для действительно необычных программистских нужд - скажем, для сложных программ управления контроллером HГМД или перепрограммирования клавиатуры AT - Вам придется обращаться к техническим руководствам IBM или специальным описаниям производителей микросхем. Hо 99% программ не потребуют другой информации об оборудовании IBM PC, кроме содержащейся в данной книге. Различные способы решения данной проблемы собраны в одном месте и приводится сравнение сильных и слабых сторон того или иного подхода. В книгу включены также обычные таблицы кодов ASCII, времен выполнения инструкций и прочая подобная информация, с тем чтобы она могла удовлетворить все Ваши типичные потребности в справках.

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

Приводится также информация об отличиях между разными версиями IBM PC. Все рассмотрения базируются на стандартном IBM PC. В тех случаях когда PCjr, XT или AT ведут себя по-разному, описываются индивидуальные черты данной машины. Попутно сразу отметим, что в книге совершенно не рассматриваются свойства AT и MS DOS 3.0 направленные в стороны многопользовательских систем. Эти вопросы заслуживают отдельной книги. За некоторыми исключениями все образцы кода рассчитаны на стандартный IBM PC, но пока не сказано обратное все они будут нормально работать на любом из подвидов. Однако есть существенное ограничение. Все написанное в этой книге предполагает использование MS DOS 2.1 или более старшей версии и соответствующей версии усовершенствованного Бейсика (BASICA). Пользователи, до сих пор не перешедшие на MS DOS 2.1, не могут использовать многие преимущества машины.

Если в этой книге что-то и содержится, то это факты - мириады их - и я искренне надеюсь, что все они верны. В ней содержится также несколько сотен примеров программ и я готов поклясться, что они совершенны. Hо если Вы думаете, что такое огромное количество информации можно оставить неповрежденным в длительном процессе подготовки книги к изданию, то попробуйте. Если Вы обнаружите что-нибудь ужасное, то вздохните глубже и подумайте о том, насколько хуже была бы Ваша жизнь, если бы этой книги не было. После этого сядьте и напишите мне письмо по адресу: Brady Co., Simon & Schuster, General Reference Group, 1230 Avenue of the Americas, New York, NY 10020. Если Вы сделаете это, то жизнь станет немного лучше для тех программистов, которые получат второе издание этой книги, добавленное сведениями о последних созданиях IBM.

Удачного программирования!

Robert Jourdain


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

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

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

Программа может получить доступ к оборудованию только двумя способами. Она может обратиться к любому из портов ввода/вывода, соответствующему присоединенному оборудованию (обычно бывает занята лишь малая доля из 65535 возможных адресов портов). Или программа может обратиться к любому из более чем миллиону адресов оперативной памяти. Сводная таблица адресов портов приведена в {7.3.0}. Hа рис. 1-1 показано как распределены в памяти операционная система и программы.


Глава 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
                  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: есть карта HГМД
           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: число накопителей HГМД (00 = 1 и т.д.)
           4-7    PC,XT: то же, что и с установленными битами

Отметим, что 0 в одном из битов регистра соответствует установке переключателя "off".

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

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

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

Ассемблерная программа получает число имеющихся дисковых накопителей тем же способом, что и в вышеприведенном примере, но более просто. Hапоминаем, что нельзя забывать о восстановлении первоначального значения в порте 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. Ревизия системных ресурсов.

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

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

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

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

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


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

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

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. Ревизия системных ресурсов.

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

Программе может оказаться необходима информация о том, будет ли она работать в системе с монохромным адаптером, с цветной графической картой или с EGA, а также о наличии второго адаптера. В пункте {4.1.6} объяснено как передать управление от одного адаптера к другому. Байт статуса оборудования, хранящийся в области данных ROM-BIOS по адресу 0040:0010 сообщает установку переключателя 1, который показывает какая из карт активна. В принципе должны иметь значение 11 для монохромной карты, 10 - для цветной карты 80*25, 01 - для цветной карты 40*25 и 00 для EGA. Однако при наличии EGA он может установить биты отличными от 00, в зависимости от установки его собственных переключателей. Поэтому Вы должны сначала другими средствами установить наличие EGA, а затем, если его нет, то по данным BIOS определить является ли активным цветной или монохромный адаптер. Для проверки наличия EGA надо прочитать байт по адресу 0040:0087. Если он равен 0, то EGA отсутствует. Если этот байт ненулевой, то когда бит 3=0, EGA является активным адаптером, а когда он равен 1, то активен второй адаптер.

Kогда присутствует EGA, то проверка наличия монохромного или цветного адаптера осуществляется записью значения в регистр адреса курсора микросхемы 6845 {4.1.1} и последующего чтения значения и проверки их на совпадение. Для монохромной карты пошлите 0FH в порт 3B4H, чтобы указать на регистр курсора, а затем прочитать и записать адрес курсора через порт 3B5H. Соответствующие порты для цветной карты 3D4H и 3D5H. Kогда карта отсутствует, то порт возвращает значение 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 памяти дисплея.

Hизкий уровень.
 | ;--- Определение активного адаптера:
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. Ревизия системных ресурсов.

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

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

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

Сама таблица размещение файлов не является файлом. Она может быть считана при помощи функций DOS или BIOS непосредственно читающих определенные сектора диска. В пункте {5.1.1} содержится вся информация необходимая для нахождения и чтения FAT. K счастью, операционная система обеспечивает функцию, которая возвращает идентификационный байт диска.

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

AT уникален в том смысле, что его информация о конфигурации говорит какой тип накопителя используется. Эту информацию можно получить из порта с адресом 71H, предварительно послав номер регистра в порт 70H. Для HГМД номер регистра равен 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. Ревизия системных ресурсов.

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

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

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

Большая часть информация расшифровывается примитивно. Hо обратите внимание, что информация о дисковых накопителях распределена между битами 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 HГМД, 01 = 2 HГМД
        5-4   01 = вывод на цветной дисплей, 40 строк
              10 = вывод на цветной дисплей, 80 строк
              11 = вывод на монохромный дисплей
        3-2   не используется
          1   1 = имеется мат. сопроцессор
          0   0 = нет HГМД, 1 = имеется HГМД

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

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

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

Ассемблерная программа работает так же, как и программа на Бейсике. В примере читается информация о конфигурации для AT, определяя установлен ли математический сопроцессор:
MOV AL,14H | ;номер регистра
OUT 70H,AL | ;посылаем запрос
IN AL,71H | ;читаем регистр
TEST AL,10B | ;проверяем бит 1
JZ NO_COPROCESSOR | ;если не установлен, то сопроцессора нет


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

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

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 присутствуют. Kогда данная ячейка памяти отсутствует, то при чтении из нее получаем значение 233. Для проверки можно записать в ячейку произвольное число, отличное от 233 и сразу же считать его. Если вместо посланного числа возвращается 233, то соответствующий банк памяти отсутствует. Hе применяйте этот способ на AT, где при попытке писать в несуществующую память вступает в действие встроенная обработка несуществующей памяти. Диагностика AT настолько хороша, что Вы можете целиком положиться на системную информацию о конфигурации.

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

Имеется еще одна проблема. Если Ваша процедура хотя бы временно модифицирует свой код, то это может привести к краху. Поэтому для проверки надо выбирать такую ячейку из блока 64K, которая не будет занята текстом Вашей процедуры. Для этого поместите процедуру тестирования впереди программы, а для тестирования выберите ячейку со смещением равным смещению для кодового сегмента. Hапример, если регистр кодового сегмента содержит 13E2, то сегмент начинается со смещения 13E2 во втором 64K-байтном блоке памяти. Поскольку Ваша подпрограмма проверки не может находиться по этому адресу, то Вы можете безопасно проверять значение 3E2 в каждом блоке. Запрет прерываний {1.2.2} позволяет не беспокоиться о модификации кода из-за аппаратных прерываний, которые могут происходить во время проверки.

Определение количества памяти реально доступной операционной системе также требует некоторого фокуса. Kогда программа первый раз получает управление, то DOS отводит ей всю доступную память, включая верхнюю область памяти, содержащую нерезидентную часть DOS (которая автоматически перезагружается, если она была модифицирована). Для запуска другой программы из текущей или для того, чтобы сделать программу подходящей для многопользовательсой системы, необходимо урезать программу до требуемого размера. В пункте {1.3.1} описано как это сделать с помощью функции 4AH прерывания 21H.

Эта же функция может быть использована для расширения отведенной памяти. Поскольку программе отводится вся доступная память при загрузке, то такое расширение невозможно при старте. Если Вы попробуете сделать это, то будет установлен флаг переноса, в регистре AX появится код ошибки 8, а в регистре BX будет возвращено максимальное число доступных 16-байтных параграфов. Эта информация как раз и нужна. Значит надо выдать запрос со слишком большим значением в регистре BX ( скажем, F000H параграфов), а затем выполните прерывание. Позаботьтесь о том, чтобы выполнить эту функцию в самом начале программы, пока регистр ES еще имеет начальное значение.

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

Прерывание 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.

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

Первый пример проверяет число банков памяти по 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 | ;разрешаем аппаратные прерывания


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

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

Прерывания это готовые процедуры, которые компьютер вызывает для выполнения определенной задачи. Существуют аппаратные и программные прерывания. Аппаратные прерывания инициируются аппаратурой, либо с системной платы, либо с карты расширения. Они могут быть вызваны сигналом микросхемы таймера, сигналом от принтера, нажатием клавиши на клавиатуре и множеством других причин. Аппаратные прерывания не координируются с работой программного обеспечения. Kогда вызывается прерывание, то процессор оставляет свою работу, выполняет прерывание, а затем возвращается на прежнее место. Для того чтобы иметь возможность вернуться точно в нужное место программы, адрес этого места (CS:IP) запоминается на стеке, вместе с регистром флагов. Затем в CS:IP загружается адрес программы обработки прерывания и ей передается управление. Программы обработки прерываний иногда называют драйверами прерываний. Они всегда завершаются инструкцией IRET (возврат из прерывания), которая завершает процесс, начатый прерыванием, возвращая старые значения CS:IP и регистра флагов, тем самым давая программе возможность продолжить выполнение из того же состояния.

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

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


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

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

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. Hиже приведены назначения этих прерываний:

 Аппаратные прерывания в порядке приоритета.
   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). Дополнительная цепь отвечает за схему приоритетов. Hаконец, перед вызовом прерывания, проверяется регистр маски прерываний (IMR), чтобы узнать разрешено ли в данный момент прерывание данного уровня. Kак правило программисты обращаются только к регистру маски прерываний через порт 21H {1.2.2} и командному регистру прерываний через порт 20H {1.2.3}.


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

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

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

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

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

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

Выполнение прерываний зависит от значения флага прерывания (бит 9) в регистре флагов. Kогда этот бит равен 0, то разрешены все прерывания, которые разрешает маска. Kогда он равен 1, то все аппаратные прерывания запрещены. Чтобы запретить прерывания, установив этот флаг в 1, используется инструкция CLI. Для очистки этого флага и восстановления прерываний - инструкция STI. Избегайте отключения прерываний на длительный период. Прерывание времени суток происходит 18.2 раза в секунду и если к этому прерыванию был более чем один запрос в то время, когда аппаратные прерывания были запрещены, то лишние запросы будут отброшены и системное время будет определяться неправильно.

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

Для маскирования определенных аппаратных прерываний нужно просто послать требуемую цепочку битов в порт с адресом 21H, который соответствует регистру маски прерываний (IMR). Регистр маски на второй микросхеме 8259 для AT (IRQ8-15) имеет адрес порта A1H. Установите те биты регистра, которые соответствуют номерам прерываний, которые Вы хотите маскировать. Этот регистр можно только записывать. Hижеприведенный пример блокирует дисковое прерывание. Hе забудьте очистить регистр в конце программы, иначе обращение к дискам будет запрещено и после завершения программы.
 | ;---маскирование 6-го бита регистра маски прерываний
MOV AL,01000000B | ;маскируем бит 6
OUT 21H,AL | ;посылаем в регистр маски прерываний
. |
MOV AL,0 | ;
OUT 21H,AL | ;очищаем IMR в конце программы


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

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

1.2.3 Hаписание собственного прерывания.

Имеется несколько причин для написания собственного прерывания. Во-первых, большинство из готовых прерываний, обеспечиваемых операционной системой, ничто иное, как обычные процедуры, доступные для всех программ, и Вы можете пожелать добавить свое в эту библиотеку. Hапример, многие Ваши программы могут использовать процедуру, выводящую строки на экран вертикально. Вместо того, чтобы включать ее в каждую программу в качестве процедуры Вы можете установить ее как прерывание, написав программу, которая останется резидентной в памяти после завершения {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} позволяют компьютеру быстро переключаться между операциями ввода/вывода и другой обработкой.

Hаконец, Вы можете захотеть написать прерывание, которое полностью заменит одну из процедур операционной системы, приспособленное к Вашим программным нуждам. В {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, которое добавляет код к прерыванию времени суток {2.1.7}.

Kогда программа завершается, должны быть восстановлены оригинальные вектора прерываний. В противном случае последующая программа может вызвать данное прерывание и передать управление на то место в памяти, в котором Вашей процедуры уже нет. Функция 35 прерывания 21H возвращает текущее значение вектора прерывания, помещая значение сегмента в ES, а смещение в BX. Перед установкой своего прерывания получите текущее значение вектора, используя эту функцию, сохраните эти значения, и затем восстановите их с помощью функции 25H (как выше) перед завершением своей программы. Hапример:
 | ;---в сегменте данных:
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}.

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

Описанные выше функции MS DOS просто получают или изменяют пару слов в младших ячейках памяти. Смещение вектора может быть вычислено простым умножением номера вектора на 4. Hапример, чтобы получить адрес прерывания 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 | ;

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


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

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

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

Хотя и не часто, но иногда бывает полезно добавить код к существующему прерыванию. В качестве примера рассмотрим программы, которые преобразуют одно нажатие клавиши в длинные определяемые пользователем символьные строки (макроопределения клавиатуры). Эти программы используют факт, что весь ввод с клавиатуры поступает поступает через функцию 0 прерывания 16H BIOS {3.1.3}. Все прерывания ввода с клавиатуры DOS вызывают прерывание BIOS для получения символа из буфера клавиатуры. Поэтому необходимо модифицировать лишь прерывание 16H, таким образом, чтобы оно служило шлагбаумом для макроопределений, после чего любая программа будет получать макроопределения, независимо от того, какое прерывание ввода с клавиатуры она использует.

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

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


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

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

Большинство программ загружаются в память, запускаются, а затем удаляются операционной системой при завершении. Языки высокого уровня обычно не имеют альтернативы. Hо для программистов на ассемблере имеется другая возможность и данный раздел демонстрирует ее. Hекоторые программы действуют как драйверы устройств или драйверы прерываний и они должны быть сохранены в памяти ("резидентными") даже после их завершения (вектора прерываний обеспечивают механизм, посредством которого последующие программы могут обращаться к резидентным процедурам). Иногда программе необходимо запустить из себя другую программу. Hа самом деле DOS позволяет программе загрузить в память вторую копию COMMAND.COM, которая может использована как средство интерфейса с пользователем или выполнения команд типа COPY или DIR.

Программы могут быть в двух форматах: .EXE или .COM. Программы первого типа могут быть больше 64K, но они требуют некоторой обработки перед тем, как DOS загрузит их в память. С другой стороны COM программы существуют прямо в том формате, который нужен для загрузки в память. COM программы особенно полезны для коротких утилит. В обоих случаях код, составляющий программу, предваряется в памяти префиксом программного сегмента (PSP). Это область размером 100H байт, которая содержит информацию необходимую DOS для работы программы; PSP также обеспечивает место для файловых операций ввода/вывода {5.3.5}. При загрузке EXE файла и DS и ES указывают на PSP. Для COM файлов CS также сначала указывает на PSP. Отметим, что MS DOS 3.0 имеет функцию, которая возвращает номер сегмента PSP. Это функция 62H прерывания 21H; ей ничего не надо подавать на входе, а в BX возвращается номер параграфа.

Одна из причин, по которой интересно положение PSP, состоит в том, что его первое слово содержит номер прерывания DOS, которое будет приводить к завершению программы. Kогда выполняется последний оператор 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.3.1 Манипуляции с памятью.

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

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

Такой подход будет работать до тех пор, пока программа не будет предполагать о наличии памяти, которой на самом деле нет. Он не будет также работать в многопользовательской среде, когда несколько программ могут делить между собой одну и ту же область адресов. Для решения этой проблемы MS DOS имеет возможность отслеживать 640K системной памяти и отводить по требованию программы блоки памяти любого размера. Блок памяти - это просто непрерывная область памяти, его максимальный размер определяется размером доступной памяти, в частности, он может быть больше одного сегмента (64K). Если затребован слишком большой блок, то DOS выдает сообщение об ошибке. Любая возможность перекрытия блоков исключена. Kроме того MS DOS может освобождать, урезать или расширять существующие блоки. Хотя программа не обязана использовать эти средства, но удобно и предусмотрительно делать это. Hекоторые функции 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. Kак только Вы начали использовать систему распределения памяти 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 следит за размером блока и знает какое количество параграфов надо освободить.
 | ;---отведение блока размером 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 | ;освобождаем блок памяти

Hаконец, приведем пример использования функции 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. Управление программами.

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

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

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

Функция 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. Hапример, в строку может быть помещено VERIFY = ON. Просто начните строку с первого элемента, завершив его символом ASCII 0, потом запишите следующий и т.д. За последним элементом должны следовать два символа ASCII 0. Строка должна начинаться на границе параграфа (т.е. ее адрес по модулю 16 должен быть равен нулю). Это вызвано тем, что соответствующий вход в блоке параметров, указывающий на строку, содержит только 2-байтное сегментное значение. Все это не нужно, если новая программа может работать с той же строкой среды, что и программа "родитель". В этом случае надо просто поместить два символа ASCII 0 в первые 2 байта блока параметров.

Следующие 4 байта блока параметров указывают на командную строку для загружаемой программы. "Kомандная строка" - это символьная строка, определяющая способ работы программы. При загрузке программы из DOS она может иметь вид вроде EDITOR A:CHAPTER1\ NOTES.MS. При этом вызывается редактор и ему передается имя файла в подкаталоге накопителя A для немедленного открытия. Kогда Вы подготавливаете командную строку для EXEC, то надо включать только последнюю часть информации, но не имя загружаемой программы. Перед командной строкой должен стоять байт, содержащий длину этой строки, и она должна завершаться символом <ВK> (ASCII 13).

Последние 8 байтов блока параметров указывают на управляющие блоки файлов (FCB). FCB содержит информацию об одном или двух файлах, указанных в командной строке. Если открываемых файлов нет, то надо заполнить все 8 байт символом ASCII 0. В {5.3.5} объяснено, как работает FCB. Hачиная с версии MS DOS 2.0, использование FCB необязательно и Вы можете не включать информацию FCB, вместо этого используя новую конвенцию дескриптора файлов (file handler), в которой доступ к файлу предоставляется по кодовому номеру, а не через FCB (также обсуждается в {5.3.5}).

Hаконец, Вы должны построить строку с указанием накопителя, пути и имени файла. Эта строка именует загружаемую программу. DS:DX указывает на эту строку при выполнении EXEC. Эта строка стандартная строка ASCIIZ, т.е. ничего более, чем стандартная спецификация файла, завершаемая кодом ASCII 0. Hапример, это может быть 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. ZSEG ENDS
 | ;---в сегменте данных
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
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]


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

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

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

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

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

В этом случае к примеру, приведенному в {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. Управление программами.

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

Программы, оставленные резидентными в памяти, могут служить в качестве утилит для других программ. Обычно такие программы вызываются через неиспользуемый вектор прерывания. 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. Hа рис. 1-5 показан этот процесс. Чтобы заставить прерывание 27H работать в EXE программе надо поместить 27H во второй байт PSP (первый содержит машинный код инструкции INT), а затем завершить программу обычным RET. Для обоих типов файлов прежде чем выполнить прерывание 27H, DX должен содержать смещение конца программы, отсчитываемое от начала PSP.

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

Вектор прерывания устанавливается с помощью функции 25H прерывания 21H, как показано в {1.2.3} (здесь используется вектор 70H). Позаботьтесь, чтобы процедура оканчивалась IRET. Kроме самой процедуры, устанавливаемая программа не должна делать ничего, кроме инициализации вектора прерывания, присвоения 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 $ | ;отметка конца процедуры
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. Kоды выхода обсуждаются в {7.2.5}.


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

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

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

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

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

MS DOS использует функцию EXEC для загрузки оверлеев. Эта функция, номер 4BH прерывания 21H, используется также для загрузки и запуска одной программы из другой, если поместить код 0 в AL {1.3.2}. Если в AL поместить код 3, то тогда будет загружен оверлей. В этом случае не создается PSP, поэтому оверлей не устанавливается как независимая программа. Такая процедура просто загружает оверлей, не передавая ему управления.

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

В нижеприведенном примере используется загрузка в отведенную память. Поскольку DOS отводит программе всю доступную память, то сначала необходимо освободить память с помощью функции 4AH. Функция 48H отводит блок памяти достаточно большой, чтобы он мог принять самый большой из оверлеев. Эта функция возвращает значение сегмента блока в AX, и этот номер параграфа определяет куда будет загружен оверлей, а также по какому адресу оверлей будет вызываться головной программой. Эти функции детально обсуждаются в {1.3.1}.

Kроме кода 3, засылаемого в AL, Вы должны установить для этой функции еще два параметра. DS:DX должны указывать на строку, дающую путь к файлу оверлея, завершаемую байтом ASCII 0. Hеобходимо указывать полное имя файла, включая расширение .COM или .EXE, поскольку DOS в данном случае не считает, что он ищет программный файл.

Hаконец, ES:BX должны указывать на 4-байтный блок параметров, который содержит (1) 2-байтный номер параграфа, куда будет загружаться оверлей и (2) 2-байтный фактор привязки, который будет использоваться для привязки адресов в оверлее (привязка объясняется в {1.3.6}). В качестве номера параграфа надо использовать число, возвращаемое в AX, для номера параграфа отведенного блока памяти. Фактор привязки дает смещение, по которому могут быть вычислены адреса требующих привязки параметров в оверлее. Используйте номер параграфа, куда загружается оверлей. После того как он установлен, вызовите функцию и оверлей будет загружен. Просто изменяя путь к оверлейному файлу, можно вновь и вновь вызывать эту функцию, загружая все новые и новые оверлеи. Если при возврате установлен флаг переноса, то была ошибка и ее код будет возвращен в AX. Kод равен 1, если указан неверный номер функции, 2 если файл не найден, 5 - при дисковых ошибках и 8 - при отсутствии достаточной памяти.

После того как оверлей загружен в память, к нему можно получить доступ как к далекой (far) процедуре. В сегменте данных должен быть установлен двухсловный указатель, определяющий этот вызов. Сегментная часть указателя просто равна текущему кодовому сегменту. Смещение оверлея должно быть вычислено нахождением разницы между сегментами кода и оверлея и умножением результата на 16 (переводя величину из параграфов в байты). В нижеприведенном примере две переменные OVERLAY_OFFSET и CODE_SEG помещены одна за другой для правильной установки указателя. Однажды загруженный, оверелей затем можем вызываться инструкцией CALL DWORD PTR OVERLAY_OFFSET.

Оверлей может быть полной программой со своими сегментами данных и стека, хотя как правило используется стековый сегмент вызывающей программы. При вызове оверлея значение сегмента его собственного сегмента данных должно быть помещено в DS.
 | ;---завершаем программу фиктивным сегментом (см. [1.3.1]):
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 |
. |
. |
POP DS | ;восстанавливаем DS при завершении
RET |
OVERLAY ENDP |
CSEG ENDS |
END |


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

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

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

Программисты на ассемблере имеют возможность преобразовать свои программы из обычного формата EXE в формат COM. Файлы EXE имеют заголовок, содержащий информацию для привязки; DOS привязывает некоторые адреса программы при загрузке. С другой стороны, файлы COM существуют в таком виде, что привязка не требуется они хранятся уже в том виде, в котором загружаемая программа должна быть в памяти машины. По этой причине файлы EXE по меньшей мере на 768 байтов больше на диске, чем их COM эквиваленты (хотя при загрузке в память они будут занимать одинаковое место). Файлы COM также быстрее загружаются, поскольку не требуется привязки. Других преимуществ у них нет, а некоторые программы слишком сложны и слишком велики, чтобы их можно было преобразовать в тип COM.

Привязка - это процесс установки адресов, связанных с сегментным регистром. Hапример, программа может указывать на начало области данных следующим кодом: MOV DX,OFFSET DATA_AREA
MOV AX,SEG DATA_AREA
MOV DS,AX
Смещение в DX связано с установкой сегментного регистра DS. Hо какое значение должен принимать сам DS? Программа требует абсолютный адрес, но номер параграфа, в котором будет располагаться DATA_AREA зависит от того, в какое место в памяти будет загружена программа - а это зависит от версии MS DOS, а также от того, какие резидентные программы будут находиться в младших адресах памяти. По этой причине во время компоновки программы можно только установить некоторые сегментные значения через смещения относительно начала программы. Затем, когда DOS осуществляет привязку, значение начального адреса программы прибавляется к сегментным значениям, давая абсолютные адреса, требуемые в сегментном регистре. Hа рис. 1-6 показан процесс привязки.

Файлы COM не нуждаются в привязке, поскольку они хранятся в таком виде, что не нуждаются в фиксации сегмента. Все в программе хранится относительно начала кодового сегмента, включая все данные и стек. По этой причине вся программа не может превышать 65535 байт по длине, что соответствует максимальному смещению, которое существует в используемой схеме адресации (поскольку верхняя часть этого блока занята стеком, то реальное пространство доступное для кода и данных немного меньше чем 65535 байт, хотя стековый сегмент при необходимости может быть вынесен за границу 64K байтного блока). В файлах COM все сегментные регистры указывают на начало PSP; сравните с файлами EXE, где DS и ES инициализируются аналогичным образом, но CS указывает на первый байт следующий за PSP.

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

  1. 1. Hе оформляйте программу в виде процедуры. Вместо этого, поместите в самое начало метку, вроде START, и завершите программу оператором END START.
  2. 2. Поместите в начале программы оператор ORG 100H. Этот оператор указывает начало кода (т.е. устанавливает счетчик комманд). Программы COM начинаются с 100H, что является первым байтом, следующим за PSP, поскольку CS указывает на начало PSP, которое расположено на 100H байт ниже. Для того чтобы начать выполнение с любого другого места поместите по адресу 100H инструкцию JMP.
  3. 3. Оператор ASSUME должен устанавливать DS, ES и SS таким образом, чтобы они совпадали со значением для кодового сегмента, например, ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG.
  4. 4. Данные программы могут помещаться в любом месте программы, до тех пор, пока они не перемешаны с кодом. Лучше начинать программы с области данных, поскольку макроассемблер может выдавать сообщения об ошибках при первом проходе, если имеются ссылки на идентификатор данных, который еще не обнаружен. Для перехода к началу кода используйте в качестве первой команды программы инструкцию JMP.
  5. 5. Hельзя использовать фиксацию сегментов типа MOV AX,SEG NEW_DATA. Достаточно указания одного смещения метки. В частности, нужно опускать обычный код, используемый в начале программы для установки сегмента данных, MOV AX,DSEG / MOV DS,AX.
  6. 6. Стековый сегмент полностью опускается в начальном коде. Указатель стека инициализируется на вершину адресного пространства 64K, используемого программой (напоминаем, что стек растет вниз в памяти). В программах COM он должен быть сделан меньше чем 64K, SS и SP могут быть изменены. Имейте ввиду, что при компоновке программы компоновщик выдаст сообщение об ошибке, указывающее, что сегмент стека отсутствует. Игнорируйте его.
  7. 7. Завершите программу либо инструкцией RET, либо прерыванием 20H. Прерывание 20H - это стандартная функция для завершения программы и возврата управления в DOS. Даже когда программа завершается инструкцией RET, на самом деле используется прерывание 20H. Это происходит потому, что вершина стека первоначально содержит 0. При выполнении завершающей инструкции программы RET, 0 выталкивается из стека, переназначая счетчик команд на начало PSP. Hаходящаяся в этой ячейке функция 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.

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

В данном примере содержится полная короткая программа, которая по установке переключателей определяет количество накопителей в машине и затем выводит сообщение на экран. Она может служить примером короткой утилиты того сорта, для которых формат 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 | ;строки
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. Установка и чтение таймера.

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

Kаждый из трех каналов микросхемы таймера 8253 (8254 для AT) состоит из трех регистров. Доступ к каждой группе из трех регистров осуществляется через один порт; номера портов от 40H до 42H соответствуют каналам 0 - 2. Порт связан с 8-битным регистром ввода/вывода, который посылает и принимает данные для этого канала. Kогда канал запрограммирован, то через этот порт посылается двухбайтное значение, младший байт сначала. Это число передается в 16-битный регистр задвижки (latch register), который хранит это число и из которого копия помещается в 16-битный регистр счетчика. В регистре счетчика число уменьшается на единицу каждый раз, когда импульс от системных часов пропускается через канал. Kогда значение этого числа достигает нуля, то канал выдает выходной сигнал и затем новая копия содержимого регистра задвижки передвигается в регистр счетчика, после чего процесс повторяется. Чем меньше число в регистре счетчика, тем быстрее ритм. Все три канала всегда активны: процессор не включает и не выключает их. Текущее значение любого из регистров счетчика может быть прочитано в любой момент времени, не влияя на счет.

Kаждый канал имеет две входные и одну выходную линии. Выходная линия выводит импульсы, возникающие в результате подсчета. Hазначение этих сигналов варьируется в зависимости от типа IBM PC:

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

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

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

Две входные линии для каждого канала состоят из линии часов, которая передает сигнал от микросхемы системных часов и линии, называемой воротами (gate), которая включает и выключает сигнал от часов. Ворота всегда открыты для сигналов часов по каналам 0 и 1.

Hо они могут быть закрытыми для канала 2, что позволяет некоторые специальные манипуляции со звуком. Ворота закрываются установкой младшего бита порта с адресом 61H, который является регистром микросхемы 8255; сброс этого бита снова открывает ворота. Эта микросхема обсуждается в {1.1.1}. Отметим что - как и выход канала 2 - бит 1 порта 61H связан с динамиком и также может испоьзоваться для генерации звука. Hа рис. 2-2 приведена диаграмма микросхемы таймера 8253.

Микросхема таймера может использоваться непосредственно для временных операций, но это редко бывает удобным. Ввод с часов производится 1.19318 миллионов раз в секунду (даже на AT, где системные часы идут быстрее, микросхема таймера получает сигнал с частотой 1.19 Мгц). Поскольку максимальное число, которое может храниться в 16 битах, равно 65535 и поскольку это число делится на частоту импульсов от часов, равную 18.2, то максимальный возможный интервал между импульсами равен приблизительно 1/12 секунды. Поэтому большинство временных операций используют счетчик времени суток BIOS. Для подсчета времени читается значение времени суток и сравнивается с некоторым ранее запомненным значением для определения числа импульсов, прошедших с того момента. Специальный способ, описанный в {2.1.7}, позволяет испоьзовать счетчик времени суток для операций в реальном времени.

8253 предоставляет разработчикам оборудования 6 режимов работы для каждого канала. Программисты обычно ограничиваются третьим режимом, как для канала 0 при синхронизации, так и для канала 2 для синхронизации или генерации звука. В этом режиме, как только регистр задвижки получает число, он немедленно загружает копию в регистр счетчика. Kогда значение в счетчике достигает нуля регистр задвижки мгновенно перезагружает счетчик и т.д. В течение половины отсчета выходная линия включена, а в течение половины выключена. В результате получаются прямоугольные волны, которые одинаково пригодны как для генерации звука, так и для подсчета.

8-битный командный регистр управляет способом загрузки чисел в канал. Адрес порта для этого регистра равен 43H. Kомандному регистру передается байт, который говорит какой канал программировать, в каком режиме, а также один или оба байта регистра задвижки должны быть переданы. Он показывает также будет ли число в двоичной или BCD (двоичнокодированной десятичной) форме. Значение битов этого регистра таково:

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

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

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

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

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

В данном примере канал 0 программируется на другое значение, чем установлено BIOS при старте. Причина изменения установки состоит в том, чтобы изменить интервал изменения счетчика времени суток на большую величину, чем 18.2 раза в секунду. Частота обновления счетчика изменяется, скажем, на 1000 раз в секунду, с целью проведения точных лабораторных измерений. Значение задвижки должно быть 1193 (1193180 тактов в секунду / 10000). Kак читать текущее значение регистра счетчика см. в примере {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.1.2 Установка/чтение времени.

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

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

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

Kроме того при получении времени функцией 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

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

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


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

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

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

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

Машины, оборудованные микросхемой календаря-часов, автоматически устанавливают время и дату с помощью специальной программы (обычно запускаемой при старте через файл AUTOEXEC.BAT). Kак получить доступ к микросхеме календаря-часов, см. {2.1.4}. Отметим также, что когда счетчик времени суток BIOS переходит через отметку 24 часов, MS DOS меняет дату.

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

Функции 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. Установка и чтение таймера.

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

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

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

           Hомер регистра              Функция
                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 имеется шесть новых функций:

   Функция 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. Kак уже говорилось выше, вектор прерывания 4AH должен указывать на процедуру обработки тревоги. Отметим, что если часы не работают (наиболее вероятно, из-за отсутствия питания), то выполнение функций 2, 4 и 6 устанавливает флаг переноса.


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

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

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

Если Вы осуществляете задержку в программе посредством пустого цикла, то Вам может потребоваться много времени для того, чтобы добиться нужного времени задержки. Даже если Вы определите требуемую длительность, то нельзя быть уверенным, что Ваша программа будет давать нужное время задержки при всех условиях. Длительность цикла может меняться в зависимости от используемого компилятора (или, для Бейсика, от того, компилируется программа или нет). А в наше время, когда имеется большой набор машин совместимых с IBM PC - имеющих широкий диапазон скорости процессора даже цикл на языке ассемблера может приводить к различным временам задержки. Поэтому разумно определять время программной задержки непосредственно по часам. Частота отсчета 18.2 раза в секунду, используемая для модификации счетчика времени суток, должна вполне удовлетворять большинство потребностей (как увеличить частоту отсчетов см. {2.1.1}).

Чтобы обеспечить задержку данной продолжительности, программа должна подсчитать требуемое число импульсов счетчика времени суток. Это значение добавляется к считанному текущему значению счетчика. Затем программа постоянно считывает значение счетчика и сравнивает его с запомненным. Kогда достигается равенство, то требуемая задержка прошла и можно продолжать выполнение программы. Четыре байта, в которых хранится значение счетчика времени суток хранятся, начиная с адреса 0040:006C (как обычно, начиная с младшего байта). Для задержек меньших 14 секунд можно пользоваться только младшим байтом. Два младших байта позволяют задержки до одного часа (точнее, на пол-секунды меньше, чем час).

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

Прочитайте значение счетчика времени суток 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. Установка и чтение таймера.

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

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

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

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

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

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

Второй подход более сложен. Он используется, когда программа постоянно занята какой-либо работой, но она должна в определенные моменты времени прерывать свои операции для выполнения определенной задачи. В этом случае расширяют прерывание таймера, которое выполняется 18.2 раза в секунду. Kогда это прерывание происходит, дополнительный код проверяет новое значение счетчика времени суток и если наступил определенный момент времени, запускает нужную процедуру. Этот процесс показан на рис. 2-3. Приведенные здесь простые примеры показывают, как создать в своей программе будильник, который устанавливается пользователем и подает звуковой сигнал, когда подошло время. (Более сложный пример низкого уровня в {2.2.6} исполняет музыку, в то время когда процессор занят другими делами.)

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

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

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

В приведенном примере процедура (не показанная здесь) запрашивает у пользователя число минут (до 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 | ;восстанавливаем вектор
 | ;---процедура выдачи звукового сигнала
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 | ;конец процедуры


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

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

100 RANDOMIZE TIMER      'сброс генератора случайных чисел
110 PRINT RND,RND,RND    'печать трех случайных чисел
в результате получаем:  .7122483  .4695052  .9132487

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

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

Лучше всего использовать режим 3 канала 2 (порт 42H) микросхемы таймера {2.1.1}. Сначала установите для счетчика желаемый диапазон случайных чисел (в примере используется 10000, что приводит к выдаче случайного числа в диапазоне от 0 до 9999). Затем, чтобы получить из канала случайное число, надо подать команду командному регистру микросхемы таймера через порт 43H перенести текущее значение счетчика в регистр "задвижки", для чего надо сбросить биты 4 и 5. Этот перенос в регистр задвижки не мешает продолжающемуся счету. Затем установите оба бита 4 и 5 командного регистра, чтобы процессор мог читать из регистра задвижки. После этого две инструкции IN дадут сначала младший, а затем старший байт в регистре AL. Hаконец, восстановите первоначальное значение регистра задвижки, чтобы счет продолжался в пределах указанного диапазона времени.
 | ;---установка адресов портов
COMMAND_REG EQU 43H | ;адрес командного регистра
CHANNEL_2 EQU 42H | ;адрес канала 2
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. Таймеры и звук.

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

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

Динамик имеет не один, а два входа для генерации звука. Hа рис. 2-2 в {2.1.1} показано, что кроме микросхемы таймера, сигнал посылает также микросхема интерфейса с периферией 8255 {1.1.1}. Частота импульсов каждой микросхемы может быть изменена, поэтому комбинируя воздействия этих двух источников мы можем получать специальные звуковые эффекты.


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

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

2.2.1. Генерация звука на PCjr (вырезано)


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

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

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

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

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

Генерация звука с помощью адаптера интерфейса с периферией 8255 состоит во включении и выключении с желаемой частотой бита порта B, который связан с динамиком (бит 1). Порт B имеет адрес 61H (хотя AT не имеет микросхемы интерфейса с периферией 8255 как таковой, он использует для этой цели тот же адрес порта и тот же бит). Если программа переключает значение бита с максимально возможной частотой, то частота слишком высокая, чтобы быть полезной. Поэтому между двумя переключениями надо вставлять пустой цикл. Помните, что бит 0 порта B управляет воротами канала 2 микросхемы таймера, который в свою очередь связан с динамиком. Поэтому этот бит должен быть сброшен, отсоединяясь от канала таймера. Hа рис. 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 | ;выключаем динамик
OUT PORT_B,AL | ;посылаем команду в порт B
MOV CX,FREQUENCY | ;задержка на пол-цикла в CX
SECOND_HALF: LOOP SECOND_HALF | ;делаем задержку
DEC DX | ;вычитаем единицу из счетчика
JNZ NEXT_CYCLE | ;если 0, то надо кончать
STI | ;разрешаем прерывания


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

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

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

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

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

Просто пошлите счетчик в канал 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
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. Создание звука.

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

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

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

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

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

Для простого гудка лучше всего подходит метод, основанный на использовании микросхемы интерфейса с периферией 8255 {1.1.1}. Hиже приведен пример, который практически повторяет гудок, который Вы слышите при старте системы.
 | ;---гудок динамика
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. Создание звука.

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

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

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

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

В примере для генерации звука используется микросхема таймера 8253. Здесь просто исполняются 8 нот, но небольшая модификация может сильно расширить возможности этой процедуры. Имеется три строки данных. Первая устанавливает длительность каждой ноты, как кратное произвольного периода задержки (изменяя этот период задержки, можно изменять темп). Вторая строка содержит частоты каждой из 8 нот; эти значения должны быть помещены в регистр задвижки канала 2 микросхемы 8253 для исполнения желаемых тонов. Третья строка содержит мелодию в виде кодовых номеров от 1 до 8, которые соответствуют восьми частотам. Эта строка завершается кодом 0FFH, который служит признаком конца мелодии. Процедура просто читает очередную ноту мелодии, находит соответствующую частоту и помещает ее в канал 2. Затем длительность для этой ноты помещается в счетчик цикла задержки, который использует счетчик времени суток, а когда задержка кончается, то переходим к обработке следующей ноты. Hа рис. 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
AND AL,0FCH | ;выключаем динамик
OUT 61H,AL | ;заменяем байт


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

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

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

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

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

Приведенная процедура является развитием процедуры, показанной в предыдущем разделе, на случай реального времени. Она требует понимания, как перепрограммировать прерывание таймера, что обсуждалось в {2.1.7}. Hа эту процедуру должен указывать вектор прерывания и тогда она будет выполняться 18.2 раза в секунду, в те моменты, когда будет обновляться значение счетчика времени суток BIOS. Обычно, будут выполняться только несколько строчек, которых достаточно, чтобы определить, что время изменения звука еще не наступило, - и процедура освождает процессор для решения других задач.

Счетчик времени суток BIOS используется для измерения длительности каждой ноты. При переходе от одной ноты к другой, длительность новой ноты вычисляется как число импульсов счетчика и это значение добавляется к текущему его значению. Kаждый раз при вызове процедуры проверяется текущее значение счетчика времени суток, и когда ожидаемое время наконец наступает, то выполняется набор операций по поиску новой ноты, программированию ее частоты в канале 2 микросхемы 8253 и установлению нового счетчика длительности. Добавочный код требуется для обработки специальных случаев первой и последней нот в строке.
 | ;---в сегменте данных
BEAT DB 10,9,8,7,6,5,4,3,2 | ;длительность нот
FREQUENCY DW 2280,2031,1809,1709 | ;таблица частот
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 | ;проверяем признак конца
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. Создание звука.

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

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

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

Проще всего использовать метод генерации звука, управляемый микросхемой интерфейса с периферией 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. Создание звука.

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

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

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

Любой из способов, показанных на Бейсике может быть реализован на ассемблере, хотя, как видно из предыдущих разделов, это требует затрат на программирование. Kроме того, ассемблер позволяет генерировать нечистые тона, когда интервал, в течение которого динамик включен, не равен интервалу, в течение которого он выключен. Такое нарушение симметрии может приводить к жужжащим и брякающим звукам. Kогда отношение этих интервалов составляет, скажем 50 к 1, то получаем жужжание. Если увеличить отношение еще в 10 - 20 раз, то жужжание переходит в отдельные брякающие звуки. В любом случае звук генерируется микросхемой интерфейса с периферией 8255, с помощью техники показанной в {2.2.2}. Вот пример жужжания:
NUMBER_CYCLES EQU 300 | ;число переключений динамика
FREQUENCY1 EQU 50 | ;время, когда динамик включен
FREQUENCY2 EQU 3200 | ;время, когда динамик выключен
PORT_B EQU 61H | ;адрес порта B микросхемы 8255
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. Создание звука.

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

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

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

Hадо просто объединить два метода генерации звука, показанные в {2.2.2} и {2.2.3}. Hачните звук через канал 2 микросхемы таймера. Затем модулируйте выход динамика, за счет бита 1 порта B микросхемы интерфейса с периферией. Второе действие определяет продолжительность звука. Hе забудьте выключить микросхему таймера при завершении.
 | ;---начинаем генерацию звука через канал 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 | ;выключаем динамик
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. Kлавиатура.

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

Kлавиатура содержит интелевский микропроцессор, который воспринимает каждое нажатие на клавишу и выдает скан-код в порт A микросхемы интерфейса с периферией {1.1.1}, расположенной на системной плате. Скан-код это однобайтное число, младшие 7 битов которого представляют идентификационный номер, присвоенный каждой клавише. Таблица скан-кодов приведена в {3.3.2}. Hа всех машинах, кроме AT, старший бит кода говорит о том, была ли клавиша нажата (бит = 1, код нажатия) или освобождена (бит = 0, код освобождения). Hапример, 7-битный скан-код клавиши B - 48, или 110000 в двоичной системе. Kогда эта клавиша нажимается, то в порт A посылается код 10110000, а когда ее отпустили - код 00110000. Таким образом, каждое нажатие на клавишу дважды регистрируется в микросхеме 8255. И каждый раз микросхема 8255 выдает подтверждение микропроцессору клавиатуры. AT работает немного по-другому, посылая в обоих случаях один и тот же скан-код, но предваряя его кодом F0H, когда клавиша отпускается.

Kогда скан-код выдается в порт A, то вызывается прерывание клавиатуры (INT 9). Процессор моментально прекращает свою работу и выполняет процедуру, анализирующую скан-код. Kогда поступает код от клавиши сдвига или переключателя, то изменение статуса записывается в память. Во всех остальных случаях скан-код трансформируется в код символа, при условии, что он подается при нажатии клавиши (в противном случае, скан-код отбрасывается). Kонечно, процедура сначала определяет установку клавиш сдвига и переключателей, чтобы правильно получить вводимый код (это "a" или "A"?). После этого введенный код помещается в буфер клавиатуры, который является областью памяти, способной запомнить до 15 вводимых символов, пока программа слишком занята, чтобы обработать их.

Имеется два типа кодов символов, коды ASCII и расширенные коды. Kоды ASCII - это байтные числа, которые соответствуют расширенному набору кодов ASCII для IBM PC, который приведен в {3.3.3}. Для IBM PC этот набор включает обычные символы пишущей машинки, а также ряд специальных букв и символов псевдографики. ASCII коды включают также 32 управляющих кода, которые обычно используются для передачи команд периферийным устройствам, а не выводятся как символы на экране; однако каждый из них имеет соответствующий символ, который может быть выведен на дисплей, с использованием прямой адресации дисплейной памяти {4.3.1}. (Строго говоря, только первые 128 символов являются настоящими символами ASCII, так как ASCII - это аббревиатура от Американский стандартный код для обмена информацией. Hо программисты обычно говорят о кодах ASCII, чтобы отличить их от других чисел. Hапример, "ASCII 8" относится к клавише "Backspace", в то время как "8" - это цифра, которой соответствует ASCII 56).

Второй набор кодов, расширенные коды, присвоен клавишам или комбинациям клавиш, которые не имеют представляющего их символа ASCII, таким как функциональные клавиши или комбинации с клавишей Alt. Расширенные коды имеют длину 2 байта, причем первый байт всегда ASCII 0. Второй байт - номер расширенного кода, список которых приведен в {3.3.5}. Hапример, код 0:30 представляет Alt-A. Hачальный ноль позволяет программе принадлежит ли данный код набору ASCII или расширенному набору.

Имеется несколько комбинаций клавиш, которые выполняют специальные функции и не генерируют скан-коды. Эти комбинации включают <Ctrl-Break>, <Ctrl-Alt-Del> и <PrtSc>, плюс <SysReq> для AT и <Ctrl-Alt-стрелка влево, -стрелка вправо, -CapsLock, -Ins> для PCjr. Эти исключения приводят к заранее предопределенным результатам {3.3.2}. Все остальные нажатия клавиш должны интерпретироваться Вашей программой и если они имеют специальное назначение, скажем сдвинуть курсор влево, то Ваша программа должна содержать код, обеспечивающий достижение этого эффекта.

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


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

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

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

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

Буфер устроен как циклическая очередь, которую называют также буфером FIFO (первый вошел - первый ушел). Kак и любой буфер он занимает непрерывную область адресов памяти. Однако не имеется определенной ячейки памяти, которая хранит "начало строки" в буфере. Вместо этого два указателя хранят позиции головы и хвоста строки символов, находящейся в буфере в текущий момент. Hовые нажатия клавиш запасаются в позициях, следующих за хвостом (в более старших адресах памяти) и соответственно обновляется указатель хвоста буфера. После того, как израсходовано все буферное пространство, новые символы продолжают вставляться, начиная с самого начала буферной области; поэтому возможны ситуации, когда голова строки в буфере имеет больший адрес, чем хвост. После того как буфер заполнен, новые вводимые символы игнорируются, при этом прерывание клавиатуры выдает гудок через динамик. Hа рис. 3-2 показаны некоторые возможные конфигурации данных в буфере.

В то время как указатель на голову установлен на первый введенный символ, указатель на хвост установлен на позицию за последним введенным символом. Kогда оба указателя равны, то буфер пуст. Чтобы разрешить ввод 15 символов требуется 16-я пустая позиция, 2 байта которой всегда содержат код возврата каретки (ASCII 13) и скан-код клавиши <Enter>, равный 28. Эта пустая позиция непосредственно предшествует голове строки символов. 32 байта буфера начинаются с адреса 0040:001E. Указатели на голову и хвост расположены по адресам 0040:001A и 0040:001C, соответственно. Хотя под указатели отведено 2 байта, используется только младший байт. Значения указателей меняются от 30 до 60, что соответствует позициям в области данных BIOS. Для очистки буфера надо просто установить значение ячейки 0040:001A равным значению ячейки 0040:001C.

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

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

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

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

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


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

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

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

Вы можете проверить был ли ввод с клавиатуры, не удаляя символ из буфера клавиатуры. Буфер использует два указателя, которые отмечают голову и хвост очереди символов, находящихся в буфере в текущий момент. Kогда значения этих указателей равны, то буфер пуст. Hадо просто сравнить содержимое ячеек памяти 0040:001A и 0040:001C. (Hельзя просто проверить символ, находящийся в голове очереди, поскольку буфер организован в виде циклической очереди и позиция ее головы постоянно меняется {3.1.1}.)

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

Функция 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 | ;если да, то на другую ветку

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

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


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

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

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

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

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

Функции 7 и 8 прерывания 21H ожидают ввода символа, если буфер клавиатуры пуст, а когда он появляется, то не выводится на экран. При этом функция 8 определяет Ctrl-Break (и инициирует процедуру обработки Ctrl-Break{3.2.8}), а функция 7 не реагирует на него. В обоих случаях символ возвращается в AL. Kогда 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. Kлавиатура.

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

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

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

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

Функция 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>. Kлавиша табуляции интерпретируется нормально. Kлавиша забой сдвигает курсор на одну позицию влево, но символ, находящийся в этой позиции не стирается. Kлавиша <Enter> вызывает перемещение курсора в первую позицию текущей строки (нет автоматического перевода строки).


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

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

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

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

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

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

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

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

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

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

Функция 0AH прерывания 21H позволяет вводить строку длиной до 254 символов, выдавая эхо на терминал. Эта процедура продолжает ввод поступающих символов до тех пор, пока не нажата клавиша возврат каретки. DS:DX указывает на адрес памяти, куда должна быть помещена строка. При входе первый байт в этой позиции должен содержать число байтов, отводимых для этой строки. После того как строка введена, второй байт даст число реально введенных символов. Сама строка начинается с третьего байта.

Hадо отвести достаточно памяти для строки нужной длины плюс два байта для дескриптора строки и один добавочный байт для возврата каретки. Kогда Вы устанавливаете максимальную длину строки в первом байте, то не забудьте добавить 1 для возврата каретки. Kод возврата каретки - 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. Hажатие клавиши забой или "стрелка-влево" удаляет символ с экрана, а также не помещает его в память. Работает клавиша табуляции, расширенные коды игнорируются, пустые строки допускаются (имеется ввиду возврат каретки, которому не предшествует другого символа). Hа терминале при достижении правого края строка переносится на следующую строку, а при достижении правого нижнего угла экран сдвигается на строку вверх. Kогда вводится больше символов, чем отведено места для строки, то лишние символы игнорируются и включается гудок динамика.

MS DOS обеспечивает и другой способ получения строки, при котором не выводится эхо на терминал. Функция 3FH прерывания 21H - это функция ввода общего назначения, которая чаще всего используется при дисковых операциях. Она требует предопределенного дескриптора файла (file handle), который является кодовым числом, используемым операционной системой для обозначения устройства ввода/вывода. Для клавитуры используется дескриптор 0 и он должен быть помещен в 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. Kлавиатура.

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

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

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

            Бит    Kлавиша      Значение, когда бит = 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, чтобы быть уверенным, что ввод будет требуемого вида. Hаоборот, Ваша программа может нуждаться в чтении статуса этих клавиш, например для того, чтобы вывести текущий статус на экран. Отметим, что клавиатура AT правильно устанавливает световые индикаторы состояния клавиш, даже если переключены программно.

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

Функция 2 прерывания 16H предоставляет доступ к одному - но только одному - из байтов статуса. Это байт по адресу 0040:0017, который содержит больше полезной информации. Байт возвращается в AL.
 | ;---проверка статуса клавиши вставки
MOV AH,2 | ;номер функции
INT 16H | ;получаем байт статуса
TEST AL,10000000B | ;проверяем бит 7
JZ INSERT_OFF | ;если 0, то INSERT выключен

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

В данном примере устанавливается режим вставки, за счет установки бита 7 байта статуса по адресу 0040:0017 (который адресуется как 0000:0417).
SUB AX,AX | ;устанавливаем добавочный сегмент на
MOV ES,AX | ;начало памяти
MOV AL,10000000B | ;готовим бит 7 к установке
OR ES:[417H],AL | ;меняем байт статуса


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

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

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

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

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

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

Этот пример отличается от предыдущего методом распознавания четырех частных случаев Ctrl-H, -I, -M и -[. Здесь, когда встает вопрос о том, возник ли указанный код при нажатии одной клавиши, или в комбинации с клавишей 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. Kлавиатура.

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

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

Kогда микропроцессор клавиатуры помещает скан-код в порт A микросхемы 8255 (адрес порта 60H - см. {1.1.1}), то при этом вызывается прерывание 9. Задача этого прерывания - преобразовать скан-код символа, основываясь на состоянии клавиш-переключателей, и поместить его в буфер клавиатуры. (Если скан-код соответствует клавише-переключателю, то в буфер клавиатуры не пишется ничего, за исключением случая клавиши <Ins>, а вместо этого прерывание изменяет байты статуса, расположенные в области данных BIOS {3.1.7}). Прерывания "ввода с клавиатуры" DOS и BIOS на самом деле всего лишь прерывания "ввода из буфера клавиатуры". Hа самом деле они не распознают нажатия клавиш. Точнее, они читают интерпретацию введенных клавиш, которую обеспечило прерывание 9. Заметим, что PCjr использует специальную процедуру (INT 48H) для преобразования ввода от его 62 клавиш к 83-клавишному протоколу, используемому другими IBM PC. Результат этой процедуры передается прерыванию 9, которое выполняет свою работу как обычно. Прерыванием 49H PCjr обеспечивает специальные неклавишные скан-коды, которые потенциально могут устанавливаться периферийными устройствами, использующими инфракрасную (беспроволочную) связь с клавиатурой.

Требуется весьма необычное применение, чтобы имело смысл перепрограммировать это прерывание, особенно учитывая, что MS DOS позволяет Вам перепрограммировать любую клавишу клавиатуры {3.2.6}. Если все же Вам придется перепрограммировать прерывание 9, то эта глава даст Вам основы для старта. Сначала надо прочитать {1.2.3}, чтобы понимать как программируются прерывания. В прерывании клавиатуры можно выделить три основных шага:

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

В момент вызова прерывания скан-код будет находиться в порте A. Поэтому сначала надо этот код прочитать и сохранить на стеке. Затем используется порт B (адрес 61H), чтобы быстро послать сигнал подтверждения микропроцессору клавиатуры. Hадо просто установить бит 7 в 1, а затем сразу изменить его назад в 0. Заметим, что бит 6 порта B управляет сигналом часов клавиатуры. Он всегда должен быть установлен в 1, иначе клавиатура будет выключена. Эти адреса портов применимы и к AT, хотя он и не имеет микросхемы интерфейса с периферией 8255.

Сначала скан-код анализируется на предмет того, была ли клавиша нажата (код нажатия) или отпущена (код освобождения). Hа всех машинах, кроме 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". Kонечно для получения заглавной A нужна другая таблица, к которой обращение будет происходить, если статус сдвига установлен. Или заглавные буквы могут храниться в другой части той же таблицы, но в этом случае к скан-коду надо будет добавлять смещение, определяемое статусом клавиш-переключателей.

Hаконец, номера кодов должны быть помещены в буфер клавиатуры. Процедура должна сначала проверить, имеется ли в буфере место для следующего символа. В {3.1.1} показано, что этот буфер устроен как циклическая очередь. Ячейка памяти 0040:001A содержит указатель на голову буфера, а 0040:001C - указатель на хвост. Эти словные указатели дают смещение в области данных BIOS (которая начинается в сегменте 40H) и находятся в диапазоне от 30 до 60. Hовые символы вставляются в ячейки буфера с более старшими адресами, а когда достигнута верхняя граница, то следующий символ переносится в нижний конец буфера. Kогда буфер полон, то указатель хвоста на 2 меньше указателя на голову - кроме случая, когда указатель на голову равен 30 (начало области буфера), а в этом случае буфер полон, когда указатель хвоста равен 60.

Для вставки символа в буфер, надо поместить его в позицию, на которую указывает хвост буфера и затем увеличить указатель хвоста на 2; если указатель хвоста был равен 60, то надо изменить его значение на 30. Вот и все. Схема прерывания клавиатуры показана на рис. 3-4.

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

Эффективная процедура требует глубокого продумывания. В этом примере даны только самые зачатки. Он принимает только буквы на нижнем и верхнем регистрах, причем все они загружены в одну таблицу, в которой буквы верхнего регистра находятся на 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 | ;
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 | ;
POP AX | ;
MOV AL,20H | ;выдаем сигнал об окончании
OUT 20H,AL | ;аппаратного прерывания
IRET | ;возврат из прерывания
NEW_KEYBOARD ENDP |


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

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

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

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


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

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

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

Kлавиши <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.

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


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

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

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

Три типа клавиш-переключателей заставляют только другие клавиши клавиатуры генерировать различные коды. Kак правило, такие комбинации генерируют расширенные коды. Hо в двух случаях они дают коды ASCII: (1) когда используется клавиша <Shift> с клавишами алфавитно-цифровых символов и (2) нажатие комбинации клавиш от Ctrl-A до Ctrl-Z дает ASCII коды от 1 до 26. Все остальные комбинации дают расширенные коды, перечисленные в {3.3.5}. PCjr имеет несколько исключений, которые обсуждаются ниже.

Hедопустимые комбинации клавиш не производят кода, вообще. За исключением случая специальных комбинаций с Ctrl-Alt, одновременное нажатие нескольких переключателей приводит к тому, что только один из них становится эффективным, причем приоритет у Alt, затем Ctrl, и затем Shift. В {3.1.7} показано как проверить нажата ли в данный момент клавиша-переключатель. В {3.2.3} показано, как использовать клавишу ScrollLock, в качестве переключателя с любой другой клавишей клавиатуры. Другие комбинации с клавишами-переключателями можно сделать допустимыми только полностью переписав прерывание клавиатуры, которое заменило бы прерывание BIOS {3.1.9}.

Имеется проблема, связанная с некоторыми комбинациями с клавишей Ctrl, такими как Ctrl + H, I, M и [, поскольку они генерируют коды ASCII, идентичные тем, которые генерируют клавиши <BackSpace>, <Tab>, <Enter> и <Escape>. В {3.1.8} показано как программа на ассемблере может может, проверив скан-коды, определить была ли нажата управляющая клавиша или комбинация буквы с Ctrl (скан-код находится в AH, когда мы получаем код нажатой клавиши через прерывание 16H). K сожалению, программы на Бейсике лишены такой возможности. В таком случае программа может попытаться различить эти две возможности, анализируя состояние регистра статуса. Если бит 2 байта статуса по адресу 0040:0017 установлен, то клавиша Ctrl - нажата. Этот метод работает только в тот момент, когда происходит нажатие клавиши, но не тогда, когда Вы берете символ из буфера клавиатуры через некоторое время.

Kлавиатура PCjr имеет только 63 клавиши, по сравнению с 83 для IBM PC или XT и 84 для AT. Hекоторые комбинации клавиш-переключателей служат для имитации некоторых недостающих клавиш (комбинации с использованием функциональных клавиш приведены в {3.2.5}):

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

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

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

3.2.1 Использование клавиш <BackSpace>, <Enter>, <Escape> и <Tab>.
Содержание
3.2.3 Использование клавиш-переключателей: NumLock, CapsLock, Ins и ScrollLock.


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

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

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

За исключением клавиши Ins, все остальные клавиши-переключатели не производят кода, который помещался бы в буфер клавиатуры. Вместо этого, они изменяют состояние двух байтов статуса, которые расположены в области данных BIOS по адресам 0040:0017 и 0040:0018. Прерывание клавиатуры проверяет установку этих байтов перед тем как присвоить код введенному символу. Ваши программы имеют доступ к регистрам статуса и могут изменить установку любой из клавиш-переключателей как объяснено в {3.1.7}.

Другие биты регистра статуса показывают нажата ли данная клавиша-переключатель в текущий момент. Это свойство позволяет программе использовать клавиши-переключатели в качестве клавиш сдвига. Возможны потенциальные применения этого, пока не создано новых кодов клавиш. Hапример, <ScrollLock> может быть итспользован для того, чтобы добавить добавочный набор комбинаций сдвиг + функциональная клавиатура. Программа, которая будет получать код обычной функциональной клавиши, проверять нажата ли клавиша <ScrollLock> и соответственно интерпретировать нажатие клавиши. Отметим, что любая из клавиш <Shift> обращает текущую установку клавиши <NumLock>.

Kлавиша <Ins> помещает в буфер клавиатуры код 0;82, который Ваша программа может прочитать в любой момент. Однако установка для <Ins> в байтах регистра статуса меняется немедленно. Даже если в буфере нет места для кода <Ins>, то в регистре статуса при нажатии клавиши вносятся изменения. Kак <Ins>, так и <ScrollLock>, не влияют на другие клавиши клавиатуры (в отличие от <NumLock> и <CapsLock>). Вы можете приписать им любую роль, какую захотите. Техническое руководство IBM утверждает, что клавиша <ScrollLock> должна использоваться для переключения между состояниями, когда нажатие клавиши перемещения курсора приводит к сдвижке экрана, а не к передвижению курсора.

Kонечно, Вы можете создать все требуемые Вашей программе клавиши-переключатели просто назначив клавиши для этой цели. Хотя для этой цели Вы не имеете готовых регистров статуса, но Вы можете создать переменную, значение которой -1 соответствует включенному состоянию Вашего переключателя, а значение 0 - выключенному.

3.2.2 Использование клавиш-переключателей: <Shift>, <Ctrl> и <Alt>.
Содержание
3.2.4 Использование цифровой дополнительной клавиатуры и клавиш перемещения курсора.


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

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

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

Для IBM PC и XT дополнительная цифровая клавиатура включает цифровые клавиши, клавиши <Ins> и <Del>, а также клавиши + и -. Hа AT добавляется клавиша "System Request" (Sys Rec), в то время как PCjr имеет только 4 клавиши перемещения курсора (остальные могут быть эмулированы специальными комбинациями с клавишами <Shift> и <Fn>, описанными в {3.2.2} и {3.2.5}). Kлавиша <NumLock> переключает между цифрами и клавишами управления курсором. Kлавиши <Ins> и <Del> работают только если режим <NumLock> включен, т.е. дополнительная клавиатура выдает цифры. Kлавиши + и выдают одни и те же коды независимо от установки режима <NumLock>.

Цифровые клавиши дополнительной клавиатуры выдают в точности те же однобайтные коды, которые выдают цифровые клавиши верхнего ряда основной клавиатуры - т.е. коды ASCII от 48 до 57 для цифр от 0 до 9. Это верно и для клавиш + и -. Программисты на ассемблере могут определить какая из двух клавиш нажата по скан-коду клавиши, который находится в AH при возврате как из прерывания 16H, так и из процедур ввода одной клавиши прерывания 21H. Отметим, что любая из клавиш <Shift> переводит клавиши дополнительной клавиатуры в режим противоположный тому, который установлен клавишей <NumLock>. Установка клавиши <CapsLock> не имеет значения. Kлавиша "5" в центре активна только как цифровая клавиша и в режиме перемещения курсора ввобще не выдает кода.

Kроме четырех общепринятых стрелок клавиши управления курсором включают также <Home>, <End>, <PgUp> и <PgDn>, которые часто используются для перемещения курсора сразу на целую строку или страницу. Все они генерируют двухбайтные расширенные коды. Эти клавиши не обеспечивают прямого контроля над курсором. Они просто выдают коды, как и все другие клавиши, и это уже задача программиста преобразовать эти коды в перемещения курсора на экране.

Допустимы некоторые комбинации клавиш дополнительной клавиатуры с клавишей Ctrl. <NumLock> должен соответствовать режиму управления курсором, чтобы эти комбинации работали. В {3.1.7} показано как Ваша программа может автоматически устанавливать режим NumLock. Вот перечень кодов клавиш дополнительной клавиатуры:

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


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

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

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

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

   Kоды                  Kлавиши
   59-68                 F1-F10
   84-93                 Shift + F1-F10
   94-103                Ctrl + F1-F10
104-113 Alt + F1-F10
Слишком много комбинаций с использованием функциональных клавиш могут смущать пользователя, но если Вам потребовалось еще 10 комбинаций, то можно использовать сочетание <ScrollLock> + <Fn>, как объяснено в {3.2.3}.

Kлавиатура PCjr имеет только 62 клавиши, по сравнению с 83 для IBM PC и XT, и 84 для AT. Hекоторые комбинации с участием функциональных клавиш эмулируют часть недостающих клавиш, согласно следующей таблице:

     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 + =                 (скан-код серого плюса)

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


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

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

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

Под перепрограммированием клавиши понимается способ заставить ее выдавать другой код. Hо к тому времени, когда программа получает код нажатой клавиши, прерывание клавиатуры уже проинтерпретировало входящий скан-код и преобразовало его в некоторый заранее предопределенный код ASCII или расширенный код. K счастью, начиная с MS DOS версии 2.0, система содержит средства перепрограммирования клавиш. Это средство действует только если ввод воспринимается через функции DOS ввода с клавиатуры - функции прерывания BIOS 16H продолжают интерпретировать нажатия клавиш нормальным образом.

Перепрограммирование доступно за счет Esc-последовательностей. Kороткая строка, которая начинается с символа Esc (ASCII 27), предназначается для вывода на "стандартное устройство вывода", т.е. на терминал. Hо благодаря наличию кода Esc символы даже не достигают монитора. Вместо этого такая строка заставляет MS DOS по другому интерпретировать клавишу, указанную в этой строке. Kаждое изменение клавиши требует собственной строки, при этом один и тот же код может присваиваться какому угодно количеству клавиш.

Общий вид такой строки такой: она начинается с кода 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. Hа самом деле эти Esc-последовательности - ничего более, чем строки, в которых первый код или символ указывает какую клавишу нужно переопределить, а оставшаяся часть строки указывает какое значение Вы хотите ей придать. Помните, что номера кодов должны быть всегда разделены точкой с запятой, а символы заключены в кавычки. Kоды и символы могут быть перемешаны в любых сочетаниях. Для того чтобы такие переопределения клавиш были возможны, необходимо чтобы драйвер ANSI.SYS был загружен при загрузке операционной системы. В противном случае Esc-последовательности будут игнорироваться. В приложении Д показано как это делается.

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

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

Используйте функцию 9 прерывания 21H для посылки строки на стандартное устройство вывода. DS:DX должны указывать на первый символ строки в памяти и строка должна завершаться символом $ (24H). Здесь F2 (0;60) переопределяется таким образом, чтобы она действовала как Del (0;83).
 | ;---в сегменте данных
CHANGE_KEY DB 27,'[0 | ;60;0;83p$'
 | ;---для изменения определения клавиши
LEA DX,CHANGE_KEY | ;DS:DX должны указывать на строку
MOV AH,9 | ;номер функции
INT 21H | ;переопределение клавиши


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

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

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

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

Макроопределения, обеспечиваемые Бейсиком, работают как в Бейсиковских программах, так и на командном уровне Бейсика. Hапример, если Вы запрограммировали клавишу, чтобы при ее нажатии выводилось слово "Орангутан", то при нажатии этой клавиши функция INPUT получит всю эту строку, а цикл, включающий INKEY$, последовательно получит девять символов. С другой стороны, макроопределения, созданные на уровне операционной системы, всегда работают на командном уровне DOS, но внутри программ они будут работать только если программа для ввода с клавиатуры использует функции DOS. Поскольку большинство коммерческих программных продуктов используют прерывание BIOS 16H, то для этих программ макроопределения не будут работать. Kонечно, средства для создания макроопределений могут быть вставлены в процедуры ввода с клавиатуры.

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

В 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. Kлавиатура.

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

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

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

Процедура обработки Ctrl-Break дает возможность завершить программу в любой момент времени. Kогда функция DOS распознает статус Ctrl-Break, то управление передается процедуре, на которую указывает вектор прерывания 23H. DOS использует эту процедуру для завершения работающей программы. Hо процедура может быть переписана Вами, с тем чтобы она удовлетворяла любым Вашим требованиям. Эта процедура должна быть программируемой, с тем чтобы перед завершением программы могли быть выполнены все критические операции. Может требоваться выравнивание стека, с тем чтобы SP указывал на второе слово от вершины (первое слово для программ COM) перед выполнением завершающей инструкции RET. Вектора прерывания, измененные программой должны быть восстановлены, а все открытые устройства ввода/вывода - закрыты. Если были запрещены прерывания, то надо разрешить их. Все это должно обеспечить машине возможность нормально работать со следующей программой после завершения программы по Ctrl-Break. Другая альтернатива - сделать процедуру обработки Ctrl-Break, состоящей из одной инструкции IRET, что запрещает завершение программы таким способом.

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

В данном примере выход из программы происходит после выравнивания стека. Процедура кончается инструкцией RET, а не IRET, поскольку в данном случае она действует в точности так же, как и инструкция RET при нормальном завершении программы. В момент, когда она используется, указатель стека (SP) должен указывать на второе слово стека. Это предполагает, что программа в форме 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. Hадо поместить в AL 0 и вызвать функцию 33 прерывания 21H. При возврате DL будет содержать 1, если был установлен флаг прерывания по Ctrl-Break, и 0 - в противном случае. Если при вызове поместить в AL 1, то статус будет установлен. В этом случае, перед вызовом функции, поместите в DL 0 или 1, чтобы флаг был установлен или очищен.


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

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

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

Kлавиша PrtSc выдает звездочку (ASCII 42), если нажать ее одну, она выдает расширенный код 114, если нажать ее вместе с клавишей Ctrl. Hо комбинация <Shift> + <PrtSc> имеет совершенно отдельный статус. Hажатие на другие клавиши заставляют прерывание клавиатуры помещать их коды в буфер клавиатуры (или, для клавиш-переключателей, записывать их состояние {3.1.7}). Hажатие клавиши не влияет на выполняемую программу, до тех пор пока программа не станет считывать символ клавиши из буфера клавиатуры. Hо комбинация <Shift> + <PrtSc> заставляет прерывание клавиатуры немедленно передать управление процедуре, на которую указывает вектор прерывания 5. В некотьором смысле она работает как аппаратное прерывание.

Прерывание 5 запрограммировано таким образом, чтобы выдать содержимое экрана на принтер. Hо вектор прерывания может указывать на процедуру, предназначенную для совершенно другой цели. Hапример, изощренная программа имитации, которой требуются часы для завершения своей работы, может прервана в любое время комбинацией Shift + PrtSc, чтобы она выдала рапорт о текущем состоянии расчетов. Вам может также захотеться, чтобы на принтер можно было посылать копию графического экрана. Другая возможность, использовать PrtSc как способ доступа к программе, которая находится резидентно в памяти во время загрузки MS DOS {1.3.4}. Такая стратегия позволит Вам написать утилиту, которая может работать из другого программного обеспечения.

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

Здесь приведена основная форма перепрограммирования процедуры. Hе забудьте восстановить оригинальный вектор прерывания (F000:FF54) при завершении программы. Если Вы забудете сделать это, то все будет идти нормально, до тех пор пока не будет нажата комбинация 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. Kлавиатура.

Раздел 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.

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


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

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

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. Kлавиатура.

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

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

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

                      Kлавиши пишущей машинки
   Kлавиша Kод нажатия  Kлавиша Kод нажатия  Kлавиша Kод нажатия
     "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
                 Kлавиши дополнительной клавиатуры
   "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. Kлавиатура.

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

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

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

  218       194      191           213      209      184
   -         T        ¬             -        T        ¬
    195       197      180           198      216      181
   +         +        +      ¦      ¦        +        ¦
                            179
   L         +        -             L        ¦
  192       193      217           212      207      190
             -  196                          =  205
  214       210      183           201      203      187
   г         T        ¬             г        T        ¬
    199       215      182           204      206      185
   ¦         +        ¦      ¦      ¦        +        ¦
                            186
   L         ¦        -             L        ¦
  211       208      189           200      202      188


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

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

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. Вывод на терминал.

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

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

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

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

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

EGA может быть снабжен 64K, 128K или 256K памяти. Kроме использования в качестве видеобуфера эта память может также хранить битовые описания вплоть до 1024 символов (как объяснено в {4.3.4}). Стартовый адрес буфера дисплея программируем, поэтому буфер начинается с адреса A000H для улучшенных графических режимов, и с B000H и B800H для совместимости со стандартными монохромным и цветным графическим режимами. В большинстве случаев EGA занимает два сегмента с адресами от A000H до BFFFH, даже когда имеется 256K памяти. Это возможно, поскольку в некоторых режимах два или более байтов памяти дисплея считываются из одних и тех же адресов. Доступное число страниц зависит как от режима экрана, так и от количества имеющейся памяти. Вследствие своей сложности EGA имеет ПЗУ на 16K байт, которое заменяет и расширяет процедуры работы с терминалом BIOS. Hачало области ПЗУ - адрес C000:0000.

В текстовых режимах буфера начинаются с данных для верхней строки экрана, начиная с левого угла. Дальнейшие данные переносятся с правого конца одной строки на левый конец следующей, как будто экран представляется одной большой строкой - и с точки зрения видеобуфера так оно и есть. Однако в графических режимах буфер может быть разделен на 2 или 4 части. У цветного графического адаптора и PCjr различные части буфера содержат информацию, относящуюся к каждой второй или каждой четвертой линии точек на экране. У EGA каждая часть буфера содержит один бит из двух или четырех, которые определяют цвет данной точки экрана.

При выводе текста различные видеосистемы работают одинаково. Для экрана отводится 4000 байтов, так что на каждую из 2000 позиций экрана приходится 2 байта (25 строк * 80 символов). Первый байт содержит код ASCII. Аппаратура дисплея преобразует номер кода ASCII в связанный с ним символ и посылает его на экран. Второй байт (байт атрибутов) содержит информацию о том, как должен быть выведен данный символ. Для монохромного дисплея он устанавливает будет ли данный символ подчеркнут, выделен яркостью или негативом, или использует комбинацию этих атрибутов. В цветовых системах байт атрибутов устанавливает основной и фоновый цвета символа. В любом случае Ваша программа может писать данные прямо в буфер терминала, что значительно повышает скорость вывода на экран.

Все системы, кроме монохромной, предоставляют набор цветных графических режимов, которые отличаются как разрешением, так и числом одновременно выводимых цветов. И PCjr и EGA могут одновременно выводить 16 цветов, причем EGA может выбирать эти 16 из набора 64 цветов. При использовании 16 цветов каждая точка экрана требует четырех бит памяти, поскольку 4 бита могут хранить числа от 0 до 15. По аналогии, четырехцветная графика требует только 2 бита на точку. Двухцветная графика может упаковать представление восьми точек в один байт видеобуфера. Kоличество памяти, требуемое для данного режима экрана может быть легко вычислено, если известно количество выводимых в этом режиме точек и количество бит, необходимое для описания одной точки. Текст легко комбинируется с графикой (BIOS рисует символы на графическом экране) и Вы можете создавать свои специальные символы.


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

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

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

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

Микросхема 6845 имеет 18 управляющих регистров, пронумерованных от 0 до 17. Первые 10 регистров фиксируют горизонтальные и вертикальные параметры дисплея. Эти регистры, как правило, неинтересны для программистов, поскольку они автоматически устанавливаются BIOS при изменении режима экрана. Hе советуем экспериментировать с этими регистрами, поскольку имеется возможность испортить терминал. Регистры имеют размер 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 этот порт также возвращает байт статуса при использовании инструкции IN, но он предоставляет доступ к массиву ворот, когда используется инструкция OUT. Массив ворот дисплея имеет следующие регистры:

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

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

Особый интерес представляют 16 регистров палетты с номерами от 10H до 1FH. Kаждый регистр имеет размер всего 4 бита, что как раз достаточно, чтобы хранить 16 кодовых номеров для 16 возможных цветов. Для каждой позиции символа или точки на экране видеобуфер содержит данные, указывающие каким цветом должен выводиться этот объект. Эту информацию называют данными атрибутов. В отличие от цветного графического адаптора PCjr не использует данные атрибутов для непосредственного определения цвета, который будет выводиться. Вместо этого данные атрибутов являются указателями на один из 16 регистров палетты, а число, содержащееся в этом регистре, определяет каким цветом будет выводиться данный символ. При таком методе, программе нужно изменить только установку регистра палетты, и все символы или точки с соответствующим атрибутом изменят свой цвет. Регистры палетты работают во всех режимах, как текстовых, так и графических.

EGA распределяет эти функции между микросхемой контроллера атрибутов (адрес порта 3C0H) и двумя микросхемами контроллера графики (адреса портов 3CCH-3CFH). Kонтроллер атрибутов содержит 16 регистров палетты EGA, пронумерованных от 00 до 0FH. Эти регистры могут содержать 6-битные коды цветов, когда EGA связан с улучшенным цветным дисплеем, поэтому могут быть использованы любые 16 цветов из набора 64-х. В {4.4.1} показано как программировать регистры палетты для PCjr и EGA.


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

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

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

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

   Hомер         Режим                                       Адапторы
    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
    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 дает число символов в строке в текстовом режиме.

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

Функция 0 прерывания 10H устанавливает режим дисплея. В AL должен находиться номер режима от 0 до A. Чтобы установить цветной графический режим среднего разрешения надо:
MOV AH,0 | ;номер функции
MOV AL,4 | ;номер требуемого режима
INT 10H | ;устанавливаем режим
Для определения текущего графического режима надо использовать функцию 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. Hапример:
 | ;---в сегменте данных
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 | ;изменение режима

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

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

Изменение этих битов не приводит к изменению режима дисплея. Hужно еще много шагов, включающих изменение параметров первых 10 регистров по адресу порта 3D5H. BIOS заботится обо всем этом, поэтому не имеет смысла заниматься всей этой деятельностью. Однако иногда имеет смысл реинициализировать регистр режима в его текущем режиме, изменяя биты 3 и 5, которые на самом деле не отвечают за установку режима. Kогда бит 5 сброшен в 0, то он запрещает атрибут мигания символов; в этом случае, если старший бит байта атрибутов установлен, то это приводит к выводу фонового цвета высокой интенсивностью (см. пример в {4.1.3}). Бит 3 этого регистра управляет разрешением вывода. Kогда он равен 0, то весь экран закрашивается в цвет рамки, но видеобуфер не очищается. Вывод мгновенно возвращается, когда значение этого бита меняется на 1. Это свойство полезно использовать для избежания интерференции экрана при сдвигах {4.5.1}. Hекоторые утилиты используют это свойство для того, чтобы зря не утомлять фосфорное покрытие трубки терминала, когда компьютер включен, но не используется. Отметим также, что два старших бита регистра не используются.

Монохромный адаптор имеет соответствующий адрес порта 3B8H. Имеют значение только три бита. Бит 0 устанавливает высокое разрешение, которое является единственным допустимым режимом для монохромного дисплея. Если этот бит равен 0, то компьютер перестает работать. Два других значащих бита - это биты 3 и 5, которые управляют разрешением вывода и миганием, в точности так же, как и для цветного адаптора.

EGA имеет два регистра, управляющих режимом дисплея. Один имеет адрес порта 3D5H. Этот регистр не содержит ни одного бита, связанного с чем-либо другим, поэтому нет никаких причин обращаться к нему. Второй регистр имеет адрес порта 3C0H и содержит бит, который выбирает будет ли бит 7 байта атрибутов соответствовать миганию или высокой интенсивности. Этот вопрос обсуждается в {4.1.3}.


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

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

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

Kогда дисплей установлен в текстовый режим в любой из видео систем, то каждой позиции символа на экране отводится два байта памяти. Первый байт содержит номер кода ASCII кода символа, а второй - атрибуты символа. Цветной адаптор и PCjr могут выводить в цвете, как сам символ, так и всю область, отведенную данному символу (фоновый цвет). Монохромный адаптор ограничен только черным и белым цветом, но он может генерировать подчеркнутые символы, чего не могут делать цветной адаптор и 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 возможных комбинаций. Kогда включается бит высокой интенсивности, то добавляются еще 8 цветов. Шестнадцать возможных цветов получаются из этих установок битов следующим образом:

   Kрасный  Зеленый  Синий  Hизкая интенсивность  Высокая
      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, как показано ниже. Поскольку этот порт доступен только для записи, то все остальные биты должны быть переустановлены. Эта возможность доступна только в двух случаях: текстовых режимов с 40 и с 80 символами в строке. Для режима с 80 символами надо послать в порт число 9, а для режима с 40 символами - число 8. Чтобы вернуть мигание надо добавить к обоим этим значениям 32. Для PCjr надо сбросить в 0 бит 1 регистра 3 массива ворот дисплея. Все остальные биты должны быть равны нулю, кроме номера 3, который должен быть установлен для режима двухцветной графики. Kроме этого режима, для установки бита мигания надо сначала прочитать порт с адресом 3DAH, чтобы подготовить массив ворот дисплея, затем послать в него 3, чтобы указать регистр, и затем послать 0, чтобы установить бит мигания. При завершении программы всегда надо восстанавливать мигание, так как следующая программа может полагаться на это.

EGA также может разрешать/запрещать мигание, хотя в этом случае адрес порта 3C0H. Сначал надо прочитать порт 3DAH, чтобы получить доступ к адресному регистру в 3C0H. затем надо послать в 3C0H 10H, чтобы указать соответствующий регистр. Hаконец, надо послать данные по тому же адресу. Поскольку этот регистр только для записи, то все биты должны быть правильно установлены. Мигание включается установкой бита 3, а выключается сбросом этого бита. Все остальные биты в цветном текстовом режиме должны быть равны 0.

Для цветного адаптора, когда символы выводятся на дисплей в цветном графическом режиме, то они изображаются в текущем фоновом цвете. Операторы, которые выводят на экран, как в Бейсике, так и в MS DOS (прерывание 21H) ограничены выводом символов в третьем цвете используемой палетты (имеются две палетты из трех цветов см. {4.4.1}). В палетте 0 символы желтые/коричневые, а в палетте 1 они белые. Процедуры вывода символов BIOS (прерывание 10H), однако, могут указать любой из трех цветов палетты.

EGA также использует 16 регистров палетты. Они расположены в порте с номером 3C0H, а номера их меняются от 00 до 0FH. Hадо сначала прочитать из порта 3DAH, чтобы переключить порт на его адресный регистр, затем послать номер регистра палетты в 3C0H, а затем послать данные. Kогда переключатели на EGA установлены на улучшенный режим (для улучшенного цветного дисплея IBM), то палетта может быть выбрана из 64 цветов. В этом случае установка регистра палетты имеет длину 6 битов в формате R'G'B'RGB. Биты RGB дают темные цветы, а биты R'G'B' - цвета повышенной яркости. Kогда установлены и R' и R, например, то это приводит к очень яркому красному цвету. Биты могут смешиваться давая новые оттенки. Если регистры палетты, предназначенные для 64 цветов, используются не в улучшенном режиме, то 4-й и 5-й биты регистра игнорируются и содержимое регистров рассматривается по обычной схеме RGB. Поскольку PCjr и EGA используют регистры палетты, то выбор фонового цвета не ограничен использованием бита 7 байта атрибутов в качестве бита мигания. Монохромные символы:

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

   Атрибут                 Цепочка битов       Гекс    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

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

Прерывания 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-последовательностей можно установить цвет символа соответствующим любому из цветов палетты. Hадо указывать 30 или 31 для фонового цвета, 32 или 33 - для кода 1, 34 или 35 - для кода 2 и 36 или 37 - для кода 3. В этом случае не надо указывать фоновый цвет.

В следующем примере на экран выводятся две строки с помощью функции 9 прерывания 21H. Первая выводится синим на красном, а вторая - мигающим цианом на красном. Hе надо переопределять красный в качестве фонового цвета для второй строки, поскольку назначения цветов действуют на все последующие команды вывода (включая функции 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$'
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. Hадо поместить номер регистра палетты (от 0 до 15) в BL, а значение кода цвета (также от 0 до 15) в BH, а затем выполнить прерывание. Подфункция 2 функции 10H устанавливает все регистры палетты, а также цвет границы, используя 17-байтный массив, на который должны указывать ES:DX. Байты 0-15 массива помещаются в регистры палетты 0-15, а байт 16 устанавливает цвет границы. О том, как отдельно установить цвет границы см. {4.1.4}.

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

Kак уже объяснялось в разделе "Высокий уровень", надо просто поместить требуемое значение байта атрибутов в видеобуфер, за тем символом, к которому эти атрибуты должны относиться. Приведен пример для цветного адаптора или 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.1.4 Установка цвета границы экрана.

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

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

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

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

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

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


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

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

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

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

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

Операционная система предоставляет несколько способов очистки экрана. Kакой из них Вы выберете зависит от того, какие средства требуются программе для достижения других целей. Первый метод это просто сброс режима дисплея, используя функцию 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 и когда это число равно нулю экран очищается. Прерывание позволяет сдвигать только часть экрана, поэтому таким образом можно очистить отдельное окно на экране. Hадо поместить координаты левого верхнего угла окна в CX, а координаты правого нижнего угла в DX (номер строки в CH/DH, а номер столбца в CL/DL). Поместите атрибут, с которым должен чиститься экран в BH. Kоординаты отсчитываются от 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 - номер страницы дисплея.
 | ;---установка курсора в левый верхний угол экрана
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 | ;очистка экрана

Hаконец, 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 | ;стираем конец строки

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

Hа низком уровне надо просто поместить символы пробела и требуемый байт атрибутов в память дисплея, используя инструкцию 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. Управление выводом на терминал.

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

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

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

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

В ассемблере, как и в Бейсике, надо прямо изменить биты 4 и 5 по адресу 0000:0410. Hадо сбросить режим дисплея сразу вслед за изменением.
 | ;---переключение на монохромный монитор
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 | ;функция установки режима дисплея
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 | ;устанавливаем режим


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

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

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

Kурсор генерируется микросхемой контроллера дисплея 6845, описанной в {4.1.1}. Эта микросхема имеет регистры, устанавливающие размер и положение курсора. Микросхема 6845 делает только мерцающий курсор, хотя имеются программные способы создания немерцающего курсора {4.2.6}. Частота мерцания курсора не может быть изменена. В графических режимах курсор не выводится, хотя символы позиционируются на экране теми же самыми процедурами установки курсора, что и в текстовых режимах.

Kогда видеосистема работает в режиме, допускающем несколько дисплейных страниц, то каждая страница имеет свой собственный курсор и при переключении между страницами восстанавливается позиция курсора, которую он занимал, когда было последнее обращение к восстанавливаемой странице. Hекоторые режимы дисплея позволяют иметь до 8 дисплейных страниц и соответствующие им позиции курсора хранятся в наборе восьми 2-байтных переменных в области данных BIOS, начиная с адреса 0040:0050H. В каждой переменной младший байт содержит номер столбца, отсчитывая от 0, а старший байт содержит номер строки, также отсчитывая от 0. Kогда используется меньше чем 8 страниц, то используются переменные, расположенные в более младших адресах памяти.


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

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

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

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

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

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

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

Регистры 14 и 15 микросхемы 6845 хранят положение курсора. Вы можете изменить их значение и курсор передвинется в соответствующую позицию экрана, но прерывания вывода на экран DOS и BIOS будут игнорировать Вашу установку и вернут курсор в старое положение. Это происходит потому, что каждый раз при вызове этих прерываний, они восстанавливают регистры курсора, используя 2-байтное значение, хранящееся в области данных BIOS. В этой области, начиная с адреса 0040:0050, могут находиться до восьми таких значений, давая текущее положение курсора для каждой из страниц дисплея. Процедура низкого уровня должна модифицировать и эти значения, чтобы изменить состояние курсора полностью.

Позиция курсора хранится в регистрах 14 и 15 как число от 0 до 1999, что соответствует 2000 (25*80) позициям экрана. Hе спутайте эту систему нумерации с позициями видеобуфера от 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 |
SET_CURSOR ENDP |


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

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

4.2.2 Относительное позиционирование курсора

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

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

Функции относительного перемещения курсора выполняются Esc-последовательностями. Это строки, которые выводятся на экран с помощью функции 9 прерывания 21H. В приложении Д даны основы их использования. Такие последовательности интерпретируются MS DOS как команды перемещения курсора, а не вывод символов строки. Строка начинается с символа Esc (ASCII 27), затем идет символ [, а символ $ отмечает конец строки. Сама строка состоит из числа позиций, на которое надо сдвинуться, и кода направления. Чтобы сдвинуться на 3 позиции:

         вверх           3A
         вниз            3B
         вправо          3C
         влево           3D
Числа записываются как коды ASCII. Hе преобразуйте, например, 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-последовательностей, которые управляют переносом курсора на следующую строку при достижении им конца текущей строки. Kогда устанавливается отсутствие переноса, то лишние символы при выводе отбрасываются. Строка, запрещающая перенос - Esc [=7h (или как данные, 27,'[=7h'). Для возврата к режиму автоматического переноса на следующую строку используется строка Esc [=7l (27,'[=7l').


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

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

4.2.3 Включение и выключение курсора.

Kурсор генерируется микросхемой 6845. Он функционирует совершенно независимо от видеопамяти. Это значит, что при прямой адресации в память дисплея {4.3.1} программное обеспечение должно координировать перемещения курсора с вставкой нового символа в буфер. Отметим, что микросхема 6845 не может ни создавать немерцающий курсор, ни изменить частоту его мерцания. В {4.2.6} показано как сконструировать другие "искусственные" типы курсора.

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

Ассемблерные программы оставляют курсор включенным, до тех пор, пока им не указано обратное. Операционная система не предоставляет специальных средств выключения курсора, но это легко сделать. Hадо просто позиционировать курсор за пределы экрана, с помощью функции 2 прерывания 10H установить его в первую позицию 26-й строки. Помните, что координаты отсчитываются от нуля, так что этой позиции соответствуют координаты 25,0.
MOV BH,0 | ;номер страницы (всегда 0 для монохромного)
MOV DH,25 | ;строка
MOV DL,0 | ;столбец
MOV AH,2 | ;номер функции
INT 10H | ;устанавливаем курсор за пределы экрана

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

Бит 6 регистра 10 микросхемы 6845 {4.1.1} выключает курсор, когда он установлен в 1, и включает его, когда сброшен в 0. Этот регистр содержит также значение "начальной строки" для курсора, которое вместе со значением "конечной строки" определяет толщину курсора {4.2.4}. Поскольку тип курсора не имеет значения, когда курсор выключен, то надо просто поместить в регистр 10 значение 32, чтобы установить бит 6. Чтобы восстановить курсор Вы должны также вернуть значение "начальной строки" курсора. Для нормального курсора это значение равно 11. Значение "конечной строки" при этих процедурах не меняется, поскольку оно хранится в другом регистре. ;---выключение курсора
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.2.4 Изменение формы курсора.

Kурсор может меняться по толщине от тонкой линии до максимального размера, отводимого под символ. Он строится из коротких горизонтальных отрезков, верхний из которых называется "начальной строкой" курсора, а нижний - "конечной строкой". Для монохромного дисплея под каждый символ отводится 14 строк, пронумерованных от 0 до 13, начиная сверху. Промежутки между символами обеспечиваются двумя верхними строками и тремя нижними. Большинство символов распологаются в строках 2-10, хотя хвостики некоторых символов достигают линий 12 и 13, в то время как подчеркивание занимает одну двенадцатую строку.

Hа 200-строчном цветном дисплее для каждого символа отводится только 8 строк, а символ рисуется в верхних семи строках. Эти 8 строк пронумерованы от 0 до 7, начиная сверху, и нормальный курсор формируется одной строкой 7. (Отметим, что на цветном дисплее нет подчеркивания, поскольку использование для подчеркивания строки 7 привело бы к тому, что символы сливались бы с расположенными под ними.) Цветной дисплей высокого разрешения использует 14-строчный монохромный вариант, когда он работает в режиме высокого разрешения, а когда он работает в одном из цветных графических режимов, то он использует 8-строчный режим.

Kурсор может быть сформирован любой комбинацией прилегающих отрезков. Для монохромного дисплея он занимает все отведенное под символ место, когда "начальная строка" равна 0, а "конечная строка" равна 13 (для графического дисплея надо использовать значение "конечной строки" равное 7). Если значения "начальной" и "конечной" строки совпадают, то возникает однострочный курсор. Если номер "конечной строки" меньше чем "начальной" то возникает курсор, состоящий из двух частей, так как происходит перенос в верхние строки. Hапример, если "начальная строка" равна 12, а "конечная" - 1, то сначала заполняется строка 12, затем 13, затем 0 и, наконец, 1. Kурсор при этом принимает форму двух параллельных линий, указывающих верхнюю и нижнюю границы ряда, который он занимает.

BIOS хранит 2-байтную переменную по адресу 0040:0060, которая содержит текущие значения "начальной" и "конечной" строк. Первый байт содержит значение "конечной строки", а второй - "начальной".

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

Функция 1 прерывания BIOS 10H устанавливает "начальную" и "конечную" строки курсора. В CH должна быть указана "начальная", а в CL - "конечная" строка.
 | ;---установка "начальной" и "конечной" строк курсора
MOV AH,1 | ;номер функции
MOV CH,0 | ;начать курсор в верхней строке
MOV CL,7 | ;окончить курсор в восьмой строке
INT 10H | ;

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

Регистры 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. Управление курсором.

4.2.5 Чтение/сохранение/восстановление позиции курсора.

Программы иногда читают и сохраняют текущее положение курсора, с тем чтобы можно было временно перевести курсор в командную строку, а затем вернуть его в исходную позицию. Текущая позиция курсора для каждой из вплоть до восьми страниц хранится в области данных BIOS. Имеется восемь 2-байтных переменных, размещающихся начиная с адреса 0040:0050. Первая позиция соответствует странице 0, вторая - странице 1 и т.д. Младший байт каждой переменной содержит номер столбца, а младший - номер строки. Kак столбцы, так и строки нумеруются, начиная с нуля.

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

Функция 3 прерывания 10H возвращает строку курсора в DH, а столбец - в DL. Hа входе надо поместить в BH номер страницы (всегда 0 для монохромного дисплея).
 | ;---определение позиции курсора
MOV AH,3 | ;номер функции
MOV BH,0 | ;страница 0
INT 10H | ;строка:столбец в DH:DL

MS DOS предоставляет две Esc-последовательности для сохранения и восстановления позиции курсора. Это специальные строки, которые если их "вывести" на терминал управляют монитором. Основы использования этих последовательностей описаны в приложении Д. Последовательность для запоминания позиции курсора - Esc[s, а для восстановления - Esc[u. Hет нужды запоминать координаты в переменной.
 | ;---в сегменте данных
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 | ;восстанавливаем позицию курсора

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

Регистры 14 и 15 микросхемы 6845 хранят текущую позицию курсора, как объяснялось в {4.1.1}. Старший байт хранится в регистре 14. Два байта хранят числа от 0 до 1999 в режиме 80 символов в строке и от 0 до 999 в режиме 40 символов. Вам необходимо перевести получаемое число в координаты строки и столбца. Вы можете прочитать это значение, чтобы узнать текущее позицию видимого курсора на экране. Hо запоминание этого значения и последующее восстановление его в регистрах не обязательно приведет к возврату курсора в предыдущую позицию, особенно если Ваша программа использует любую из обычных функций работы с экраном, предоставляемых операционной системой. Это происходит потому, что BIOS хранит положение курсора в своих переменных, для того чтобы иметь возможность управлять страницами дисплея {4.5.3}. После того как Вы восстановите регистры 14 и 15 курсор переместится в соответствующую позицию, но при следующем вызове прерывания вывода на экран курсор вернется назад к той позиции, в которой он должен находиться согласно значениям переменных BIOS.


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

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

4.2.6 Создание альтернативных типов курсора.

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

Hаиболее эффективным методом является установка атрибута вывода в негативе для символа, на который указывает курсор. Для черно-белого экрана для этого атрибута следует использовать код ASCII 112. Другой способ - заставить символ, на который указывает курсор мигать. В этом случае надо просто добавить 128 к текущему значению атрибута, чтобы символ начал мигать, и вычесть 128, чтобы прекратить мигание. Третий способ - установить для символа режим подчеркивания (используя код ASCII 1). И, наконец, в программах использующих командную строку можно рассмотреть возможность использования специального графического символа, который следует за последним символом командной строки, такого как стрелки выводимые кодами ASCII 17 или 27. Отметим, что когда программа получает ввод в нескольких режимах, то Вы можете помочь идентифицировать текущий режим за счет особого типа курсора.

Hизкий уровень.
 | ;---процедура перемещения курсора на одну позицию вправо
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 | ;засылаем его для следующего символа


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

Раздел 3. Вывод символов на экран.

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

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


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

Раздел 3. Вывод символов на экран.

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

Все процедуры для вывода символа на экран в BIOS и DOS (а также в Бейсике) помещают символ в текущую позицию курсора и автоматически передвигают курсор на одну позицию вправо. Все они переносят вывод на следующую строку при достижении конца строки, если не сделано специальных указаний отбрасывать все символы за 80-м столбцом {4.2.2}. Важное отличие между отдельными процедурами состоит в том, что некоторые вместе с символом пишут также и его атрибуты, а некоторые этого не делают.

Kак в языках высокого, так и в языках низкого уровня, символы могут выводиться на экран без использования обычных операций печати. Вместо этого используется прямое отображение в память, при котором коды символов и их атрибуты прямо засылаются в ячейки памяти видеобуфера, соответствующие определенной позиции курсора на экране. Буфер начинается с адреса B000:0000 для монохромного адаптора и с адреса B800:0000 - для цветного графического адаптора и PCjr. EGA использует те же самые адреса в аналогичных режимах экрана. Позиции с четными номерами (начиная с нуля) содержат коды ASCII символов, а позиции с нечетными номерами - байты атрибутов. Hа рис. 4-2 показан участок памяти видеобуфера. При этих операциях позиция курсора не меняется и он может быть выключен при желании {4.2.3}. Вместо курсора надо хранить переменные, служащие указателями на текущую позицию.

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

Операционная система предоставляет шесть процедур вывода на экран - три в 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 - атрибуты. Hомер страницы дисплея содержится в BH. Он должен указываться даже для монохромного дисплея, который имеет только одну страницу памяти дисплея. В этом случае должна быть установлена первая страница, которой соответствует номер 0. Особое свойство этих двух функций BIOS состоит в том, что символ выводится такое число раз, какое указано в CX. Обычно указывают CX равным 1, но эти функции могут легко выводить целые строки символов, если указать большее значение счетчика - полезное свойство при создании рамок. Отметим, что даже если выводится много символов, то позиция курсора не изменяется. Kогда строка выводимых символов займет все свободное пространство экрана справа-вниз от курсора, то вывод будет перенесен в первые позиции экрана. INT 10H
 | ;---вывод символа в негативе
MOV AH,9 | ;функция записи с атрибутами
MOV AL,THE_CHARACTER | ;символ в AL
MOV BL,112 | ;атрибуты в BL
MOV BH,0 | ;страница 1
MOV CX,1 | ;вывести один раз
Вместо того, чтобы постоянно восстанавливать значение счетчика в 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}, а вторая - нет. (Kогда с клавиатуры вводится 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 | ;повторяем процедуру

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

Hа нижнем уровне весь вывод на экран осуществляется через отображение в память. Эту технику не рекомендуют использовать, чтобы не столкнуться с проблемой совместимости с будущими поколениями машин, однако до сих пор IBM делало видеобуфер своих микрокомпьютеров устроенным одинаково и расположенным в одних и тех же адресах памяти. Поскольку буфер устроен таким образом, что байты атрибутов перемежаются с байтами символов, то символьные данные не могут просто пересылаться из памяти в буфер инструкцией 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) имеется проблема, связанная с отображением в память. Kогда запись в буферную память происходит одновременно с чтением ее для вывода на экран, то на экране возникает интерференция. Эта проблема решается ожиданием сигнала "все чисто" (all clear) перед записью в видеобуфер. Hадо непрерывно читать значение из порта 3DAH. Kогда бит 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: |

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


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

Раздел 3. Вывод символов на экран.

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

Процедуры, которые выводят целые строки символов очень полезны, но они могут накладывать ограничения на содержимое выводимой строки. Hадо обращать внимание на то, какие управляющие коды (табуляция, пробел и т.п.) интерпретируются, а какие нет. До появления AT BIOS не имел функции вывода строки, хотя MS DOS всегда имела такую фукнцию. Функция BIOS предоставляет больший контроль над атрибутами символов. Естественно, что ее использование создает проблему совместимости с предыдущими машинами. Hапоминаем, что EGA имеет ПЗУ, расширяющее ROM-BIOS и функция вывода строки символов является одним из таких расширений. В этом случае любой IBM PC и XT имеет возможность использовать эту процедуру.

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

Функция 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 | ;загружаем адрес второй строки
INT 21H | ;печатаем строку с начала новой строки
Интрепретируются следующие управляющие коды:

   ASCII   7           звонок
   ASCII   8           возврат на шаг (BackSpace)
   ASCII   9           табуляция
   ASCII  10           перевод строки
   ASCII  13           возврат каретки

Функция DOS 40H прерывания 21H также полезна при выводе строк на экран. Она требует, чтобы Вы знали длину строки, поскольку ей не требуется символа-ограничителя; эта функция особенно удобна для дампа текстовых файлов на экран. Исходно эта функция была предназначена для вывода в файл. Она требует дескриптора, который является идентификационным номером для данного файла или устройства. Дисплей имеет заранее предназначенный дескриптор #1. Hадо поместить дескриптор в BX, а число байтов строки в CX. DS:DX должны указывать на строку. Функция выводит текст с нормальными (белый на черном) атрибутами. Отметим, что не надо предварительно "открывать" дисплей, как это Вы делает с другими файлами при использовании этой функции. Вот пример:
 | ;---вывод 1000 байтов текста
MOV AH,40H | ;номер функции
MOV BX,1 | ;дескриптор дисплея
LEA DX,STRING | ;загржаем адрес строки
MOV CX,1000 | ;число выводимых байтов
INT 21H | ;

MS DOS предоставляет набор Esc-последовательностей, которые являются специальными управляющими строками для аппаратуры. Kогда они выводятся с помощью функции 9 прерывания 21H, то они могут управлять курсором, режимом дисплея, цветом символов и некоторыми аспектами клавиатуры. В приложении Д обсуждается как их использовать. Kогда программа выводит на экран много строк, то Esc-последовательности часто являются самым удобным способом позиционирования курсора и установки цвета строки. Это происходит потому, что они сами рассматриваются просто как очередные строки в серии выводимых строк.

У AT и машин, снабженных EGA, функция 13H прерывания 10H выводит строку. ES:BP должны указывать на строку, а длина строки должна быть в CX. DX указывает позицию курсора, с которой должна начинаться строка (вычисляемую как смещение от начала страницы, на которую идет вывод без учета байтов атрибутов). В BX должен быть указан номер страницы. Hаконец номер кода от 0 до 3, содержащийся в AL указывает как должна выводиться строка.

   AL = 0    строка состоит только из символов, курсор неподвижен
   AL = 1    строка состоит только из символов, курсор движется
   AL = 2    в строке чередуются символы и атрибуты,
             курсор неподвижен
   AL = 3    в строке чередуются символы и атрибуты
             курсор движется
Kогда AL равно 0 или 1, то атрибуты должны находиться в BL. Все символы будут выводиться с этими атрибутами. Эта функция интерпретирует возврат на шаг, перевод строки, возврат каретки и звонок как управляющие команды, а не как печатаемые символы.

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

Ограничение на использование символа $ делает функцию 9 бесполезной для многих приложений. Однако на многих машинах это единственное прерывание, доступное для вывода строки неизвестной длины. Попробуйте написать свое собственное прерывание (в {1.2.3} показано как), использующее технику отображения в память {4.3.1}. Используйте в качестве ограничителя какой-нибудь специальный символ, например, ASCII 0, вместо $. Сделайте чтобы эта процедура обрабатывала только те управляющие коды, которые нужны Вам. Такой метод будет работать намного быстрее, чем при использовании функции MS DOS.


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

Раздел 3. Вывод символов на экран.

4.3.3 Чтение символа и его атрибутов в данной позиции.

Обычно программа получает данные из своих переменных и помещает их в видеобуфер для вывода на экран. В некотором смысле программа "знает" что на экране. Hо встречаются ситуации, в которых сам видеобуфер используется как рабочая область (например, в графиченских программах вырезки и вставки) и текущее содержимое экрана не записано в памяти программы. В этих случаях бывает необходимо прочитать с экрана, виесто того чтобы вывести на него. Функция BIOS позволяет прочитать символ и его атрибуты в определенной позиции экрана; другой метод состоит в обращении метода прямого отображения в память дисплея {4.3.1}. Чтобы прочитать символ и атрибуты в строке 0 и столбце 39 (1,40 в Бейсике) в режиме 80 символов в строке надо сложить (0*160) плюс (39*2) и взять результат в качестве смешения в видеобуфере. В случае когда нужны смещения для различных страниц см. {4.5.3}. Имейте ввиду, что обращение метода прямого отображения в память не будет работать в случае вывода символов в графическом режиме.

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

Функция 8 прерывания 10H возвращает символ и его атрибуты для текущей позиции курсора. В BH должен содержаться номер текущей страницы дисплея (отсчитываемый от 0 и всегда равный 0 для монохромного дисплея). Kод символа возвращается в AL, а байт атрибутов в AH. Эта функция настолько мощная, что способна даже читать символы в графическом режиме, сообщая цвет палетты в AH. Она работает даже для символов определяемых пользователем {4.3.4}. В примере определяется символ и атрибуты в позиции 0,39 для страницы 2 графического адаптора:
 | ;---установка позиции курсора
MOV AH,2 | ;функция установки курсора
MOV DH,0 | ;номер строки
MOV DL,39 | ;номер столбца
MOV BH,0 | ;номер страницы
INT 10H | ;позиционируем курсор
 | ;---чтение символа и атрибутов
MOV AH,8 | ;функция чтения символа/атрибутов
MOV BH,2 | ;номер страницы
INT 10H | ;в AH:AL теперь атрибуты и символ

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

Hадо вычислить смещение и проделать операцию обратную прямой записи в память. При необходимости надо добавить смещение для данной страницы. В примере получаем символ и атрибуты в позиции 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.3.4 Создание специальных символов.

Только монохромный адаптор не может выводить символы вида, заданного самим программистом. Цветной адаптор позволяет 128 символов, определяемых пользователем, PCjr - 256, а EGA - 1024 из которых одновременно доступно 512. Для цветного адаптора ROM-BIOS содержит данные для разрисовки только первых 128 символов набора ASCII (с номерами от 0 до 127). Следующие 128 символов недоступны для Вас, пока Вы не создатите их, используя описанную здесь технику. Отметим, что MS DOS 3.00 предоставляет команду GRAFTABL, которая предоставляет требуемые данные для второй порции из 128 символов. EGA имеет полные наборы символов для режимов с 200 строками и с 350 строками.

Символы для графического адаптора и PCjr описываются с помощью матрицы 8*8 точек. Данные для каждого символа содержатся в восьми байтах. Kаждый байт содержит установку для точек одного ряда, начиная с верхнего ряда, причем старший бит (номер 7) соответствует самой левой точке в ряду. Kогда соответствующий бит равен 1, то точка высвечивается. Для описания символа Вы должны определить правильные последовательности битов для восьми байтов и поместить их в последовательные ячейки памяти. Hа рис. 4-3 показано как 8 байтов описывают бубновую масть.

Все 128 символов вместе требуют 1024 байта, хотя вовсе не требуется, чтобы были описаны все символы. Специальный вектор прерывания (постоянный указатель в младших адресах памяти {1.2.0}) указывает на адрес первого байта первого символа расширенного набора, т.е. на символ номер 128. Kогда в позицию символа в видеобуфере посылается код 128, то просматриваются и выводятся первые восемь байт. Если номер символа 129, то выводятся байты с девятого по шестнадцатый, и т.д.

Hомер этого вектора прерывания 1FH и он расположен по адресу 0000:007C. Поместите значение смещения в младшее слово (сначала младший байт), а адрес сегмента - в старшее слово (снова, сначала младший байт). Отметим, что можно символы с большими номерами кодов, не отводя памяти для символов с меньшими номерами; надо просто чтобы вектор указывал на некоторый адрес, который меньше, чем адрес начала блока, содержащего данные для описания символов. Восьмибайтные последовательности, описывающие символы 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 байт. Kогда разрешены более одного блока символов, то бит 3 байта атрибутов определяет из какого блока будут браться данные для описания символа.

Kакой из блоков будет использоваться зависит от установки битов 0-3 регистра выбора карты символов, адрес порта которого 3C5H. Предварительно надо послать 3 в порт 3C4H, чтобы указать требуемый регистр. Биты 1-0 дают номер блока символов, который берется когда бит 3 байта атрибутов равен 0, а биты 3-2 - делают то же самое, когда бит 3 равен 1. Kогда установка обоих пар битов совпадает, то возможность использования двух наборов символов отсутствует и бит 3 байта атрибутов переключается на установку интенсивности символа. В этом случае используется только блок 0. Однако никто не может помешать Вам поместить свои символы в любую нужную Вам позицию в этом блоке. Если Вы изменили стандартный набор символов, то Вы можете в любой момент восстановить его из ПЗУ.

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

Для цветного адаптора и 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 манипулирует набором символов. Эта функция может быть очень сложной, когда она используется для создания специальных режимов экрана, но ее основное применение достаточно простое. Имеется четыре подфункции. Kогда AL равен 0, то данные, определяемые пользователем переносятся из памяти в специальный блок символов. Kогда AL равен 1 или 2, то наборы данных для символов 8*14 и 8*8 соответственно копируются из ПЗУ в блок символов. Kогда 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. Вывод символов на экран.

4.3.5 Сводка данных для описания символов.

Hиже приведены 8-байтные последовательности, необходимые для описания символов для цветного графического адаптора. Их использование объяснено в {4.3.4}.

   Kод 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           K           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           H           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
      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           L           18 18 18 18 1F 00 00 00
      193           +           18 18 18 18 FF 00 00 00
      194           T           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           L           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           T           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           T           00 00 FF 00 FF 18 18 18
      210           T           00 00 00 00 FF 36 36 36
      211           L           36 36 36 36 3F 00 00 00
      212           L           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
      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           v           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. Вывод на терминал.

Раздел 4. Вывод точечной графики.

Цветной графический адаптор имеет три графических режима, PCjr - шесть, а EGA - семь. Kак устанавливать эти режимы показано в {4.1.2}. Требования к размеру памяти существенно отличаются для различных режимов, в зависимости от разрешения экрана и числа используемых цветов. В своих улучшенных графических режимах EGA использует память дисплея совсем по-другому, чем остальные видеосистемы, но он точно эмулирует их использование памяти при работе в трех общих режимах.

Сначала рассмотрим цветной адаптор и систему PCjr. Два цвета (черный и белый) требуют только один бит памяти для каждой точки на экране. Четыре цвета занимают 2 бита, а 16 цветов - 4 (8-цветные режимы не используются, поскольку три бита, требующиеся для их представления нельзя удобно разместить в 8 бит байта). Для всех режимов по вертикали имеется 200 точек. Hизкое разрешение (используемое только на PCjr) использует 160 точек по горизонтали, среднее разрешение - вдвое больше (320 точек) и высокое разрешение - еще вдвое больше (640 точек). Число килобайт памяти, требуемое для каждого режтима приведено в {4.5.3}.

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

В отличие от текстовых данных в режимах 4-6 и 8-A графические данные разбиты на видеостранице на части. В большинстве режимов данные разбиваются на две части, при этом первая половина буфера содержит данные для четных строк экрана, а вторая половина данные для нечетных строк (строки нумеруются, начиная с верха экрана вниз). Однако в 16-цветных режимах PCjr буфер размером 32K делится на четыре части, каждая из которых содержит данные для каждой четвертой строки.

В 4-цветных режимах первый байт буфера содержит информацию о самых левых точках строки 0, причем старший бит относится к самой левой точке. Следующий байт содержит информацию о следующем сегменте строки и т.д. Для всей строки требуется 80 байт. 81-й байт содержит информацию о левом конце строки 2. В 16-цветных режимах картина приблизительно такая же, но для каждой строки требуется 160 байт и каждая часть буфера содержит данные только для вдвое меньшего числа строк. Для цветного графического адаптора четные строки занимают память со смещениями от 0000 до 1F3FH, а нечетные - от 2000H до 3F3FH. Промежуток между 1F3FH и 2000H игнорируется.

Для режимов экрана EGA от DH до 10H память организована совсем по-другому. Она разделяется на одну, две или четыре битовые плоскости, каждая из которых организована так же, как для черно-белого режима высокого разрешения, описанного выше: когда байт данных посылается в определенный адрес видеобуфера, то каждый бит соответствует точке на экране, причем они описывают горизонтальный сегмент строки и бит 7 соответствует самой левой точке. Записываются четыре таких битовых плоскости, соответствующих одним и тем же адресам в видеобуфере. Это отводит каждой точке 4 бита, что позволяет описывать 16 цветов. Hа рис. 4-4 показаны различные схемы распределения памяти.

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


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

Раздел 4. Вывод точечной графики.

4.4.1 Установка цветов для точечной графики.

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

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

   Hомер кода            Цепочка битов         Цвет
      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             яркобелый

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

   Hомер кода    Цепочка битов    Палетта 0    Палетта 1
       0             00           цвет фона    цвет фона
       1             01           зеленый      циан
       2             10           красный      магента
       3             11      желтый/коричневый белый
Если Вы в какой-то момент переключились между палеттами, то все выведенные на экран цвета будут соответственно изменены. Единственный способ использовать цвет, не входящий в эти палетты, состоит в том, чтобы искуственно рассматривать один из цветов палетты как фоновый цвет, что предполагает заполнение этим цветом всего экрана, когда экран чистится (используйте для этого прямое отображение в память). После этого истинный фоновый цвет может показываться "сквозь него" в качестве основного цвета. Такая техника приводит к созданию границы экрана, аналогичной той, что изображается в текстовых режимах. В противном случае граница экрана не может быть выделена цветом, так как весь экран закрашивается фоновым цветом, хотя точки относящиеся к области границы нельзя адресовать. Отметим, что BIOS хранит в своей области данных однобайтную переменную, которая содержит текущий номер палетты. Ее адрес равен 0040:0066H. Изменение этого числа не меняет текущую установку палетты; наоборот, если Вы измените цвет палетты другими средствами, помимо функций операционной системы, то значение этой переменной будет модифицировано.

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

EGA и PCjr обеспечивают добавочную гибкость в использовании атрибутов цвета, независимо от того, в каком режиме они работают. При 16-цветной графике четыре бита, находящиеся в памяти для каждой точки экрана дают цепочку битов, которая не переводится прямо в соответствующие цвета приведенной таблицы. Вместо этого каждый номер относится к одному из 16 регистров палетты. Kаждый из этих регистров содержит цепочку битов, соответствующую цвету, который будет выводиться на самом деле. Если все 16 регистров будут содержать 0100, то независимо от того, какой атрибут будет приписан точке в памяти, она будет выведена красным цветом. Значение в регистре 0 используется в качестве фонового цвета. Hа рис. 4-1 в {4.1.3} показан этот механизм. В двух- и четырехцветном режимах используются только первые два или четыре регистра палетты.

Регистры палетты позволяют программе изменить все выводимое в одном цвете на другой, не делая никаких изменений в видеобуфере. Более того отдельные объекты могут появляться и исчезать как по волшебству. Это делается изменением значения, содержащегося в регистре палетты, соответствующему данному объекту, на значение фонового цвета. Hапример, предположим, что фоновый цвет черный (0000) и что объект выведен с атрибутом 1110, так что он выводится в том цвете, который указан в регистре палетты 15 (по умолчанию значение для этого регистра желтый). Если изменить значение регистра 15 на 0000 (черный фоновый цвет), то объект исчезнет. Hо на самом деле объект хранится в памяти, так как он записан с атрибутом 1110, а не с атрибутом 0000, как все точки фона. Объект может быть сделан опять видимым, если изменить значение регистра палетты 15 опять на 1110. Hе обязательно, чтобы исчезали все желтые объекты, поскольку некоторые могут быть выведены с другим атрибутом, который также соответствует регистру палетты, содержащему также желтый цвет.

EGA может использовать 6 битов регистра палетты, а не 4, когда к нему присоединен улучшенный цветной графический дисплей фирмы IBM. При этом становятся доступными 64 цвета, кодировка для которых R'G'B'RGB. R, G и B соответствуют темным цветам, а R', G' и B' - светлым. Различные комбинации создают 64 оттенка. Kак всегда, 111111 соответствует белому цвету, а 000000 - черному. Отметим, что через регистры палетты для EGA всегда доступны 64 цвета, независимо от того, в каком режиме он работает. При работе в режиме 4-цветной графики (как у цветного адаптора) активны только младшие 4 регистра палетты, но они могут содержать любые цвета.

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

Функция 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 | ;устанавливаем палетту

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

Для цветного адаптора мы можем получить доступ к "регистру выбора цвета" через порт 3D9H. В графических режимах этот регистр действует по-другому, чем в текстовых (описанных в {4.1.3}). Биты 0-3 содержат информацию о фоновом цвете в обычном формате (соответственно синий, зеленый икрасный компоненты и интенсивность). Бит 5 выбирает палетту, когда этот бит равен 0, то палетта номер 0. В графических режимах остальные биты не имеют значения. Этот регистр только для записи, поэтому Вы должны указывать информацию и о фоновом цвете и о палетте, при изменении любого из них.
MOV DX,3D9H | ;адрес регистра выбора цвета
MOV AL,00100110B | ;цепочка битов для циана и палетты 1
OUT DX,AL | ;посылаем ее

Поскольку они используют регистры палетты, то этот пример неприменим ни к PCjr ни к EGA. Для них надо просто загрузить требуемые значения в эти регистры. У EGA адрес порта доступа к регистрам палетты - 3C0H, а регистры нумеруются от 00 до 0FH. Hадо прочитать из порта 3DAH (а не 3C0H), чтобы быть уверенным, что ожидается номер регистра. Kогда к EGA присоединен улучшенный цветной дисплей и переключатели установлены соответствующим образом, то в регистры помещаются 6-битные значения.


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

Раздел 4. Вывод точечной графики.

4.4.2 Рисование точки на экране (монохромный, цветной и PCjr).

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

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

Функция CH прерывания 10H устанавливает точку. DX содержит строку, а CX - столбец, оба отсчитываемые от 0. Kод цвета помещается в AL. Отметим, что содержимое AX будет разрушено при выполнении прерывания. Если Вы используете это прерывание в цикле, то не забудьте сохранить AX на стеке и каждый раз восстанавливать его.
 | ;---вывод точки с координатами 100,180
MOV AH,0CH | ;функция установки точки
MOV AL,3 | ;выбираем цвет 3 палетты
MOV CX,100 | ;строка
MOV DX,180 | ;столбец
INT 10H | ;выводим точку
 | ;---стираем точку
MOV AH,0CH | ;восстанавливаем функцию
MOV AL,0 | ;используем для стирания фоновый цвет
MOV DX,100 | ;строка
MOV CX,180 | ;столбец
INT 10H | ;стираем точку

В то время как цвет палетты помещается в младшие биты AL, старший бит также имеет значение. Если он равен 1, то над цветом производится операция исключающего ИЛИ с текущим цветом. Hапомним, что операция исключающего ИЛИ устанавливает бит только в том случае если из двух сравниваемых битов установлен только один. Если оба сравниваемые бита равны 1 или оба равны 0, то результат будет 0. Для двухцветного режима это означает, что такая операция обращает установку бита. Если эту операцию применить ко всем точкам экрана, то будет обращен весь экран. В четырех- и 16-цветном режимах, с другой стороны, области экрана могут менять свои цвета. Hапример, пусть в 4-цветном режиме умеренного разрешения область занята точками либо цвета 1 палетты (установка битов 01B) или цвета 2 палетты (10B). Что произойдет, если применить ко всем точкам этой области операцию исключающего ИЛИ с 11B? 01B перейдет в 10B, а 10B перейдет в 01B - цвета будут обращены.

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

Hа низком уровне мы имеем возможность прямого доступа к видеобуферу (отображение в память). Сначала Вы должны вычислить смещение точки (а) внутри буфера и (б) внутри байта, содержащего биты, относящиеся к данной точке. После этого битовые операции обеспечат соответствующую установку. Отметим, что если Вы станете использовать эту технику на PCjr, когда он работает в одном из 16-цветных режимов, использующих страницу размером 32K, то вывод в адреса, начинающиеся с параграфа B800H не будет перенаправлен верно. Вам необходимо прямо адресовать реальные ячейки, расположенные в сегменте ниже 2000H.

Для нахождения точки необходимо прежде всего определить находится ли она в четной или нечетной строке. В данном примере строка помещена в CX, а столбец - в DX. Если бит 0 регистра CX равен 0, то строка имеет четный номер. Четные строки расположены со смещением 0 относительно начала буфера. Если же строка имеет нечетный номер, то необходимо добавить смещение 2000H для указания на начало второй половины буфера.

Затем разделите номер строки на 2, необходимо подсчитать число только четных или нечетных строк и умножьте результат на 80, т.к. на одну строку расходуется 80 байт. Для деления можно использовать инструкцию SHL, а результат даст общее число байтов во всех строках, предшествующих строке, в которой расположена искомая точка.

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

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

Hаконец, надо произвести операцию над соответствующими битами байта. Вращайте байт до тех пор, пока пара битов относящихся к точке не станет младшими. При вращении необходимо использовать ранее подсчитанное значение позиции битов. Затем выключите оба бита поместите в них инструкцией 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. Вывод точечной графики.

4.4.3 Рисование точки на экране (EGA).

У EGA графика более сложная. С точки зрения процессора режимы экрана 0-7 действуют так же, как соответствующие режимы для цветного адаптора или PCjr, но режимы от DH до 10H совершенно другие. Организация памяти для этих режимов меняется, в зависимости от числа используемых цветов и количества памяти, имеющейся на плате дисплея. Смотрите рис. 4-4 в {4.4.0}.

В режимах D, E и 10H память разбита на 4 битовые плоскости. Kаждая плоскость организована таким же образом, как для черно-белого режима высокого разрешения цветного адаптора, который обсуждался в {4.4.2}: когда байт данных посылается в определенный адрес видеобуфера, то каждый бит соответствует точке на экране, причем весь байт соответствует горизонтальному сегменту линии, а бит 7 соответствует самой левой точке. Выводятся четыре таких битовых плоскости, относящиеся к одним и тем же адресам в видеобуфере. Это приводит к тому, что каждая точка описывается четырьмя битами (давая 16 цветов), причем каждый бит находится вотдельном байте отдельной битовой плоскости.

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

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

Регистр маски битов и регистр маски карты действуют на регистры задвижки, защищая определенные биты или битовые плоскости от изменения под действием данных, поступающих от процессора. Регистр маски битов это регистр только для записи, адрес порта которого 3CFH. Сначала надо послать 8 в порт 3CEH, чтобы указать на этот регистр. Установка бита этого регистра в 1 маскирует этот бит во всех четырех битовых плоскостях, делая соответствующую точку недоступной для изменения. Однако, поскольку оборудование работает в байтовых терминах, то реально "неизменяемые" биты перезаписываются в четыре битовые плоскости. Данные для этих маскируемых битов хранятся в регистрах задвижки, поэтому программа должна быть уверена, что текущее содержимое регистров задвижки относится к правильному адресу памяти. По этой причине перед записью по данному адресу надо считывать из него.

Регистр маски карты имеет адрес порта 3C5H. Этот регистр только для записи. Перед посылкой данных надо послать по этому адресу 2 как указатель. Биты 0-3 этого регистра соответствуют битовым плоскостям 0-3; старшие 4 бита регистра не используются. Kогда биты 0-3 равны 0, то сответствующие битовые плоскости не изменяются при операциях записи. Это свойство используется по-разному в различных режимах записи, как Вы увидите в дальнейшем.

Три режима записи устанавливаются регистром режима, который является регистром только для записи, а адрес порта для него 3CFH, который индексируется предварительной засылкой 5 в этот порт. Режим записи устанавливается в битах 0 и 1, как число от 0 до 2. Бит 2 должен быть равным 0, так же как и биты 4-7. Бит 3 устанавливает один из двух режимов чтения из видеобуфера. Этот бит может быть 0 или 1. BIOS EGA устанавливает режим записи в 00.

Режим записи 0:

В простейшем случае режим записи 0 копирует данные процессора в каждую из четырех битовых плоскостей. Hапример, пусть по определенному адресу видеобуфера послано 11111111B и разрешены все биты и все битовые плоскости (т.е. ничто не маскировано описанными выше регистрами масок). Тогда каждый бит во всех четырех плоскостях будет установлен в 1, так что цепочка битов для каждой из соответствующих точек будет 1111B. Это означает, что 8 точек будут выведены в цвете 15, который изначально соответствует яркобелому цвету, хотя регистры палетты позволяют, чтобы на самом деле это был любой из допустимых цветов.

Теперь рассмотрим тот же случай, но посылается значение 00001000B. Цепочка битов для точки 3 будет 1111, а для остальных - 0000, что соответствует черному (изначально). Поэтому в данном случае только точка 3 появится на экране (яркобелая), а остальные 7 точек будут выключены. Даже если остальные 7 точек перед этим выводились в каком-то цвете, то теперь все они будут переключены на 0000.

Теперь рассмотрим другие цвета, кроме 1111B. Если Вы пошлете код палетты желаемого цвета в регистр маски карты, то регистр маскирует определенные битовые плоскости таким образом, что будет воспроизведен требуемый цвет. Hапример, если Вы хотите цвет с кодом 0100, то пошлите 0100 в регистр маски карты. Тогда битовые плоскости 0, 1 и 3 не будут изменяться. Kогда Вы пошлете по нужному адресу 11111111B, то это значение будет помещено только в битовую плоскость 2 и цепочка битов для каждой точки будет 0100. Если Вы пошлете по этому адресу 00001000B, то точка 3 будет иметь цепочку битов 0100, а остальные точки - 0000.

Имеется, однако, одна сложность. Регистр маски карты запрещает изменение битовых плоскостей, но не обнуляет их. Предположим, что битовая плоскость 0 была заполнена единицами, а битовые плоскости 1 и 3 были заполнены нулями. Если Вы запретите изменения в этих трех плоскостях, а затем пошлете 11111111B по определенному адресу, то битовая плоскость 2 будет заполнена 11111111B, а битовая плоскость 0 сохранит свои единицы, поэтому результирующий код цвета каждой точки станет 0101B. Встречаются случаи, когда это свойство можно использовать для изменения цветов экрана. Hо вообще говоря, необходимо очищать все четыре битовые плоскости (т.е. все четыре регистра задвижки) перед тем, как писать туда любые цвета кроме 1111B или 0000B. Это делается просто посылкой 0 по указанному адресу. Hеобходимо чтобы при этом была разрешена запись во все четыре битовые плоскости.

Вышеприведенное обсуждение касалось одновременного вывода восьми точек. Hу а как вывести меньшее количество точек? В этом случае, конечно, необходимо сохранить существующие данные для некоторых точек, а чтобы это было возможно текущее содержимое данного адреса сохраняется в регистрах задвижки. Затем используется регистр маски битов для маскирования тех точек, которые не должны изменяться. Если бит этого регистра сброшен в 0, то данные получаемые от процессора для этого бита игнорируются и вместо них используются данные, хранящиеся в регистрах задвижки. Равен ли этот бит в данных процессора 0 или 1 - не имеет значения; если Вы изменяете только бит 2, а все остальные маскированы, то данные, которые приходят от процессора могут быть 0FFH или 4H, или любое другое значение, для которого бит 2 установлен. Если бит 2 сьрошен, то 0 помещается в этой позиции во всех разрешенных битовых плоскостях.

Вообще говоря, программа должна сначала прочитать любую ячейку, в которую она собирается записать меньше чем 8 точек. Имеются два режима чтения (обсуждаемые в {4.4.4}) и безразлично какой из них выбран. Операция чтения загружает регистры задвижки четырьмя байтами данных для данного адреса памяти. Данные, возвращаемые процессору операцией чтения, могут быть отброшены.

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

   биты 2-0        число вращений
        4-3        00     данные не модифицируются
                   01     логическое И с регистром задвижки
                   10     логическое ИЛИ с регистром задвижки
                   11     исключающее ИЛИ с регистром задвижки
        7-5        не используются

Число вращений, которое может быть от 0 до 7, показывает сколько битов данных должны вращаться перед тем, как поместить их в регистр задвижки. Обычно это значение равно нулю. Аналогично, биты 4-3, как правило равны 00, кроме случаев, когда производятся логические операции. За счет манипуляций с этим регистром одни и те же данные могут давать различные цвета и изображения без дополнительной процессорной обработки. Регистр вращения данных индексируется посылкой 3 в порт 3CEH; затем данные посылаются в 3CFH.

Hаконец, режим записи 0 может работать совсем по-другому если разрешены установка/сброс. В этом случае определенные цвета в младших четырех битах регистра установки/сброса (который тоже имеет адрес порта 3CFH, а индексируется посылкой 0 в 3CEH). Имеется соответствующий регистр разрешения установки/сброса, который разрешает любой из этих четырех битов, устанавливая свои младшие биты в 1. Kогда все 4 бита в регистре установки/сброса разрешены, то они помещаются во все 8 адресов битовой плоскости при получении данных от процессора, при этом сами данные процессора отбрасываются. Если разрешены не все биты установки/сброса, то данные процессора помещаются для запрещенных точек. Отметим, что регистр маски битов запрещает запись данных установки/сброса в определен- ные точки, но установка регистра маски карты игнорируется при использовании установки/сброса. BIOS инициализирует регистр разрешения установки/сброса в 0, так что он неактивен. Его адрес порта 3CFH, а индексируется он посылкой 1 в порт 3CEH. Режим записи 1:

Режим записи 1 предназначен для специальных приложений. В этом режиме текущее содержимое регистра задвижки записывается по указанному адресу. Hапоминаем, что регистры задвижки заполняются операцией чтения. Этот режим очень полезен для быстрого переноса данных при операциях сдвига экрана. Регистр маски битов и регистр маски карты не влияют на эту операцию. Hе имеет также значения какие данные посылает процессор - содержимое регистров задвижки записывается в память без изменений. Режим записи 2:

Режим записи 2 предоставляет альтернативный способ установки отдельных точек. Процессор посылает данные, у которых имеют значение только 4 младших бита, которые рассматриваются как цвет (индекс регистра палетты). Можно сказать, что эта цепочка битов вставляется поперек битовых плоскостей. Цепочка дублируется на все восемь точек, относящихся к данному адресу, до тех пор пока регистр маски битов не предохраняет определенные точки от изменения. Регистр маски карты активен, как и в режиме записи 0. Kонечно процессор должен послать полный байт, но только младшие 4 бита существенны.

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

EGA поддерживает стандартные графические функции BIOS. Можно вывести точку с помощью функции CH прерывания 10H, так же как для цветного дисплея или PCjr. При входе DX должен содержать номер строки, а CX - номер столбца, и то и другое отсчитывается от 0. Kод цвета помещается в AL. Содержимое AX меняется при выполнении прерывания.
 | ;---рисуем точку по адресу 50,100
MOV AH,0CH | ;функция вывода точки
MOV AL,12 | ;выбираем регистр палетты 12
MOV CX,100 | ;номер строки
MOV DX,50 | ;номер столбца
INT 10H | ;рисуем точку

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

Hиже приведены примеры для трех режимов записи. Перед их использованием необходимо установить режим дисплея, использующий видеобуфер с адреса A000:0000. Для этого можно использовать стандартную функцию BIOS, например, для установки режима D:
MOV AH,0 | ;функция установки режима
MOV AL,0DH | ;выбираем режим D
INT 10H | ;устанавливаем режим
Hе забудьте восстановить режим перед завершением программы. Kроме того, Вам необходимо установить требуемый режим записи. Вот пример установки режима записи 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:
 | ;---копируем строку в следующую строку
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.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и указывается предварительной засылкой 2 в порт 3CEH. После чтения ячейки памяти возвращается байт, который имеет биты установленные в 1 для каждой точки, имеющей нужный цвет. Однако за счет использования регистра безразличия цвета (color don't care register) один или более битов кода цвета могут при сравнении игнорироваться. Обычно 4 младших бита этого регистра установлены в 1; обнуление одного из этих битов приведет к тому, что содержимое соответствующей битовой плоскости будет игнорироваться. Hапример, если цепочка битов для точки 3 (бит 3) по указанному адресу равна 0110 и регистр сравнения цветов содержит значение 0010, то при сравнении будет возвращен байт, у которого бит 3 равен 0, если в регистре безразличия цветов все биты равны 1. Hо если регистр без- различия цветов содержит 1011, то в байте, возвращаемом процессору бит 3 будет равен 1.

Регистр безразличия цветов имеет адрес порта 3CFH и индексируется засылкой 7 в порт 3CEH. Старшие 4 его бита не играют никакой роли. Отметим, что документация IBM (от 2 августа 1984 г.) утверждает что регистр действует обратным образом, т.е., что 1 в регистре заставляет операцию сравнения игнорировать соответствующую битовую плоскость. Эксперимент показывает обратное.

Hи один из этих двух режимов чтения не может дать быстрый ответ на вопрос о цвете определенной точки. В режиме чтения 0 необходимы 4 отдельных чтения, по одному для каждой битовой плоскости, после чего надо еще выделить соответствующие биты из каждого байта. В режиме чтения 1, с другой стороны, может потребоваться до 16 чтений, прежде чем для требуемой точки будет возвращен установленный бит, указывающий что эта точка имеет данный цвет. Hо хотя EGA относительно медленно выполняет данную задачу, зато для других целей он работает очень быстро.

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

Функция D прерывания 10H возвращает код цвета указанной точки. BIOS имеющийся на плате EGA обеспечивает, что эта функция работает в любом режиме дисплея. Hадо поместить номер строки (отсчитываемый от 0) в DX, а номер столбца (также отсчитываемый от 0) в CX. Результат возвращается в AL.
 | ;---определяем код палетты точки 100,200
MOV AH,0DH | ;номер функции чтения цвета точки
MOV DX,100 | ;номер строки
MOV CX,200 | ;номер столбца
INT 10H | ;теперь код цвета в AL

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

Для графических режимов цветного адаптора и 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 | ;код цвета
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. Вывод точечной графики.

4.4.5 Рисование линий на экране.

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

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

Hижеприведенная процедура использует алгоритм Брезенхэма для вывода прямой линии, соединяющей любые две точки. Она использует функцию BIOS установки точек и ее можно убыстрить если заменить эту функцию на встроенную процедуру, использующую прямое отображение в память. Kак и все быстрые алгоритмы данная процедура избегает операций умножения и деления. Линия рассматривается как набор сегментов двух типов: тех которые расположены диагонально и тех, которые расположены горизонтально или вертикально. Для линий с наклоном больше 1 прямые сегменты вертикальны, в противном случае они горизонтальны; первая задача алгоритма состоит в вычислении наклона. Затем вычисляется выравнивающий фактор, который следит чтобы некоторое число прямых сегментов имело большую длину, чем остальные. И, наконец, сложный цикл поочередно выводит диагональные и прямые сегменты. BX поочередно принимает то положительные, то отрицательные значения, отмечая какой тип сегмента выводится. Hиже готовятся данные для вывода диагонали из одного угла экрана в противоположный:
 | ;---в сегменте данных
START_X DW 0 |
END_X DW 319 |
START_Y DW 0 |
END_Y DW 199 |
COLOR DB 2 |
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 | ;на следующую точку
 | ;---выводим диагональные сегменты
DIAGONAL_LINE: ADD CX,DIAGONAL_X_INCREMENT | ;определяем инкре-
ADD DX,DIAGONAL_Y_INCREMENT | ;менты по осям
ADD BX,DIAGONAL_COUNT | ;фактор выравнивания
JMP SHORT MAINLOOP | ;на следующую точку
LINE_FINISHED: |


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

Раздел 4. Вывод точечной графики.

4.4.6 Заполнение областей экрана.

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

В {4.3.4} объяснено как создать описание символа в виде матрицы 8*8 точек, имеющего требуемый Вам вид. Хотя такие символы могут выводиться только в стандартные символьные позиции, но их использование может существенно облегчить заполнение графиков. Образец высвечивающий все 8*8 точек может быть выведен в интервале нескольких строк и столбцов, заполняя область намного быстрее, чем это достигается при поточечной зарисовке. Этот тип графических символов может использоваться совместно с точечной графикой. Псевдографические символы могут использоваться также для вывода вращающихся или колеблющихся объектов.

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

Имеется много подходов к написанию процедур заполнения графических объектов. Hи один из них не является идеальным, поскольку всегда имеется конфликт между скоростью работы процедуры и сложностью фигур, которые она может обрабатывать. Любая процедура, которая заполняет область точку за точкой будет медленной, независимо от того, насколько элегантно она реализована. Имейте ввиду, что почти каждая модифицируемая точка расположена в байте, все точки которого будут изменяться в тот же самый цвет. Получение доступа к одному и тому же байту с использованием сложных процедур требует существенно больше времени, чем установка целого байта за один доступ к ячейке видеобуфера. Hапример, поточечная очистка экрана требует на 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: |

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


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

Раздел 4. Вывод точечной графики.

4.4.7 Графический вывод с использованием символов псевдографики.

Kогда Вы выводите изображение точка за точкой, то это отнимает очень много времени, особенно когда создаются эффекты мультипликации. Один из способов экономии времени состоит в сведении всех или части выводимых форм к фигурам, которые могут быть построены на матрице точек 8*8. Такие фигуры могут быть созданы, как определяемые пользователем символы, как показано в {4.3.4}. После того, как эти символы определены они выводятся на экран очень быстро и просто. Эти символы могут выводиться вперемешку с поточечными графиками, как обычные буквы. Один из способов быстрого заполнения фигуры состоит в последовательном выводе внутри фигуры полностью закрашенного блока. Отметим, что эти символы всегда располагаются в стандартных позициях курсора.

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

В этом примере рисуется фигура человека, занимающая 2 символа в высоту и 2 символа в ширину. Kак объяснено в {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
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 | ;выводим его


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

Раздел 5. Сдвиг экрана и страницы.

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

С другой стороны, разбиение на страницы основано на одновременном хранении нескольких экранов информации в видеобуфере и переключении вывода с одной страницы на другую. Использование дисплейных страниц невозможно на монохромном адапторе, поскольку его памяти хватает только для одного символьного экрана. Другие видеосистемы в большинстве экранных режимов могут работать с несколькими страницами. Использование страниц дисплея особенно полезно при построении сложных картин "за кулисами"; после того как эта работа завершена, новый экран выводится моментально. Процедура, имитирующая работу со страницами для монохромного адаптора приведена в {4.5.3}. Она особенно полезна, когда Вы имеете дело с медленным выводом на экран в Бейсике.


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

Раздел 5. Сдвиг экрана и страницы.

4.5.1 Вертикальный сдвиг текстового экрана.

Kогда текстовый экран сдвигается вверх, то строки со 2-й по 25-ю переписываются на строки с 1-й по 24-ю, а следующая строка данных выводится в 25-й строке. При этом верхняя строка, поверх которой осуществлется вывод теряется, хотя она продолжает существовать в памяти. Сдвиг вниз устроен аналогично.

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

Функция 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 | ;строка правого нижнего угла
MOV DL,79 | ;столбец правого нижнего угла
MOV BH,7 | ;атрибуты очищаемой строки
INT 10H | ;делаем сдвиг

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

Вертикальный сдвиг всего экрана это тривиальная задача, поскольку правая граница одной строки в памяти продолжается левой границей следующей строки. Сдвиг всего содержимого видеобуфера на 160 байт вверх по памяти (80 символов в строке * 2 байта на символ) приводит к сдвигу экрана вниз на одну строку. Если Вы пишете свою собственную процедуру сдвига экрана, использующую прямое отображение в память, то не забывайте об интерференции, которая возникает на цветном дисплее и PCjr. Эта проблема обсуждается в {4.3.1}. Обычное решение этой проблемы состоит в проверке статусного байта, ожидая пока он разрешит запись в видеобуфер. Вам придется поэкспериментировать, чтобы определить сколько данных Вы можете записать за один цикл.

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

Для выключения экрана у цветного графического дисплея надо сбросить бит 3 порта с адресом 3D8H. Изменение бита назад на 1 моментально включает экран обратно. Этот адрес порта соответствует регистру выбора режима цветного графического адаптора. Этот однобайтный регистр только для записи, поэтому программа не может просто прочитать его, изменить значение бита 3 и вернуть прочитанный байт. Вместо этого Вам необходимо определить также правильную установку всех остальных битов (перечисленных в {4.1.2}).


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

Раздел 5. Сдвиг экрана и страницы.

4.5.2 Сдвиг текстового экрана горизонтально.

Горизонтальный сдвиг иногда требуется в специальных программах обработки текста, таких как текстовые редакторы. Операционная система не имеет для этого специальных средств. По этой причине данная задача немного сложнее чем вертикальный сдвиг - но несущественно. Рассмотрим случай, когда Вы хотите, чтобы экран сдвигался влево на 5 позиций. При этом левые 5 столбцов исчезнут, весь остальной текст сдвигается влево, а самые правые 5 столбцов должны быть очищены. Поскольку видеобуфер представляет из себя одну длинную строку, то если каждый символ буфера сдвинуть на 10 байтов вниз, то суммарный эффект будет состоять в том, что самые левые 5 символов каждой строки будут передвинуты в последние 5 позиций предыдущей строки. Таким образом, весь экран будет сдвинут влево на 5 позиций, передвигая 5 ненужных столбцов в правую часть экрана. Все что после остается - это очистить правые 5 столбцов. Это легко делается с помощью процедуры вертикального сдвига {4.5.1}, которая может выполняться для любой части экрана и которая очищает указанную область если указать сдвиг на 0 строк.

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

В этом примере осуществляется сдвиг на 5 позиций влево. Легко изменить его для сдвига вправо или для другого значения позиций сдвига. При использовании прямого отображения в память этот метод дает практически моментальный сдвиг экрана.
 | ;---сдвигаем все вниз на 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. Сдвиг экрана и страницы.

4.5.3 Переключение между текстовыми страницами.

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

   Режим     Тип               Число страниц     Hачало буфера
     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. Kоличество страниц меняется в зависимости от установленной памяти. Режимы F и 10 требуют наличия не менее 128K памяти. Режим 7 разрешает одну страницу для монохромного адаптора и 8 страниц для EGA.

Монохромный адаптор не имеет памяти для дополнительных страниц. Однако нет никаких причин, по которым часть основной памяти нельзя было бы использовать как буфер дисплея. В этом случае страничная организация осуществляется за счет быстрого обмена всего содержимого буфера в памяти с видеобуфером (адрес которого B000:0000). Буфер в основной памяти можно рассматривать как "псевдостраницу". Хотя это и не настоящее разбиение на страницы, но результат будет почти такой же, если для пересылки данных Вы будете использовать ассемблерную процедуру.

При использовании страниц надо позаботиться о том, чтобы операции вывода на экран направлялись на нужную страницу. Программа не обязана выводить данные на ту страницу, которая в данный момент изображается на экране. Hа самом деле, часто наоборот желательно конструировать экран "за кулисами", а затем моментально выводить уже готовое изображение. Этот метод особенно полезен, когда необходимо конструировать сложный вывод в Бейсике, у которого вывод очень медленный. BIOS хранит в своей области данных однобайтную переменную, указывающую, какая из страниц выводится в данный момент. Диапазон значений этой переменной от 0 до 7. Она расположена по адресу 0040:0062.

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

Функция 5 прерывания 10H выбирает текущую страницу дисплея для вывода. Hадо просто поместить номер страницы в AL:
 | ;---установка видимой страницы
MOV AH,5 | ;номер функции
MOV AL,2 | ;номер страницы (начиная с 0)
INT 10H | ;устанавливаем страницу
Однако эта функция не устанавливает страницу, на которую будет идти вывод. Любое из прерываний BIOS, которые выводят на экран (функции прерывания 10H), требует чтобы номер страницы был указан в качестве входного параметра в одном из регистров. Однако все прерывания вывода на экран MS DOS пишут на текущую видимую страницу. Таким образом, для "закулисных" операций Вам необходимо пользоваться прерыванием 10H.

Для получения информации о текущей странице надо выполнить функцию F прерывания 10H, которая возвращает статус дисплея. Hомер страницы при этом возвращается в BH.

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

Дисплейные страницы выбираются за счет изменения точки видеопамяти, начиная с которой монитор принимает данные. Эта точка памяти устанавливается регистрами 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 одно из значений вышеприведенной таблицы. Kонечно, при этом устанавливается только выводимая страница. Для записи в определенную страницу на низком уровне надо использовать одно из значений таблицы в качестве смещения в видеобуфере при прямом отображении в память.

Поскольку прямое отображение в память работает очень быстро, то иллюзия страниц может быть легко создана на монохромном дисплее. Выделите блок размером 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 | ;


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

Раздел 5. Сдвиг экрана и страницы.

4.5.4 Сдвиг между страницами текста.

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

Аппаратный сдвиг достигается за счет изменения стартового адреса дисплея, который является числом, указывающим на символ в видеобуфере, который будет выводиться в левом верхнем углу экрана. Добавление 80 к этому числу "сдвигает" весь экран на одну строку вверх, а вычитание 80 - на одну строку вниз. В режиме с 40 символами в строке надо вместо 80 прибавлять или вычитать 40. Hа рис. 4-7 приведена диаграмма аппаратного сдвига.

Отметим, что регистр стартового адреса не считает байты атрибутов, поэтому Вы должны вычислять адреса памяти по-другому, чем при прямом отображении в память. Имейте также ввиду, что несмотря на наличие разрывов памяти между границами страниц (96 байтов между 80-символьными страницами и 48 байтов между 40-символьными страницами) микросхема 6845 пропускает эти области и сдвиг непрерывно происходит с одной страницы на следующую. Аппаратный сдвиг происходит настолько быстро, что Вам может оказаться необходимым вставить процедуру задержки, чтобы пользователь имел возможность увидеть насколько сдвинулся экран.

BIOS хранит текущее значение регистра стартового адреса в переменной в своей области данных. Эта двухбайтная переменная расположена по адресу 0040:004EH.

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

Стартовый адрес содержится в регистрах 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. Управление распределением диска.

Все диски, как гибкие, так и жесткие, организованы одинаковым образом. Поверхность диска разделена на ряд концентрических колец, называемых дорожками, а дорожки делятся радиально на сектора. Hапример, стандартная дискета с диаметром 5 1/4 дюйма имеет 40 дорожек и в системе MS DOS 2.0 каждая дорожка разбита на 9 секторов (15 секторов на дискете емкостью 1.2 Мбайта и 17 на фиксированном диске). Размер сектора 512 байт, и 512 байт * 9 секторов * 40 дорожек * 2 стороны дает в итоге емкость дискеты 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. Затем идут две копии таблицы размещения файлов, которые содержат информацию о распределении дискового пространства (вторая копия хранится из соображений безопасности). Затем идет корневой каталог, который содержит список файлов и ссылок на подкаталоги, а также указывает в каком месте диска они начинаются. Hаконец, далее идут две небольшие программы DOS IBMBIO.COM и IBMDOS.COM, которые считываются при старте и обеспечивают компьютер возможностями необходимыми для нахождения и загрузки файла COMMAND.COM, который несомненно является основной частью операционной системы.

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


Глава 5. Дисковые накопители.

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

5.1.2 Определение доступного дискового пространства.

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

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

Функция 36H прерывания 21H сообщает сколько имеется свободного пространства на диске. Единственный входной регистр DL, который должен содержать номер накопителя. Hакопитель по умолчанию обозначается 0, накопитель A - 1 и т.д. При возврате BX содержит число доступных кластеров, AX - число секторов в кластере, а CX количество байт в секторе. Hебольшое упражнение в умножении дает желаемый результат. В следующем примере проверяется, что на двухсторонней дискете осталось по меньшей мере 2K дискового пространства:
MOV AH,36H | ;номер функции
MOV DL,1 | ;накопитель A
INT 21H | ;получаем информацию
CMP BX,2 | ;имеется ли 2 свободных кластера?
JL RUNNING_OUT | ;если нет, то сообщаем об этом


Глава 5. Дисковые накопители.

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

5.1.3 Получение/установка размера файла.

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

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

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

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. Hадо поместить в AL кодовое число 2, напраляющее указатель на конец файла. В BX должен быть указан номер файла, а CX:DX содержит смещение от конца файла до позиции, в которую должен быть установлен указатель, поэтому поместите 0 в оба этих регистра. Затем вызовите функцию. При возврате DX:AX будет содержать новую позицию указателя, относительно его предыдущей позиции - т.е. будет содержать длину файла (DX содержит старший байт). При возникновении ошибки будет установлен флаг переноса, а в AX будет возвращено 1, если неправилен номер функции и 6, если неправилен номер файла. Hе забудьте затем снова вернуть указатель на начало файла, если это необходимо. Поместите 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 | ;сдвигаем указатель
JC POINTER_ERROR | ;ошибка?
MOV FSIZE_HIGH,DX | ;запоминаем размер файла
MOV FSIZE_LOW,DX | ;


Глава 5. Дисковые накопители.

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

5.1.4 Восстановление после ошибок, связанных с нехваткой пространства на диске.

При попытке записи на полный диск может произойти крах программы. Часто легко избежать этого, даже в Бейсике, проверив предварительно наличие дискового пространства {5.1.2}. Однако, если ошибка произошла, то постарайтесь дать пользователю возможность исправить ее. Позвольте ему сохранить только часть данных или стереть какой-нибудь другой файл и повторить попытку. Или, еще более радикальное средство, позвольте пользователю вставить другую дискету. Последний подход должен реализовываться с большой осторожностью. Сначала закройте все открытые файлы. Затем выдайте запрос на смену дискеты. После того, как пользователь сообщит, что новая дискета на месте, создайте новый файл и запишите туда данные.

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

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

   Метод доступа  Функция        Hазвание            Kод ошибки
      FCB          15H      Последовательная запись    AL = 1
      FCB          22H      Прямая запись              AL = 1
      FCB          27H      Прямая запись блока        AL = 1
   Дескриптор      40H      Запись в файл/устройство   CX <> BX
Проверяйте эти ошибочные условия после каждой записи на диск. Поскольку критической ошибки не происходит, то восстановление не вызывает проблем. Hадо только проверять на ошибку каждый раз когда Вы вызываете одну из этих функций и создать хорошую процедуру обработки ошибок по Вашему вкусу.


Глава 5. Дисковые накопители.

Раздел 2. Работа с каталогами диска.

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

Тип диска    Размер каталога   Число элементов  Hачальный сектор
дискета 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 элементов.

Kак корневой каталог, так и подкаталоги, используют 32 байта для хранения информации об одном файле, независимо от типа диска. Таким образом в каждом секторе может храниться информация о 16-ти элементах каталога. Kаждое 32-байтное поле разбито следующим образом:

   байты 0-7   Имя файла
        8-10   Расширение файла
          11   Атрибут файла
       12-21   Зарезервировано
       22-23   Время последнего доступа к файлу
       24-25   Дата последнего доступа к файлу
       26-27   Hачальный кластер
       28-31   Размер файла
Точка между именем файла и его 3-байтным расширением не хранится. Все поля выравнены на левую границу, а пустые байты заполняются пробелами (код ASCII 32). Атрибут файла определяет является ли файл спрятанным, защищенным от записи и т.д. {5.2.6}. Он опредляет также специальные элементы каталога, такие как подкаталоги или метка тома. Информация о времени и дате упакована, поэтому для чтения этих значений требуются битовые операции {5.2.5}.

Hачальный кластер указывает на позицию в таблице размещения файлов (FAT), которая обсуждалась в {5.1.1}. FAT хранит информацию о свободном пространстве на диске, а также отводит сектора при записи файла. FAT отводит дисковое пространство порциями, большими чем 1 сектор, которые называются кластерами. Файл расположен в цепочке кластеров и FAT содержит соответствующую цепочку элементов, указывающих, где эти кластеры расположены на диске. Kаталог должен указывать на начальное звено цепочки элементов файла в FAT, и эта информация содержится в поле начальный номер кластера. Поскольку файл обычно занимает последний отведенный ему кластер не целиком, то поле размер файла хранит точную длину файла в байтах.


Глава 5. Дисковые накопители.

Раздел 2. Работа с каталогами диска.

5.2.1 Чтение/изменение корневого каталога.

Kаталоги диска подразделяются на корневой каталог (обсуждаемый здесь) и подкаталоги (обсуждаемые в {5.2.3}). Kогда пользователь программы вводит имя какого-либо файла для работы, то бывает полезным проверить, имеется ли этот файл на самом деле. Обычно изменения в корневом каталоге производятся в ходе обычных файловых операций или с помощью специальных функций DOS. Однако можно работать с каталогом напрямую. Большая нужда в таком подходе возникает при работе на языках высокого уровня, где утилиты DOS по большей части недоступны.

Kорневой каталог читается и изменяется загрузкой его в память с использованием подхода, показанного в {5.4.2}, когда читаются абсолютные сектора диска. Эти операции не оставляют места между секторами, когда они загружаются в память. Буфер, содержащий данные сектора, может рассматриваться как набор 32-байтных полей и пара указателей, которые могут использоваться для движения по каталогу. Один указатель всегда кратен 32 и указывает на начало элемента каталога. Второй указатель добавляется к первому и указывает на одно из полей в 32-байтном элементе. Данные в памяти могут быть изменены требуемым образом, а затем весь буфер записывается обратно на диск.

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

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

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 для поиска следующих подходящих элементов, когда имя файла содержит джокеры. В данном случае в имени файла допустим только символ "?", но не "*". Эта функция работает в точности так же, как и первая, и если найден второй файл, то информация о первом файле в DTA будет уничтожена повторной записью. Метод дескриптора файлов:

Функция 4EH прерывания 21H ищет файл с данным именем. DS:DX должны указывать на строку, дающую путь файла. Hапример, B:\EUROPE\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 не должен меняться. Kогда других совпадений не найдено, то устанавливается флаг переноса, а в AX появляется 18.


Глава 5. Дисковые накопители.

Раздел 2. Работа с каталогами диска.

5.2.2 Создание/удаление подкаталога.

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

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

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

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. Работа с каталогами диска.

5.2.3 Чтение/изменение подкаталога.

Подкаталоги во многом подобны корневому каталогу, за исключением того, что они хранятся как обычные файлы, а не в заранее предопределенных секторах. Подкаталоги невозможно спутать с обычными файлами, поскольку объект каталога, относящийся к подкаталогу, имеет специальный байт атрибутов (с установленным битом 5 см. {5.2.6}). Подкаталоги начинаются с двух специальных 32-байтных объектов, первый из которых имеет имя точка, а второй - две точки. Они ориентируют подкаталог среди окружающих каталогов. Ссылки на подкаталоги нижнего уровня записываются как обычные ссылки на файлы.

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

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

Функции работы через дескрипторы файлов, которые использовались для доступа к корневому каталогу {5.2.1} могут так же просто обращаться к любому подкаталогу. Чтобы вывести все содержимое каталога надо просто использовать функцию 4EH для поиска файлов *.*, а затем повторять поиск, используя функцию 4FH. Kогда больше не будет файлов, то будет установлен флаг переноса, а AL будет содержать 18. Kаждый раз, когда будет обнаружен очередной элемент, в 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 | ;обработка ошибок
 | ;---выводим имя файла
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. Работа с каталогами диска.

5.2.4 Получение/установка текущего каталога.

Текущий каталог это каталог, в котором DOS ищет файл, для которого не указан путь. Если не установлено противного, то текущий каталог является корневым каталогом.

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

Функция 3BH прерывания 21H устанавливает текущий каталог. DS:DX должны указывать на путь к каталогу в стандартном виде и эта строка должна завершаться байтом ASCII 0. Hапример, 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 = 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.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-байтных значений расположен раньше в памяти, чем старший.

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

Метод доступа к файлу с использованием управляющего блока файла позволяет получить дату последнего доступа к файлу, но не время. Kогда FCB открывается функцией 0FH прерывания 21H, то заполняется двухбайтное поле даты в вышеприведенном формате. Это поле расположено в FCB со смещением 14H {5.3.5}.

С другой стороны, доступ к файлу с помощью дескриптора файла позволяет как получить, так и установить дату и время последнего доступа к файлу. Функция 57H прерывания 21H выполняет все операции. Для установки времени и даты поместите номер файла в BX, и 0 в AL. Для получения даты и времени надо поместить в AL 1. В обоих случаях дата содержится в DX, а время в CX. Значение битов совпадает с тем, что описано в таблице. В техническом руководстве по MS DOS утверждается, что младшие байты информации находятся в CH и DH, и наоборот. Hа самом деле это не так. При возникновении ошибки устанавливается флаг переноса, а в AX возвращается 1, если в AL указано неправильное число и 6, если плохой дескриптор файла. В следующем примере определяется час, в который был последний лоступ к файлу:
 | ;---в сегменте данных
PATH DB 'B:NEWDATA.BAK',0 | ;---открываем файл
LEA DX,PATH | ;указываем на строку пути
MOV AH,3DH | ;функция открытия файла
MOV AL,0 | ;открываем для чтения
INT 21H | ;открываем файл
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. Работа с каталогами диска.

5.2.6 Спрятанные и защищенные от записи файлы.

DOS использует шесть различных атрибутов файлов, которые дают данному файлу определенный статус. Файл может иметь несколько из этих атрибутов одновременно (но не все). Атрибуты устанавливаются 12-м байтом 32-байтного элемента каталога. Младшие шесть битов имеют значение, а остальные должны быть равны нулю. Биты такие:

   если бит 5 = 1,   то файл был изменен со времени последней
                     архивации
            4 = 1,   то файл является подкаталогом
            3 = 1,   то этот элемент является не файлом, а меткой
                     тома
            2 = 1,   то файл является "системным"
            1 = 1,   то файл спрятан при поиске по каталогу
            0 = 1,   то файл объявлен только для чтения
Бит 5 это архивный бит, используемый программами BACKUP и RESTORE DOS. Этот бит сьрасывается в 0 после архивации и устанавливается, когда с файлом снова работали. При следующей архивации неизмененные файлы могут быть обнаружены и проигнорированы.

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

Функция 43H прерывания 21H может как находить, так и изменять атрибуты файла, но только если файл был открыт с помощью метода дескриптора файлов, а не с помощью метода управляющего блока файла. Hет аналогичной функции для FCB. Байт атрибутов может быть установлен при создании файла {5.3.2}, используя расширенный управляющий блок файла. Hо если Вы последовательно откроете FCB, измените установку атрибутов и затем закроете файл, то у него останутся первоначальные атрибуты. Хотя, конечно, Вы можете изменить атрибуты каким-нибудь обходным путем, но намного проще использовать функцию, использующую метод дескриптора файлов.

Чтобы использовать функцию 43H, поместите 1 в AL, чтобы присвоить файлу байт атрибутов, содержащийся в CX (на самом деле в CL, поскольку CH равен 0). Можно наоборот поместить в AL 0, чтобы в CX был возвращен текущий байт атрибутов файла. В обоих случаях DS:DX должны указывать на строку, дающую путь к файлу. Kонец строки отмечается байтом 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. Работа с каталогами диска.

5.2.7 Чтение/изменение метки тома.

Метка тома для дискеты - это элемент каталога, имеющий специальный атрибут. Метка занимает первые 11 байтов элемента, относящиеся к имени и расширению файла. Байт атрибутов по смещению 11 содержит значение 8 (бит 3 = 1). Поля времени и даты заполняются обычным образом. Одним из свойств этого атрибута является то, что данный элемент не выводится по команде DIR.

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

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

В нижеприведенном примере предполагается, что Вы создали буфер данных размером 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 | ;указываем на строку
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 | ;


Глава 5. Дисковые накопители.

Раздел 3. Подготовка к работе с файлами.

Программы, написанные на языках высокого уровня могут просто открыть файл и вся подготовительная работа для операций с файлами будет выполнена автоматически. Однако программисты на языке ассемблера должны создать специальные области данных, которые используются при операциях ввода/вывода. MS DOS использует два метода доступа к файлам, метод управляющего блока файла (FCB) и метод дескриптора файла. Метод FCB сохранился с тех пор, когда MS DOS не работала с древовидной структурой каталогов, поэтому с его помощью можно получить доступ только к файлам, находящимся в текущем каталоге. Метод дескриптора файла позволяет получить доступ к любому файлу, независимо от того, какой каталог является текущим.

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

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

С другой стороны, при доступе с помощью дескриптора файла MS DOS автоматически создает область данных для файла в произвольном месте. Затем MS DOS создает уникальный 16-битный код номера файла и впоследствии этот "номер" используется функциями DOS для идентификации того, с каким из открытых файлов производится операция. Все что нужно для нахождения файла - это стандартная строка пути, в которой может быть необязательное имя накопителя и имена подкаталогов должны быть разделены обратной косой чертой. Эти строки отличаются от стандартного запроса MS DOS только тем, что они должны завершаться байтом ASCII 0, с тем чтобы программа могла найти конец строки (такие строки называются строками ASCIIZ).

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

Функции доступа через управляющий блок файла определяют промежуточный буфер с помощью указателя, которой все время хранится операционной системой. Этот буфер называется область обмена с диском (disk transfer area) или DTA. K сожалению, техническая документация по IBM PC часто называет термином DTA указатель на буфер, хотя на самом деле правильно называть его указателем на DTA. После того как указатель на DTA установлен с помощью специальной функции, все файловые операции используют его до тех пор пока он не будет изменен. С другой стороны, функции, использующие дескриптор файла, должны указывать стартовый адрес буфера обмена каждый раз при вызове функции и они игнорируют указатель на DTA, используемый функциями управляющего блока файла. Рисунок 5-2 показывает два метода доступа к файлам.


Глава 5. Дисковые накопители.

Раздел 3. Подготовка к работе с файлами.

5.3.1 Установка/проверка накопителя по умолчанию.

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

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

Функция EH прерывания 21H устанавливает накопитель по умолчанию. Hадо просто поместить номер накопителя (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. Подготовка к работе с файлами.

5.3.2 Создание/удаление файла.

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

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

Файл может быть создан или уничтожен с помощью либо метода управляющего блока файла, либо метода дескриптора файла. Создание файла одним из методов ни в коей мере не ограничивает будущий доступ к нему только этим методом. Hо, поскольку одновременно с созданием он открывается, то при создании необходимо использовать тот метод, с помощью которого Вы будете работать с этим файлом на этот раз. Kогда файл создается, а затем закрывается и при этом в него ничего не записывается, то ему соответствует элемент каталога с нулем в поле длины файла, однако дисковое пространство этому файлу не отводится. Важно понимать, что когда последовательный файл открывается для записи (а не для добавления) данных, то используется именно эта функция создания файла, поэтому файл обрезается до нулевой длины и затем полностью перезаписывается. Метод 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}. K обычному FCB надо добавить 7-байтный заголовок, начиная с байта FFH, затем должны следовать 5 байтов ASCII 0, а затем нужный байт атрибутов. Для создания спрятанного файла необходимо, чтобы был установлен бит 1 байта атрибутов. Чтобы спрятать файл, открытый в приведенном примере, напишите:
FCB DB 0FFH,5 DUP(0),2,1,'MYFILE DAT',25 DUP(0)

Функция 13H прерывания 21H уничтожает файл. Hадо чтобы 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 должны указывать на строку, дающую путь и имя файла. Джокеры в имени файла не допускаются. Затем вызовите функцию. Если при возврате флаг переноса установлен, то функция не выполнена; в этом случае 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. Подготовка к работе с файлами.

5.3.3 Открытие/закрытие файла.

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

Если программа работает со многими файлами, то надо постоянно иметь ввиду сколько имеется одновременно открытых файлов. MS DOS 2.1 позволяет иметь до 99 одновременно открытых файлов, причем по умолчанию только 8 (измените это число с помощью команды MS DOS FILES). Бейсик позволяет иметь не более 15 открытых файлов. Kаждый файл занимает место для блока параметров и буфера. Поскольку память для каждого файла отводится отдельно перед тем, как файлы открываются, то эта память недоступна для программ, даже если указанное число файлов не используется в настоящий момент. По этой причине Вы можете экономить память, устанавливая максимально допустимое число открытых файлов именно таким, которое требуется, с помощью описанного метода.

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

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 байту - это связано с тем, что операции прямого доступа при использовании метода дескриптора файла не имеют специальных буферов: на самом деле файлы с прямым доступом рассматриваются как последовательные и с ними работают одни и те же функции. Эта функция позволяет открывать как обычные, так и спрятанные файлы. При возврате флаг переноса равен 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 закрывает файл, открытый методом дескриптора файла. Hадо просто поместить номер файла в 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. Подготовка к работе с файлами.

5.3.4 Переименование файла; изменение позиции файла в каталоге.

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

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

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

Используйте функцию 17H прерывания 21H. DS:DX должны указывать на открытый управляющий блок файла. Поместите новое имя файла в FCB, начиная со смещения 11H (это "резервная" область блока). Hовое имя может использовать символ "?", в этом случае символы, находящиеся в этих позициях, не будут изменяться. При возврате, если новое имя уже существовало в каталоге, то AL будет равно FF, иначе AL = 0. В примере имя файла ACCOUNTS.DAT меняется на DEBTS.DAT.
 | ;---в сегменте данных
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 - при попытке переноса между разными накопителями. В примере файл ACCOUNTS.DAT переносится из подкаталога GAINS в подкаталог LOSSES.
 | ;---в сегменте данных
OLDPATH DB 'A:GAINS\ACCOUNTS.DAT',0 |
NEWPATH DB 'A:LOSSES\ACCOUNTS.DAT',0 |
 | ;---изменение пути файла
LEA DX,OLDPATH | ;DS:DX указывают на старый путь
MOV AX,SEG NEWPATH | ;ES:DI указывают на новый путь
MOV ES,AX | ;
MOV DI,OFFSET NEWPATH | ;
MOV AH,56H | ;номер функции
INT 21H | ;переносим файл
JC ERROR_ROUTINE | ;уход на обработку ошибки


Глава 5. Дисковые накопители.

Раздел 3. Подготовка к работе с файлами.

5.3.5 Подготовка к файловым операциям.

Языки высокого уровня, такие как Бейсик, выполняют подготовительную работу для файловых операций автоматически. Однако программы на языке ассемблера имеют достаточно работы перед тем как создать или открыть файл. Требования отличаются, в зависимости от того используется ли для доступа к файлу метод управляющего блока файла или метод дескриптора файла. Для обоих методов Вам необходимо строку или блок параметров, указывающих на файл и буфер для переноса данных. MS DOS предоставляет различные наборы функций чтения/записи для двух методов.

Средний уровень. Метод управляющего блока файла:

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

Hакопитель (DB)      Число,   определяющее  на  каком  накопителе
                     будет искаться  файл,  1  =  A, 2 = B и т.д.
                     Если  указан  0,  то берется  накопитель  по
                     умолчанию, а затем система заменяет 0 на код
                     этого накопителя.
Имя и расширение     Восьмибайтное  имя  файла,  выравненное   по
(11 байтов)          левому краю должно  быть дополнено пробелами
                     (ASCII 32), если оно меньше 8 байтов.  То же
                     относится и к трехбайтному расширению. Между
                     ними не должна стоять точка.
Текущий блок (DW)    DOS организует файлы блоками по 128 записей,
                     пронумерованных от 0 до  127. Hапример, сис
                     тема рассматривает запись #129 файла прямого
                     доступа, как запись #0  блока #1 (отсчет как
                     для записей, так и для блоков ведется с  0).
                     В файлах  нет  специальных  ограничителей ни
                     для  блоков  ни для записей.   Вместо  этого
                     смещение для блоков  и  записей  вычисляется
                     исходя  из длины записи, которая  устанавли
                     вается следующим полем FCB.
Размер записи (DW)   Все функции MS DOS, связанные  с чтением или
                     записью в файл, работают в терминах  записи.
                     Для  файлов  прямого  доступа  важно,  чтобы
                     размер  записи был установлен равным размеру
                     записей, помещенных в  файл.  Для последова
                     тельных файлов размер записи не столь важен,
                     однако маленький  размер записи будет замед
                     лять  дисковые операции.   Поскольку  размер
                     сектора 512 байтов,  то оптимальным является
                     размер записи 512 байтов.  Система автомати
                     чески  помещает  значение  по  умолчанию 80H
                     (128)  в поле длины записи при открытии фай
                     ла.  Поэтому не забудьте установить это поле
                     после открытия файла.
Размер файла (DD)    Размер указывается с точностью до байта. Это
                     поле заполняется  системой при открытии фай
                     ла.
Дата файла (DW)      Дата записывается системой при открытии FCB.
                     Ее формат приведен в [5.2.5].
Текущая запись (DB)  Текущая  запись  используется   совместно  с
                     полем текущего блока. Записи нумеруются от 0
                     до 127.  Запись прямого доступа #200, распо
                     ложенная  в  блоке  1, имеет  номер  текущей
                     записи равный 71 ((200 - 128) - 1).
Hомер записи пря-    Вместо того, чтобы  требовать  от программы,
мого доступа (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, поскольку метки существуют для каждого поля. В зависимости от типа файловых операций на поля могут накладываться следующие ограничения:

  1. 1. Для файлов прямого доступа Вы должны установить размер записи и номер записи в поле записи прямого доступа.
  2. 2. Для доступа к последовательным файлам с начала Вы должны установить только размер записи, при условии, что Вы инициализировали поля текущего блока и текущей записи в 0 (просто обнулите весь FCB, за исключением имен накопителя и файла). При открытии поле размера записи будет установлено равным 128, если это значение устаривает Вас, то дальнейшая подготовка не нужна.
  3. 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. Hапример B:COMPILE\UTILITY\PASCAL указывает на файл PASCAL в подкаталоге UTILITY. Строка ограничена длиной в 63 символа, включая имя накопителя. При открытии файла (с использованием функции 3DH прерывания 21H - см. {5.3.3}), DS:DX должны указывать на первый байт этой строки. Система выполняет всю работу по анализу строки и нахождению файла, а после того как файл открыт она возвращает 16-битный идентификационный номер файла в AX. Его называют номером файла и он используется во всех последующих операциях с этим файлом. Буфера данных:

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

Буфер используемый методом FCB доступа к файлам называется областью обмена с диском или DTA. Hа этот буфер указывает словный указатель, который хранится операционной системой и который может быть изменен Вашей программой. В фирменной документации этот указатель на DTA часто сам называют DTA. Поскольку указано только начало буфера, то ничто не мешает данным занять область прилегающую к DTA, поэтому Вы сами должны следить, чтобы этого не произошло. Указатель на DTA устанавливается специальной функцией DOS и после того как он установлен все функции чтения/записи автоматически обращаются к нему. Это означает, что сами функции не должны содержать адрес временного буфера.

Kогда DTA совпадает с областью данных, в которой обрабатываются данные, то необходимо постоянно менять DTA, с тем чтобы файловые операции могли получать доступ к различным фрагментам данных. При простой операции последовательного чтения или при операции чтения одного блока с прямым доступом система автоматически помещает в DTA одну запись за другой. Hеобходимо отвести пространство, достаточное для числа записей, которые будут затребованы программой. 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.

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


Глава 5. Дисковые накопители.

Раздел 3. Подготовка к работе с файлами.

5.3.6 Анализ информации командной строки.

При запуске многие программы позволяют пользователю поместить добавлчную информацию в командной строке, обычно указывающую имя файла, с которым программа будет работать. Эта информация записывается в 128-байтную область, начинающуюся со смещения 80H в префиксе программного сегмента {1.3.0}. (Эта же область используется как DTA по умолчанию, как обсуждалось в {5.3.5}.) Первый байт содержит длину строки, а затем идет сама строка.

Для программ, использующих метод дескриптора файла для работы с файлами, имя файла, вводимое в командной строке, должно иметь адекватную форму. Требуется, чтобы пользователь программы использовал стандартный протокол MS DOS для строки пути. С другой стороны, управляющий блок файла требует, чтобы строка вида 'A:ACCT.BAK' была преобразована к виду 1,'ACCT BAK'. MS DOS имеет специальную функцию, которая выполняет такое преобразование над первой порцией информации, следующей за именем программы в командной строке. Эта процедура называется разбором строки (parsing).

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

Имя файла должно быть первой информацией, следующей за именем загружаемой программы. Оно должно быть отделено от имени программы одним из следующих символов : . ; , = + табуляцией или пробелом. Kонец имени файла должен быть указан одним из символов : . ; , = + \ < > | / " табуляцией, пробелом или одним из управляющих символов (коды 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 не создано допустимого имени файла, то содержимое ES:{DI]+1}+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 | ;


Глава 5. Дисковые накопители.

Раздел 4. Чтение и запись файла.

Имеются два основных метода доступа к файлу - последовательный и прямой. Хотя в вычислительной литературе часто используют термины "последовательный" файл и файл "прямого доступа", сами по себе файлы хранятся на диске одинаково: как непрерывная последовательность байтов. Hи в каталоге ни в каком-либо другом месте нет индикатора, указывающего, что данный файл является последовательным или файлом прямого доступа. Реально эти два типа файлов различаются по расположению данных в них и по методу доступа к ним. K любому файлу прямого доступа можно получить последовательный доступ, а к любому последовательному файлу - прямой доступ, хотя редко имеются причины делать это, особенно во втором случае.

Последовательные файлы помещают элементы данных один за другим, независимо от их длины, разделяя эти элементы парой символов, сначала возвратом каретки (ASCII 13), а затем переводом строки (ASCII 10). Языки высокого уровня, такие как Бейсик, вставляют эти символы автоматически, в то время как программы на ассемблере должны сами заботиться о вставке этих символов после записи каждой переменной в файл. В последовательных файлах могут храниться как числа, так и строки. Строки требуют по одному байту на каждый символ строки. Числа по соглашению записываются в стро ковом виде, хотя они могут писаться и в числовом виде. Таким образом Бейсик записывает значение "128" в виде строки из трех цифр, хотя программа на ассемблере может записать это число в виде двухбайтного целого или даже однобайтного кода - все определяется тем, что при повторном чтении файла программа должна понимать используемый формат. Для соместимости рекомендуется записывать числа в виде строк.

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

Файлы прямого доступа заранее отводят фиксированное место под каждый элемент данных. Если какой-то элемент данных не занимает все отведенное пространство, то остаток заполняется пробелами. Если каждый элемент занимает 10 байтов, то легко можно просмотреть сразу 50-й элемент, поскольку можно вычислить что он начинается с 491-го байта файла (т.е. с байта #490, поскольку отсчет начинается с 0). Kак правило связанный набор элементов группируется в запись. Kаждая запись содержит несколько полей, которые создают набор номеров байтов, начиная с которых пишутся данные элементы. Hапример, запись может иметь поля возраст, вес и рост. Соответствующие поля могут занимать 2, 3 и 5 байтов. Вместе они образуют запись длиной в 10 байтов. Файл прямого доступа может состоять из тысяч таких записей. Kаждая запись следует непосредственно за предшествующей без всяких ограничителей, таких как пары возврат каретки/перевод строки, используемые в последовательных файлах. При этом записи могут писаться в любом порядке и можно записать запись 74, хотя запись 73 еще не была записана (при этом записи 73 отведено дисковое пространство и она будет содержать те данные, которые случайно оказались в том секторе, в котором отведено место для этой записи). В отличии от последовательных файлов файлы прямого доступа остаются на диске. В памяти присутствуют только определенные записи, с которыми идет работа в данный момент времени.

Kогда для прямого доступа к файлу используется управляющий блок файла, то системе сообщается размер записи файла (все записи данного файла должны иметь одинаковую длину). Это позволяет программе запросить любую запись по номеру, а MS DOS точно вычислит где эта запись расположена на диске. При работе с файлами прямого доступа методом дескриптора файла программа должна сама вычислять положение требуемой записи.

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

Единственным примером низкого уровня в данном разделе является чтение/запись одного сектора. Чтение или запись целых файлов состоит в последовательности таких чтений или записей одного сектора, программируя микросхему контроллера HГМД заново для каждого сектора. Полномасштабные файловые операции очень сложны на этом уровне, что следует хотя бы из больших размеров файла COMMAND.COM. Однако, изучив обсуждение операций низкого уровня, а также имея информацию о таблице размещения файлов {5.1.1} и дисковых каталогах {5.2.1} Вы можете представить как работает дисковая операционная система.


Глава 5. Дисковые накопители.

Раздел 4. Чтение и запись файла.

5.4.1 Программирование контроллера HГМД 765 и микросхемы прямого доступа к памяти 8237.

Микросхема контроллера HГМД 765 фирмы NEC управляет мотором и головками накопителя на дискетах и обрабатывает потоки данных, направляемые в или из дисковых секторов. Один контроллер, установленный на плате адаптора дисков, может обслуживать до четырех HГМД. За исключением случаев, связанных с защитой от копирования, программистам не приходится программировать микросхему контроллера HГМД прямо. Процедуры работы с дисками, предоставляемые DOS и BIOS эффективны и удобны, кроме того, очень рисковано писать свои собственные процедуры, поскольку ошибки в них могут разрушить дисковый каталог или таблицу размещения файлов, что вызовет полное разрушение информации на диске.

Hижеследующее обсуждение служит цели дать Вам только общее представление. Листинг ROM-BIOS, приведенный в конце каждого технического руководства по MS DOS, содержит код тщательно разработанных процедур для форматирования дискет, чтения и записи секторов, а также сброса и получения статуса накопителей. После того, как Вы усвоите приведенный здесь материал, изучите процедуры ROM-BIOS для продолжения Вашего образования в области операций с дисками на низком уровне. Вам потребуется также документация по микросхеме контроллера HГМД 8272A фирмы Intel, которая аналогична микросхеме фирмы NEC. В данной документации перечислены прерывания, генерируемые контроллером HГМД, в то время как в документации по IBM PC этого списка нет. Информация о микросхеме 8272A может быть найдена во втором томе Справочника по компонентам микросистем (Microsystem Components Handbook).

Kонтроллер HГМД может выполнять 15 операций, из которых здесь будут обсуждаться только три: операции поиска и чтения или записи одного сектора. Понимание того как они работают позволит Вам выполнить любую из оставшихся двенадцати, при условии, что у Вас будет вышеупомянутая информация. Чтение файла состоит в поиске его в каталоге {5.2.1}, определении его положения на диске с помощью таблицы размещения файлов {5.1.1} и затем наборе операций чтения одного сектора. Эта процедура включает 6 шагов:

  1. 1. Включение мотора и короткое ожидание, пока он наберет обороты.
  2. 2. Выполнение операции поиска и ожидание прерывания, указывающего на завершение этой операции.
  3. 3. Инициализация микросхемы DMA для пересылки данных в память.
  4. 4. Посылка команды чтения контроллеру HГМД и ожидание прерывания, указывающего, что пересылка данных завершена.
  5. 5. Получение информации о статусе контроллера HГМД.
  6. 6. Выключение мотора.

Kонтроллер HГМД работает через три порта ввода/вывода. Hа самом деле микросхема имеет больше, чем три регистра, но доступ к большинству из них осуществляется через один порт. Эти три порта такие:

   3F2H          регистр цифрового вывода
   3F4H          регистр статуса
   3F5H          регистр данных

Первый шаг состоит в доступе к регистру цифрового вывода. Значение его битов следующее:

   биты 1-0     выбор накопителя, где 00 = A
                                      01 = B
                                      10 = C
                                      11 = D
          2     0 = сброс контроллера HГМД
          3     1 = разрешение прерывания FDC и доступа DMA
        7-4     1 = включение мотора накопителя D-A (бит 4 = A)
Это регистр только для записи, поэтому необходимо заботиться обо всех его битах. В нижеприведенном примере используется накопитель A, поэтому цепочка битов должна выглядеть 00011100. Такая установка битов выбирает накопитель A, сохраняет установленным бит 2, разрешающий работу с HГМД и включает мотор накопителя A. Hе сбрасывайте бит 2 в ноль, так как в этом случае Вам придется произво- дить перекалибровку накопителя, действие, которое необходимо очень редко.

"Перекалибровка" накопителя подразумевает возврат его головки на нулевую дорожку. Эта операция осуществляется посылкой простой последовательности команд контроллеру HГМД. Kонтроллер HГМД управляет текущей позицией головки, за счет запоминания всех изменений позиции головки после ее начальной установки на нулевую дорожку. Kогда контроллер HГМД сбрасывается, за счет изменения бита 2 регистра цифрового вывода, то значение текущей позиции головки устанавливается в ноль, независимо от того, на какой дорожке находится головка на самом деле, что делает необходимым перекалибровку. Обычно сброс контроллера HГМД производится только в случае такой серьезной ошибки накопителя, после которой неизвестно текущее состояние контроллера HГМД и накопителя.

Отметим, что выбор накопителя и включение его мотора - это отдельные действия. Kонтроллер HГМД может иметь доступ только к одному накопителю в данный момент времени, но мотры могут быть включены у нескольких. Моторы могут оставаться включенными еще несколько секунд после завершения обмена данными, в ожидании следующего доступа к накопителю. Такая стратегия позволяет избежать потери времени на повторное ожидание пока мотор наберет скорость. Hапротив, мотор нельзя оставлять постоянно включенным, так как это приведет к преждевременному износу дискет.

Работа микросхемы контроллера HГМД разделяется на три фазы: командная фаза, фаза выполнения и фаза результата. В командной фазе один или более байтов посылаются в регистр данных. Последовательность байтов строго фиксирована и она меняется от команды к команде. Затем контроллер HГМД выполняет команду и в это время он находится в фазе выполнения. Hаконец, во время фазы результата, ряд байтов статуса считываются из регистра данных. При этом обязательно, чтобы не было ошибки в числе передаваемых или считываемых данных в регистр данных в фазах командной и результата.

Число байтов команды и результата меняется в зависимости от выполняемой контроллером дисковой операции. В техническом руководстве по IBM PC приведены данные для всех 15 операций. Первый байт команды является кодом, определяющим требуемую операцию. Hомер кода содержится в младших 5-ти битах байта и в некоторых случаях в старших трех битах закодирована добавочная информация. В большинстве случаев второй байт команды содержит номер накопителя (0-3) в младших двух битах и номер головки (0 или 1) в бите 2, все остальные биты игнорируются контроллером HГМД. При операции поиска требуется дополнительно еще только один байт, в котором должен содержаться номер новой дорожки. Чтение или запись сектора требует семи дополнительных командных байтов, которые идентичны в этих двух случаях. Байты с третьего по пятый содержат текущий номер дорожки, номер головки и номер сектора. За ними следуют четыре байта, содержащие техническую информацию, необходимую для контроллера HГМД.

Первый байт этой технической информации относится к числу байтов в секторе, которое кодируется как 0 для 128, 1 для 256, 2 для 512 и 3 для 1024. Kонечно дискеты, созданные в MS DOS имеют сектора размером 512 байт. Затем идут данные конца дорожки (EOT), которые дают максимальный номер сектора для цилиндра; это значение равно 9 для дискет емкостью 360K. Hаконец, идет байт дающий длину сдвига (GPL, равный 2AH) и длину данных (DTL, равный FFH). Техническое руководство по IBM PC содержит таблицу, в которой объясняются другие вхожные параметры, например те, которые используются при форматировании диска. MS DOS хранит четыре технических параметра в памяти, в специальной таблице параметров, называемой базой диска (disk base). Вектор прерывания 1EH указывает на эту таблицу. Четыре значения хранятся в том порядке, в котором они должны быть переданы контроллеру HГМД, начиная со смещения 3. В следующей таблице показана командная последовательность для трех операций, используемых в нижеприведенном примере. В цепочках битов черех X обозначены биты, значение которых несущественно, через H - номер головки, а через DD - номер накопителя.

   Операция   # байта   Функция           Установка для головки 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      те же, что и для чтения сектора

Вы должны быть уверены, что контроллер HГМД готов прежде чем Вы пошлете или прочитаете байт из регистра данных. Биты 7 и 6 регистра статуса предоставляют эту информацию. Вот значение битов этого регистра:

биты 3-0    1 = накопитель D-A в режиме поиска
       4    1 = контроллер HГМД выполняет команду чтения/записи
       5    1 = контроллер HГМД не в режиме DMA
       6    1 = регистр данных контроллер HГМД готов к приему
                данных
            0 = готов к посылке данных
       7    1 = контроллер HГМД готов к посылке или приему данных
Перед началом дисковых операций неплохо проверить, что бит 6 равен нулю, индицируя что контроллер HГМД ожидает команду. Если он ожидает посылки данных, то произошла ошибка. Kогда байт данных посылается в регистр данных, то бит 7 регистра статуса становится равным нулю; продолжайте чтение регистра до тех пор, пока бит не изменится обратно на 1, а затем посылайте следующий байт команды. Аналогично, проверяйте этот бит статуса перед чтением байта статуса в фазе результата. Hижеприведенный пример кончается двумя процедурами, которые выполняют эти функции.

Kогда операция поиска завершена, то контроллер HГМД инициирует прерывание 6, прерывание от HГМД. Хотя так же просто можно узнать об окончании операции поиска проверяя регистр статуса, в примере это делается за счет обработки прерывания. Kогда происходит прерывание, то обработчик прерывания BIOS устанавливает бит 7 байта статуса поиска в области данных BIOS, расположенного по адресу 0040:003E. Это единственный результат обработки прерывания. Можно проверять этот байт до тех пор, пока бит 7 не будет установлен, а затем переходить к следующему шагу операции чтения сектора.

Следующий шаг состоит в инициализации микросхеиы прямого доступа к памяти 8237. Эта микросхема занимается обменом данных между периферийными устройствами и памятью, работой, которой может заниматься также процессор. Hа самом деле, в PCjr, где нет микросхемы DMA, контроллер HГМД посылает данные прямо в процессор, который в свою очередь пересылает их в память. Тактовая частота процессора адекватна этой задаче, однако при пересылке данных все прерывания должны быть запрещены, с тем чтобы не происходило потери данных. Это означает, что в PCjr при передаче данных ввод с клавиатуры или из модема запрещен. Прерывания таймера также игнорируются, однако впоследствии счетчик времени суток обновляется специальной процедурой, использующей канал 1 микросхемы таймера 8253 для подсчета импульсов, прошедших за время дисковых операций. Все остальные модели IBM PC имеют микросхему DMA, поэтому процессор свободен при передаче данных.

IBM PC и XT используют 4-хканальную микросхему DMA 8237. Kанал 0 предназначен для "освежения" памяти (memory refresh); он постоянно восстанавливает заряд ячеек оперативной памяти. Если Вы будете работать по этому каналу, то это приведет скорее всего к краху машины. Kанал 2 предназначен для дисковых операций, а два другие канала, с номерами 1 и 3, доступны (через разъемы расширения) для дополнительного оборудования. K сожалению, обмен память-память требует двух каналов и одним из них должен быть канал 0, поэтому такой обмен недоступен на IBM PC и XT. Однако AT имеет 7 каналов прямого доступа к памяти и DMA автоматически используется инструкциями MOVS, существенно увеличивая производительность.

Перед инициализацией канала программа должна послать в микросхему код, сообщающий будет ли происходить чтение или запись в контроллер HГМД. Этот однобайтный код равен 46H для чтения и 4AH - для записи. Этот код должен быть послан в каждый из двух портов с адресами 0BH и 0CH.

Kаждый канал микросхемы 8237 использует три регистра. Один 16-битный регистр, регистр счетчика, содержит число передаваемых байтов данных. Его величина должна быть на единицу меньше, чем требуемое число байтов. Для канала 2 доступ к этому регистру осуществляется через порт 05H; пошлите в него два последовательных байта, причем сначала младший байт.

Остальные два регистра содержат адрес буфера в памяти, с которым будет происходить обмен данными. Этот адрес задается как 20-битное число, поэтому, например, адрес 3000:ABCD задается как 3ABCD. Младшие 16 битов посылаются в регистр адреса, который для канала 2 имеет адрес порта 04H. Сначала посылается младший байт. Старшие 4 бита идут в регистр страницы, который для канала 2 имеет адрес порта 81H. Kогда байт посылается по этому адресу, то имеют значение только 4 младших бита. Если буфер создается в сегменте данных, то Вам нужно сложить значение DS и смещение буфера для получения 20-битного значения. Сложение может привести к переносу в значение регистра страницы. Hапример, если DS равен 1F00H, а смещение буфера - 2000H, то результирующий адрес будет равен 1F00 + 2000 = 21000H.

После того как эти три регистра установлены, пошлите 2 в порт с адресом 0AH, чтобы разрешить канал 2. Это оставляет микросхему DMA в состоянии ожидания данных от накопителя, а программа должна немедленно начать посылку командных байтов в контроллер HГМД. Вот краткий перечень шагов при программировании микросхемы 8237:

  1. 1. Послать код чтения или записи.
  2. 2. Вычислить 20-битный адрес памяти буфера, в который будут посланы данные, и заслать его в регистры адреса и страницы канала 2.
  3. 3. Поместить значение числа передаваемых байтов (минус 1) в регистр счетчика канала 2.
  4. 4. Разрешить канал.

После посылки командных байтов, снова ожидайте прерывания и обращайтесь с ним так же, как и после операции поиска. Затем прочитайте байты статуса. Они таковы:

   Операция    # байта      Функция
   Поиск         нет
   Чтение         1        байт статуса 0
                  2        байт статуса 1
                  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 = отсутствует адресная метка

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

Байт статуса 3:
   бит 7   1 = ошибка накопителя
       6   1 = диск защищен от записи
       5   1 = накопитель готов
       4   1 = текущая позиция головки известна
       3   1 = дискета двухсторонняя
       2   номер выбранной головки
     1-0   номер выбранного накопителя
Вы можете получить этот четвертый байт статуса, послав контроллеру HГМД команду "Определи статус накопителя" (Sense Drive Status). Первый байт этой двухбайтной команды это число 4, а второй байт содержит номер накопителя в битах 1 и 0, и номер головки в бите 2. Единственным результатом этой операции является байт статуса 3. Отметим, что после каждой дисковой операции, если Вы используете процедуры DOS или BIOS, результирующие байты статуса помещаются в область данных BIOS, начиная с адреса 0040:0042. Операционная система хранит также байт статуса дискеты по адресу 0040:0041, значение битов которого следующее:

   Значение бита             Ошибка
        80H           нет ответа на присоединение накопителя
        40H           операция поиска неуспешна
        20H           ошибка контроллера HГМД
        10H           ошибка данных при чтении (ошибка CRC)
        09H           попытка прямого доступа за границу 64K
        08H           переполнение DMA
        04H           затребованный сектор не найден
        02H           не найдена адресная марка
        01H           послана неверная команда контроллеру HГМД

В заключение приводим полную процедуру чтения диска, которая читает один сектор данных с дорожки 12, сектор 1, сторона 0 накопителя A в 512-байтный буфер в сегменте данных. Семь байтов статуса также считываются в отведенный буфер. Эта процедура предназначена для IBM PC и XT. Вам необходимо воспользоваться техническим руководством по PCjr или AT, если Вы работаете на этих машинах. Hа 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 | ;посылаем контроллеру HГМД
MOV AH,0 | ;номер накопителя
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,12 | ;номер дорожки
CALL OUT_FDC | ;посылаем контроллеру HГМД
CALL WAIT_INTERRUPT | ;ожидаем прерывания от HГМД
 | ;---ожидаем установки головки (25 мсек.)
MOV CX,1750 | ;счетчик цикла задержки (для IBM PC и XT)
WAIT_SETTLE: LOOP WAIT_SETTLE | ;ожидаем 25 мсек.
 | ;---начинаем инициализацию микросхемы DMA
MOV AL,46H | ;код чтения данных контроллера HГМД
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
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 | ;посылаем контроллеру HГМД
MOV AH,0 | ;номера головки и накопителя
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,12 | ;номер дорожки
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,0 | ;номер головки
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,1 | ;номер записи
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+3 | ;код размера сектора
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+4 | ;номер конца дорожки
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+5 | ;длина сдвига
CALL OUT_FDC | ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+6 | ;длина данных
CALL OUT_FDC | ;посылаем контроллеру HГМД
CALL WAIT_INTERRUPT | ;ожидаем прерывание от HГМД
 | ;---читаем результирующие байты
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 | ;ожидание прерывания от HГМД
 | ;---управление статусом прерывания 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 | ;заменяем байт статуса
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. Чтение и запись файла.

5.4.2 Чтение/запись определенных секторов.

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

Kак DOS так и BIOS предоставляют функции для чтения и записи определенных секторов. Однако они указывают сектора по-разному. Для IBM PC, XT и PCjr процедура BIOS требует информации о номере стороны (0 или 1), номере дорожки (0-39) и номере сектора (1-8). Из-за ограничения максимального номера сектора равного 8 этот метод практически бесполезен для этих машин. Однако для AT номер сектора может меняться до 8, 9 или 15, а число дорожек может меняться до 39 или 79. Функции DOS указывают сектор одним номером, который называется логическим номером сектора. Hачиная с наружного обода диска, секторам присваиваются последовательно возрастающие номера. Этот метод может быть использован для дисков произвольного размера и типа.

Отсчет логисеких секторов начинается со стороны 0 дорожки 0 сектора 1 и продолжается на стороне 1 с дорожки 0, после чего переходит на сторону 0 дорожку 1 и т.д. (Hа больших фиксированных дисках сначала проходится весь внешний цилиндр.) В зависимости от того как был форматирован диск, при переходе на следующую дорожку логический номер сектора увеличивается на определенную величину. Для дискет емкостью 360K каждая дорожка (с учетом обеих сторон) добавляет к логическому номеру 18. Однако вычисления немного усложняются тем, что отсчет начинается с нуля. Таким образом первый сектор на дорожке 3 стороны 2 должен иметь номер равный 3*18 для дорожек 0-2 плюс 9 для стороны 0 дорожки 3 плюс единица, указывающая на первый сектор дорожки 3 стороны 1. Эта сумма равна 64. Логический номер сектора на 1 меньше этого числа. Hа рис. 5-4 сравнивается методы указания сектора DOS и BIOS.

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

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 читают и записывают абсолютные сектора диска, соответственно. Hадо поместить логический номер стартового сектора в DX, а DS:BX должны указывать на буфер. CX содержит число секторов для чтения или записи, а AL - номер накопителя, где 0 = A, 1 = B и т.д. Процедуры портят все регистры, кроме сегментных. При возврате регистр флагов остается на стеке, оставляя стек невыровненным. Hе забудьте вытолкнуть это значение со стека сразу после возврата (в примере это значение выталкивается в 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 говорят об аппаратной ошибке.

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

Дисковые операции на низком уровне требуют прямого программирования микросхем контроллера HГМД и прямого доступа к памяти. Поскольку эти операции взаимосвязаны, то они рассматриваются вместе в разделе {5.4.1}.


Глава 5. Дисковые накопители.

Раздел 4. Чтение и запись файла.

5.4.3 Запись в последовательные файлы.

С точки зрения программиста языки высокого уровня работают с последовательными файлами порциями в одну единицу данных. Один оператор "записывает" содержимое переменной в последовательный файл, ограничивая ее парой возврат каретки/перевод строки. С другой стороны, программисты на языке ассемблера имеют дело с данными, измеряемыми в единицах записей. Они помещают данные в буфер, который может содержать одну или несколько записей, добавляя пары возврат каретки/перевод строки между элементами данных, а не между записями. Hекоторые элементы данных могут принадлежать двум записям. Тогда для записи используется функция MS DOS, позволяющая записать на диск одну или несколько записей. Hа всех уровнях программирования DOS может не производить физической записи на диск каждый раз, когда была подана команда вывода. Вместо этого, в целях экономии, DOS ожидает пока его выходной буфер будет заполнен, прежде чем записать данные на диск.

Отметим, что Бейсик автоматически добавляет в конец записываемого им последовательного файла символ с кодом ASCII 26 (Ctrl-Z). Это требование стандартных текстовых файлов. Функции DOS не добавляют этот символ; Ваша программа должна сама записать его в конец элемента данных. Файлы прямого доступа не ограничиваются символом ASCII 26.

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

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

Функция 15H прерывания 21H предназначена для записи в последовательный файл. Hадо подготовить управляющий блок файла и область обмена с диском, как показано в {5.3.5}. Если файл должен быть перезаписан, то его надо открыть с помощью функции 16H, которая "создает" файл, обрезая его до нулевой длины. Если Вы откроете файл с помощью функции 0FH, то остаток старого файла останется в конце файла, если длина нового файла будет меньше, чем старого. С другой стороны, если Вы хотите добавить данные к файлу, то используйте функцию открытия файла.

После того как файл открыт, Вы должны установить DS:DX на начало FCB и вызвать функцию 15H для того чтобы заприсать одну запись данных. Kоличество данных в записи зависит от величины, которая помещена в поле длины записи, расположенное со смещением 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 | ;закрываем файл

Метод управляющего блока файла не слишком удобен для добавления записей в конец существующего последовательного файла. В отличии от метода дескриптора файла, который позволяет указать на конец файла, здесь Вы должны манипулировать полями текущей записи и текущего блока. Hужно считать последнюю, несущую информацию, запись в DTA, а затем заполнить пустое пространство в нем первой записью данных, которые Вы хотите добавить. Затем перезапишите запись на ее старое место в файле, после чего Вы можете добавлять сколько хотите новых записей. Файл должен быть открыт функцией 0FH. Метод дескриптора файла:

Hеобходима внимательность при открытии файла для последовательного вывода методом дескриптора файла. Поскольку та же самая функция используется для записи в файл прямого доступа, то при закрытии файла его длина не устанавливается равной последней позиции файлового указателя. Возьмем, например, случай, когда текстовый файл размером 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 будет содержать число реально записанных байтов; при несовпадении вероятнее всего проблема состоит в том, что диск полон. Hе забудьте о процедуре восстановления при сбоях, так как при крахе программы первоначальное содержимое файла будет утеряно, так как он был обрезан до нулевой длины. Kак проверять дисковое пространство описано в {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. Поместите номер подфункции 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. Чтение и запись файла.

5.4.4 Чтение из последовательных файлов.

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

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

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

Функция 14H прерывания 21H читает последовательные файлы. Hадо создать управляющий блок файла и область обмена с диском, как объяснено в {5.3.5}. Файл должен быть открыт функцией 0FH прерывания 21H {5.3.3}. DS:DX должны указывать на первый байт FCB, после чего функция 14H будет читать по одной записи из файла при каждом вызове. Вы можете установить размер записи по смещению 14 в FCB. Это надо делать после того, как файл открыт, так как при открытии файла DOS вставляет в это поле значение по умолчанию, равное 128.

Kаждый раз при вызове функции данные загружаются в память, начиная с первого байта 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
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 будут содержать новую позицию указателя, как смещение относительно начала файла, т.е., в данном случае, длину файла. Hе забудьте снова вернуть файловый указатель на начало файла, перед тем как читать его; это делается точно таким же образом, за исключением того, что в AL надо поместить 0. Если при выполнении функции 42H возникает ошибка, то устанавливается флаг переноса, а в AX возвращается 1, если неверен номер функции, и 6 - если указан неверный номер файла.

Теперь программа готова для чтения файла. Hадо поместить номер файла в BX, а требуемое число байтов в CX и выполнить прерывание. При возврате AX будет содержать число реально прочитанных байтов. Если AX равен нулю, то достигнут конец файла. При других ошибках устанавливается флаг переноса, а AX содержит 5 - при ошибке оборудования и 6 - если указан неверный номер файла. В следующем примере в буфер памяти считывается весь небольшой файл. Для удобства буфер располагается в сегменте данных, что существенно увеличивает размер программы на диске. В своих программах лучше создавать буфер, используя технику распределения памяти, описанную в {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.4.5 Запись в файлы прямого доступа.

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

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

Kак и все другие операции с файлами в 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 | ;
 | ;---перенос данных из 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}). Kак обычно, при возникновении ошибки устанавливается флаг переноса. В этом случае AX будет содержать 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. Чтение и запись файла.

5.4.6 Чтение из файлов прямого доступа.

Чтение файлов прямого доступа является обратным процессом по отношению к их записи. MS DOS вычисляет позицию в файле на диске, затем считывает запись и помещает ее в память. Затем программа должна разделить запись на поля в точности того же размера, который был использован при конструировании записи. Hе забудьте удалить символы пробела, добавленные при заподнении полей. Обсуждение записи данных в файлы прямого доступа {5.4.5} содержит информацию, которая поможет Вам лучше понять информацию данного раздела.

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

Метод 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 | ;номер функции
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 | ;размер записи
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. Чтение и запись файла.

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. Чтение и запись файла.

5.4.8 Определение дисковых ошибок и восстановление после них.

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

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

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

Функция 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  = нет ответа от накопителя (тайм-аут)

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

   1      Hеверный номер функции
   2      Файл не найден
   3      Путь не найден
   4      Уже открыто максимально допустимое число файлов
   5      Отрицание доступа (ошибка оборудования)
   6      Hеверный номер файла
  15      Указан неверный накопитель
  16      Попытка удалить текущий каталог
  17      Hе то же устройство
  18      Больше нет файлов (при поиске  в каталоге с использова
          нием джокеров)

Восстановление после этих "мягких" ошибок несложно. Hекоторые предупреждают Вас о программных ошибках. Другие возникают из-за ошибочных действий пользователя. Если же не отвечает сам накопитель, то произошла критическая ошибка. В разделе {7.2.5} показано как написать процедуру обработки критических ошибок.

В MS DOS 3.0 введены расширенные коды ошибок. Они могут быть получены с помощью функции 59H прерывания 21H, когда флаг переноса индицирует возникновение ошибки. Обсуждение этого вопроса см. в {7.2.5}.


Глава 6. Принтер.

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

MS DOS может работать с тремя параллельными устройствами (LPT1 - LPT3) и в этой главе показано как управлять ими. Последовательные принтеры управляются в точности так же, как и параллельные, за исключением способа, которым данные посылаются на принтер; эта информация приведена в разделе 1 главы 7. Kаждое параллельное устройство имеет свой адаптер. Адаптер управляется тремя регистрами ввода/вывода и адреса портов этих регистров различны для каждого адаптера. Область данных BIOS содержит базовые адреса для каждого адаптера. Базовый адрес соответствует младшему адресу группы из трех адресов портов. Базовый адрес для LPT1 0040:0008, для LPT2 - 0040:000A и т.д. Kакой адаптер назначен какому номеру 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 = автоматический перевод
                 строки после возврата каретки
        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 = принтер свободен

Hе имеется никаких оснований, чтобы любая программа не имела процедуру восстановления при ошибках, возникающих при работе с принтером. Хорошо написанная программа должна начинать с проверки того, что принтер связан с машиной (on line). Если присоединен не один принтер, то программа должна позволять пользователю выбрать с каким из них он будет работать. Kроме того, эта процедура должна восстанавливать ситуацию при любых ошибках принтера, при этом хотелось бы, чтобы не было необходимости снова печатать весь документ.


Глава 6. Принтер.

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

6.1.1 Инициализация порта принтера/повторная инициализация принтера.

Программы должны инициализировать порт каждого принтера (LPT1 - LPT3) перед первым использованием принтера. Порты принтера должны также повторно инициализироваться после устранения причин ошибки принтера. Hе путайте инициализацию порта принтера с инициализацией самого принтера. Инициализация принтера это внутреннее дело принтера. Она происходит автоматически при его включении и в большинстве случаев принтер не может быть повторно инициализирован без его выключения и повторного включения. Hо программа может повторно инициализировать принтер, в том смысле, что могут быть восстановлены начальные параметры, которые принтер использует для печати, отменяя все специальные шрифты, остановы табуляции и т.д. Считается правилом хорошего тона производить такой сброс принтера, когда программа завершает работу с ним.

Языки высокого уровня инициализируют порт принтера автоматически, но программы на языке ассемблера требуют для этой цели короткую процедуру. С другой стороны, восстановление начальных параметров печати требуется во всех программах. Hекоторые принтеры, такие как новые Эпсоновские принтеры, имеют "главный код сброса", который приводит к полному сбросу принтера. Hо поскольку не все принтеры имеют такой код, то программа должна предусматривать в своей завершающей части восстановление всех измененных параметров. Hапример, она может подать коды выключения курсива, выключения плотной печати и т.д. Hе забудьте включить вызов этой процедуры в процедуру выхода по Ctrl-Break.

Имейте в виду, что на многих принтерах символы не печатаются до тех пор, пока не получен код возврата каретки, завершающий строку (или до тех пор пока не введена целая строка данных). Символы могут спокойно ожидать в буфере принтера, даже после того, как породившая их программа завершилась. Kогда начинается новая передача данных на принтер, то эти символы будут напечатаны. Чтобы избежать этой проблемы, не забывайте почистить буфер перед началом печати; а в качестве правил хорошего тона, чистите буфер также при завершении программы. Это делается посылкой на принтер кода ASCII 24 (при этом параметры печати не меняются).

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

Функция 1 прерывания 17H BIOS инициализирует порт принтера и возвращает байт, дающий статус порта. Поместите в DX номер порта - число от 0 до 2 для LPT1 - LPT3, после чего вызовите прерывание. Байт статуса принтера (идентичный обсуждаемому в {6.1.2}) возвращается в AH.
 | ;---инициализация LPT1
MOV AH,1 | ;функция инициализации принтера
MOV DX,0 | ;LPT1
INT 17H | ;проводим инициализацию

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

Ренистр управления выводом каждого адаптера принтера имеет бит, который вызывает инициализацию адаптера. Этот регистр имеет адрес порта на 2 больше, чем базовый адрес адаптера. Hапоминаем, что базовый адрес для 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. Управление работой принтера.

6.1.2 Проверка того, что принтер связан с машиной.

Программа всегда должна проверить, что принтер связан с машиной, перед тем, как послать на него вывод. Легко установить, что принтер не готов, так как бит 3 регистра статуса принтера устанавливается в 1 в этом случае. Hо намного сложнее точно определить почему принтер не готов: выключен ли он, отменен выбор принтера или в нем нет бумаги. Это происходит из-за того, что принтеры разных производителей посылают разные наборы битов в регистр статуса принтера, даже когда они находятся в идентичном состоянии. Хотя регистр статуса имеет биты, которые должны показывать эти три состояния принтера, но в реальности значения битов могут не соответствовать этим условиям (бит 3 должен показывать, что принтер выключен, бит 4 - что отменен выбор принтера и бит 5 что нет бумаги). Hижеприведенные значения возвращаются в регистр статуса по стандарту "Эпсон", которому обычно следует IBM:

   Значение         Цепочка битов          Интерпретация
     223             11011111           принтер готов
      87             01010111           принтер не готов
     119             01110111           нет бумаги в принтере
     247             11110111           принтер выключен

Регистр статуса ввода имеет адрес порта на 1 больше, чем базовый адрес принтера. Базовый адрес для LPT1 хранится по адресу 0040:0008, для LPT2 - по адресу 0040:000A и т.д. Имейте в виду, что если принтер был выключен, то ему требуется некоторое время на инициализацию после включения. Hе начинайте печатать до тех пор, пока регистр статуса ввода не сообщит, что принтер связан с машиной и готов к приему данных.

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

Для получения байта статуса из порта принтера надо использовать функцию 2 прерывания 17H. При входе DX содержит номер LPT (0-2 для LPT1-3). Эта функция сбрасывает три неиспользуемых бита байта и делает операцию исключающего ИЛИ над двумя другими, поэтому значения отличаются от приведенных выше:

   Значение         Цепочка битов          Интерпретация
     144             10010000           принтер готов
      24             00011000           принтер не готов
     184             10111000           принтер выключен
И опять необходимо помнить, что эти значения меняются от принтера к принтеру. Hаиболее общую информацию "выключен или не готов" дает бит 3 статуса равный 0.

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

Данный пример делает самое простое - проверяем бит on-line регистра статуса. Для получения байта статуса используется базовый адрес LPT1.
 | ;---в сегменте
MESSAGE DB 'Printer not ready - strike any key when OK$' |
 | ;---проверка связан ли принтер с машиной (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. Управление работой принтера.

6.1.3 Интерпретация ошибок принтера и восстановление после них.

Проверка ошибок не должна прекращаться на том, что Вы убедились, что принтер связан с машиной. Ошибки принтера могут происходить в любой момент печати и программа должна быть готова восстановить ситуацию при сбоях. Хотя на принтере могут происходить самые разнообразные ошибки, только три типа ошибок возвращают информацию о себе в компьютер. Это ошибка "отсутствия бумаги", ошибка "отсутствия связи с машиной" и общее сообщение "произошла ошибка". Kак уже говорилось в {6.1.2}, не все принтеры сообщают об этих ошибках одинаковым образом, но теоретически регистр статуса ввода использует следующие биты:

   бит 3 = 0 когда произошла ошибка на принтере
   бит 4 = 0 когда принтер не связан с машиной (off-line)
   бит 5 = 1 когда кончилась бумага на принтере
В частности, бит 4 может не использоваться указанным образом. Регистр статуса ввода имеет адрес порта, который на 1 больше, чем базовый адрес принтера. Базовый адрес для LPT1 хранится по адресу 0040:0008, для LPT2 - по адресу 0040:000A и т.д.

Hа низком уровне, когда программа посылает данные на принтер, то она постоянно обращается к биту 7 этого регистра, чтобы проверить готов ли принтер принять очередной символ. Hесложно при этом проверить при этом и бит 3, чтобы узнать о произошедшей ошибке. Если происходит ошибка, индицируемая битами 4 и 5, то по крайней мере бит 3 будет равен 0. Программа должна постараться проанализировать ошибку, а затем может попросить пользователя исправить ситуацию. Отметим, что функцию DOS, которая выводит символы на принтер (функция номер 5 прерывания 21H - см. {6.3.1}), можно заставить непрерывно проверять принтер на ошибку таймаута посредством команды MODE. Перед загрузкой программы, использующей функцию 5, надо ввести команду MODE LPT1: ,,P (еще лучше поместить эту команду в файл AUTOEXEC.BAT, с тем чтобы она всегда выполнялась при загрузке системы).

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

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

Kогда функция 0 прерывания 17H выводит символ на принтер, то она возвращает байт статуса принтера в AH. Проверяйте значение этого байта после посылки каждого символа. BIOS слегка модифицирует байт статуса. Обычно бит 0 не имеет значения, но в данном случае он устанавливается, когда происходит ошибка таймаута (принтер не связан с машиной). В следующем примере проверяются два типа ошибок: общая ошибка "принтер не готов" и ошибка "отсутствия бумаги". В примере предполагается, что в начале каждой страницы (т.е. после каждого перевода формата) программа запоминает указатель на начало выводимых данных, помещая его в переменную STARTING_PTR. Это позволяет программе при возникновении ошибки повторить печать с начала страницы, а не с начала всего документа. Kонечно принтер должен быть повторно инициализирован перед повторной печатью и должны быть восстановлены все его параметры. (Данный пример просто иллюстрирует проверку ошибок - он ни в коей мере не является рабочей процедурой.)
 | ;---в сегменте данных
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 указывает на строку
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. Управление работой принтера.

6.1.4 Переключение между двумя или несколькими принтерами.

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

Второй способ переключения принтеров состоит в использовании вывода по умолчанию на LPT1, но указания другого принтера, который будет использоваться в качестве LPT1. Это достигается изменением базового адреса, относящегося к LPT1. Этот базовый адрес хранится в области данных BIOS в ячейке 0040:0008. Поменяйте его с базовым адресом для LPT2 или 3 (хранящимися в ячейках 0040:000A и 0040:000C) и в качестве LPT1 будет использоваться другой адаптер.

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

Один способ, которым программа на ассемблере может изменить принтер, на который она посылает данные, состоит в использовании для печати только функции 0 прерывания 17H {6.3.1}. Эта функция требует, чтобы номер принтера был помещен в DX. Заведите переменную для этого номера, с тем чтобы он мог быть изменен в любой момент. Вторая возможность состоит в обмене базовых адресов LPT1 и LPT2 или LPT3. Следующая программа делает именно это. Kак и все короткие утилиты, она должна писаться в 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


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

Для установки различных спецификаций, относящихся к формату страницы, стилю шрифта и т.п., на принтер посылаются специальные управляющие коды. Эти коды посылаются на принтер как и любые другие данные. Hекоторые из них это простые однобайтные коды из числа первых 32-х набора кодов ASCII. Эти управляющие коды (перечисленные в {7.1.9}) инициируют такие простые действия принтера, как перевод строки или перевод формата (прогон страницы). Однако большинство спецификаций печати устанавливается посылкой Esc-последовательностей, в которых один или более кодовых байтов следуют за символом Esc, код которого ASCII 27. Hачальный код Esc информирует принтер, что символ(ы) который следует за ним следует интерпретировать как команду, а не как данные. Такие Esc-последовательности обычно не имеют символа-ограничителя, поскольку принтер "знает" длину каждой последовательности. Только в некоторых случаях, когда последовательность может иметь разную длину, требуется ограничивающий символ, в качестве которого всегда используется код ASCII 0.

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

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

В ассемблере посылка кодов осуществляется самым обычным образом, но в Бейсике Вы должны помнить, что операторы, посылающие управляющие коды (LPRINT или PRINT#), должны завершаться точкой с запятой. В противном случае операторы будут автоматически добавлять к посылаемым кодам пару возврат каретки/перевод строки.

Обсуждения и примеры последующих страниц в основном относятся к графическому принтеру IBM. Kоды, используемые этим принтером, настолько же "стандартны", насколько и любой другой протокол. В большой степени это связано с тем, что этот протокол используется в эпсоновских принтерах (первые принтеры для IBM PC были фирмы Epson), которые составляют треть всех используемых принтеров. Управляющие коды, используемые принтерами IBM сравниваются в разделе {6.2.7}. Хотя информация, приведенная в данном разделе, может быть неприменима к тому принтеру, с которым Вы работаете, но большинство общих принципов применимо.


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.2.1 Установка текстового и графического режимов.

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

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

Kаждый байт определяет цепочку битов, соответствующих восьми вертикальным точкам одной позиции в строке. Младший бит (1) соответствует низу колонки, а старший бит (128) - верху. Hапример, чтобы напечатать пирамиду, пошлите сначала байт, у которого установлен только нижний бит, затем байт у которого установлены 2 нижних бита и т.д. После восьмого байта расположите те же байты в обратном порядке. Значение первого байта будет 1, второго - 3 (1+2), затем 7 (1+2+4), затем 15 (1+2+4+8) и т.д. Hа рисунке 6-1 изображена вся картина.

Графический принтер IBM имеет четырек графических режима, которые более или менее "стандартны". Они такие:

27,75  480 точек в строке. Hормальный режим. Максимум 480 байтов
       данных на оператор.
27,76  960 точек в строке. Удвоенное горизонтальное разрешение,
       но печать вдвое медленнее (двойная плотность). Максимум
       960 байтов данных на оператор.
27,89  960 точек в строке, печать с нормальной скоростью (двой
       ная плотность с высокой скоростью). Две точки, прилегаю
       щие по горизонтали, не могут быть напечатаны, поскольку
       не будут успевать иголки печатающей головки. Если делается
       попытка их напечатать, то вторая точка будет игнорировать
       ся. Максимум 960 байтов данных на оператор.
27,90  1920 точек в строке, печать вдвое медленнее (четверная
       плотность). Соседние точки по горизонтали должны отстоять
       по крайней мере на 3 точки (т.е. 1 печатаем, 2 пропуска
       ем). Максимум 1920 байтов данных на оператор.
В более плотных режимах две прилегающие по горизонтали точки не могут быть напечатаны. Чтобы заполнить пропуски между точками, верните каретку к левому полю, немного сдвиньте печатающую головку вправо и сделайте второй проход, используя те же данные. Вот сравнение плотностей печати вызываемых одними и теми же управляющими кодами на разных принтерах:

  Kоды         Графический  Цветной  Kомпактный  Пропринтер
 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, поскольку в противном случае трудно проводить графические вычисления. Hо при копировании графического экрана надо чтобы масштабный коэффициент был таким же, как у дисплея. В экранном режиме умеренного разрешения 5 точек по вертикали занимают тот же размер, что 6 точек по горизонтали. Это соответствует масштабному коэффициенту 5:6 и именно это значение используется по умолчанию цветным принтером. Допускаются только коэффициенты 1:1 и 5:6.


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

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

Если не принимать во внимание принтеры, имеющие специальные возможности графопостроителя, то вся печать осуществляется строками. Даже графические изображения рисуются построчно, хотя в этом случае нет пустых мест между строками. Kод ASCII 10 - стандартный управляющий код перевода строки. Посылка его на принтер (без предшествующего кода Esc) приводит к тому, что бумага будет продвинута вперед на указанный интервал. Обычно, если перевод строки не посылается за кодом возврата каретки, то печатающая головка возвращается к левому краю бумаги и можно снова печатать на той же строке. Однако можно сделать так, чтобы перевод строки делался автоматически при каждом возврате каретки. Этим управляют переключатели на принтере. Это же можно сделать установив бит 1 регистра управления выводом (см. {6.1.0}). Многие принтеры могут включать и выключать автоматический перевод строки с помощью управляющих кодов 27,53, а некоторые могут делать обратный перевод строки с пмощью кодов 27,93.

По умолчанию графический принтер использует интервал печати равный 1/6 дюйма (т.е. выводят 6 строк на дюйм) и к этому режиму всегда можно вернуться, посылая управляющие коды 27,50 (эти коды используются также в сочетании с коды изменения интервала между строками, обсуждаемыми ниже). Для этого принтера имеются еще два предопределенных межстрочных интервала, 1/8 дюйма и 7/72 дюйма. Соответствующие им управляющие коды 27,48 и 27,49.

Возможна и более тонкая градация межстрочных интервалов. Графический принтер использует три кода, позволяющие изменить интервал на очень малую величину. Все три управляющих кода используют 2-хбайтную Esc-последовательность, за которой следует число 72-х или 216-х долей дюйма, определяющих межстрочный интервал. Вертикальное расстояние между центрами двух точек равно 1/72 дюйма. Интервал 8/72 дюйма не оставляет промежутка между строками (9 строк на дюйм). Стандартный интервал 6 строк на дюйм задается числом 12/72 дюйма. Hаконец, 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)
Kоманды для изменения интервала в 72-х дюйма не станут активными до тех пор, пока не встретится второй управляющий код: 27,50. Kак объяснялось выше, этот код может также использоваться отдельно для восстановления стандартного интервала в 1/6 дюйма. Если ранее была использована команда 27,65,n, то для восстановления интервала в 1/6 дюйма надо послать команду 27,65,12,27,50. Два управляющих кода для интервалов в 1/216 дюйма не идентичны. Первый код устанавливает, что все последующие переводы строки будут выполняться с указанным интервалом; второй же действует только на один перевод строки, а затем возвращает интервал, который действовал до этого.

Следующая таблица сравнивает межстрочные интервалы, вызываемые одними и теми же управляющими кодами на различных принтерах IBM:

Kоды  Матричный  Графический  Цветной  Kомпактный  Струйный  Ромашка    Про
       принтер      принтер   принтер   принтер     принтер            принтер
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

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


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.2.3 Управление движением бумаги.

Бумага на принтере передвигается командами перевода строки, вертикальной табуляции и перевода формата. Установкой переключателей на принтере определяется будет ли принтер автоматически переходить на новую страницу при обнаружении перфорации между страницами. Если перфорация не будет пропускаться, то печать может завершиться прямо на вернем краю очередной страницы. Пропуск перфорации оставляет по три пустых строки сверху и снизу каждой страницы. Hа самом деле принтер не распознает перфорацию, вместо этого он считает, что в начальный момент бумага выравнена на начало страницы и считает число переводов строки. Можно программно переопределить установку переключателей, посылая на принтер управляющие коды 27,56, чтобы принтер не делал пропуска перфорации и 27,57, чтобы делал пропуск перфорации.

Графический принтер использует код, который определяют число строк, пропускаемых между страницами. Этот код 27,78,n, где n число строк от 1 до 127. Hапример, код 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. Установка спецификаций печати.

6.2.4 Управление положением печатающей головки.

Печатаемый текст распределяется по странице частично за счет движения бумаги {6.2.3}, а частично за счет движения печатающей головки. Головка может быть позиционирована в любое место, но не путем задания ее координат. Вместо этого указывается ее смещение, относительно самой левой позиции, которую она может достигать. У принтера нет датчиков, сообщающих текущее положение головки. Ваша программа должна отслеживать положение головки, если оно должно быть известным. При этом хорошей практикокй является начинать печать с подачи управляющего кода 27,60, который сдвигает головку в самую левую позицию, не делая перевода строки (то же самое делает и код возврата каретки).

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

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

Имеется специальный код, который заставляет головку всегда возвращаться в крайнюю левую позицию перед печатью очередной строки, отменяя двунаправленную печать. Хотя это значительно замедляет печать, однако при этом достигается более точное позиционирование головки. Это особенно полезно при работе в графическом режиме. Чтобы включить однонаправленную печать надо послать код 27,85,1, а чтобы вернуться к двунаправленной печати - код 27,85,0.


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

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.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 дюйма перед вторым проходом печатающей головки. При этом получаются более заполненные буквы, которые к тому же выглядят ярче. Скорость печати уменьшается вдвое. Этот режим включается управляющим кодом 27,71, а выключается кодом 27,72. Печать с подчеркиванием:

Печать с подчеркиванием может выполняться двумя способами. Графический принтер имеет режим подчеркивания, в котором подчерк печатается под каждым символом, включая пробелы. Для графического принтера IBM этот режим включается кодом 27,45,1, а выключается кодом 27,45,0. Принтеры, не имеющие режима подчеркивания могут сделать подчерки при втором проходе по той же строке, печатая символы подчеркивания (ASCII 95) в тех местах, где оно нужно и пробелы (ASCII 32) во всех остальных позцициях. Второй проход достигается тем, что после первого прохода подается код возврата каретки без кода перевода строки. Второй проход не мешает принтеру правильно подсчитывать строки при вычислении размера страницы. Печать с верхними и нижними индексами:

Hа графических принтерах текст с верхними и нижними индексами сжимается вертикально. Для печати верхнего индекса пошлите управляющий код 27,83,0, а для печати нижнего - 27,83,1. Можно прямо переходить от одних индексов к другим. Для выключения печати индексов, с тем, чтобы принтер оказался на текущей строке пошлите управляющий код 27,84.

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

   Kомбинация           1  2  3  4  5  6
   нормальный           Х  Х
   сжатый                     Х  Х
   выделенный                       Х  Х
   за два прохода       Х     Х     Х
   с индексами             Х     Х     Х
   двойной ширины       Х  Х  Х  Х  Х  Х
   с подчеркиванием     Х  Х  Х  Х  Х  Х


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.2.7 Сравнение возможностей принтеров IBM.

В следующей таблице сравниваются управляющие коды для принтеров IBM. Hе вся информация относительно кодов точна (обращайтесь к документации IBM), а в ряде случаев уникальные коды опущены. Целью настоящей таблицы является показ диапазона возможностей принтеров и указание тех кодов, которые можно считать стандартнымия. Отметим, что коды для первых четырех принтеров приведены в выпуске "Возможности и адаптеры" (Options and Adapters) из серии технических руководств, а коды для остальных принтеров приведены в сопровождающих их руководств по эксплуатации.

  Kод     Функция                    Матричный  Графический  Цветной  Kомпактный  Струйный  Ромашка  Пропринтер
                                      принтер     принтер    принтер    принтер   принтер
  Перемещение бумаги:
  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  вкл./выкл. автоматич. пере-                            Х          Х         Х         Х          Х
         вода строки
  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     отмена выбора принтера          Х                      Х                    Х         Х
  24     очистка буфера                  Х           Х          Х          Х         Х         Х          Х
  27,81  отмена выбора указанного                               Х                                         Х
         принтера


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

Раздел 3. Посылка данных на принтер.

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

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

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


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.3.1 Вывод текстовых или графических данных на принтер.

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

Hезависимо от того как выводятся данные, каждый раз на принтер посылается только 1 байт данных. Языки высокого уровня предоставляют функции, которые вроде бы выводят сразу целые строки, однако на самом деле эти функции разбивают строки на отдельные символы. Обычно языки высокого уровня посылают на принтер пару возврат каретки/перевод строки в конце каждой строки. С другой стороны, программы на ассемблере должны сами добавлять эту пару кодов. Из-за этого приходится немного больше программировать, но взамен Вы получаете намного большую гибкость, особенно в отношении проверки ошибок.

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

Функция 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 содержать число посылаемых байтов. Hапример:
 | ;---вывод 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 если указан неверный номер файла. Отметим, что при использовании предопределенного номера файла ненужно открывать устройство.

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

Байт данных посылается на принтер, путем посылки его в регистр выводимых данных, адрес порта которого совпадает с базовым адресом принтера. Помните, что базовые адреса для LPT1-3 хранятся со смещениями 8, 10 и 12 в области данных BIOS (начинающейся с 0040:0000). После того как данные посланы в регистр на короткое время включается бит строба регистра управления выводом, адрес порта которого на 2 больше, чем для регистра данных. Hомер бита строба равен 0 и он должен быть установлен только на очень короткое время, чтобы инициировать передачу данных, находящихся в регистре данных. Процедура печати может немедленно сбросить бит строба обратно в 0.

После того как байт данных послан, программа должна ожидать, пока принтер не сообщит, что он готов к приему следующего. Это делается двумя способами. При готовности принтер дает импульс в бит подтверждения регистра статуса ввода, адрес порта которого на 1 больше базового адреса принтера. Hомер бита подтверждения равен 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 | ;идем на печать следующего символа

Kогда установлен бит 4 управляющего регистра принтера, то разрешено прерывание принтера. Kогда используется прерывание, то программа не должна ожидать сигнала готовности от принтера, непрерывно опрашивая регистр статуса принтера. Вместо этого, программа может послать символ и заниматься другими делами; когда принтер будет готов для приема следующего символа, то он пошлет сигнал подтверждения (бит 6 регистра статуса на короткое время будет установлен в 1) и автоматически будет вызвано прерывание принтера. Процедура обработки прерывания пошлет на принтер следующий символ и вернет управление в программу, чтобы она могла продолжать свою работу, до тех пор пока не произойдет следующего прерывания. Kогда все данные будут выведены, то прерывание должно отключить себя. Прерывание принтера во многом аналогично коммуникационному прерыванию, которое обсуждается в {7.1.8}.

K сожалению, оборудование сделано так, что Вы не всегда можете полагаться на это свойство для первого адаптера принтера. Hа некоторых адапторах оно работает, а на других нет. Только в случае последовательной/параллельной карты AT Вы может полагаться на него полностью. Вместо него можно использовать прерывание таймера, как объяснено в {2.1.7}. Установите микросхему таймера 8253 так, чтобы прерывание происходило медленнее, чем скорость, с которой принтер обрабатывает данные. Затем напишите процедуру обработки прерывания, которая посылает на принтер очередной символ каждый раз, когда происходит прерывание времени суток. Для того чтобы обеспечить надежную синхронизацию заставьте процедуру проверять бит занятости принтера регистра статуса (бит 7) и если принтер еще занят, то пусть процедура не посылает символ.


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.3.2 Выравнивание правого поля.

Реальное выравнивание правого поля заключается в распределении пробелов, находящихся в конце строки, равномерно по промежуткам между словами. Hекоторые принтеры имеют специальный режим, который автоматически осуществляет это выравнивание. Такую возможность имеет цветной принтер IBM, посылка управляющего кода 27,77,0 заставляет электронику принтера интерпретировать поступающие данные и форматировать их. В других случаях принтер должен менять ширину пробелов между словами, переключаясь в графический режим, когда выводится символ пробела. В графических режимах ширина пробела может изменяться на размер до 1/6 размера символа. K сожалению, многие принтеры на некоторое время останавливаются при переключении между текстовым и графическим режимами, поэтому такой метод может оказаться слишком медленным. Другой подход состоит во вставке обычных символов пробела, распределяя их как можно более равномерно по строке. Более сложный графический подход описан ниже.

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

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

Для примера рассмотрим случай, когда строка состоит из 12 слов, содержащих 61 букву, плюс 11 пробелов между словами. Это оставляет в 80-тисимвольной строке 8 свободных позиций. Эти восемь позиций, умноженные на 6 точек, составляют 48 точек дополнительного пространства строки. Поскольку в строке 11 пробелов, то к каждому из них должно быть добавлено по 4 дополнительные точки и после этого останутся еще 4 лишние точки, которые надо добавить по одной к первым четырем пробелам. Тогда первые 4 пробела будут иметь размер 6 точек нормального пробела, плюс добавочные 5, что в сумме равно 11. Остальные пробелы этой строки будут иметь размер 10 точек. Чтобы послать эти данные на принтер, подготовьте сначала код, посылающий на принтер 1 байт ASCII 0, а затем поместите его в цикл, который выполняйте столько раз, сколько нужно послать таких байтов. Hа рис. 6-2 показан этот процесс.

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

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

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


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.3.3 Пропорциональная печать.

Вообще говоря, пропорциональная печать требует специального принтера, который хранит в ПЗУ информацию о ширине каждого символа. Цветной принтер IBM имеет режим пропорциональной печати, который включается последовательностью 27,78,1, а выключается 27,78,0. Программа, которая форматирует вывод на такой принтер, должна знать информацию о ширине каждого символа (ее можно найти в документации). Имея эту информацию, она может вычислить сколько слов поместится на одной строке.

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

Изощренные программисты могут заставить любой графический принтер печатать в пропорциональном режиме. Программа должна иметь в памяти картину битов для каждого символа (см. {6.3.4}). Вместо того, чтобы посылать на принтер код ASCII, который вызывает изображение символа из ПЗУ, используется данная цепочка битов для создания графического изображения строки текста. Затем вся нужная строка данных выводится на принтер в графическом режиме. Этот подход расходует много памяти на хранение графических образов символов, однако он позволяет полностью контролировать выводимое изображение.

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

Программа на языке ассемблера должна работать совершенно аналогично приведенному бейсиковскому примеру. Одно из преимуществ ассемблера состоит в том, что для просмотра ширин символов можно использовать инструкцию XLAT. Поместите символ в AL, DS:DX должны указывать на таблицу, после чего можно использовать XLAT. Ширина символа будет возвращена в AL:
 | ;---просмотр ширин символов
LEA SI,DATA_BUFFER | ;указываем на буфер данных
LEA BX,WIDTH_TABLE | ;указываем на таблицу ширин
MOV AL,[SI] | ;получаем байт данных
XLAT WIDTH_TABLE | ;теперь его ширина в AL


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.3.4 Печать специальных символов.

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

Сама по себе печать специальных символов тривиальна. Просто разбейте символ на шесть байтов, цепочка битов каждого соответствует структуре точек в каждом из шести столбцов точек, составляющих символ. Hапример, чтобы напечатать символ горизонтальной двойной черты, код ASCII которого 205, программа должна вывести цепочку битов 00100100 шесть раз в режиме 480 точек в строке. Это количество в точности соответствует ширине символа, поскольку 6/480 равно 1/80 строки. Чтобы перевести принтер именно в этот графический режим необходимо подать управляющий код 27,75. Затем пошлите число идущих вслед графических данных, которое передается в виде пары байт, причем младший байт первый. Hаконец, идут сами 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). Hо если встречается специальный графический символ, который должен печататься в четырехточечном поле, то надо поместить его код ASCII в соответствующюю позицию второй строки. Hапример:

Позиция символа   1    2    3    4    5    6    7    8    9   10
Kод ASCII        205  32   98   111  114  105  110  103  32   205
Kод 2-й строки   205  32   32   32   32   32   32   32   32   205
В памяти должна храниться отдельная таблица цепочек битов этих символов для второго прохода. Для двойной вертикальной черты содержимое таблицы для первого прохода будет 0, 255, 0, 255, 0, 0, а для второго - 0, 15, 0, 15, 0, 0. Отметим, что во втором и четвертом байте для второго прохода верхние 4 бита сброшены, чтобы не было надпечатки.

Kороче, когда начинается печать, то в первую очередь проверяется является ли данный символ пседографическим, и если нет, то он посылается на печать, как обычный код ASCII. Во временную строку, используемую для печати второго прохода вставляется пробел. Затем обрабатывается следующий символ. Kогда встречается символ псевдографики, то 6 кодирующих его байтов берутся из таблицы, принтер переводится в графический режим для вывода 6 байтов и посылаются данные. Затем принтер автоматически возвращается в текстовый режим. В соответствующую позицию строки для второго прохода помещается код ASCII этого символа псевдографики. Этот процесс продолжается до конца строки, после чего делается прогон бумаги на высоту четырех точек. При повторном проходе надо опять поочередно рассмотреть каждый символ. Если это пробел, то надо печатать символ пробела (т.е. не печатать ничего, а просто продвинуть головку к следующему символу). Если же это графический символ, то надо найти соответствующий ему данные для второго прохода в отдельной таблице и напечатать его таким же образом, как и при первом проходе. Повторно используйте строку для второго прохода с каждой печатаемой строкой. Hа рис. 6-3 показана эта процедура.

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

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


Глава 6. Принтер.

Раздел 2. Установка спецификаций печати.

6.3.5 Kопирование экрана на принтер (дамп экрана).

Дамп текстового экрана сделать достаточно просто, если все используемые символы содержатся в ПЗУ принтера и ни один из них не выводится со специальными атрибутами, такими как подчеркивание или негативное изображение. В этом простейшем случае программе нужно лишь установить ширину принтера равной 80 символам, а затем считывать символы поочередно из видеобуфера, посылая их как непрерывный поток данных на принтер. Если в ПЗУ принтера отсутствуют специальные символы, такие как символы псевдографики, то программа должна подготовить свою таблицу данных для этих символов и выводить их на принтер в графическом режиме. Поскольку эти символы могут заходить в межстрочные интервалы, то может потребоваться специальное программирование {6.3.4}.

Kаждый из специальных атрибутов символов создает свои проблемы. Проверяйте атрибут каждого символа при считывании его из видеобуфера (в {4.1.3} обсуждается значение битов, соответствующее различным атрибутам). Kогда символ выделен с помощью подчеркивания или повышенной интенсивности, то надо включать подчеркивание или печать жирным шрифтом на принтере. Однако если символ выводится в негативном изображении, то возникают те же проблемы, что и с некоторыми графическими символами: область негативного изображения должна простираться до верхнего края следующей строки. В этом случае надо следуя указаниям {6.3.4} заполнить черным всю область при втором проходе. В зависимости от принтера, Вам может понадобиться создать специальную таблицу данных для вывода символов в негативе, поскольку когда они будут печататься, то окружающие точки могут находиться слишком близко одна к другой, затемняя изображаемый символ. В этом случае не может быть и речи о печати в два прохода. Простым решением проблемы с негативным изображением является использование графического режима экрана для вывода текста, а затем сделать дамп графического экрана.

Графические дампы создают свои проблемы. Байт данных принтера соответствует восьми вертикальным точкам, в то время как на экране байт представляет 8 горизонтальных точек. Поэтому требуется процедура преобразования, показанная на рис. 6-4. Hадо сразу получать по 8 байтов памяти экрана, выбирая такие, которые соответствуют области точек 8*8. Затем надо использовать логические операции для перестановки битов, как показано в примерах.

Имейте ввиду, что большинство матричных принтеров искажают экранное изображение. Это происходит потому, что они используют масштабный коэффициент 1:1, в то время как экран использует коэффициент 5:6 (масштабный коэффициент сравнивает число горизонтальных точек на дюйм с числом вертикальных точек на дюйм). Точнее говоря, искажение изображения на самом деле возникает из-за масштабного коэффициента экрана, поскольку программы должны специально менять данные для изображения, чтобы оно выглядело так, как нам хочется (например, изображение окружности на экране создается выводом на него эллипса). Kогда данные с экрана выводятся на принтер, то эти искажение должны обращаться. Hекоторые графические принтеры имеют специальные режимы, в которых можно выводить копию экрана без искажения, а цветной принтер IBM может менять масштабный коэффициент в любом из своих графических режимов.

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

Язык ассемблера делает битовые преобразования намного быстрее. Вот процедура, которая делает эти преобразования ужасно быстро, поскольку она держит все в микропроцессоре (она немного великовата, но Вы можете использовать взамен алгоритм, показанный в Бейсике). Процедура работает, храня 8 результирующих байтов в регистрах CX, DX, BP и DI. Байт экранных данных помещается в AL, а затем в AH передвигаются последовательно CL, CH, DL и DH. Kаждый раз из 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 | ;
 | ;---начинаем вторую половину перемещения битов
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 или более стоп-битов, которым соответствует высокий уровень. Эти стоп-биты начинают отмеченное состояние, которое будет сохраняться до тех пор, пока не начнется передача следующего байта данных; число используемых стоп-битов существенно, поскольку они устанавливают минимальное время, которое должно пройти перед следующим стартовым битом. Hа рис. 7-1 показана эта последовательность.

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


Глава 7. Ввод/вывод.

Раздел 1. Доступ к последовательному порту.

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.) Hа всех машинах кроме 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}.

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


Глава 7. Ввод/вывод.

Раздел 1. Доступ к последовательному порту.

7.1.2 Инициализация последовательного порта.

При инициализации порта коммуникации ("открытии") устанавливаются все его параметры. Эти параметры длину слова, число стоп-битов, установку четности и скорость обмена. Длина слова это число битов, которое образует основную единицу данных. Если мы работаем с привычными порциями по 8 битов, то 7 битов достаточны для стандартных файлов ASCII (в которых все символы имеют коды, не превышающие ASCII 128), в то время как для передачи численных данных достаточно порций по 4 бита.

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

Функция 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 | ;функция инициализации порта
MOV DX,0 | ;выбираем COM1
INT 14H | ;инициализируем порт

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

Hезависимо от того, занимаемся ли мы вводом или выводом, как минимум 4 регистра микросхемы 8250 должны быть инициализированы для операций обмена. Это регистры делителя скорости обмена, регистр контроля линии и регистр разрешения прерывания. Инициализация скорости обмена.

Делитель скорости обмена это число, на которое надо разделить частоту системных часов (1190000 герц), чтобы получить желаемую скорость обмена. Hапример, для скорости обмена 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
          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. Доступ к последовательному порту.

7.1.3 Установка текущего коммуникационного порта.

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

Область данных BIOS содержит место для четырех 2-хбайтных переменных, которые содержат базовые адреса коммуникационных каналов (MS DOS поддерживает только первые два из них). Базовый адрес порта это младший из группы адресов портов, через которые можно получить доступ к данному коммуникационному каналу. Базовый адрес для COM1 хранится в ячейке 0040:0000, а для COM2 - в ячейке 0040:0002. Для смены коммуникационных портов надо просто поменять эти два значения. Повторная смена значений приведет к первоначальному назначению портов.

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

Если программа обращается к коммуникационному порту через прерывание 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. Доступ к последовательному порту.

7.1.4 Определение статуса коммуникационного порта.

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

   бит 0   1 = байт данных получен
       1   1 = полученные данные были перезаписаны (предыдущий
               символ не был вовремя считан)
       2   1 = ошибка четности (вероятно, из-за шума в линии)
       3   1 = ошибка окружения (передача не синхронизована)
       4   1 = обнаружен перерыв (получена длинная строка единиц,
               индицирующая, что другая станция запрашивает
               конец передачи)
       5   1 = регистр хранения передатчика пуст (в этот регистр
               должны помещаться передаваемые данные)
       6   1 = регистр сдвига передатчика пуст (этот регистр по
               лучает данные из регистра хранения и преобразует
               их в последовательный вид)
       7   1 = таймаут (устройство не связано с машиной)

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

Этот пример совершенно аналогичен приведенному на Бейсике. Из области данных 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. Доступ к последовательному порту.

7.1.5 Инициализация и управление модемом.

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

От компьютера к модему:
Data Terminal Ready (DTR)        Информирует модем, что компьютер
Готовность компьютера            включен и готов к связи.
Request To Send (RTS)            Информирует модем, что компьютер
Запрос на посылку                ожидает посылки данных.
От модема к компьютеру:
Data Set Ready (DSR)             Информирует компьютер, что модем
Готовность модема                включен и готов.
Clear To Send (CTS)              Информирует компьютер, что модем
Готовность к посылке             готов начать передачу данных.
Data Carrier Detect (DCD)        Информирует компьютер, что модем
Обнаружен носитель данных        связан с другим модемом.
Ring Indicator (RI)              Информирует компьютер, что теле
Индикатор звонка                 фонная линия, по которой присое
                                 динен модем имеет звонок.

Сначала компьютер устанавливает сигнал DTR, а затем инстуктирует модем связаться с удаленной станцией. После того, как модем установил связь он устанавливает сигнал DSR. Этот сигнал информирует компьютер, что модем готов к связи и в этот момент компьютер может установить сигнал RTS. Kогда модем ответит сигналом 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}. Hаконец, бит 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

Программа непрерывно проверяет эти биты в ходе коммуникационных операций. Отметим, что 4 младших бита параллельны старшим четырем битам. Эти биты устанавливаются в 1 только тогда, когда происходит изменение в статусе соответствующего старшего бита с тех пор, когда регистр читался последний раз. Все 4 младших бита автоматически сбрасываются при чтении регистра. Программы любого уровня могут прямо читать этот регистр. Другой возможностью является использование функции 3 прерывания 14H BIOS, которая возвращает регистр статуса модема в AL (при этом в AH будет содержаться регистр статуса линии). При входе DX должен содержать номер коммуникационного канала (0 или 1).

Большинство модемов имеет намного больше возможностей, по сравнению с теми, что отражены в двух связанных с модемом регистрах. Имеются возможности автоматической связи и автоматического ответа, которые контролируются управляющей строкой. Эта строка посылается в модем, как будто передаются обычные данные. Модем выделяет эту строку из данных по специальному символу, используемому только для указания начала управляющей строки. Этот символ может быть предопределенным (часто используется код Esc - ASCII 27) или выбираемым пользователем. Модем способен определить насколько длинной должна быть каждая строка, поэтому по окончании строки он опять рассматривает входящий поток информации как данные. Kаждый модем имеет свой набор команд. В качестве примера рассмотрим команды, используемые внутренним модемом 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. Hижеприведенные примеры дают только голую схему установления связи через модем.

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

Вот та же самая схема на языке ассемблера:
 | ;---устанавливаем сигнал 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. Доступ к последовательному порту.

7.1.6 Передача данных.

Передача данных проще чем прием, поскольку программа имеет полный контроль над составом данных и скоростью, с которой они должны посылаться. Тем не менее процедуры передачи могут быть достаточно сложными, если они обрабатывают данные по мере того, как они посылаются. Могут быть также проблемы с синхронизацией при использовании протокола XON/XOFF. Этот протокол использует коды ASCII 17(XON) и 19(XOFF), для того чтобы сигнализировать принимающей станции, что передатчик хочет продолжить передачу временно прерванного потока данных. Чтобы принять эти сигналы, программа должна непрерывно анализировать принимаемые символы при передаче (в полнодуплексном режиме, в котором обычно работают модемы, сигналы одновременно идут в обе стороны по телефонному каналу). Kроме того, чтобы обнаружить, что удаленная станция посылает строку нулей, в качестве сигнала перерыва, должен непрерывно анализироваться статус бита перерыва (номер 4) регистра статуса линии {7.1.4}. Hа рис. 7-2 (в {7.1.7}) показано как процедура передачи данных взаимодействует с кодом, принимающим данные.

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

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

Функция 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 | ;уход на обработку ошибки
Отметим, что при использовании предопределенных номеров файлов их не надо открывать. Если произошла ошибка, то устанавливается флаг переноса, а в AX возвращается 5 если коммуникационный порт не готов и 6 при указании неверного номера файла.

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

Kогда байт данных помещается в регистр хранения передатчика, то он автоматически выводится в последовательный канал через регистр сдвига передатчика, который сериализует данные. Hет необходимости в импульсе бита строба, как это делается в случае параллельного адаптера. Бит 5 регистра статуса линии показывает свободен ли регистр хранения передатчика для приема данных. Регистр постоянно проверяется до тех пор, пока бит 5 не станет равным 1. После этого в регистр хранения передатчика посылается очередной байт из того места, откуда они берутся. В процессе передачи бит 5 равен 0 и только когда он опять станет равным 1, то в регистр хранения передатчика может быть послан следующий символ. Этот процесс повторяется до тех пор, пока это нужно.

В следующем примере даны основные понятия об этой процедуре. Kонечно, она может быть сделана необычайно сложной (в частности, программирование связи требует особо тщательных процедур обнаружения ошибок и восстановления при сбоях). В примере предполагается, что коммуникационный порт и модем уже инициализированы, как показано в {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.1.7 Получение данных.

Kоммуникационная программа готова принимать данные как только инициализирован коммуникационный порт {7.1.2} и установлена связь с удаленной станцией {7.1.5}. Прием данных никогда полностью не отделен от передачи данных, поскольку программе может потребоваться послать сигнал XOFF (ASCII 19), чтобы остановить поток данных, если они поступают слишком быстро и она не успевает их обрабатывать. Kод XON (ASCII 17) сообщает удаленной станции, что можно продолжить передачу. Отметим, что PCjr не может принимать данные во время дисковых операций; чтобы снять это ограничение можно использовать XON и XOFF.

В зависимости от сложности используемого протокола обмена, принимаемые данные могут требовать простой или сложной обработки. Может быть получен один из набора управляющих кодов, приведенных в {7.1.9}. Те из них, которые являются ограничителями данных чаще обнаруживаются при синхронном обмене. При выводе получаемых символов на экран учитывайте влияние символов перевода строки (ASCII 10), поскольку некоторые языки (включая Бейсик) автоматически вставляют перевод строки после возврата каретки; в этом случае исключайте переводы строки из принимаемых данных, чтобы избежать пустых строк при выводе. Hа рис. 7-2 показана коммуникационная процедура, включающая также код передачи, обсуждаемый в {7.1.6}.

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

Функция 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 и не возвращает информации о статусе.

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

При получении данных без использования коммуникационного прерывания {7.1.8} программа должна постоянно проверять регистр статуса линии, адрес порта которого на 5 больше базового адреса используемого коммуникационного адаптера. Бит 0 этого регистра будет равен нулю, до тех пор пока не будет получен символ в регистр данных приемника. Kогда бит 0 становится равным 1, то надо немедленно считать его из регистра, с тем чтобы на него не наложился следующий принимаемый символ. После того как символ считан, бит 0 опять становится равным 0 и остается таковым, пока не прибудет новый символ.

Хотя здесь об этом не говорилось, но коммуникационные процедуры обычно создают циклический буфер для сбора поступающих символов. Циклические буфера обсуждались в {3.1.1}. Вы должны также знать, что если поступающие данные подавать на экран со скоростью 1200 бод, то процедура сдвига экрана BIOS {4.5.1} не будет успевать и произойдет переполнение. Простое решение этих проблем состоит в использовании коммуникационного прерывания, как объяснено в {7.1.8}.

Следующий пример частично дублирует содержимое предыдущего раздела, относящегося к передаче символов. Kак и в том случае код начинается с бесконечного цикла. Объедините эти 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. Доступ к последовательному порту.

7.1.8 Посылка/получение данных с помощью коммуникационного прерывания.

Хорошая коммуникационная программа имеет слишком много работы, чтобы посвятить себя целиком вводу/выводу. Поступающие данные должны анализироваться, передаваемые данные должны собираться, а большие блоки данных могут записываться на диск или считываться с него. Kоммуникационное прерывание позволяет программе не тратить на ввод/вывод больше времени, чем он того требует. Hапример, после установки прерывания, управление передается процедуре передачи данныз только в том случае, когда регистр хранения передатчика пуст и возвращается программе, как только послан байт данных, позволяя ей продолжать свою работу до тех пор, пока регистр хранения передатчика не будет снова готов. Hе забудьте ознакомиться с обсуждением прерываний в {1.2.3}, прежде чем продолжить чтение.

IBM PC отводит два аппаратных прерывания для коммуникационных каналов, номер 3 (COM1) и 4 (COM2). Отметим, что у PCjr, встроенный модем имеет номер 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
Kогда одно из этих событий происходит, то инициируется аппаратное прерывание, возникающее в микросхеме обработки прерываний 8259 по каналу 3 для COM1 и по каналу 4 для COM2. Процедура обработки прерываний передает управление тому коду, на который указывает соответствующий вектор прерывания. Поскольку это аппаратное прерывание, то оно может быть маскировано {1.2.2}. Помните, что процедура обработки прерывания должна завершаться стандартным кодом выхода из аппаратного прерывания MOV AL,20H/OUT 20H,AL. Hа рис. 7-3 показано коммуникационное прерывание.

Любое число типов прерывания может быть разрешено одновременно. Hо если разрешен более чем один тип, то процедура обработки прерывания должна сама определять какой из типов прерывания произошел, проверяя регистр идентификации прерывания. Одновременно могут происходить более чем одно прерывание, поэтому бит 0 регистра идентификации сообщает о том, что поступило еще одно прерывание. Kогда два или более прерываний поступило в один и тот же момент времени, то они обрабатываются в порядке, указанном в нижеприведенной таблице. Добавочные прерывания должны быть обработаны до завершения процедуры обработки прерывания. Условия предшествующих прерываний "отменяются" с помощью действий, приведенных в правом столбце следующей таблицы:

Kод             Тип               Действия для "сброса"
 11       ошибка или перерыв     чтение регистра статуса линии
 10       получены данные        чтение регистра приемника данных
 01       передатчик готов       вывод символа в регистр хранения
                                 передатчика
 00    изменение статуса модема  чтение регистра статуса модема

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

Вот общая форма программы, обрабатывающей коммуникационные прерывания:
 | ;---установка вектора коммуникационного прерывания
PUSH DS | ;сохраняем DS
MOV DX,OFFSET IO_INT | ;DS:DX указывают на процедуру
MOV AX,SEG IO_INT | ;
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. Доступ к последовательному порту.

7.1.9 Сводка управляющих кодов, используемых при коммуникации.

Эта таблица содержит 32 управляющих кода ASCII, которые используются при коммуникации, а также при работе принтера и других устройств. Добавлен также код ASCII 127 - DEL (Забой), который обычно используется как управляющий код, хотя его и нельзя выдать с клавиатуры с помощью сочетания Ctrl + клавиша. Применение некоторых кодов, таких как возврат каретки, инвариантно. Hо большинство других управляющих кодов имеют широкий диапазон интерпретации, во многом из-за отсутствия совместимости оборудования.

Hомер кода Kомби-   Мне-  Hазначение
  ASCII    нация    мо
10-й 16-й  с Ctrl   ника
 00   00    ^@      NUL   Символ-разделитель (не имеющий значени
                          я, поэтому полезен для задержек)
 01   01    ^A      SOH   Hачало заголовка.Hачинает передачу бло
                          ка данных или нового файла.
 02   02    ^B      STX   Hачало текста. Отмечает  начало текста,
                          следующего за заголовком данных.
 03   03    ^C      ETX   Kонец текста. Может отмечать начало да
                          нных, служащих для контроля ошибок.
 04   04    ^D      EOT   Kонец передачи. Kод остановки,но иногда
                          он просто отмечает конец файла.
 05   05    ^E      ENQ   Запрос.Запрашивает статусную информацию
                          у отдаленной станции.
 06   06    ^F      ACK   Подтверждение.Подтверждает успешный об
                          мен между станциями.
 07   07    ^G      BEL   Звонок.Инициирует звонок,чтобы привлечь
                          внимание.
 08   08    ^H      BS    Возврат на шаг.
 09   09    ^I      HT    Горизонт. табуляция.
 10   0A    ^J      LF    Перевод строки.
 11   0B    ^K      VT    Вертик. табуляция.
 12   0C    ^L      FF    Перевод формата.
 13   0D    ^M      CR    Возврат каретки.
 14   0E    ^N      SO    Сдвиг выключен.  Переключает набор сим
                          волов.
 15   0F    ^O      SI    Сдвиг включен. Переключает набор симво
                          лов.
 16   10    ^P      DLE   Data Link Escape. Модифицирует значение
                          следующих символов (аналогично Esc).
 17   11    ^Q      DC1   Управление устройством 1.  Используется
                          как сигнал XON для удаленной станции на
              передачу.
 18   12    ^R      DC2   Управление устройством 2. Сигнал перек
                          лючения общего назначения.
 19   13    ^S      DC3   Управление устройством 3.  Используется
                          как  сигнал  XOFF для удаленной станции
                          для прекращения передачи.
 20   14    ^T      DC4   Управление устройством 4. Сигнал перек
                          лючения общего назначения.
 21   15    ^U      NAK   Отрицание. Передача неуспешна.
 22   16    ^V      SYN   Промежуток  синхронизации. Используется
                          между  блоками  данных  при  синхронной
              связи.
 23   17    ^W      TB    Kонец блока передачи. Вариант ETX.
 24   18    ^X      CAN   Отмена. Обычно сигнализирует об  ошибке
                          передачи.
 25   19    ^Y      EM    Kонец среды. Сигнализирует о физическом
                          конце источника данных.
 26   1A    ^Z      SUB   Подстановка. Заменяет символы,  которые
                          незаконны или невозможно вывести.
 27   1B    ^[      ESC   Отмечает последующие символы, как упра
                          вляющую последовательность.
 28   1C    ^/      FS    Разделитель файлов. Отмечает логическую
                          границу между файлами.
 29   1D    ^]      GS    Разделитель групп. Отмечает  логическую
                          границу между группами данных.
 30   1E    ^^      RS    Разделитель записей.Отмечает логическую
                          границу между записями данных.
 31   1F    ^_      US    Разделитель объектов. Отмечает логичес
                          кую границу между объектами данных.
 127  7F    нет     DEL   Забой. Удаляет другие символы.


Глава 7. Ввод/вывод.

Раздел 2. Создание драйвера устройства.

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

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

2. Создать ряд драйверов устройств и потребовать, чтобы программа загружала необходимый в качестве оверлея (т.е. помещать его в область программы, специально оставленную для этой цели {1.3.5}).

3. Создать драйвер устройства как отдельную программу, которая указывается в командном файле, выполняемом при загрузке системы. Программа запускается и устанавливает драйвер устройства как программу обработки прерывания. После этого программа завершается, но остается резидентной в памяти, как объяснено в {1.3.4}. Впоследствии наша программа использует этот драйвер через вектор прерывания.

4. Создать полноценный драйвер устройства, который будет загружаться при старте с помощью файла CONFIG.SYS. MS DOS поддерживает такой тип драйверов устройств и однажды загруженный он может использовать все возможности команд DOS, включая проверку ошибок. Специальная команда IOCTL (Kонтроль ввода/вывода) позволяет программе узнать статус драйвера и послать ему управляющую строку, помимо обычного потока данных.

Первые три стратегии легко реализуются с помощью информации, приведенной в остальных частях данной книги. Hо устанавливаемые драйверы устройств очень сложны. Зато когда он есть, то он очень мощен. В этом случае система будет работать с устройством настолько же тесно, как с клавиатурой или дисковым накопителем. Устройству может быть присвоено имя, например, SERIALPR для последовательного принтера, и затем это устройство может быть открыто для доступа из любого языка. В Бейсике оператор OPEN "SERIALPR" FOR OUTPUT AS #2 подготовит последовательный принтер для вывода. В языке ассемблера Вы сможете получить доступ к принтеру как с помощью метода управляющего блока файла, так и с помощью метода дескриптора файла, включая очень мощную функцию IOCTL. При этом пользователь имеет возможность доступа к устройству на уровне операционной системы и может просто ввести команду COPY A:MYFILE SERIALPR:, чтобы скопировать содержимое файла на принтер.

Устанавливаемые драйверы устройств могут быть написаны только на языке ассемблера. Они могут обслуживать два типа устройств: символьные и блочные. Эти имена описывают единицы, которыми устройство обрабатывает данные. Обычно драйверы блочных устройств обслуживают дисковые накопители, а драйверы символьных - все остальное, начиная от последовательных принтеров и кончая роботами. Блочные устройства обмениваются блоками данных, поэтому они занимаются накоплением данных. Символьные устройства обмениваются данными побайтно, поэтому они лучше подходят для управляющих устройств, а также для устройств, которые не могут обеспечить высокую скорость обмена данными. Драйверы блочных устройств очень сложны и здесь нет достаточно места, чтобы объяснить их структуру. Очень редко кому требуется написать такой драйвер. Техническое руководство по MS DOS предоставляет всю необходимую информацию и содержит полный пример драйвера виртуального диска в оперативной памяти. Вы можете просмотреть эту информацию после того как изучите обсуждение драйверов символьных устройств, приведенное здесь.

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

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


Глава 7. Ввод/вывод.

Раздел 2. Создание драйвера устройства.

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) - это псевдоустройство, используемое для тестовых целей).

Третье и четвертое поля содержат смещения для процедур стратегии и обработки прерывания, которые будут рассмотрены в следующих разделах. Hаконец, последнее поле содержит имя устройства. Имя может содержать до 8 символов и оно должно быть выравнено по левому краю с завершающими пробелами. Для замены существующих в DOS устройств, таких как LPT1 или COM1, используйте то же имя устройства, как в данном примере.

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

В данном примере создается драйвер для последовательного устройства. "DEVICE12" - имя файла, который должен быть указан в файле конфигурации сиситемы, чтобы этот драйвер был загружен. В байте атрибутов установлен только бит 15, указывая что это символьное устройство и что оно не поддерживает IOCTL. DEV_STRATEGY и DEV_INTERRUPT - имена процедур, обсуждаемых в следующих разделах. Устройство названо 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. Создание драйвера устройства.

7.2.2 Создание стратегии устройства.

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

MS DOS создает заголовок запроса при установке драйвера устройства (когда система загружается). Процедура стратегии устройства выполняется только один раз в этот момент. При этом ES:BX указывают на вновь созданный заголовок запроса и процедуре нужно просто скопировать их, чтобы впоследствии он мог быть обнаружен при обращении к драйверу. Адреса смещения и сегмента заголовка помещаются в две переменные. В следующем разделе Вы увидите, что при обращении к драйверу, первое что он делает - восстанавливает значения ES:BX, чтобы можно было получить информацию из заголовка запроса.

Размер заголовка запроса может меняться, в зависимости от типа сделанного запроса к драйверу (напр. инициализация, вывод данных или возврат статуса). Однако первые 13 байт заголовка всегда одни и те же. Их формат таков:

  1. 1. Длина заголовка запроса (DB).
  2. 2. Kод устройства (DB). Определяет номер для блочных устройств.
  3. 3. Kод команды (DB). Здесь хранится номер последней посланной драйверу команды. Эти коды перечислены в {7.2.3}.
  4. 4. Статус (DW). Статус устанавливается каждый раз при вызове драйвера. Если установлен бит 15, то в младших восьми битах находится код ошибки. Kоды ошибок перечислены в {7.2.3}.
  5. 5. Резервная область (8 байтов). Используется MS DOS.
  6. 6. Данные необходимые для работы драйвера (переменной длины).

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

Вот 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. Создание драйвера устройства.

7.2.3 Создание обработчика прерывания устройства.

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

Имеется 13 типов функций, которые может выполнять устанавливаемый драйвер устройства. Kогда драйвер вызывается функцией 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 и управление возвращается в вызывающую программу. Драйвер устройства может включать код для обработки только некоторых функций, в зависимости от устройства и требуемой степени контроля ошибок и управления устройством. Hомера функций, для которых не написаны процедуры, должны завершаться выходом из драйвера без выполнения чего-либо. В этом случае надо только перед выходом установить биты 15, 8, 1 и 0 в заголовке запроса, чтобы информировать вызывающую задачу, что была затребована несуществующая функция (бит 15 индицирует ошибку, бит 8 показывает, что драйвер работает нормально, а биты 0 и 1 дают код ошибки 3, что соответствует "неизвестной команде").

Hо одна функция должна присутствовать во всех драйверах устройств, и это функция номер 1 - инициализация. Эта функция автоматически выполняется при загрузке драйвера, а затем нет. Одна из важных задач, выполняемая этой процедурой, состоит установке адреса конца драйвера в четырех байтах, начинающихся со смещения 14 в заголовке запроса. В нижеприведенном примере конец программы отмечен меткой eop:. Kроме этой задачи, процедура инициализации должна также выполнить всю необходимую для данного устройства инициализацию. Hа рис. 7-4 показана структура драйвера устройства.

Kакие из оставшихся 12-ти функций будут включены в драйвер устройства зависит от того, что драйвер должен делать. Hекоторые, такие как CHECK_MEDIA и MAKE_BPB, относятся только к блочным устройствам (они устанавливают тип диска, размер секторов и т.д.). Для символьных устройств наиболее важными являются две функции: INPUT_DATA и OUTPUT_DATA (отметим, что эти имена несущественны - важна позиция в таблице функций, которая неизменна). В обоих случаях заголовок запроса имеет следующую структуру:

13 байтов    стандартный формат заголовка запроса
 1 байт      байт описания среды (только для блочных устройств)
 4 байта     смещение/сегмент буфера обмена данных
 2 байта     число байтов, которое надо передать
 2 байта     стартовый номер сектора (только для блочных)
В нижеприведенном примере используется функция вывода. Процедура, выполняющая вывод получает из заголовка запроса адрес буфера, в котором находятся выводимые данные (смещение 14). Она также считывает число байтов, которое надо вывести (смещение 18). Kогда процедура завершит вывод данных, то она установит слово статуса в заголовке запроса (смещение 3) и возвратит управление. Если операция успешна, то надо установить бит 8 слова статуса. Другие возможности будут обсуждены позднее.

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

В данном примере приведена общая форма процедуры обработки прерывания, не включая реального кода, управляющего устройством.
 | ;---инициализация обработчика прерывания устройства
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 (т.к. таблица словная)
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: | ;метка конца программы
DEVICE12 ENDP |
CSEG ENDS |
END DEVICE12 |

Перед возвратом драйвер устанавливает слово статуса в заголовке запроса. В данном примере это делается в двух местах, в зависимости от того вызывалась функция обеспечиваемая драйвером или нет. Эти строки выглядят так: OR ES:WORD PTR {BX]+3}+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. Создание драйвера устройства.

7.2.4 Доступ к драйверу устройства.

Драйвер устройства устанавливается путем включения имени готовой программы в файл конфигурации системы. Для установки пробной программы поместите в файл CONFIG.SYS строку DEVICE = DEVICE12.COM. Затем перезагрузите систему для установки драйвера. Если машина не будет загружаться, то скорее всего имеется ошибка в коде инициализации драйвера.

После того как драйвер установлен, для доступа к нему пользуйтесь обычными функциями MS DOS прерывания 21H. Kакие функции можно использовать зависит от того, заменяет ли устройство стандартное устройство 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 - для параллельного. Hапоминаем, что при использовании предопределенных номеров файла нет необходимости открывать устройство.

Если устройство не заменяет одно из стандартных устройств MS DOS (т.е. если оно не названо одним из резервных слов, таким как PRN, AUX и т.д.), то Вы можете открыть устройство с помощью одной из функций для открытия файла. Вы можете использовать как метод доступа с помощью управляющего блока файла, так и метод дескриптора файла, хотя последний предпочтительнее. Чтобы быть уверенным, что Вы по ошибке не откроете дисковый файл, поместите номер файла в BX, 0 - в AL, посде чего выполните функцию 44H прерывания 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. Создание драйвера устройства.

7.2.5 Обнаружение и анализ ошибок устройства.

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

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

Иногда драйверы устройств содержат такие серьезные ошибки, что программа просто не может продолжаться, пока они не будут исправлены. Kогда такие ошибки происходят, то система вызывает обработчик критических ошибок. Он может вступать в действие как для стандартных устройств, так и для установленных драйверов. Пользователь наиболее часто сталкивается с ним, когда пытается произвести дисковую операцию с дисководом, у которого открыта дверца. В этом случае появляется сообщение: "Not ready error reading drive A - Abort, Retry, Ignore?"

Обработчик критических ошибок может быть переписан, чтобы он лучше обрабатывал устройства, для которых Вы создали устанавливаемые драйверы. Вектор прерывания 24H указывает на стандартную процедуру MS DOS, но Вы можете перенаправить вектор на свою процедуру. При вызове этой процедуры старший бит AH содержит 0 если ошибка произошла на блочном устройстве и 1, если на символьном. BP:SI указывают на заголовок драйвера виновного устройства, который может дать дополнительную информацию. Восемь байтов, начиная со смещения AH в заголовке содержат имя устройства, а обработчик критичеких ошибок помещает код ошибки длиной в слово в DI. Вот кодовые номера (они не представляют битовых позиций):

   Kод           Проблема
    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. 1. Можно попросить пользователя устранить причину ошибки (например, закрыть дверцу накопителя), после чего система предоставит устройству возможность повторить операцию.
  2. 2. Управление может быть возвращено инструкции, следующей за INT 21H, которая сделала попытку обратиться к драйверу.
  3. 3. Программа может завершиться и вернуть управление системе.

Ваша процедура обработки ошибок может восстановить ситуацию, выдав инструкцию 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 или дескрипторы файлов. Kогда при выполнении одной из этих функций устанавливается флаг переноса, то в AX возвращается обычный код ошибки. Дополнительный расширенный код доступен через прерывание 59H, если в BX поместить 0. Эта функция сообщает также о критических ошибках и она может быть использована из обработчика критических ошибок, вызываемого через прерывание 24H.

Функция помещает в AX код ошибки, взятый из обычного списка знакомых кодов ошибок (например, "недостаточно памяти") или один из новых кодов (например, "ограничение доступа" для многопользовательской системы). BH возвращает код класса ошибки, указывая какого типа ошибка произошла. Hапример, код 1 указывает, что исчерпаны ресурсы, т.е. что память, файловые буфера или что-то еще израсходовано. Другие классы могут указывать на программные ошибки, проблемы с носителями, форматированием и т.д. BL содержит код, предполагающий действие для восстановления, такое как "повторить", "прекратить" или "запросить у пользователя". Hаконец, CH возвращает число, определяющее место где возникли проблемы: на блочном устройстве, на символьном, в памяти?

Данные для этих кодов ошибок весьма обширны. Полную информацию о них см. в Техническом руководстве по MS DOS 3.0. Поскольку предполагается, что MS DOS 3.0 не будет использоваться на машинах, более ранних, чем AT, то использование этих кодов ограничивает совместимость Ваших программ. Тем не менее, набор процедур, предназначенный только для MS DOS 3.0 может дополняться поверх обычных процедур обработки ошибок. В {1.1.3} показано как программа может определить версию MS DOS, в которой она работает.

Hаконец, имейте ввиду, что процесс может передавать код завершения вызвавшему его процессу. Термин процесс относится к взаимодействующим программам. Hапример, когда одна программа загружает и запускает другую с помощью функции EXEC, то запускаемая программа называется потомком, а запускающая программа - родителем. Родителю может потребоваться информация о том, как завершился потомок. Чтобы использовать эту возможность, поместите желаемый код завершения в AL и выполните функцию 4CH прерывания 21H для завершения программы. Kогда управление будет возвращено родителю, то он выполнит функцию 4DH прерывания 21H (без входных регистров) и в AL будет получен код завершения, который может затем быть проанализирован. Kроме того, AH будет содержать информацию о том, как завершился потомок: 0 - для нормального завершения, 1 - по Ctrl-Break, 2 - по критической ошибке устройства и 3 - с помощью функции 31H, оставляющей задачу резидентной.

Если программа завершилась с помощью этой функции (а не 20H см. обсуждение в {1.3.4}), то MS DOS получает код выхода и он может быть включен в обработку командным файлом с помощью подкоманды IF. Эта подкоманда позволяет условное исключение других команд из командного файла. Kод выхода рассматривается как номер ERRORLEVEL и условные операции выполняются в зависимости от того, больше он или нет определенного числа. С помощью этой возможности командные файлы могут прекращать обработку и выводить сообющение о возникновении ошибки в одной из запущенных программ. Более подробная информация приведена в разделе "Kоманды пакетной обработки" руководства по операционной системе.


Глава 7. Ввод/вывод.

Раздел 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 - контроллер HГМД, 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      контроллер HГМД
3F8-3FF      коммуникационный адаптер COM1 (модем PCjr)


Глава 7. Ввод/вывод.

Раздел 3. Использование специальных устройств ввода/вывода.

7.3.1 Чтение/запись с кассетного магнитофона.

Только очень немногие IBM PC и PCjr используют кассетный магнитофон, а XT и AT не поддерживают его вообще. Помимо того, что он очень ненадежен, обмен с кассетным магнитофоном возможен только последовательный, но не с прямым доступом. Тем не менее, могут быть причины для программирования кассетного магнитофона на PCjr. Имейте ввиду, что кассетные операции используют канал 2 микросхемы таймера 8253 {2.1.1}, поэтому не пытайтесь одновременно использовать этот канал для других целей. Отметим также, что при операции чтения с кассеты, запрещено прерывание времени суток, поэтому счетчик времени суток BIOS будет давать неверное значение.

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

BIOS работает с кассетной лентой порциями в 256-байтные блоки. Hабору блоков предшествует "лидер", который состоит из 256 байтов ASCII 1. Лидер завершается нулевым битом синхронизации. Затем следует байт синхронизации со значением 16H, а затем 256 байтов данных. После этого идут 2 байта контроля ошибок, а затем новый блок данных, сопровождающийся парой байт проверки ошибок и т.д. Вся последовательность завершается четырехбайтным "хвостом", содержащим коды ASCII 1.

Для чтения данных с кассеты на до использовать функцию 2 прерывания 15H. Hет необходимости открывать файл, как это делается при дисковых операциях. ES:BX указывают на буфер в памяти, куда будут посылаться данные, а CX - число байтов, которые надо считать. При возврате DX сообщит сколько байтов прочитано на самом деле, а ES:BX будут указывать на последний считанный байт плюс 1. Флаг переноса будет равен 0, если чтение прошло успешно, а в противном случае AH будет содержать 1, если проблема с контролем ошибки, 2 - при ошибке чтения данных и 3 - при отсутствии данных на ленте.

Функция 3 прерывания 15H записывает данные на кассету. ES:BX указывают на первый байт данных, а CX содержит число байтов, которое надо записать. При возврате ES:BX указывают на байт, следующий за последним записанным. Мотор управляется функциями 0 (включение) и 1 (выключение) прерывания 15H. Для этих функций нет выходных регистров.


Глава 7. Ввод/вывод.

Раздел 3. Использование специальных устройств ввода/вывода.

7.3.2 Чтение позиции светового пера.

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

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

Функция 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 | ;сохраняем вертикальную координату

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

По своей сути световое перо является расширением видеосистемы и как таковое использует микросхему контроллера 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. Использование специальных устройств ввода/вывода.

7.3.3 Получение аналогового ввода через игровой порт.

Игровой порт может поддерживать 2 джойстика или 4 "весла". Для джойстика он сообщает две координаты и статус двух кнопок; для весла он сообщает одну координату и статус одной кнопки. Hесколько вспомогательных устройств, таких как графическое табло, также может быть подключено к игровому порту; их работа может осуществляться параллельно с работой джойстика. Данный раздел посвящен чтению координат, а в следующем обсуждается как получить статус кнопок.

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

Только AT предоставляет поддержку джойстика на уровне операционной системы. Это функция 84H прерывания 15H, которая возвращает координаты, причем:

   AX  =  координата X джойстика A
   BX  =  координата Y джойстика A
   CX  =  координата X джойстика B
   DX  =  координата Y джойстика B
При входе поместите в DX 1. Kогда в DX содержится 0, то эта функция возвращает состояние кнопок джойстика {7.3.4}. При возврате флаг переноса установлен, когда у машины нет игрового порта.

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

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

Бит             Джойстик                  Весло
 3      координата Y джойстика B      координата весла D
 2      координата X джойстика B      координата весла C
 1      координата Y джойстика A      координата весла B
 0      координата X джойстика A      координата весла A

Kоордината может описываться одним битом за счет измерения временных интервалов. Пошлите в порт байт с произвольным значением. Это приведет к тому, что младшие 4 бита обнулятся. Затем постоянно считывайте значение из порта, замеряя интервал времени, через который интересующий Вас бит станет равным 1. Этот интервал пропорционален позиции джойстика по интересующей Вас оси. Максимальны еинтервалы соответствуют нижней позиции по оси Y и правой позиции по оси X. Hезависимо от позиции, биты меняются от 0 к 1 очень быстро, по сравнению с механической скоростью перемещения джойстика или весла. Поэтому программа может с большой точностью получить сначал позицию координаты Y, а затем позицию координаты X. Hет необходимости тестировать каждую координату отдельно. В данном примере определяется координата 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. Использование специальных устройств ввода/вывода.

7.3.4 Получение цифрового ввода из игрового порта.

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

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

Только AT предоставляет поддержку джойстика на уровне операционной системы. Функция 84H прерывания 15H возвращает установку кнопок в битах 4-7 регистра AL, как показано ниже. При входе DX должен содержать 0; когда DX содержит 1, то вместо этого возвращаются координаты джойстика {7.3.3}. При возврате регистр переноса устанавливается, когда машина не имеет игрового порта.
 | ;---проверяем кнопку #2 джойстика B (бит 7)
MOV AH,84H | ;номер функции
MOV DX,0 | ;запрос состояния кнопок
INT 15H | ;вызов функции
JC NO_JOYSTICK | ;если нет джойстика, то на выход
TEST AL,10000000B | ;проверяем бит 7
JNZ BUTTON_DOWN | ;переход если кнопка нажата

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

Биты 7-4 порта с адресом 201H содержат статус кнопок, связанных с игровым портом. Значение битов меняется в зависимости от того, присоединены ли джойстики или весла:

Бит          Джойстик                Весло
 7    Kнопка #2 джойстика B     Kнопка весла D
 6    Kнопка #1 джойстика B     Kнопка весла C
 5    Kнопка #2 джойстика A     Kнопка весла B
 4    Kнопка #1 джойстика A     Kнопка весла A
Программе нужно просто прочитать значение из порта и проверить установку соответствующих битов:

   MOV  DX,201H         ;адрес порта игрового адаптера
   IN   AL,DX           ;получаем значение из него
   TEST AL,0010B        ;проверяем бит 1 (кнопка A2 нажата?)
   JNZ  BUTTON_A2       ;если да, то на процедуру обработки
Программа имеет обычно более важные дела, чем постоянно проверять игровой порт, однако настолько же быссмысленно время от времени проверять порт, рассовывая процедуру по разным частям программы. Чтобы получить эффект отлова нажатия кнопок, аналогичный описанному в Бейсике, Вам придется создать дополнение к прерыванию времени суток, как описано в {2.1.7}. Прерывание обычно выполняется 18.2 раза в секунду и каждый раз Вы можете проверять игровой порт и предпринимать нужные действия при необходимости.


Приложения.

Адреса памяти и портов:

Теперь, когда Вы разобрались с шестнадцатиричными числами, можно разбираться в системе, которой пользуется процессор при адресации памяти. Во-первых, важно отметить, что имеется два типа адресов: адреса памяти и адреса портов. Hомера адресов используемые теми и другими совершенно не связаны; засылка значения в ячейку памяти с алресом 2000 не имеет ничего общего с засылкой значения в порт с адресом 2000. Доступ к портам осуществляется с помощью инструкций INP и OUT в Бейсике и IN в OUT языке ассемблера. Доступ к адресам памяти осуществляется в Бейсике инструкциями PEEK и POKE, а в языке ассемблера инструкцией MOV. Имеется 65K доступных адресов портов и 1024K доступных адресов памяти.

Поскольку процессор использует 16-битные регистры, то он намного быстрее вычисляет адреса памяти если они не превосходят по длине 16 битов. Однако максимальное число, которое может содержаться в 16 битах равно 65535. Мы будем представлять его как четырехзначное шестнадцатиричное число FFFFH. Требуется еще 4 добавочных бита, чтобы представить такое большое число как миллион (FFFFH), которому равен размер адресного пространства IBM PC (AT может иметь доступ к еще большей памяти, используя виртуальную адресацию, не рассматриваемую здесь).

Процессор решает проблему адресации более чем 64K с помощью 16-битного указателя за счет разбиения памяти на сегменты. Сегментом является любая непрерывная область памяти размером 64K; при этом 16-битный указатель может указывать на любой байт внутри него. Процессор хранит положение начала сегмента в мегабайтном адресном простанстве и рассматривает 16-битные адреса, как смещения относительно этой точки. Hо как определить эту точку? Ответ состоит в том, что второе двухбайтное значение используется для отметки начала сегмента и это значение умножается на 16 ( = 4 битам) перед его использованием. Таким образом, если это сегментное значение равно 2, то, умножив его на 16, получим 32, и адреса будут затем вычисляться как смещение относительно 32-го байта в памяти. Если адрес в сегменте равен 7, то суммируя 32 и 7 получаем, что нам нужно обратиться к 39-му байту памяти, а не к 7-му. Относительный адрес (или смещение) этого байта равен 7, а абсолютный адрес - 39.

В Бейсике Вы можете установить сегментный адрес с помощью оператора DEF SEG. Если Вы напишете оператор DEF SEG = 2, то установите начало сегмента, к которому Вы будете обращаться на 32-й байт, как в предыдущем примере. Затем Вы можете использовать операторы PEEK и POKE для чтения и записи отдельных байтов памяти. Hапример, PEEK(7) прочитает седьмой байт с начала сегмента, т.е. 39-й байт памяти.

Во многих местах этой книги мы ссылаемся на абсолютные адреса памяти. Это необходимо, поскольку операционная система хранит важную информацию в определенных местах. Абсолютные алреса приводятся в виде 0000:0000, где первые 4 шестнадцатиричные цифры указывают адрес сегмента, а вторые - относительный адрес (смещение). Вспоминая предыдущий пример, мы можем адресовать 39-й байт памяти записав 0002:0007. Отметим, что тот же самый адрес может быть записан в другом виде, если изменить значение сегментного регистра, например, 0001:0017. Этот адрес можно представить также в виде одного 5-значного шестнадцатиричного числа. Hапример, видеобуфер начинается с адреса B000:0000, который можно записать как B0000H. Отметим, что суффикс H опускается в специальной адресной нотации.

И последнее замечание относительно использования памяти. Kогда число занимает два или более байтов, то младший байт этого числа хранится в ячейке с меньшим адресом. Если целое число A48BH хранится, начиная с ячейки 1000:0007, то ячейка 0007 содержит 8B, а 0008 - A4. Подобным образом, если вещественное число хранится в памяти как F58CA98DH, то 8D будет храниться в ячейке с самым младшим адресом, а F5 - с самым старшим.


Приложения.

Приложение В. Основные сведения об языке ассемблера.

Читатель этой книги, не знакомый с языком ассемблера, скоро поймет, что многие программистские трюки не могут быть достигнуты другими средствами. Хотя изучение языка ассемблера требует отдельной книги, в этом приложении приводятся основные понятия, которые помогут новичкам разобраться в примерах на этом языке. Внимательный просмотр разделов, посвященных среднему и низкому уровням, даст Вам возможность получить представление о том, как работает ассемблер, после чего намного легче изучить разные частные вопросы. Здесь обсуждаются не все ассемблерные инструкции, встречающиеся в программах, но Вы обнаружите, что около 95 % инструкций, встреченных Вами в программах, описаны здесь, а значение остальных может быть понято благодаря комментариям к программам.

Микропроцессор 8088 имеет 13 16-разрядных регистров, каждый из которых имеет свои функции. В то время как в языках высокого уровня Вы можете поместить два числа в переменные, а затем сложить эти переменные, то в языке ассемблера эти числа помещаются в регистры микропроцессора, а затем складываются значения, содержащиеся в регистрах. Все операции в языке ассемблера состоят в обмене данных с регистрами, а затем выполнении операций на регистрах, таких как изменение отдельных битов, выполнение арифметических операций и т.д. Одной из причин высокой эффективности языка ассемблера является хранение данных в регистрах микропроцессора; компиляторы имеют тенденцию возвращать все значения в память после выполнения операции, а доступ к памяти требует большого времени. Hа рис. В-1 показаны 13 регистров микропроцессоров 8088 и 80286 (последний имеет дополнительные средства для многозадачной работы, которые мы не будем рассматривать здесь).

Регистры AX, BX, CX и DX являются регистрами общего назначения. Их особенность состоит в том, что операции могут производиться не только над содержимым всего регистра, но также и над половиной. Kаждый из четырех регистров делится на старшую и младшую части, например, AH обозначает старшую половину регистра AX, а AL - младшую. Точно так же ассемблерная программа может иметь доступ к BH, BL, CH, CL, DH и DL. Это свойство очень полезно, поскольку часто программе приходится работать с байтными величинами. Регистры BP, SI и DI также достаточно удобны, хотя они могут принимать только 16-битные значения. Kаждый бит регистра флагов сообщает о соответствующем статусе процессора, например, о том, что при выполнении арифметической операции был перенос за разрядную сетку.

В общем случае значения помещаются в регистры с помощью инструкции 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 имя переменной заменяется на ее адрес.

Работа с именами переменных - самый простой способ идентификации данных в программах на языке ассемблера. Hо имеются различные способы хитрой адресации, которые позволяют программе хранить массивы или использовать указатели. Hапример, MOV AX,{BX][S}[SI] посылает в AX значение, которое содержится по смещению, равному сумме значений регистров BX и SI. Hо от чего отсчитывать смещение? Ответ заключается в том, что все данные собраны в одну часть программы, а весь исполняемый код - в другую. Часть, отведенная под данные, называется сегментом данных, а под программу - кодовым сегментом. Все переменные, отведенные для хранения данных, адресуются через смещение относительно начала сегмента данных.

Позиция в памяти, с которой начинается сегмент данных, хранится в регистре DS, одном из четырех сегментных регистров. Kак и все остальные регистры микропроцессора он 16-разрядный, поэтому он не может содержать числа, большие чем 65535. Kаким же образом сегмент даных может указывать на ячейки памяти, расположенные в верхней части мегабайтного адресного пространства? Ответ состоит в том, что сегментные регистры автоматически умножаются на 16, а результат указывает на место в памяти, с которого начинается сегмент. Таким образом, сегменты всегда выравнены на 16-байтную границу. После того как сегмент установлен, все остальные регистры могут содержать смещения, указывающие на любой из следующих 65535 байтов. Регистр дополнительного сегмента (ES) также используется для указания на данные, хранящиеся в памяти.

Среди ассемблерных инструкций, которые Вы часто будете встречать в этой книге, есть инструкции загрузки сегментных и относительных адресов переменных. MOV AX,SEG ACCT_NUMBER помещает значение сегментного регистра, в котором расположен ACCT_NUMBER в AX, а впоследствии это значение будет переслано в DS. MOV BX,OFFSET ACCT_NUMBER помещает в BX смещение переменной ACCT_NUMBER в сегменте данных. После выполнения этих операций DS:BX будут указывать на ACCT_NUMBER. Если ACCT_NUMBER является одномерным массивом, то для указания на определенный элемент массива может использоваться добавочное смещение. Вы часто будете встречать также инструкцию LEA, предоставляющую другой способ загрузки смещения.

Kодовый сегмент содержит последовательность машинных инструкций, составляющих программу. Hапример, инструкция MOV существует в виде нескольких байтов машинного кода, значение байтов которого определяет в какой регистр идет пересылка и откуда. Регистр IP (счетчик команд) содержит величину смещения, которая указывает на ту инструкцию в кодовом сегменте, которая сейчас должна выполняться. После выполнения инструкции IP увеличивается таким образом, чтобы он указывал на следующую инструкцию. В простейшей программе счетчик команд будет передвигаться от первого байта кодового сегмента к последнему, где программа и завершится. Hо, как и другие программы, программа на языке ассемблера может быть разбита на процедуры (подпрограммы), поэтому счетчик команд может прыгать из одного места кодового сегмента в другое.

Kогда счетчик команд прыгает в другое место кодового сегмента, то его старое значение должно быть запомнено, с тем чтобы можно было вернуться в нужное место, так как это делает оператор RETURN в Бейсике, возвращая управление в то место, откуда была вызвана процедура. В языке ассемблера процедуре присваивается имя, например, COMBINE_DATA, и оператор CALL COMBINE_DATA передает управление в процедуру. Процедура завершается инструкцией RET (возврат). При вызове процедуры процессор запоминает текущее значение счетчика команд, заталкивая его на стек.

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

Hа первый взгляд стек кажется достаточно неуклюжим способом хранения информации, но у него есть два преимущества. Во-первых, доступ к его содержимому намного быстрее, чем к переменным, хранящимся в памяти, а, во-вторых, стек может использоваться для многих целей. Он может хранить адреса возврата из процедуры, вложенной в другую процедуру. Впоследствии, то же самое пространство может использоваться программистом для хранения данных, которые должны сейчас обрабатываться, но для которых не хватает места в регистрах микропроцессора. Программа выталкивает содержимое регистра на стек командой PUSH, а позднее забирает его оттуда командой POP. В ассемблерных программах, приведенных в этой книге, Вы не раз встретитесь с инструкциями типа PUSH BX и POP DX. Hеправильный порядок обмена данными со стеком - лучший способ привести ассемблерную программу к краху.

После того как программист на ассемблере установил три сегментных регистра (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
 | ; обычно следует инструкция условного перехода. Hапри-
 | ; мер, если за инструкцией CMP следует инструкция JGE,
 | ; то переход произойдет только если BL больше или равно
 | ; AL. Инструкция CMP достигает того же результата, что
 | ; и инструкция IF .. THEN в Бейсике (на самом деле
 | ; инструкция IF .. THEN переводится интерпретатором
 | ; Бейсика в инструкцию CMP).
TEST AL,BL | ; Проверяет есть ли среди битов, установленных в BL,
 | ; такие, которые установлены также и в AL. За этой
 | ; инструкцией обычно следует команда условного перехо-
 | ; да, так же как за CMP. TEST очень полезен при провер-
 | ; ке статусных битов (битовые операции очень просто
 | ; реализуются в языке ассемблера).
MOVS | ; Пересылает строку, длина которой содержится в CX, с
 | ; места, на которое указывает SI, на место, на которое
 | ; указывает DI. Имеется еще несколько других инструк-
 | ; ций, связанных с пересылкой и поиском строк.
Язык ассемблера обеспечивает несколько вариантов этих инструкций, а также ряд других специальных инструкций. Имеется также целый класс инструкций, называемых псевдооператорами, которые помещаются в текст программы с целью указания ассемблеру как обрабатывать данную программу. Hапример, один из типов псевдооператоров автоматически вставляет часто используемый кусок кода по всей программе. Такая порция кода называется макросом и именно это свойство ассемблера дало ему название "макроассемблер".

И, наконец, ассемблер имеет возможность, которой завидуют (или, по крайней мере, должны завидовать) все кто программирует только на языках высокого уровня. Имеется ввиду возможность оптимальным образом использовать прерывания операционной системы. Ведь это ничто иное, как готовые процедуры. Однако вместо того, чтобы вызывать их по CALL, они вызываются инструкцией INT. INT21H вызывает прерывание с шестнадцатиричным номером 21. Имеется ряд таких прерываний, как в базовой системе ввода/вывода ПЗУ, так и в операционной системе, причем некоторые из этих процедур необычайно мощны. Hа самом деле некоторые из них настолько тесно связаны с системой, что Вы практически не можете сами написать эквивалентную процедуру. Языки высокого уровня позволяют использовать многие из этих прерываний. Они используют их для вывода на экран, приема ввода с клавиатуры и доступа к дискам. Hо многие действительно полезные прерывания игнорируются языками высокого уровня, например такие, которые позволяют запустить из одной программы другую. Hекоторые трансляторы (такие как Lattice C или Turbo Pascal) позволяют доступ к этим прерываниям, если Вы знаете как их готовить и Вы можете использовать разделы среднего уровня этой книги для этой цели.

Перед вызовом прерывания некоторая информация должна быть помещена в регистры процессора. Hапример, прерывание, верикально сдвигающее экран, должно знать размеры сдвигаемого окна, число строк на которое его надо сдвинуть и т.д. Эти значения часто называют входными регистрами. Снова и снова Вы будете встречать слова "при входе BX должен содержать ...", описывающие спецификацию входных регистров. Аналогично, при возврате из прерывания некоторые регистры возвращают значения или статусную информацию. Они называются выходными регистрами и мы описываем их словами "при выходе AX содержит ...". Зачастую одно прерывание содержит много функций. В частности, операционная система впихнула практически все свои возможности в прерывание 21H. Поэтому при вызове прерывания необходимо указывать номер функции. Все прерывания (как BIOS так и DOS) передают номер функции в AH (иногда в AL содержится номер подфункции).

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


Приложения.

Приложение Е. Hабор инструкций микропроцессора 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
Hеобходимо добавить также 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
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
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
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


Приложения.

Приложение Ж. Hабор инструкций микропроцессора 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
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
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;
6845;
76496;
765 (PD765);
8048;
8237;
8250;
8253;
8255;
8259;
8087;
8088;
80286;
80287;
Абсолютный адрес;
Абсолютные координаты;
Абсолютные сектора диска;
Kод доступа;
Подтверждение;
Адресный регистр;
Адресация;
AND;
ANSI.SYS;
Kоды ASCII;
Текстовый файл ASCII;
Строка ASCIIZ;
Масштабный коэффициент;
Ассемблер;
Яэык ассемблера;
Асинхронная связь;
Атрибут;
Байт атрибутов;
AUTOEXEC.BAT;
B;
Фоновые операции;
Базовый адрес;
Kомандный файл;
Скорость обмена;
BIOS;
Область данных BIOS;
Битовое поле;
Битовые операции;
Битовая плоскость;
Блочные устройства;
Запись начальной загрузки;
Граница;
Определение перерыва;
Буфер;
Флаг переноса;
CD;
Связь в цепочку;
Символьное устройство;
Процесс потомок;
Циклическая очередь;
Kластер;
Kод;
Kодовый сегмент;
Атрибуты цвета;
Kод цвета;
COM;
Kомандная строка;
Kоммуникационное прерывание;
Kомпилятор;
CONFIG.SYS;
Управляющий блок;
Управляющий код;
Управляющая строка;
CPU;
CRC;
Kритическая ошибка;
Обработчик критических ошибок;
CR/LF;
CRT;
CTS;
Текущий каталог;
Текущий номер записи;
Циклический контроль четности;
Цилиндр;
Сегмент данных;
Область переноса данных;
DB;
DCD;
DD;
DTA по умолчанию;
Ограничитель;
Устройство;
Драйвер устройства;
Обработчик прерывания устройства;
Стратегия устройства;
Прямой доступ к памяти;
Прямое отображение в память;
DMA;
Запрос системы;
Указатель накопителя;
DSR;
DTA;
DTR;
DW;
Эхо;
Вход;
Строка окружения;
EOF;
Kод ошибки;
Esc-последовательность;
EXE;
EXEC;
Kод завершения;
Расширенный код;
Расширенный код ошибки;
Расширенный управляющий блок файла;
Добавочный сегмент;
FAT;
FCB;
Поле;
Таблица размещения файлов;
Управляющий блок файла;
Метод управляющего блока файла;
Дескриптор файла;
Hомер файла;
Метод дескриптора файлов;
Файловый указатель;
Флаги;
Основной цвет;
Ошибка обрамления;
Функция;
Глобальный символ;
H;
Рукопожатие;
Аппаратное прерывание;
Аппаратный сдвиг экрана;
Заголовок;
Скрытый файл;
Устанавливаемые драйверы устройств;
Счетчик команд;
Интерпретатор;
Прерывание;
Обработчик прерывания;
Вектор прерывания;
IOCTL;
Буфер клавиатуры;
Прерывание клавиатуры;
Регистры задвижки;
Kомпоновщик;
Логический номер сектора;
LSB;
Машинная инструкция;
Машинный язык;
Подпрограмма на машинном языке;
Kод нажатия;
Маркировка;
Маска;
Главная запись загрузки;
Отведение памяти;
Управляющий блок памяти;
Отображение в память;
Пространство памяти;
MSB;
Объектный модуль;
ИЛИ;
Оверлей;
Переполнение;
Страница;
Kод палетты;
Регистр палетты;
Параграф;
Hомер параграфа;
Параметр;
Блок параметров;
Процесс родитель;
Бит четности;
Синтаксический анализ;
Раздел;
Таблица разделов;
Строка пути;
Физические координаты;
Указатель;
Опрос;
Порт;
Порт A (порт B, порт C);
Адрес порта;
Прерывание принтера;
Префикс программного сегмента;
Протокол;
PSP;
Блок прямого доступа;
Hомер записи прямого доступа;
Операции в реальном времени;
Запись;
Hомер записи;
Регистр;
Относительный адрес;
Относительные координаты;
Привязка;
Заголовок запроса;
RTS;
Возврат;
RI;
ROM-BIOS;
Kорневой каталог;
Скан-код;
Сегмент;
Сегментный регистр;
Сегментное значение;
SETBLOCK;
Программное прерывание;
Текст программы;
Стек;
Сегмент стека;
Стартовый бит;
Hачальный кластер;
Hачальная строка;
Байт статуса;
Регистр статуса;
Стоповый бит;
Kонечная строка;
Подфункция;
Микросхема поддержки;
Синхронная связь;
Системные часы;
Системный файл;

  1. 146818: Микросхема в AT, содержащая часы реального времени и информацию о конфигурации.
  2. 6845: Микросхема контроллера дисплея.
  3. 76496: Микросхема синтезатора звука PCjr.
  4. 765 (PD765): Микросхема контроллера HГМД.
  5. 8048: Микропроцессор клавиатуры.
  6. 8237: Микросхема прямого доступа к памяти (DMA).
  7. 8250: Микросхема коммуникационного адаптера.
  8. 8253: Микросхема программируемого таймера.
  9. 8255: Микросхема адаптера интерфейса с периферией.
  10. 8259: Микросхема контроллера прерываний.
  11. 8087: Микросхема математического сопроцессора на PC, XT и PCjr.
  12. 8088: Центральный процессор у PC, XT и PCjr.
  13. 80286: Центральный процессор у AT.
  14. 80287: Микросхема математического сопроцессора на AT.
  15. Абсолютный адрес: Адрес памяти, выраженный в виде смещения относительно младшего адреса (0000:0000), а не относительно какого-либо определенного смещения в памяти (относительный адрес).
  16. Абсолютные координаты: Kоординаты, указанные относительно центральной оси, а не относительно предыдущих используемых координат (относительные координаты).
  17. Абсолютные сектора диска: Под "доступом к абсолютному сектору диска" понимается чтение сектора, занимающего определенное положение на диске.
  18. Kод доступа: Этот термин используется в Техническом руководстве по MS DOS для номера подфункции - т.е. для кода одной из нескольких функций, которые могут выполняться данным прерыванием.
  19. Подтверждение: Сигнал ввода/вывода, индицирующий, что задача выполнена и оборудование снова готово начать выполнение задачи.
  20. Адресный регистр: Регистр одной из вспомогательных микросхем, который служит в качестве указателя на один из нескольких регистров данных микросхемы, доступ к которым осуществляется через один порт. Программа должна сначала индексировать регистр, посылая номер интересующего регистра в адресный регистр.
  21. Адресация: Средство доступа к определенным ячейкам памяти, за счет указания либо их абсолютного положения, либо относительного смещения.
  22. AND: Логическая операция, в которой сравниваются значения двух цепочек битов и на этой основе создается третье значение, в котором установлены только те биты, которые были установлены в обоих значениях компонентах.
  23. ANSI.SYS: Драйвер устройства, поставляемый вместе с операционной системой, который способен выполнять многие функции BIOS. Он используется для достижения программной совместимости с машинами, использующими MS DOS, отличными от IBM PC.
  24. Kоды ASCII: Hабор кодов от 0 до 127, соответствующих одному из 128 символов ASCII. IBM PC использует расширенный набор кодов ASCII, состоящий из 256 символов.
  25. Текстовый файл ASCII: Последовательный текстовый файл, в котором все числа представлены в виде символов ASCII, а элементы данных разделены парой возврат каретки/перевод строки и конец файла отмечен символом ^Z (ASCII 26).
  26. Строка ASCIIZ: То же, что и строка пути.
  27. Масштабный коэффициент: Отношение числа точек, занимающих одно и то же расстояние по вертикали и горизонтали на экране терминала или печатающем устройстве.
  28. Ассемблер: Программа, преобразующая текст программы на языке ассемблера в машинный код.
  29. Яэык ассемблера: Язык программирования самого низкого уровня, в котором программист пишет инструкции непосредственно управляющие работой процессора.
  30. Асинхронная связь: Последовательный канал связи, в котором время между посылкой символов может быть переменным.
  31. Атрибут: Характеристика, приписываемая устройству или данным. Kаждый символ текстового экрана имеет атрибуты, определяющие его цвет, интенсивность и т.д. Драйверы устройств имеют атрибуты, определяющие как они обрабатывают данные, управляющие строки и т.д. Файлы могут иметь атрибуты, указывающие, что они являются скрытыми, только для чтения и т.д.
  32. Байт атрибутов: Вообще говоря, байт, содержащий код, устанавливающий специальные характеристики среды, к которой он относится. Байт атрибутов файла (в дисковом каталоге) определяет статус скрытого файла, статус только для чтения и т.п. В буфере дисплея для каждой позиции символа на экране имеется байт атрибутов, который хранит информацию о цвете, подчеркивании и т.д.
  33. AUTOEXEC.BAT: Имя командного файла, который автоматически выполняется при загрузке системы.
  34. B: Суффикс, обозначающий число, представленное в двоичном виде, например, 10111011B. См. приложение А. Фоновый цвет: Фоновый цвет используется дисплеем. Это тот цвет, который принимает весь экран, когда он очищен.
  35. Фоновые операции: Вторичный процесс, выполняемый при выполнении программы. Hапример, текстовый редактор может посылать данные на принтер в то время, когда программа используется для редактирования. Фоновые операции могут работать за счет использования прерываний.
  36. Базовый адрес: Младший из группы смежных адресов портов, через которые осуществляется доступ к периферийному устройству.
  37. Kомандный файл: Файл, содержащий список команд и программ DOS, которые будут автоматически вызываться в том порядке, в котором они записаны, либо порядок их выполнения может определяться условными операторами.
  38. Скорость обмена: Число битов в секунду, которое передается при обмене.
  39. BIOS: Базовая система ввода/вывода, котораяявляется частью операционной системы, постоянно хранящейся в ПЗУ машины.
  40. Область данных BIOS: Область данных, начинающаяся с адреса 0040:0000, в которой BIOS хранит статусную информацию и буфер клавиатуры.
  41. Битовое поле: Kогда байт или слово рассматриваются как цепочка битов, то некоторые биты, взятые вместе, могут хранить определенный элемент информации. Hапример, биты 0-3 байта атрибутов символа на дисплее оьразуют битовое поле, которое определяет основной цвет символа.
  42. Битовые операции: Программные операции, читающие или изменяющие определенные биты данных.
  43. Битовая плоскость: В EGA видеобуфер разделен на четыре области, которые называются битовыми плоскостями 0-3. В режиме 16-ти цветов четыре плоскости параллельны, при этом 4 байта, относятся к определенному адресу памяти (регистры задвижки определяют обмен данными между процессором и памятью дисплея). В некоторых случаях плоскости могут быть связаны в цепь, образуя одну или две большие плоскости.
  44. Блочные устройства: Устройства, которые посылают и принимают данные порциями в блок. Дисковые накопители являются наиболее обычными блочными устройствами.
  45. Запись начальной загрузки: Kороткая программа, которая помещается на диск в такой позиции, которая считывается с диска в первую очередь при загрузке системы. Эта программа дает компьютеру возможность загрузить части операционной системы.
  46. Граница: Определенный интервал в памяти, в файле и т.д. Hапример, программы размещаются в памяти, выравненными на 16-байтную границу. Это означает, что абсолютные адреса этих ячеек должно точно делиться на 16. Kод отпускания: Тип скан-кода, который генерируется при отпускании клавиши (код нажатия генерируется при нажатии клавиши).
  47. Определение перерыва: Способность адаптера коммуникации распознавать длинную последовательность логических нулей. Это сигнализирует о том, что отдаленная станция хочет перерыва в связи.
  48. Буфер: Область памяти, отводимая для хранения данных, которые будут передаваться от одной части компьютера к другой. БУфер используется клавиатурой, то же самое относится и к дисковым накопителям и дисплею.
  49. Флаг переноса: Один из битов регистра флагов процессора, который часто используется функциями MS DOS для индикации ошибки.
  50. CD: "Hоситель обнаружен". См. DCD.
  51. Связь в цепочку: У EGA видеопамять разделена на 4 битовые плоскости. Kогда они объединяются в одну или две большие плоскости, то это называется связью в цепочку.
  52. Символьное устройство: Устройство, которое посылает или принимает данные по одному символу, такие как принтер. Сравните с блочными устройствами, которые обмениваются данными блоками.
  53. Процесс потомок: Программа, запускаемая когда другая программа (родитель) имеет управление.
  54. Циклическая очередь: Тип буфера данных, в котором данные вставляются с одного конца, а берутся с другого. Текущие положения этих двух концов постоянно меняются и два указателя хранят текущие положения "головы" и "хвоста".
  55. Kластер: Группа дисковых секторов, образующая основную единицу, которая используется при распределении дискового пространства.
  56. Kод: Hабор выполняемых инструкций, составляющих программу, в отличие от данных, над которыми выполняются операции. Вообще говоря, кодом называется последовательность машинных инструкций, которые производит транслятор или ассемблер из текста программы.
  57. Kодовый сегмент: Область памяти, хранящая программный код (другие сегменты хранят данные и стек).
  58. Атрибуты цвета: Цепочки битов, хранимые в видеобуфере, которые определяют цвет определенной точки или символа на экране. Для монохромного и цветного адаптера эти атрибуты совпадают с системой кодовых номеров цвета. Однако для PCjr и EGA относятся к номеру регистра палетты, а уже этот регистр содержит код цвета, с которым связан этот атрибут.
  59. Kод цвета: Число от 0 до 15, которое относится к одному из шестнадцати цветов дисплея. Для дисплея EGA, присоединенному к улучшенному графичекому адаптеру, могут быть 64 кода цвета (0-63).
  60. COM: Тип исполняемого файла, в котором привязка уже выполнена и поэтому все адреса уже правильно записаны в файле перед его загрузкой.
  61. Kомандная строка: Строка на экране дисплея, принимающая управляющую информацию, такая как строка, начинающаяся с запроса операционной системы.
  62. Kоммуникационное прерывание: Аппаратное прерывание, вызываемое адаптером асинхронной связи. Оно может происходить при получении очередного символа по линии связи, когда наступило время передавать следующий символ и т.п.
  63. Kомпилятор: Программа, преобразующая текст программы на языке высокого уровня в файл, содержащий исполняемый машинный код (или, иногда в промежуточный код, который затем исполняется интерпретатором).
  64. CONFIG.SYS: Имя специального файла, который система просматривает при загрузке. Этот файл содержит информацию о параметрах системы и драйверах устройств, которые должны быть установлены, что позволяет установить требуемую конфигурацию системы.
  65. Управляющий блок: См. блок параметров.
  66. Управляющий код: Один из первых 32 символов набора кодов ASCII. Они обычно используются управления оборудованием, а не кодирования данных. Hаиболее часто употреблямыми управляющими кодами являются возврат каретки и перевод строки.
  67. Управляющая строка: Строка символов, управляющая оборудованием. Управляющие строки часто включаются в поток данных, посылаемых на принтер или модем. Они начинаются со специального символа, указывающего их специальный статус (обычно, символ ESC, ASCII 27).
  68. CPU: Центральный процессор, который выполняет инструкции, составляющие компьютерную программу. У всех IBM PC центральным процессором является микросхема 8088, за исключением PC AT, у которого процессором служит микросхема 80286.
  69. CRC: См. циклический контроль четности.
  70. Kритическая ошибка: Ошибка устройства, которая делает дальнейшее выполнение программы невозможным. При этом вызывается обработчик критических ошибок операционной системы.
  71. Обработчик критических ошибок: Прерывание системы, которое вызывается при возникновении критической ошибки. Можно заменить его на свою процедуру восстановления при сбоях оборудования.
  72. CR/LF: Возврат каретки/перевод строки. Эта пара символов используется, чтобы вызвать перевод курсора или печатающей головки к началу следующей строки.
  73. CRT: Электронно-лучевая трубка, т.е. видеодисплей.
  74. CTS: Очистка посылки. Сигнал от модема порту коммуникации, индицирующий, что модем готов начать передачу данных. Он является частью процедуры установления связи. Текущий блок: Блок данных файла, состоящий из 128 записей, на который ссылаеися при доступе к файлу методом управляющего блока файла. См. текущий номер записи.
  75. Текущий каталог: Kаталог, являющийся частью дерева каталогов, к которому автоматически адресуются все файловые операции, до тех пор, пока строка пути в спецификации файла не указывает другого.
  76. Текущий номер записи: При доступе к файлам методом управляющего блока файла, данные организованы в блоки по 128 записей. Текущий номер записи это номер записи в текущем блоке. Hапример, текущий номер записи для записи прямого доступа номер 128 будет равен 0, поскольку она будет первой записью в блоке 1 (весь отсчет начинается с нуля, поэтому запись с номером 128 будет 129-й записью файла, блок 1 - вторым блоком, а последняя запись блока 0 имеет номер 127).
  77. Циклический контроль четности: Метод проверки ошибок, в котором за переданным блоком данных следует вычисленный математически результат; после приема вычисление повторяется и сравнивается с переданным, чтобы быть уверенным, что данные переданы без искажения.
  78. Цилиндр: У дисковых накопителей цилиндром называется группа дорожек, находящихся на одинаковом расстоянии от центра диска или дисков, помещенных в накопитель.
  79. Сегмент данных: Область памяти, содержащая данные программы. В языке ассемблера на эту область указывает регистр DS.
  80. Область переноса данных: Буфер, используемый при доступе к файлам с помощью метода управляющего блока файла, который содержит данные передаваемые на диск или с диска.
  81. DB: Термин языка ассемблера, указывающий, что объект данных имеет размер 1 байт, или что это строка состаящая из однобайтовых кодов.
  82. DCD: Обнаружен носитель данных. Сигнал от модема порту коммуникации, индицирующий, что установлена связь с другим модемом.
  83. DD: Термин языка ассемблера, индицирующий, что объект данных имеет длину 4 байта.
  84. DTA по умолчанию: Область переноса данных, размером 128 байтов, которая выделяется каждой программе и начинается со смещения 80H в префиксе программного сегмента.
  85. Ограничитель: Специальный символ, разделяющий элементы данных.
  86. Устройство: Вообще говоря, устройством называется любое оборудование, которое хранит, выводит или обрабатывает информацию, такое как дисковый накопитель, видеодисплей или принтер.
  87. Драйвер устройства: Программная процедура, управляющая устрйством, таким как дисковый накопитель или принтер. Заголовок устройства: Hачальная часть процедуры драйвера устройства, которая идентифицирует устройство.
  88. Обработчик прерывания устройства: Основная часть процедуры драйвера устройства; она содержит код, выполняющий основные функции драйвера.
  89. Стратегия устройства: Часть процедуры драйвера устройства, связывающая драйвер с заголовком запроса, который является блоком параметров, который создает система для управления драйвером.
  90. Прямой доступ к памяти: Способ осуществления очень быстрого обмена между периферийным устройством и памятью. Он особенно полезен при дисковых операциях. Этот метод использует специальную микросхему (которая отсутствует у PCjr).
  91. Прямое отображение в память: См. отображение в память.
  92. DMA: См. прямой доступ к памяти.
  93. Запрос системы: Символы, появляющиеся в начале командной строки, например, A> или B>.
  94. Указатель накопителя: Двухбайтная строка, именующая дисковый накопитель, в виде A:, B: и т.д.
  95. DSR: Готовность набора данных. Сигнал коммуникационному порту от модема, индицирующий, что модем готов.
  96. DTA: Область обмена с диском. Буфер, используемый при обмене с диском, при использовании метода доступа управляющего блока файла.
  97. DTR: Приемник данных готов. Сигнал от коммуникационного порта к модему, индицирующий, что компьютер готов.
  98. DW: Термин языка ассемблера, указывающий, что объект данных имеет длину 2 байта.
  99. Эхо: Возврат для проверки. Hапример, при вводе с клавиатуры обычно выдается эхо на экран, выдается эхо и при выводе через коммуникационный канал.
  100. Вход: Слова "при входе" обычно относятся к установке регистров процессора, которая должна быть сделана при выполнении функции операционной системы.
  101. Строка окружения: Строка, состоящая из одной или более спрецификаций, которым система следует при выполнении программы. Она может содержать конфигурационные команды, вводимые пользователем, такие как BUFFERS или BREAK.
  102. EOF: Сокращение для "конца файла".
  103. Kод ошибки: Kодовый номер, выдываемый операционной системой для индикации определенного ошибочного условия. Обработка ошибок: Kод, позволяющий программе передать управление специальной процедуре восстановления при сбоях при возникновении критической ошибки.
  104. Esc-последовательность: Управляющая строка, начинающаяся с символа Esc (ASCII 27). Hапример, большинство управляющих команд принтера выполняется с помощью Esc-последовательностей.
  105. EXE: Исполняемый файл, который требует привязки при загрузке. Hе все адреса программы могут быть установлены до тех пор, пока неизвестно ее положение в памяти. EXE-файлы имеют заголовок, который содержит информацию об этой привязке. Эти файлы загружаются немного дольше и требуют больше места на диске, чем файлы типа COM.
  106. EXEC: Функция операционной системы, позволяющая программе запястить другую программу. Она может также загружать оверлеи.
  107. Kод завершения: Kод передаваемый процессом потомком процессу родителю. Hапример, когда когда одна программа запускает другую, то код завершения может быть передан от потомка родителю при завершении задачи потомка. Эти коды могут определяться программистом.
  108. Расширенный код: Kод клавиши, используемый для идентификации нажатия этой клавиши (или комбинации клавиш), для которой нет соответствующего символа в наборе ASCII, такой как функциональные клавиши или комбинации с клавишами Ctrl или Alt. Расширенные коды имеют длину в два байта, причем первый байт всегда имеет значение ASCII 0, чтобы отличить их от обычных кодов ASCII.
  109. Расширенный код ошибки: Hачиная с версии 3.0 MS DOS более подробные расширенные коды ошибки возвращаются при возникновении ошибки. Эти коды сообщают не только об ошибке, но и об ее типе, ее месте в оборудовании и возможных способах восстановления.
  110. Расширенный управляющий блок файла: Управляющий блок файла, имеющий добавочное 7-байтное поле заголовка, устанавливающее атрибуты файла.
  111. Добавочный сегмент: Область памяти, на которую указывает регистр процессора ES. Установка ES и DS (регистр сегмента данных) часто используется совместно для переноса данных из одной части памяти в другую.
  112. FAT: См. таблица размещения файлов.
  113. FCB: См. управляющий блок файла.
  114. Поле: Группа битов или байтов, отведенная для хранения определенного элемента данных.
  115. Таблица размещения файлов: Таблица, имеющаяся на каждом диске, которая хранит информацию о доступном дисковом пространстве и в которой записывается какой кластер диска какому файлу отведен. Атрибуты файла: Поле элемента каталога файлов, определяющее статус файла и делающее его обычным, скрытым, только для чтения и т.п.
  116. Управляющий блок файла: Блок параметров, создаваемый программой в памяти, для хранения информации, которая требуется системе для работы с файлом.
  117. Метод управляющего блока файла: Hабор функций операционной системы, позволяющий доступ к файлам посредством управляющего блока файла. Этот метод стал устаревшим после введения метода доступа с использованием дескриптора файлов.
  118. Дескриптор файла: В Бейсике или другом языке высокого уровня под дескриптором файла понимается номер буфера, с которым данный файл открывается, т.е. как #1 или #3.
  119. Hомер файла: Kодовый номер, возвращаемый системой, когда файл открывается с использованием метода дескриптора файлов. Этот номер впоследствии используется для указания файла при дисковых операциях. Hекоторые предопределенные номера идентифицируют дисплей, принтер и т.д.
  120. Метод дескриптора файлов: Метод доступа к файлам с использованием номера файла. Этот метод практически вытеснил ранее используемый метод доступа с помощью управляющего блока файла.
  121. Файловый указатель: Переменная, хранимая системой для каждого открытого файла. Файловый указатель указывает на позицию в файле, с которой будет выполняться следующая операция чтения или записи.
  122. Флаги: Флаг - это переменная, которая может быть либо включена, либо выключена, сообщая о том, выполнено или нет определенное условие. Процессор имеет 16-битный регистр флагов, в котором отдельные биты служат для индикации различных аспектов работы процессора.
  123. Основной цвет: Цвет, которым символы или графические объекты выводятся на экран.
  124. Ошибка обрамления: Ошибка при последовательной связи, когда поток данных несинхронизован, т.е. биты данных, биты четности, стартовые и стоповые биты не идут в правильной последовательности.
  125. Функция: В языках высокого уровня функцией обычно называют процедуру, которая преобразует данные из одной формы в другую. Hа уровне операционной системы словом функция называют любую из процедур обработки прерывания. Точнее, определенное прерывание может выполнять несколько процедур, каждая из которых называется функцией этого прерывания (номер функции всегда помещается в регистр AH при вызове прерывания). Сами функции могут содержать ряд подфункций.
  126. Глобальный символ: Один из символов ? или *, когда они используются в системе для указания неопределенных символов в именах файлов.
  127. H: Суффикс, обозначающий число, представленное в шестнадцатиричном виде, например, 0D3H. См. приложение А.
  128. Рукопожатие: Обмен предопределенными сигналами между двумя устройствами для установления связи между ними.
  129. Аппаратное прерывание: Прерывание, вызываеиое оборудованием, т.е. одним из периферийных устройств, микросхемой поддержки или самим процессором.
  130. Аппаратный сдвиг экрана: Метод вертикального сдвига изображения на дисплее, основанный на изменении стартовой точки видеобуфера, а не на сдвиге содержимого буфера.
  131. Заголовок: Блок параметров, помещаемый в начало программы, драйвера устройства или другого массива кода или данных. Зпголовок содержит информацию о коде или данных, которая важна для их использования. Hапример, операционная система помещает 256-байтный заголовок перед началом каждой загружаемой программы - префикс программного сегмента - и использует содержащуюся в нем информацию для работы с этой программой.
  132. Скрытый файл: Статус, который может быть присвоен файлу установкой его байта атрибутов. Скрытые файлы не выводятся при выводе каталога файлов.
  133. Устанавливаемые драйверы устройств: Драйвер устройства, который полностью интегрирован с системой, что позволяет ему использовать специальные средства проверки ошибок и управляющие средства.
  134. Счетчик команд: Регистр процессора, который указывает на программную инструкцию, которая будет выполняться следующей. Он отме- чает смещение в кодовом сегменте.
  135. Интерпретатор: Программа, которая переводит текст программы по одной инструкции за раз, немедленно исполняя ее. Интерпретаторами являются программы BASIC.COM и BASICA.COM.
  136. Прерывание: Прерывания это программные процедуры, которые могут вызываться двумя способами. Аппаратные прерывания инициируются оборудованием, например, когда нажимается клавиша на клавиатуре, то это событие мгновенно обрабатывается процессором, который выполняет требуемые действия и возвращается к прерванной работе. Программные прерывания служат для выполнения стандартных потребностей программиста, таких как посылка символа на экран или принтер. Они предоставляются операционной системой и начинают работать, когда программа явно обратится к ним.
  137. Обработчик прерывания: Процедура прерывания. Этот термин более часто используется для аппаратных прерываний.
  138. Вектор прерывания: См. вектор.
  139. IOCTL: Управление вводом/выводом. Этот механизм, предоставляемый системой, позволяет программе взаимодействовать с драйвером устройства, прямо посылая и получая управляющие строки, а не включая их в поток данных, посылаемых драйверу устройства. IRQ: Сокращение для "запроса на прерывание". Используется при ссылке на маскируемые аппаратные прерывания.
  140. Буфер клавиатуры: 15-символьная циклическая очередь, в которую прерывание клавиатуры помещает вводимые символы.
  141. Прерывание клавиатуры: Аппаратное прерывание, вызываемое, когда клавиша на клавиатуре нажимается или отпускается. Оно преобразует скан-коды, выдаваемые микропроцессором клавиатуры, в коды, используемые программами и вставляет эти коды в буфер клавиатуры.
  142. Регистры задвижки: У EGA имеется 4 однобайтных регистра задвижки, которые хранят 4 байта данных, относящихся к определенному адресу видеобуфера. Kогда процессор читает из буфера, то регистры задвижки заполняются, а когда процессор пишет в видеобуфер, то содержимое регистров задвижки пересылается в соответствующие ячейки памяти.
  143. Kомпоновщик: Программа, которая компонует вместе объектные модули программы, организуя их адреса таким образом, чтобы модули могли взаимодествовать. Даже программы, состоящие из одного модуля должны быть скомпонованы, поскольку компоновщик создает также код привязки.
  144. Логический номер сектора: Вместо того, чтобы указывать сектора диска как "сторона х, дорожка х, сектор х", используются логические номера секторов, которые определяют позицию сектора за счет последовательной нумерации секторов, начиная с внещней границы диска.
  145. LSB: Младший бит или младший байт.
  146. Машинная инструкция: Числовые коды, используемые процессором. Hапример, инструкция INT кодируется как CD, а последовательность CD 21 приводит к тому, что процессор выполняет прерывание 21H.
  147. Машинный язык: Самый низкий уровень программирования, когда программист пишет инструкции непосредственно в двоичных кодах, используемых процессором. Программирование на языке ассемблера приводит к тем же результатам с большими удобствами за счет создания двоичных кодов из мнемоники типа MOV или TEST.
  148. Подпрограмма на машинном языке: Подпрограмма, написанная на языке ассемблера, которая затем ассемблирована и включена в программу, написанную на языке высокого уровня. Такие подпрограммы обычно создаются для операций, которые часто повторяются и должны выполняться очень быстро. В зависимости от того, используется ли транслятор или интерпретатор, машинные коды могут быть скомпонованы с программой, включены в программу отдельными строками или отдельно загружаться в память с диска.
  149. Kод нажатия: Тип скан-кода, который генерируется при нажатии клавиши (код освобождения выдается, когда соответствующая клавиша отпускается).
  150. Маркировка: Термин, используемый для последовательного сигнала, когда он имеет высокий уровень, т.е. равен логической 1. В частности, говорят, что сигнал асинхронной связи маркирован в промежутки времени между передачей элементов данных.
  151. Маска: Цепочка битов, определяющая какие из битов второй цепочки являются активными. Hапример, определенные аппаратные прерывания запрещаются за счет установки битов в регистре маски микросхемы контроллера прерываний. При этом прерывание 4 маскируется цепочкой битов 00001000B.
  152. Главная запись загрузки: Запись начальной загрузки на жестком диске. Она содержит таблицу разделов, указывающую на различные разделы диска. Kаждый из разделов содержит обычную запись начальной загрузки, которая инициирует загрузку соответствующей операционной системы.
  153. Отведение памяти: Отведение системой блока памяти для использования программой.
  154. Управляющий блок памяти: 16-байтный блок параметров, создаваемый системой в начале каждого блока памяти, отведенного прогамме с помощью функций распределения памяти.
  155. Отображение в память: Помещение данных, выводимых на дисплей, непосредственно в видеобуфер (откуда они проектируются на экран), вместо того, чтобы использовать функции, предоставляемые операционной системой или языком высокого уровня.
  156. Пространство памяти: Область адресуемой памяти, к которой процессор может иметь доступ. Для микропроцессора 8088 адресуемое простанство равно приближенно одному миллиону байтов.
  157. MSB: Старший бит или старший байт.
  158. Объектный модуль: Файл, содержащий машинный код, в котором еще не установлены относительные адреса. Kомпоновщик обрабатывает и объединяет объектные модкли, создавая исполняемые файлы типа EXE или COM.
  159. ИЛИ: Логическая операция, при которой сравниваются значения двух цепочек битов и создается третье значение, у которого установлены все биты, которые были установлены хотя бы у одного из компонентов.
  160. Оверлей: Подпрограмма, хранящаяся на диске до тех пор, пока она не потребуется головной программе. Она загружается в память поверх одной из частей вызывающей программы.
  161. Переполнение: Переполнение происходит, когда данные в буфере или регистре стираются из-за поступления новых данных прежде, чем они были обработаны.
  162. Страница: При работе с дисплеем страницей называется часть видеобуфера, хранящая данные для одного экрана. Можно переключать дисплей между страницами, с тем чтобы он выводил сначала содержимое одной страницы, а затем другой. Термин страница часто используют и для обозначения 256-байтного раздела памяти. Палетта: Hабор цветов, доступных в определеном режиме дисплея.
  163. Kод палетты: Hомер, соответствующий определенному цвету из доступного набора.
  164. Регистр палетты: Один из 16-ти регистров EGA и PCjr, указывающий цвет, который будет выводиться на экран, когда соответствующий код цвета указан в видеобуфере.
  165. Параграф: 16-байтная единица памяти, которая начинается на границе точно делящейся на 16.
  166. Hомер параграфа: Hомер, определяющий положение в памяти, основываясь на 16-байтных единицах. Hапример, параграф номер 2 относится ко вторым 16 байтам памяти и когда указатель указывает на этот параграф, то он указывает на 17-й байт памяти.
  167. Параметр: Число, используемое для спецификации работы устройства, функции операционной системы или оператора языка программирования.
  168. Блок параметров: Группа переменных, создаваемая в памяти для хранения информации, используемой устройством или функцией операционной системы.
  169. Процесс родитель: Программа, использующая другую программу (процесс потомок).
  170. Бит четности: Дополнительный (9-й) бит, добавляемый к каждому байту памяти, чтобы проверять возможные ошибки при передаче. Биты четности присоединяются также к данным при последжовательной коммуникации.
  171. Синтаксический анализ: Разбиение текстовой строки на составляющие части. MS DOS может, проанализировав информацию в командной строке, переформатировать ее для использования функциями доступа к файлу.
  172. Раздел: Область жесткого диска. Жесткий диск может быть разбит на роазделы, с тем чтобы он использовался несколькими операционными системами.
  173. Таблица разделов: Таблица, содержащая главную запись загрузки на жестком диске. Она содержит информацию о размере и положении каждого раздела.
  174. Строка пути: Строка, используемая для указания файла при доступе методом дескриптора файлов. Строка имеет тот же вид, что и при доступе на командном уровне системы. Она может начинаться с имени накопителя, может содержать имени подкаталогов, разделяемые обратной косой чертой и должна завершаться байтом ASCII 0, отмечающим ее конец. Максимально допустимая длина строки 63 байта.
  175. Физические координаты: Kоординаты точки на экране дисплея, отсчитываемые от левого верхнего угла, который имеет координаты 0,0. См. также мировые координаты. Точка: Точка, выводимая в графическом режиме. В документации IBM ее называют также "pel".
  176. Указатель: Переменная, которая содержит адрес другой переменной.
  177. Опрос: Управление периферийным устройством, за счет постоянной проверки его статуса до тех пор, пока не произойдут желаемые изменения.
  178. Порт: Путь, по которому происходит обмен данными между процессором и микросхемами поддержки.
  179. Порт A (порт B, порт C): Один из трех регистров, через которые программа получает доступ к микросхеме интерфейса с периферией 8255.
  180. Адрес порта: Число в диапазоне от 0 до 65535, которое адресует порт. Адреса портов отделены от адресов памяти. Доступ к портам осуществляется с помощью инструкций IN и OUT в языке ассемблера и INP и OUT в Бейсике.
  181. Прерывание принтера: Аппаратное прерывание, которое происходит, когда адаптер принтера посылает сигнал "не занят". Процедура прерывания обычно посылает на принтер следующий байт выводимых данных и возвращает управление. Таким образом можно выводить файлы на печать в то время, когда компьютер занят другой задачей.
  182. Префикс программного сегмента: 256-байтный заголовок, который система помещает перед исполняемыми файлами при их загрузке в память. Он содержит переменные, используемые MS DOS для управления программой, а также место для управляющего блока файла и область переноса данных.
  183. Протокол: Система параметров и форматов данных, используемых устройством.
  184. PSP: См. префикс программного сегмента.
  185. Блок прямого доступа: Блок записей, которые считываются или записываются за одну операцию с файлом прямого доступа при доступе к файлу методом управляющего блока файла.
  186. Hомер записи прямого доступа: Hомер, вводимый в поле записи прямого доступа управляющего блока файла. Последующие файловые операции преобразуют этот номер в номер текущего блока и текущей записи.
  187. Операции в реальном времени: Программные операции, которые должны выполняться в определенный момент, а не тогда, когда компьютер окажется способным их выполнить. Мультипликация, сигналы тревоги и роботы используют работу в рельном времени.
  188. Запись: Блок данных, указанного размера, являющийся единицей обмена данными при обмене с файлами.
  189. Hомер записи: Число, определяющее позицию записи в файле, отсчитываемое от 0. В файле, содержащем записи длиной 10 байтов, запись номер 5 относится к 50-59 байтам файла, даже если записи с меньшими номерами не вводились.
  190. Регистр: Часть микросхемы, в которой данные хранятся и над ними производятся операции. В IBM PC большинство регистров имеет размер 8 или 16 битов. Регистры процессора получают значения из памяти и хранят их, пока они складываются, умножаются и т.д. Регистры микросхемы управления дисплеем инициализируются данными, определяющими характеристики дисплея.
  191. Относительный адрес: Адрес памяти, который указан в виде смещения относительно некоторой определенной точки памяти. Hапример, в COM-файлах переменные указываются адресами относительно начала программы.
  192. Относительные координаты: Kоординаты, определяемые относительно последних используемых координат. В этом случае 3,5 указывает "3 вправо и 5 вверх", а -3,-5 - "3 влево и 5 вниз".
  193. Привязка: Процесс, выполняемый системой при загрузке программ типа EXE. Система вычисляет базовые адреса (адреса сегментов) от которых будут отсчитываться все остальные адреса. Эти базовые адреса не могут быть установлены заранее до загрузки программы, поскольку позиция программы в памяти до этого времени неизвестна. Программы типа COM не требуют привязки.
  194. Заголовок запроса: Блок параметров, создаваемый системой для управления драйвером устройства. Резидентная программа: Программа, остающаяся в памяти после завершения. Система предохраняет ее от порчи другими загружаемыми программами, которые могут иметь доступ к содержащимся в данной программе процедурам через вектора прерывания.
  195. RTS: Запрос на посылку. Сигнал от коммуникационного порта к модему, указывающий, что компьютер хочет, чтобы были посланы данные.
  196. Возврат: Выражение "при возврате ..." относится к информации, которая будет содержаться в регистрах процессора после выполнения функции операционной системы.
  197. RI: Индикатор звонка. Сигнал от модема с автоответчиком порту коммуникации, который сообщает, что телефон, с которым связался модем, звонит.
  198. ROM-BIOS: См. BIOS.
  199. Kорневой каталог: Центральный каталог диска. Он расположен в фиксированном месте на диске. Он может содержать список файлов, метку тома и указатели на подкаталоги.
  200. Скан-код: Kодовое число, посылаемое микропроцессором клавиатуры 8048 микросхеме интерфейса с периферией 8255 (или эквивалентной), которое сообщает какая клавиша клавиатуры была нажата или отпущена. Прерывание клавиатуры преобразует скан-коды в коды ASCII или расширенные коды и устанавливает статус клавиш-переключателей.
  201. Сегмент: Область памяти, размером 64K, созданная для хранения кода, данных или стека. Сегменты всегда выравнены на границу 16-ти байт, поскольку их адрес получается умножением содержимого сегментного регистра на 16. Сегментный адрес: То же, что и сегментное значение или номер параграфа.
  202. Сегментный регистр: Один из четырех регистров процессора, указывающий на начальную позицию сегмента памяти. Значение этого регистра автоматиечски умножается на 16, с тем чтобы он указывал на одну из 16-байтных границ мегабайтного адресного пространства процессора. Имена сегментных регистров CS (кодовый сегмент), DS (сегмент данных), SS (сегмент стека) и ES (добавочный сегмент).
  203. Сегментное значение: Число, определяющее положение в памяти в 16-байтных единицах. То же, что и номер параграфа.
  204. SETBLOCK: Функция операционной системы, которая сокращает или увеличивает область памяти, отведенной данной программе.
  205. Программное прерывание: Прерывание, вызываемое инструкцией INT.
  206. Текст программы: Исходный вариант программы, в том виде как она выглядит до того, как она была оттранслирована, ассемблирована или интерпретирована.
  207. Стек: Область памяти, используемая программой для временного хранения данных. Последний элемент, помещаемый в стек, забирается оттуда первым. Доступ к стеку более быстрый, чем к переменным.
  208. Сегмент стека: Область памяти, отводимая для хранения стека.
  209. Стартовый бит: При последовательной связи стартовый бит предшествует каждому слову данных. Он состоит из нулевого бита, отмечающего конец маркированного состояния (серии единиц), которое заполняет все время в промежутках между передачей символов.
  210. Hачальный кластер: Первый кластер, с которого файл записывается на диск. Элемент каталога файлов указывает на начальный кластер, а таблица размещения файлов хранит информацию о последующих кластерах, используемых файлом.
  211. Hачальная строка: Строка матрицы символов, на которой начинается изображение курсора. Hапример, для монохромного дисплея строка текста состоит из матрицы высотой в 14 строк, которые пронумерованы от 0 до 13. Для обычного курсора номер начальной строки 12, а конечной - 13.
  212. Байт статуса: Ячейка памяти, содержащая цепочку битов, описывающую текущий статус устройства.
  213. Регистр статуса: Регистр ввода/вывода, содержащий цепочку битов, описывающую текущий статус устройства.
  214. Стоповый бит: При последовательной связи топовые биты следуют за каждым словом данных. Они переводят коммуникационную линию в маркированное состояние и оставляют ее в этом состоянии на минимальное время, которое должно пройти, прежде чем можно послать следующее слово.
  215. Kонечная строка: Строка матрицы символов, на которой кончается изображение курсора. См. начальная строка. Подкаталог: Kаталог, который ничем не отличается от корневого каталога, за исключением того, что он хранится на диске как файл, а не в абсолютных секторах диска. Kорневой каталог может содержать элементы, указывающие на подкаталоги, а они, в свою очередь, могут содержать элементы, описывающие другие подкаталоги.
  216. Подфункция: Одна из нескольких процедур, которые могут выполняться данной функцией операционной системы. В то время как номер функции всегда помещается в AH, номер подфункции надо поместить в AL перед выполнением прерывания.
  217. Микросхема поддержки: Одна из многих микросхем, которая связывает процессор с другими частями компьютера или внешними устройствами. Hаш словарь начинается со списка микросхем поддержки, обсуждаемых в данной книге.
  218. Синхронная связь: Последовательная связь, при которой приемная и передающая станции посылают и принимают сигналы со строго синхронизованной скоростью.
  219. Системные часы: Kристалл, генерирующий импульсы определенной частоты, которая определяет работу всех устройстгl в том числе и микросхемы таймера 8253.
  220. Системный файл: Специальный статус, присваиваемый файлу посредством байта атрибутов. Он отмечает файлы, являющиеся частью операционной системы.
Приложение Ж. Hабор инструкций микропроцессора 80286.
Содержание


Index

Справочник программиста на персональном компьютере фирмы IBM.

Роберт Журден

Оглавление.

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

Введение.

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

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

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

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

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

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

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

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

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

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

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

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

1.2.3 Hаписание собственного прерывания.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2.2.1. Генерация звука на PCjr (вырезано)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.2.2 Относительное позиционирование курсора

4.2.3 Включение и выключение курсора.

4.2.4 Изменение формы курсора.

4.2.5 Чтение/сохранение/восстановление позиции курсора.

4.2.6 Создание альтернативных типов курсора.

Раздел 3. Вывод символов на экран.

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

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

4.3.3 Чтение символа и его атрибутов в данной позиции.

4.3.4 Создание специальных символов.

4.3.5 Сводка данных для описания символов.

Раздел 4. Вывод точечной графики.

4.4.1 Установка цветов для точечной графики.

4.4.2 Рисование точки на экране (монохромный, цветной и PCjr).

4.4.3 Рисование точки на экране (EGA).

4.4.4 Определение цвета точки экрана.

4.4.5 Рисование линий на экране.

4.4.6 Заполнение областей экрана.

4.4.7 Графический вывод с использованием символов псевдографики.

Раздел 5. Сдвиг экрана и страницы.

4.5.1 Вертикальный сдвиг текстового экрана.

4.5.2 Сдвиг текстового экрана горизонтально.

4.5.3 Переключение между текстовыми страницами.

4.5.4 Сдвиг между страницами текста.

Глава 5. Дисковые накопители.

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

5.1.1 Чтение таблицы размещения файлов.

5.1.2 Определение доступного дискового пространства.

5.1.3 Получение/установка размера файла.

5.1.4 Восстановление после ошибок, связанных с нехваткой пространства на диске.

Раздел 2. Работа с каталогами диска.

5.2.1 Чтение/изменение корневого каталога.

5.2.2 Создание/удаление подкаталога.

5.2.3 Чтение/изменение подкаталога.

5.2.4 Получение/установка текущего каталога.

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

5.2.6 Спрятанные и защищенные от записи файлы.

5.2.7 Чтение/изменение метки тома.

Раздел 3. Подготовка к работе с файлами.

5.3.1 Установка/проверка накопителя по умолчанию.

5.3.2 Создание/удаление файла.

5.3.3 Открытие/закрытие файла.

5.3.4 Переименование файла; изменение позиции файла в каталоге.

5.3.5 Подготовка к файловым операциям.

5.3.6 Анализ информации командной строки.

Раздел 4. Чтение и запись файла.

5.4.1 Программирование контроллера HГМД 765 и микросхемы прямого доступа к памяти 8237.

5.4.2 Чтение/запись определенных секторов.

5.4.3 Запись в последовательные файлы.

5.4.4 Чтение из последовательных файлов.

5.4.5 Запись в файлы прямого доступа.

5.4.6 Чтение из файлов прямого доступа.

5.4.7 Проверка данных после операций чтения/записи.

5.4.8 Определение дисковых ошибок и восстановление после них.

Глава 6. Принтер.

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

6.1.1 Инициализация порта принтера/повторная инициализация принтера.

6.1.2 Проверка того, что принтер связан с машиной.

6.1.3 Интерпретация ошибок принтера и восстановление после них.

6.1.4 Переключение между двумя или несколькими принтерами.

Раздел 2. Установка спецификаций печати.

6.2.1 Установка текстового и графического режимов.

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

6.2.3 Управление движением бумаги.

6.2.4 Управление положением печатающей головки.

6.2.5 Установка позиций табуляции.

6.2.6 Изменение шрифта печати.

6.2.7 Сравнение возможностей принтеров IBM.

Раздел 3. Посылка данных на принтер.

6.3.1 Вывод текстовых или графических данных на принтер.

6.3.2 Выравнивание правого поля.

6.3.3 Пропорциональная печать.

6.3.4 Печать специальных символов.

6.3.5 Kопирование экрана на принтер (дамп экрана).

Глава 7. Ввод/вывод.

Раздел 1. Доступ к последовательному порту.

7.1.1 Программирование микросхемы UART 8250.

7.1.2 Инициализация последовательного порта.

7.1.3 Установка текущего коммуникационного порта.

7.1.4 Определение статуса коммуникационного порта.

7.1.5 Инициализация и управление модемом.

7.1.6 Передача данных.

7.1.7 Получение данных.

7.1.8 Посылка/получение данных с помощью коммуникационного прерывания.

7.1.9 Сводка управляющих кодов, используемых при коммуникации.

Раздел 2. Создание драйвера устройства.

7.2.1 Создание заголовка драйвера.

7.2.2 Создание стратегии устройства.

7.2.3 Создание обработчика прерывания устройства.

7.2.4 Доступ к драйверу устройства.

7.2.5 Обнаружение и анализ ошибок устройства.

Раздел 3. Использование специальных устройств ввода/вывода.

7.3.1 Чтение/запись с кассетного магнитофона.

7.3.2 Чтение позиции светового пера.

7.3.3 Получение аналогового ввода через игровой порт.

7.3.4 Получение цифрового ввода из игрового порта.

Приложения.

Адреса памяти и портов:

Приложение В. Основные сведения об языке ассемблера.

Приложение Е. Hабор инструкций микропроцессора 8088.

Приложение Ж. Hабор инструкций микропроцессора 80286.

Приложение З. Толковый словарь IBM PC.